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 std::sync::{Arc, Mutex};
4use cudarc::driver::{CudaContext, CudaStream, CudaModule, CudaFunction, CudaSlice, LaunchConfig, PushKernelArg};
5use cudarc::nvrtc::Ptx;
6
7#[cfg(debug_assertions)]
8pub(crate) fn debug_assert_tensor_stream_device<T>(
9    tensor: &CudaSlice<T>,
10    stream: &CudaStream,
11    site: &str,
12) {
13    let tensor_dev = tensor.ordinal();
14    let stream_dev = stream.context().ordinal();
15    assert_eq!(
16        tensor_dev, stream_dev,
17        "PP cross-device tensor read at {site}: tensor on dev{tensor_dev}, stream on dev{stream_dev}"
18    );
19}
20
21pub use memra_gguf;
22pub use memra_runtime;
23
24pub mod model;
25pub mod forward;
26pub mod hybrid;
27pub mod hybrid_forward;
28pub mod sigrouter_contract;
29/// The dual cache lives in the shared `memra-kv` crate (Phase D extraction); this
30/// re-export keeps every `crate::cache::` / `memra_engine::cache::` path unchanged.
31pub mod cache {
32    pub use memra_kv::*;
33}
34pub mod decode;
35pub mod decode_batch;
36pub mod moesd;
37/// MLA (multi-head latent attention) CPU f32 reference — GLM-5.2 bring-up lane increment 1.
38/// Naive vs absorbed decode forms + NORM/NEOX rope permutation, unit-tested; the permanent
39/// oracle for the MLA kernel family (`research/mla-bringup-20260801/DESIGN.md`). No CUDA deps.
40pub mod mla;
41pub mod pp;
42pub mod spec;
43pub mod gemma_spec;
44pub mod round_stream;
45pub mod graph_update;
46pub mod dflash;
47pub mod eagle;
48pub use memra_sampling as sampler;
49
50/// In-house MoE router GEMV on the spec-verify small-t path (DEFAULT ON since 2026-07-10:
51/// battery green on 35B p2/p3 K=1..8, acceptance bit-identical, +2-4% spec e2e — replaces
52/// ~240 per-column cuBLAS gemv launches/round). MEMRA_ROUTER_KERNEL=0 is the rollback seam.
53/// MoE grouped f16 GEMM door (experimental until gated), f16-mirror numeric class:
54/// per-layer expert dequant to f16 + one grouped f16 GEMM over the CSR groups.
55///   MEMRA_MOE_F16G=1  cublasGemmGroupedBatchedEx (round 46 arc 2). The grouped API issues
56///                     through cublas-internal streams NOT ordered with ours — v1 pays a full
57///                     stream sync per projection (round-47 ledgered defect).
58///   MEMRA_MOE_F16G=2  single-kernel grouped GEMM on the engine stream (round 49): ordered by
59///                     construction, zero syncs, f32 C with the act row-scale folded in.
60/// DEFAULT (2026-08-01, round 49 promotion): mode 1 on the Hopper lane — with the 41/41
61/// dequant coverage fix the q35 board-2048 prime measured 5490 (MMQ) / 8380 (mode 1,
62/// +53%) / 7990 (mode 2) x3 interleaved on the H100, argmax MATCH — the last board loss
63/// flips. The 5090 measured FLAT (858GB/s makes the dequant-workspace traffic cancel the
64/// GEMM win) — but that verdict is for expert banks the int8-MMA MMQ arm can take
65/// (IQ3_S/IQ4_XS/Q4_0). MEMRA_MOE_F16G=0 kills anywhere.
66///
67/// HOPPER RE-VERDICT (2026-08-02, lane/h100-flip-full): mode 2 with full direct coverage
68/// (Q4_K/Q6_K/IQ4_XS/IQ3_S tile loaders, lane/iq-direct-loaders) + the deep tail
69/// (lane/sk-tail-form) FLIPS past cublas mode 1 on the H100 — q35 board-2048 prime
70/// 13163.6 (mode 2, cross=32) vs 8626.5 (mode 1) vs 8073.4 (round-51 sk form), +52.6%,
71/// interleaved x5 zero overlap, argmax MATCH 30/30. The round-54 NO-FLIP (8547 vs 8112)
72/// was coverage-priced at 5.2% direct; ~100% coverage kills the workspace pass and the
73/// verdict inverts. Hopper naked default -> mode 2 (this arm); the gemma (gelu) site
74/// stays env-explicit-only via moe_f16g_gemma_on (Err => closed, unaffected by this arm).
75///
76/// MODE-2 DEFAULT (sm_120a naked, 2026-08-02, lane/f16g-default-rearb): with the direct
77/// tile loaders covering Q4_K/Q6_K/IQ4_XS/IQ3_S, the sk visitor beats the int8-MMA MMQ
78/// tiles on the IQ-bank models too (q35 board-2048 +33.9%, KAT pp512 +46.7% / pp2048
79/// +30.6% — research/iq-direct-loaders-20260802 §3-5, confirmed + full battery in
80/// research/f16g-default-rearb-20260802/), so every f16g-admitted expert layer rides
81/// mode 2 naked. Decode/verify stay on dp4a (t >= 16 floor). f16-mirror numeric class
82/// for naked q35/KAT prefill+prime — new token-sha anchors stamped in the rearb lane.
83///
84/// AUTO-KQUANT (mode 3, 2026-08-02, lane/q4k-expert-prefill): the previous sm_120a
85/// default, kept reachable via MEMRA_MOE_F16G=3. The mode-2 sk form is admitted ONLY for
86/// layers the MMA MMQ arm rejects (k-quant expert projections — Q3_K/Q4_K/Q6_K), i.e.
87/// exactly where the baseline is the per-pair moe_pairs_matvec_q8_em fallback with zero
88/// token reuse (Ornith-35B Q4_K_M board-2048 1098.2 -> 3453.7, 3.14x,
89/// research/q4k-expert-prefill-20260802/). Its "IQ banks keep their measured-faster MMQ
90/// tiles" ruling was priced BEFORE the IQ direct loaders and is refuted on the 5090 —
91/// the k-quant-only admission survives as the rollback seam, not the default.
92/// The gemma (gelu) site stays env-explicit-only (moe_f16g_gemma_on).
93pub fn moe_f16g_mode() -> u8 {
94    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
95    *M.get_or_init(|| match std::env::var("MEMRA_MOE_F16G").as_deref() {
96        Ok("0") => 0,
97        Ok("2") => 2,
98        Ok("3") => 3,
99        Ok(_) => 1,
100        // Both arches independently re-arbitrated to mode 2 on 2026-08-02
101        // (5090: lane/f16g-default-rearb; H100: lane/h100-flip-full) — unset = 2 everywhere.
102        Err(_) => 2,
103    })
104}
105/// Mode-2 sk kernel form policy (round 51, lane/sk-bm128): the single-kernel grouped GEMM runs
106/// as a persistent problem-visitor over the real CSR tiles with two tile forms. Returns
107/// (shape_sel, cross) for the FFI:
108///   MEMRA_F16G_SK=0    -> (-1, _): the round-49 grid-scan kernel (rollback seam).
109///   MEMRA_F16G_SK=32   -> all groups on the 32x64x32 2-stage form (cross = i32::MAX).
110///   MEMRA_F16G_SK=128  -> all groups on the 128x64x64 3-stage form (cross = 1; groups fall
111///                         back to 32x64 in-launcher when the device/in_f can't take it).
112///   unset              -> hybrid split: groups with m_e >= MEMRA_F16G_SK_CROSS ride the 128
113///                         form. Default cross = 64 (5090 sweep 2026-08-01, receipts
114///                         research/sk-bm128-20260801/; H100 re-swept on the direct+tail
115///                         form 2026-08-02, lane/h100-flip-full: {16,32,64} ->
116///                         12868/13192/13225 — 64 wins there too, the pre-direct 32
117///                         verdict was stale).
118pub fn moe_f16g_sk_params() -> (i32, i32) {
119    static P: std::sync::OnceLock<(i32, i32)> = std::sync::OnceLock::new();
120    *P.get_or_init(|| match std::env::var("MEMRA_F16G_SK").as_deref() {
121        Ok("0") => (-1, 0),
122        Ok("32") => (0, i32::MAX),
123        Ok("128") => (0, 1),
124        _ => {
125            let cross = std::env::var("MEMRA_F16G_SK_CROSS").ok()
126                .and_then(|v| v.parse().ok()).unwrap_or(64);
127            (0, cross)
128        }
129    })
130}
131/// DIRECT-FROM-QUANT sk tile loaders (lane/kquant-tile-loaders, 2026-08-02; IQ classes added
132/// by lane/iq-direct-loaders): Q4_K/Q6_K/IQ4_XS/IQ3_S expert projections on the mode-2/3 sk
133/// visitor forms dequant their weight tiles in-register from the quant superblocks instead of
134/// running the per-(layer,projection) dequant pass into an f16 workspace (41.8% of Ornith-35B
135/// t=512 kernel time — the pp512 wall, research/q4k-expert-prefill-20260802 §5; the IQ classes
136/// are 94.8% of q35's bank bytes — the h100-sk-direct coverage pricing). Bit-identical to the
137/// workspace path by construction (kernel-check "f16g-kq-direct" gates it bitwise) — a
138/// data-movement change, not a numeric-class change. Default ON; MEMRA_F16G_DIRECT=0 reverts
139/// to the workspace path everywhere; MEMRA_F16G_DIRECT=kq keeps the k-quant loaders and
140/// reverts only the IQ classes (the iq-direct-loaders A/B seam — the pre-lane shipped config).
141pub fn moe_f16g_direct_on(qtype: i32) -> bool {
142    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
143    let m = *M.get_or_init(|| match std::env::var("MEMRA_F16G_DIRECT").as_deref() {
144        Ok("0") => 0,
145        Ok("kq") => 1,
146        _ => 2,
147    });
148    match m {
149        0 => false,
150        1 => qtype == QT_Q4_K || qtype == QT_Q6_K,
151        _ => true,
152    }
153}
154/// DEEP-TAIL sk form (lane/sk-tail-form, 2026-08-02): groups below the visitor crossover ride
155/// a 32x64x64 3-STAGE cp.async tile instead of the round-51 32x64x32 2-stage — the same 32-row
156/// tile (zero extra padding), 2 k-blocks in flight instead of 1 and half the syncs per k. The
157/// H100 ncu pricing (research/sk-bm128-20260801) put the 2-stage tail at 31% of the sk GEMM
158/// stage under q35's routing skew. Bit-identical to every other sk form by construction
159/// (kernel-check "f16g-sk" gates all tail arms maxdiff==0); exists in both the workspace-f16
160/// and direct-from-quant variants. Default ON; MEMRA_F16G_TAIL=0 = rollback to the 2-stage
161/// tail. in_f % 64 != 0 falls back in-launcher.
162pub fn moe_f16g_tail_on() -> bool {
163    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
164    *ON.get_or_init(|| std::env::var("MEMRA_F16G_TAIL").as_deref() != Ok("0"))
165}
166
167/// Per-model door for the gemma-MoE (gelu) grouped path: round 49's Hopper default
168/// REGRESSED g26 board-2048 prefill -8.3% interleaved x5 on-box (def median 10380,
169/// wild 8.9k-11.7k spread; off 11317, ±0.13%) — the +6-15% probe verdict didn't
170/// survive the board workload (stale-verdict law, round 50). The silu/qwen class
171/// keeps the round-49 default (q35 +53% board-2048). Explicit MEMRA_MOE_F16G=1/2
172/// still opens this door for A/B.
173pub fn moe_f16g_gemma_on() -> bool {
174    static M: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
175    *M.get_or_init(|| !matches!(std::env::var("MEMRA_MOE_F16G").as_deref(), Ok("0") | Err(_)))
176}
177
178/// Fused act-epilogue (silu/gelu-mul + q8_1_mmq quantize in one launch) for the MoE prefill
179/// MMA arms. Byte-identical to the two-pass path (kernel-check gated) — default ON.
180/// MEMRA_MOE_FUSE_ACTQ=0 is the rollback/A-B seam.
181pub fn moe_fuse_actq_on() -> bool {
182    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
183    *ON.get_or_init(|| std::env::var("MEMRA_MOE_FUSE_ACTQ").as_deref() != Ok("0"))
184}
185
186/// PREFILL router m-invariance (lane/concat-prime-exact, 2026-08-02). The batched cuBLASLt
187/// router GEMM changes a row's logits when OTHER rows join the call (probed: first change at
188/// m=65 on the Ornith-35B router, 3.9e-3 — while the MMQ/f16 trunk GEMMs are bit-identical
189/// across m). Feeding a top-k discontinuity, that made a served request's expert selection a
190/// function of its CO-ARRIVALS under cross-request prime batching. The in-house router GEMV
191/// is m-invariant, so prefill uses it too and routing depends on a session's own tokens only.
192/// DEFAULT ON: it is the serving isolation contract, and it is the same kernel decode and spec
193/// verify already use (dispatch parity, one router kernel for every t).
194/// MEMRA_ROUTER_PREFILL_EXACT=0 reverts to the batched GEMM.
195pub fn router_prefill_exact_on() -> bool {
196    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
197    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_PREFILL_EXACT").as_deref() != Ok("0"))
198}
199
200pub fn router_kernel_on() -> bool {
201    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
202    *ON.get_or_init(|| {
203        let on = std::env::var("MEMRA_ROUTER_KERNEL").as_deref() != Ok("0");
204        if !on { eprintln!("[memra] router kernel OFF (rollback: per-column cuBLAS gemv)"); }
205        on
206    })
207}
208
209/// FAST-ROUTER batch twin (lane/fast-router, 2026-08-02). The concat-prime exactness fix
210/// (router_prefill_exact_on) routes prefill through router_gemv — m-invariant, but a
211/// per-(expert,token) GEMV program with zero operand reuse, so q35 board-2048 prefill paid
212/// -10% on the 5090. router_gemv_f32_w8_batch register-tiles (8x8 expert-x-token) the same
213/// per-row FP chains (BIT-IDENTICAL per row — kernel-check sweeps m=1..2048 on real router
214/// weights), so the t crossover below is pure perf, not a numeric config. Swept on-box
215/// (research/fast-router-20260802/crossover-router*.jsonl): plain wins t<=4, batch +7-9%
216/// at t=8, 1.9x at t=16 rising to 3.45x at t=2048 — MIN_T=8. Decode t=1 and spec verify
217/// t<8 keep the plain w8 form. MEMRA_ROUTER_BATCH=0 forces plain at every t (rollback
218/// seam, perf-only: bits are equal by the kernel-check gate).
219/// Killed arms (same sweep, JSONL is the record): the 8x16 tile lost to 8x8 at every t
220/// (128-accumulator register pressure beats the halved w-traffic), and the same-shape
221/// sigmoid_dot_rows twin (out_f=1) measured 0.62-0.89x at every prefill t
222/// (launch-latency-bound, ~7us/layer at m=2048) — both bit-identity-PASSED before dying.
223pub const ROUTER_BATCH_MIN_T: usize = 8;
224pub fn router_batch_on() -> bool {
225    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
226    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_BATCH").as_deref() != Ok("0"))
227}
228mod cpu_experts;
229pub mod moe_cache;
230pub mod spill;
231mod spill_pread;
232#[cfg(memra_cutlass)]
233pub mod cutlass_ffi;
234pub mod mmq_ffi;
235pub mod f16_ffi;
236pub mod prime_graph;
237pub mod fp8_ffi;
238
239// Fatbins are EMBEDDED (crates-release lane, 2026-08-04): build.rs still writes them to
240// OUT_DIR, but the bytes ship inside the binary via include_bytes! and load through
241// cuModuleLoadData. Distribution contract: a prebuilt or cargo-installed binary must be
242// self-contained — the old baked OUT_DIR *paths* pointed at the builder's temp dir and
243// broke every machine that wasn't the build machine. Same bytes, same module image;
244// the runtime MEMRA_GEMM_FATBIN tune-seam override below is preserved.
245const FATBIN: &[u8] = include_bytes!(env!("MEMRA_ENGINE_FATBIN"));
246const HYBRID_FATBIN: &[u8] = include_bytes!(env!("MEMRA_HYBRID_FATBIN"));
247const QMATVEC_FATBIN: &[u8] = include_bytes!(env!("MEMRA_QMATVEC_FATBIN"));
248const FLASH_FATBIN: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN"));
249const GEMM_FATBIN: &[u8] = include_bytes!(env!("MEMRA_GEMM_FATBIN"));
250const ROUTER_FATBIN: &[u8] = include_bytes!(env!("MEMRA_ROUTER_FATBIN"));
251/// spec_sample.cu: sampled-spec primitives (Philox Gumbel-max / softmax gather / residual sampler).
252const SAMPLE_FATBIN: &[u8] = include_bytes!(env!("MEMRA_SAMPLE_FATBIN"));
253
254/// TUNE SEAM (tools/sweep): a RUNTIME `MEMRA_GEMM_FATBIN=<path>` overrides the baked-in
255/// qmatvec_gemm.cu fatbin path (build.rs bakes the same name at COMPILE time via
256/// cargo:rustc-env — that constant is the default). Lets the sweep harness swap in a
257/// `-D`-tuned fatbin per process with NO rust rebuild. Unset at runtime => the
258/// compile-time default (zero behavior change).
259fn gemm_fatbin_bytes() -> std::borrow::Cow<'static, [u8]> {
260    assert!(!(portable_mma_gated() && std::env::var_os("MEMRA_GEMM_FATBIN").is_some()),
261            "MEMRA_GEMM_FATBIN overrides are not allowed in the portable CUDA lane");
262    match std::env::var("MEMRA_GEMM_FATBIN") {
263        Ok(path) => std::borrow::Cow::Owned(
264            std::fs::read(&path).unwrap_or_else(|e| panic!("MEMRA_GEMM_FATBIN read {path}: {e}"))),
265        Err(_) => std::borrow::Cow::Borrowed(GEMM_FATBIN),
266    }
267}
268
269/// Phase A (ARCHITECTURE-H100.md): sm_90a re-enables the portable-PTX tensor-core paths
270/// (int8 mma.m16n8k32/k16.s8, bf16 m16n8k16, ldmatrix, cp.async — all sm_80-class, native
271/// on Hopper) that the portable boot lane gates off. Dispatch guards that used to test
272/// `cfg!(memra_portable_cuda)` test this instead; sm_89 keeps the pure-portable behavior.
273/// The sm_120a/sm_100a-only MMA kinds (mxf4nvf4, kind::f8f6f4) are NOT covered — their
274/// launchers stay fail-closed stubs on 90a and their dispatch arms stay arch-gated.
275pub(crate) const fn portable_mma_gated() -> bool {
276    cfg!(memra_portable_cuda) && !cfg!(memra_hopper_mma)
277}
278
279/// The legacy quantized prefill GEMMs are tuned and validated for sm_120a; sm_90a re-admits
280/// them through the Hopper-MMA lane (int8 m16n8k32.s8 is sm_80-class PTX).  Keep the policy
281/// in a pure helper so the dispatch guard can be regression-tested without constructing an
282/// Engine or allocating a GPU tensor.
283const fn legacy_quant_gemm_allowed(portable_cuda: bool, hopper_mma: bool, no_gemm: bool) -> bool {
284    (!portable_cuda || hopper_mma) && !no_gemm
285}
286
287// ---- KV-cache format selection (kvbytes lane, 2026-07-08; default OFF = daily config) ----
288// `MEMRA_KV_K` = q8_0 (default, 34 B/32elem) | fp8 (raw e4m3, 32 B — the -6% K-bytes arm)
289// `MEMRA_KV_V` = q5_1 (default, 24 B/32elem) | q4_0 (18 B, -25% V bytes) | fp8 (32 B, +33%)
290// A non-default format is a NEW NUMERIC CONFIG: its own run-gen argmax baseline is legal,
291// but the gate battery (kernel-check, run-spec self-consistency) must pass WITHIN it and
292// the choice is explicit env, never silent. flash_attn.cu is compiled once per format pair
293// (build.rs); the kernels keep their names — Engine::new just loads the matching fatbin.
294const FLASH_FATBIN_VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VQ4"));
295const FLASH_FATBIN_VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VF8"));
296const FLASH_FATBIN_KF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8"));
297const FLASH_FATBIN_KF8VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VQ4"));
298const FLASH_FATBIN_KF8VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VF8"));
299
300/// KV format policy moved to the shared `memra-kv` crate (Phase D); re-exported so the
301/// fatbin router below and every existing `crate::kv_blk_bytes()` call site is unchanged.
302pub use memra_kv::{kv_blk_bytes, kv_cache_formats};
303
304/// The flash_attn fatbin matching the selected KV formats.
305fn flash_fatbin_bytes() -> &'static [u8] {
306    match kv_cache_formats() {
307        ("q8_0", "q5_1") => FLASH_FATBIN,
308        ("q8_0", "q4_0") => FLASH_FATBIN_VQ4,
309        ("q8_0", "fp8")  => FLASH_FATBIN_VF8,
310        ("fp8",  "q5_1") => FLASH_FATBIN_KF8,
311        ("fp8",  "q4_0") => FLASH_FATBIN_KF8VQ4,
312        ("fp8",  "fp8")  => FLASH_FATBIN_KF8VF8,
313        other => unreachable!("kv_cache_formats returned {other:?}"),
314    }
315}
316
317/// TUNE SEAM (tools/sweep): kernel1 (Q8_0/Q4_K/Q5_K) launch-tile override,
318/// `MEMRA_GEMM_K1_LAUNCH="BM,BN,NWARP"`. MUST match the `-D K1_BM/K1_BN/NWARP` the swept
319/// fatbin was compiled with (the .cu tile and the host launch grid/block have to agree —
320/// the hardcoded (128,128,8) in qmatvec_gemm/qmatvec_gemm_raw is the shipped default).
321/// Kernel2 (Q6_K/NVFP4) launch is untouched. Unset or malformed => None => shipped
322/// defaults (zero behavior change).
323fn k1_launch_override() -> Option<(u32, u32, u32)> {
324    static K1: std::sync::OnceLock<Option<(u32, u32, u32)>> = std::sync::OnceLock::new();
325    *K1.get_or_init(|| {
326        let v = std::env::var("MEMRA_GEMM_K1_LAUNCH").ok()?;
327        let p: Vec<u32> = v.split(',').filter_map(|s| s.trim().parse().ok()).collect();
328        match p.as_slice() { [bm, bn, w] => Some((*bm, *bn, *w)), _ => None }
329    })
330}
331
332/// H100 wgmma prefill-GEMM seam (task 8, ARCHITECTURE-H100.md): OPT-IN (MEMRA_WGMMA=1).
333/// v0 verdict (2026-07-26, N=5 pp512 9B-Q8_0): wgmma 3845 tok/s vs MMQ 8692 — the
334/// standalone harness's "688us MMQ ref" was a pp2048-shape figure, so v0 (unpipelined,
335/// 64x64 tile, wait_group<0> every 32-K step) is ~3x SLOWER per launch at m=512 model
336/// shapes. Default stays MMQ until the pipelined version beats it N=5 (repo law).
337/// Correctness stays pinned regardless: kernel-check's wgmma case is cfg-gated, not env-gated.
338pub(crate) fn wgmma_gemm_enabled() -> bool {
339    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
340    *V.get_or_init(|| std::env::var("MEMRA_WGMMA").as_deref() == Ok("1"))
341}
342
343/// TUNE SEAM: keys per FA-decode split (`MEMRA_FA_SPLIT` forces a fixed size; default 64). Smaller
344/// splits raise grid.y so grid = n_head_kv * n_splits fills the 82 SMs at short/mid ctx (vec path
345/// launches only n_head_kv=8 CTAs per split). Swept clock-locked 2026-07-03 (graph tg128): 32 beat
346/// 64 at ctx 128/512 (+0.5/+1.2%) and lost at 2048 (-3%) — BUT the adaptive 32/64 default BROKE the
347/// MTP spec-decode exact-match gate (run-spec K=1/2 self-consistency FAIL with 32; PASS with 64):
348/// the split count changes the combine's FP summation order, and the spec verify's batched forward
349/// only argmax-matches single-step decode under the 64-split order on real prompts. Spec exactness
350/// (the bigger lever) outranks a <=1.2% decode win -> default stays FIXED 64; sweeps use the env.
351/// Takes t_kv so eager, _dc capture, and fa_geom_eager stay signature-compatible for future
352/// adaptive retries (any retry MUST pass run-spec self-consistency first).
353/// Minimum t_kv for the warp-per-token vec FA path (below it the scalar path's 4x-more-blocks
354/// hides latency better — measured crossover, see `fa_decode`). Shared by fa_decode / fa_decode_dc /
355/// fa_geom_eager / fa_decode_rows-eligibility (spec verify) so the kernel pick NEVER diverges
356/// between eager decode and the verify (the spec-exactness law).
357pub const FA_VEC_MIN_TKV: usize = 96;
358/// Env-overridable crossover (MEMRA_FA_VEC_MIN, default FA_VEC_MIN_TKV). The 96 floor was
359/// measured on the qwen geometry (nkv=2); gemma4 SWA layers run nkv=8 = 4x the vec grid,
360/// which moves the crossover — sweep per model, adopt per the battery.
361pub fn fa_vec_min_tkv() -> usize {
362    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
363    *V.get_or_init(|| std::env::var("MEMRA_FA_VEC_MIN").ok()
364        .and_then(|v| v.parse().ok())
365        .unwrap_or_else(|| FA_VEC_MIN_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)))
366}
367
368/// f16-P/V class (DEFAULT since 2026-07-23 stamp v4; MEMRA_FA_F16PV=0 = f32-class rollback):
369/// llama-fa=1-style f16 P + f16 P@V accumulation on the hd512/SWA prefill stamps
370/// (KQ/softmax/normalize stay f32). Laptop stamp: 12B 1.045x, 31B 0.979x vs llama.
371///
372/// SPEC-SERVING FLIP (2026-07-26, the wkv acceptance-law pattern): with MEMRA_DRAFT set the
373/// default is OFF. f16 P/V shifts the PRIME's hidden states/KV in the sub-argmax logit
374/// space the drafter feeds on — argmax gates stay MATCH while depth acceptance falls off a
375/// cliff (26B d1736 0.883 -> 0.405, -40% e2e; f16pv-off alone restores 0.846/314 tok/s —
376/// the perf-ci acceptance battery is the only gate that sees this class). Explicit
377/// MEMRA_FA_F16PV always wins; plain serving keeps the f16 prefill win.
378pub fn fa_f16pv_on() -> bool {
379    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
380    *ON.get_or_init(|| std::env::var("MEMRA_FA_F16PV").map(|v| v != "0")
381        .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err()))
382}
383
384/// hd512 head-pair arm (DEFAULT since stamp v4; MEMRA_FA512_HP=0 reverts to sp16): GQA
385/// ncols2=2 — 2 heads per CTA share each staged K/V tile, Q register-resident. Engages
386/// when n_head is even and the GQA group (n_head/n_head_kv) is even.
387pub fn fa512_hp_on() -> bool {
388    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
389    *ON.get_or_init(|| std::env::var("MEMRA_FA512_HP").as_deref() != Ok("0"))
390}
391
392/// SWA head-pair arm (DEFAULT since stamp v4; MEMRA_FAW_HP=0 reverts to p1): llama-class
393/// windowed geometry — 32 q-rows x 2 heads per CTA sharing staged K/V, f16 P@V
394/// accumulation. Even n_head and even GQA group required (guarded per call).
395pub fn faw_hp_on() -> bool {
396    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
397    *ON.get_or_init(|| std::env::var("MEMRA_FAW_HP").as_deref() != Ok("0"))
398}
399
400/// 4-warp sp16 experiment arm (MEMRA_FA512_W4=1, requires the f16pv door): GEMM0 split-K
401/// 4-way + GEMM1 4x128 O-dims. Own partial-sum order — oracle-band gated. Returns warp
402/// count (2 = base sp16). 8-warp arm measured NEGATIVE 2026-07-23 (jsonl) and removed.
403pub fn fa512_wide_warps() -> usize {
404    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
405    *N.get_or_init(|| match std::env::var("MEMRA_FA512_W4").as_deref() {
406        Ok("1") => 4, _ => 2,
407    })
408}
409
410/// hd-512 vec crossover floor (MEMRA_FA512_MIN, default 512) — shared by fa_decode dispatch
411/// and the gemma global-layer rows/parity call sites.
412pub fn fa512_min_tkv() -> usize {
413    static FA512_MIN: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
414    *FA512_MIN.get_or_init(|| std::env::var("MEMRA_FA512_MIN").ok()
415        .and_then(|v| v.parse().ok()).unwrap_or(512))
416}
417/// Per-model crossover default, set at model load BEFORE the first decode (per-model
418/// numeric-config adoption law). qwen keeps the measured 96; gemma4 (nkv=8 SWA) measured
419/// vec-always fastest: 119.9 (96) / 130.0 (48) / 133.2 (1) tok/s tg128-regime, 2026-07-10.
420pub static FA_VEC_MIN_DEFAULT: std::sync::atomic::AtomicUsize =
421    std::sync::atomic::AtomicUsize::new(FA_VEC_MIN_TKV);
422/// Per-model windowed-split default (MEMRA_FA_SPW overrides): gemma MoE (26B, nkv=8) measured
423/// 32 (grid-limited t=1 under the raw-e4m3 sV ceiling, 2026-07-12); dense gemma (31B)
424/// measured 64 (37.13/37.12 vs 36.87/36.86 at 1.7k, N=2 — different attention geometry).
425pub static FA_SPW_DEFAULT: std::sync::atomic::AtomicUsize =
426    std::sync::atomic::AtomicUsize::new(32);
427/// Per-model hd512 (gemma globals) split default (MEMRA_FA_SP512 overrides): 26B measured 16
428/// (2026-07-11 N=2), dense 31B measured 32 (36.86/36.93 vs 36.73/36.73 at 1.7k, 2026-07-12).
429/// fused t=1 q4_0 pair/triple row mapping: true = mr1 (one row/warp). Per-model default
430/// (dense gemma wins +1.1% short / +0.6% depth on the 31B; MoE 26B REGRESSES −1.2% —
431/// its shared-expert fused2 shapes lose to the finer grid). MEMRA_Q40_MR env still wins.
432pub static FUSED_MR1_DEFAULT: std::sync::atomic::AtomicBool =
433    std::sync::atomic::AtomicBool::new(false);
434/// Per-model router-GEMV form (2026-07-31): the 8-warp twin is +8.8% on the H100 q35
435/// decode step (router was 14.8% of it) with argmax + spec self-consistency green on
436/// qwen-class MoE both rigs. The gemma-4 26B knife-edge block (2026-07-31, single
437/// synthetic prompt) was RE-ARBITRATED 2026-08-01 on 6 real prompts — gate outcomes
438/// identical to the lone-warp arm, +13% g26 decode — so gemma4 rides the default too
439/// (research/g26-decode-20260801/). MEMRA_ROUTER_V2 env overrides either way.
440pub static ROUTER_W8_DEFAULT: std::sync::atomic::AtomicBool =
441    std::sync::atomic::AtomicBool::new(true);
442pub static FA_SP512_DEFAULT: std::sync::atomic::AtomicUsize =
443    std::sync::atomic::AtomicUsize::new(16);
444/// Per-model rms_norm block size (per-model numeric-config law: the per-thread partial-sum
445/// split changes with blockDim -> different FP order -> battery-arbitrated per model).
446/// qwen keeps the shipped 256; gemma4 adopts 1024 (single-row 2816-col norms are one-block
447/// latency-bound at 256 threads — 7us/launch measured).
448pub static RMS_BLOCK_DEFAULT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(256);
449/// gemma4 fa split ladder switch (set at model load; see fa_split_keys).
450pub static FA_SP_GEMMA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
451/// Per-model stream-k override for SPEC serving (-1 = unset → env/default; 0 = force
452/// tiling; 1 = force sk). Set by generate_spec_gemma per model tier — the sk autotune's
453/// per-process kernel coin made 12B-class spec cells bimodal, while the 26B's drafter
454/// measures BETTER under sk's fold order (2026-07-27). mmq_ffi reads this before the env.
455pub static MMQ_SK_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
456/// Per-model FP8-KV door — lives in memra-kv next to the format policy it drives
457/// (re-export keeps `crate::KV_FP8_FORCE` setters in model.rs/hybrid.rs working).
458pub use memra_kv::KV_FP8_FORCE;
459pub(crate) fn rms_block() -> u32 {
460    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
461    *V.get_or_init(|| std::env::var("MEMRA_RMS_BLOCK").ok()
462        .and_then(|v| v.parse().ok())
463        .unwrap_or_else(|| RMS_BLOCK_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)))
464}
465
466pub(crate) fn fa_split_keys(t_kv: usize, n_head_kv: usize) -> usize {
467    static S: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
468    if let Some(forced) = *S.get_or_init(|| {
469        std::env::var("MEMRA_FA_SPLIT").ok().and_then(|v| v.parse().ok())
470            .filter(|&s: &usize| s >= 8 && s % 8 == 0)
471    }) { return forced; }
472    // CTX-ADAPTIVE default (2026-07-05 40k sweep: sp32 24.5 vs sp128 26.0 tok/s = +5.8% — at
473    // deep ctx the n_splits count explodes (40k/32 = 1265 splits x 8 kv-heads) and the combine
474    // + partial-buffer cost dominates; at short ctx small splits fill the SMs). Exactness: split
475    // size only changes the PARTITION of keys; the rows/combine order per split is fixed and the
476    // gate battery (kernel-check + run-spec K=1..8) arbitrates every default change.
477    //
478    // SM-AWARE SHORT-CTX RUNG (2026-07-06 g7e): the 32-key rung was tuned on the 82-SM 5090.
479    // On 188 SMs the vec grid (n_head_kv x n_splits CTAs) starves at short ctx — the 35B has
480    // n_head_kv=2, so ctx128/split32 = 8 CTAs on 188 SMs. Measured on g7e (N=1 sweep + N=3
481    // interleaved confirm): 35B ctx128 sp16 179 vs sp32 161 (+11%), ctx512 178 vs 158, ctx2048
482    // flat, ctx>=4096 sp64 edges sp16 by ~3%; 27B ctx128 70.9 vs 66.3 (+7%); 9B 177 vs 163
483    // (+9%). Rigs <=100 SMs keep the validated 5090 ladder EXACTLY (default unchanged there —
484    // rig-divergence law: this branch is measured on 188 SMs only).
485    // gemma4 all-16 ladder probe REVERTED (2026-07-10): +1.3 plain at d1736 (157.5 vs 156.2)
486    // but depth VERIFY collapsed (spec 203.5 -> 169 — the windowed rows' per-row combine over
487    // 64 splits). The mixed default (swa nkv=8 -> 32, globals nkv=2 -> 8-ladder) stays; a
488    // caller-split policy would break row-vs-decode split parity. FA_SP_GEMMA kept as a seam.
489    if FA_SP_GEMMA.load(std::sync::atomic::Ordering::Relaxed)
490        && std::env::var("MEMRA_FA_SP16").as_deref() == Ok("1") {
491        return if t_kv <= 8192 { 16 } else if t_kv <= 16384 { 64 } else { 128 };
492    }
493    let big_rig = fa_sm_count() >= 128;
494    if big_rig {
495        let _ = n_head_kv;
496        if t_kv <= 2048 { 16 } else if t_kv <= 16384 { 64 } else { 128 }
497    } else if n_head_kv <= 4 {
498        // KV-HEAD-AWARE RUNG (2026-07-08, 5090): the 8192->32 rung was validated on kv=8 models
499        // (27B/9B: 8 heads x n_splits fills 82 SMs). The 35B has n_head_kv=2 — at ctx512/sp32
500        // the vec grid is 2 x 20 = 40 CTAs on 82 SMs (half idle). Measured (35B, run-gen 128tok
501        // N=1 sweep + N=3 confirm): sp8 162.1 / sp16 161.3 / sp32 159.4 at short ctx.
502        // DEPTH TAPER (same day, the deep-ctx lesson re-learned on this rung): sp8 at d6257 =
503        // 782 splits -> combine + partial-buffer cost dominates (141.2 tok/s); the d6257 sweep
504        // says sp64 = 153.0 (sp16/32 147, sp96 147.6, sp128 141). Few-kv-head models need the
505        // taper EARLIER than kv=8 (per-split grid 4x thinner, same per-split combine cost).
506        // Crossover hunt: sp8 vs sp64 = 156.7/155.9 at d3072, 151.7/155.6 at d4096 -> boundary 3072.
507        // RUNG RE-SWEPT UNDER THE DEEP KERNEL (2026-08-02, lane/ladder-3072 — the stale-verdict
508        // law: the 3072 boundary was calibrated on the conflicted v4 core; the deep rewrite cut
509        // vec cost ~1.2-1.4x while combine scales with n_splits, so sp8's combine bill
510        // dominates far earlier). Kernel receipts (quiet-rig nsys, deep vec + combine us):
511        // d1024 sp8 17.1 vs sp64 10.6; d2048 31.0 vs 12.2; d3072 44.0 vs 18.3. e2e run-gen
512        // tg128 N=3 interleaved (KAT + q35, research/ladder-3072-20260802/): sp8 loses at
513        // EVERY depth >= 1024 (KAT d2048 182.6 vs 188.0 = -2.9%, d3072 175.9 vs 186.4 =
514        // -5.6%; q35 d4096 169.2 vs 182.6 = -7.4%); d512 flat (+-0.2%, inside noise). sp32
515        // ties sp64 within noise in the mid band and loses at d4096 -> no extra rung.
516        // Boundary 3072 -> 512: sp8 keeps only the short-ctx band it was validated on
517        // (ctx128-512); sp64 takes over where the deep kernel made combine the bill.
518        if t_kv <= 512 { 8 } else if t_kv <= 16384 { 64 } else { 128 }
519    } else {
520        if t_kv <= 8192 { 32 } else if t_kv <= 16384 { 64 } else { 128 }
521    }
522}
523
524/// SM count of device 0, cached (used by fa_split_keys' rig-size rung; primary-context query,
525/// same attribute Engine::batched_variant reads).
526fn fa_sm_count() -> i32 {
527    static N: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
528    *N.get_or_init(|| {
529        cudarc::driver::result::init().ok();
530        cudarc::driver::result::device::get(0)
531            .and_then(|d| unsafe { cudarc::driver::result::device::get_attribute(
532                d, cudarc::driver::sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) })
533            .unwrap_or(82)
534    })
535}
536
537/// FA-prefill kernel-name suffix for a head_dim (the template-stamped twins in flash_attn.cu):
538/// 256 = the original names (qwen35 class, dispatch unchanged), 128 = `_hd128` (MiniMax-M3).
539/// Any other dim errors — callers gate to sdpa_naive before dispatching FA.
540fn fa_hd_suffix(head_dim: usize) -> Result<&'static str, Box<dyn std::error::Error>> {
541    match head_dim {
542        256 => Ok(""),
543        128 => Ok("_hd128"),
544        d => Err(format!("fa_prefill: no kernel stamped for head_dim={d} (only 256/128); \
545                          callers must gate to sdpa_naive").into()),
546    }
547}
548
549/// Quant type codes matching qmatvec.cu QType enum.
550pub const QT_Q8_0: i32 = 0;
551pub const QT_Q4_K: i32 = 1;
552pub const QT_Q6_K: i32 = 2;
553pub const QT_Q5_K: i32 = 3;
554pub const QT_Q3_K: i32 = 4;
555pub const QT_IQ4_XS: i32 = 5;
556pub const QT_IQ3_S: i32 = 6;
557pub const QT_NVFP4: i32 = 7;
558/// Checkpoint-native FP8-E4M3 (MEMRA_ST_E4M3, lane e4m3dec): raw safetensors e4m3 weight bytes
559/// [out_f, in_f] row-major (row_bytes == in_f), per-tensor f32 weight_scale in GpuTensor `scale`
560/// (fused at the mmvq write / post-matmul scale_inplace). Decode = qmatvec_e4m3_mmvq (+ _b2/_b4/_b8
561/// batched twins); prefill (m>=16) = the cuBLASLt FP8 GEMM on the SAME resident bytes (fp8_ffi.rs)
562/// — ONE weight copy total, no Q8_0 re-encode duplicate.
563pub const QT_F8_E4M3: i32 = 10;
564/// Device-side tag for the A6 SPLIT-PLANE repacked NVFP4 layout (Stage-A generic kernel only;
565/// GpuTensor keeps qtype=QT_NVFP4 + an `rp` flag — this tag never lives in a GpuTensor).
566pub const QT_NVFP4_RP: i32 = 9;
567/// Unquantized f32 weight (safetensors MoE Path A: experts dequantized to f32 host-resident).
568pub const QT_F32: i32 = 8;
569pub const QT_BF16: i32 = 11;
570pub const QT_Q4_0: i32 = 12; // gemma-4 QAT GGUF weight format (18B/32: fp16 d + nibbles)
571/// GGUF Q2_K. Appended after the existing Q4_0 code so kernel ABI values do not move.
572/// Mixed-expert artifacts use the generic f32-dequant staged kernel until a target-rig-gated
573/// dp4a/MMQ implementation exists.
574pub const QT_Q2_K: i32 = 13;
575/// Checkpoint-native FP8-E4M3 with a BLOCK-128 weight-scale GRID (lane/fp8-blk128-decode,
576/// 2026-08-05) — the Qwen-official FP8 / DeepSeek-V3 scale class. Same raw e4m3 bytes as
577/// `QT_F8_E4M3` ([out_f, in_f] row-major, row_bytes == in_f), but the dequant scale is
578/// `GpuTensor::Quant.blk` (`Fp8BlockScales`, [ceil(out_f/128), ceil(in_f/128)] f32) and the
579/// scalar `scale` field is 1.0 by the layout contract.
580///
581/// WHY A DISTINCT CODE rather than `QT_F8_E4M3` + a `blk` flag: every existing QT_F8_E4M3
582/// consumer (qmatvec_e4m3_mmvq and its batched/fused twins, e4m3_fused_params,
583/// matmul_pre_dual_noscale's F8 arm, try_fp8_gemm) threads exactly ONE scalar weight scale. Under
584/// a shared code, any consumer that was not taught the grid would still MATCH and would dequant
585/// every tile at scale 1.0 — a silent numeric corruption. Under a distinct code every untaught
586/// consumer refuses loudly instead (`mmvq_supports`/`gemm_supports`/`mmq_supports` return false;
587/// the mmvq name match panics), so a missed dispatch site is a crash or a refusal receipt, never
588/// wrong numbers. Decode = `qmatvec_e4m3_blk_mmvq`; prefill (m>=16) = the per-block FP8 MMQ tile
589/// on the SAME resident bytes+grid (fp8_ffi::try_fp8_blk_mmq) — ONE weight copy total.
590pub const QT_F8_E4M3_BLK: i32 = 14;
591
592/// Engine device context: CUDA context, stream, loaded kernel modules, cuBLASLt (via runtime::Gpu).
593pub struct Engine {
594    pub gpu: memra_runtime::Gpu,
595    module: Arc<CudaModule>,
596    hybrid: Arc<CudaModule>,
597    qmatvec: Arc<CudaModule>,
598    flash: Arc<CudaModule>,
599    /// FP8-GLOBALS module (2026-07-11): the kf8vf8 fatbin loaded ALONGSIDE the default —
600    /// gemma GLOBAL layers (hd512) append + attend in e4m3 (dequant-latency arc, HANDOVER).
601    /// Lazy: loaded on first global-format use; None until then.
602    flash_g: std::sync::OnceLock<Arc<CudaModule>>,
603    gemm: Arc<CudaModule>,
604    router: Arc<CudaModule>,
605    /// Sampled-spec kernels (research/sampled-spec-impl-map.md piece A).
606    sample: Arc<CudaModule>,
607    /// EDGE-1 §B: one shared SLRU expert-residency cache, lazily built on first MoE dispatch under
608        /// MEMRA_MOE_CACHE. `Mutex` makes it multi-agent safe (§E.2); the lock covers only lookup/admit/
609    /// memcpy-issue (µs), NOT the GEMM, so streams still overlap. `None` => cache disabled.
610    moe_cache: Mutex<Option<crate::moe_cache::MoeSlotCache>>,
611    /// Exact retained expert-block lengths collected after model load. Mixed-layout models use
612    /// this inventory to preallocate fixed-address size classes instead of sizing every slot to
613    /// the single largest block. The cache still owns every address for its full lifetime.
614    moe_cache_layout: Mutex<Option<Vec<usize>>>,
615    /// CAPTURE-RETAIN mode (graph arc, 2026-07-12): while a graph capture (and its allocator
616    /// warmups) runs, every Engine allocation is ALSO kept alive here — a captured graph's
617    /// transient buffers must never return to the pool, or later allocations (e.g. the spec
618    /// verify between replays) reuse their addresses and the replay reads/writes live memory
619    /// (the draft-graph corruption root cause). Fast-path cost when off: one relaxed atomic.
620    capture_keep_on: std::sync::atomic::AtomicBool,
621    /// VERIFY-EXACT scope (dflash lane, 2026-07-13): when set, matmul/matmul_pre skip the
622    /// m>=16 prefill-GEMM branches so a t>=16 batched VERIFY rides the decode-exact b-tier
623    /// class (the parity law). The t=16 dflash verify tripped the GEMM threshold — 770us/
624    /// matmul (54% of the round) AND a different FP order than decode (issue-10 landmine).
625    verify_exact: std::sync::atomic::AtomicBool,
626    capture_keep: Mutex<Vec<Box<dyn std::any::Any + Send>>>,
627    /// EDGE-1 §C.2: dedicated H2D copy stream for async prefetch (event-synced to the compute stream).
628    pub copy_stream: Arc<CudaStream>,
629    /// Resident CUTLASS NVFP4 prefill scratch (workspace + a_packed + sfa_linear + sfa_sw + y + alpha),
630    /// allocated ONCE and grown to the largest prefill GEMM shape, then reused per-call. Removes the
631    /// 6 fresh allocations + alpha htod that `cutlass_fp4_gemm` did every prefill matmul (~200/prefill).
632    /// Safe as a single shared buffer because all GPU compute serializes on the one `gpu.stream` worker
633    /// thread (the server runs one GPU worker; no concurrent CUTLASS GEMMs share this scratch). `None`
634    /// until the first CUTLASS FP4 GEMM. Mutex guards lazy build/grow only (matches `moe_cache`).
635    #[cfg(memra_cutlass)]
636    cutlass_scratch: Mutex<Option<crate::cutlass_ffi::CutlassScratch>>,
637    /// FP8-ACT PREFILL scratch (MEMRA_PP_FP8): quantized-activation buffer + scale block + cuBLASLt
638    /// workspace, allocated once and grown to the largest prefill m*k (see fp8_ffi.rs). `None`
639    /// until the first FP8 prefill GEMM; Mutex guards lazy build/grow only (matches cutlass_scratch).
640    fp8_scratch: Mutex<Option<crate::fp8_ffi::Fp8Scratch>>,
641    /// f16-P/V door: pooled V re-encode buffer (bf16->f16) for the hd512 _pre path. Lazy-grow;
642    /// per-call cudaMalloc was a laptop-regression suspect (VRAM pressure, 31B nkv=4 = 4x bytes).
643    fa_vf16_scratch: Mutex<Option<CudaSlice<u8>>>,
644    /// Pooled fa-decode split partials (part_o, part_m, part_l): per-call zeros() was 3
645    /// alloc+memset pairs per fa launch (~144 mem nodes per decode token — the graph door's
646    /// residual launch tax) — lazy-grow, memset-prefix per use, stream-ordered reuse.
647    fa_part_pool: Mutex<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
648    /// Retired fa-part pool generations (#68): old buffers whose addresses captured graphs may
649    /// have baked — kept alive for the Engine's lifetime instead of returning to the async pool
650    /// (see the RETIRE-ON-GROW comment at the realloc sites). Doubling growth bounds the total.
651    fa_part_retired: Mutex<Vec<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
652    /// name -> resolved CudaFunction (capture-safe lookups; see `func`).
653    fn_cache: Mutex<std::collections::HashMap<String, CudaFunction>>,
654    f16_scratch: Mutex<Option<crate::f16_ffi::F16Scratch>>,
655    /// RANK1 LEVER (parallel argmax): resident pass-1 partials scratch (part_v[NB] f32, part_i[NB] i32),
656    /// allocated ONCE on first parallel-argmax call and reused. Stable pointers so the 2-pass argmax
657    /// is CUDA-graph-capturable (the buffer is referenced by both captured passes; lazy-allocated
658    /// before capture under the generate_graph tracking-off window so it carries no events).
659    argmax_partials: Mutex<Option<(CudaSlice<f32>, CudaSlice<i32>)>>,
660    /// ARC B (chunk-prime dequant-once): resident bf16 K/V workspace for `fa_prefill_view_ws`
661    /// ((K bytes, V bytes) u8 buffers holding [t_kv, kv_dim] bf16). Grown lazily to the largest
662    /// (t_kv, kv_dim) seen, REUSED across layers/chunks/calls (contents rewritten per launch —
663    /// safe because all compute serializes on the one gpu.stream). ~82MB at 40k ctx on the 27B.
664    prime_deqw_ws: Mutex<Option<(CudaSlice<u8>, CudaSlice<u8>)>>,
665    /// LAUNCH-STRUCTURE STAGE 1: persistent PINNED (cacheable, flags=0) host staging buffer for the
666    /// fused-router sel/w readback — one async DtoH pair + ONE sync instead of two synced dtohs.
667    /// Grown lazily; reused every MoE layer (single-threaded decode serializes on the sync).
668    router_stage: Mutex<Option<PinnedStage>>,
669}
670
671/// FAVENDOR lane env gate (2026-07-08): MEMRA_FA_V2=1 dispatches the llama-fattn-vec-mechanism
672/// decode kernels (fa_decode_vec_q_v2 / fa_decode_vec_q_rows_v2 / fa_decode_vec_q_v2_dc):
673/// tile-batched online softmax (one alpha rescale per 32-key tile instead of per key) + wide-load
674/// block dequant in the staging phase. NOTE rev2: llama's register streaming (no smem) was ALSO
675/// tried and measured 2x WORSE at depth in our gqa-warps frame — the smem KV-tile broadcast stays
676/// (see the kernel comment). NEW NUMERIC CONFIG (tile-level softmax regrouping changes FP order vs
677/// the per-key twins) — own argmax baseline; eager decode, the spec-verify rows path AND the
678/// graph _dc path switch TOGETHER (the spec-exactness law). Default OFF. Read per call (not
679/// OnceLock) so the gate battery can A/B within one process, matching the MEMRA_NO_FA_VEC pattern.
680fn fa_v2_on() -> bool {
681    // DEFAULT ON since 2026-07-08 (MEMRA_FA_V2=0 reverts): tile-batched online softmax, e2e
682    // measured across every model x depth — 35B 168.7->173.4 (d512) / 153.1->158.5 (d6257),
683    // 9B 131.2->132.7 / 108.4->124.5 (+15% — the engine-wide depth-slope fix), 27B 47.2->47.7 /
684    // 42.2->44.9. One-time numeric-config change; kernel-check + argmax + spec self-consistency
685    // + graph bit-identity green on all three models.
686    std::env::var("MEMRA_FA_V2").map(|v| v != "0").unwrap_or(true)
687}
688
689/// FA v3 gate (default ON since 2026-07-09; MEMRA_FA_V3=0 reverts to v2 — research/fa/fa_v3_design.md):
690/// HYBRID decode twins (fa_decode_vec_q_v3 / _rows_v3 / _v3_dc): llama's int8-dp4a K.Q with
691/// register-quantized Q (no K dequant, no K smem) + OUR CTA-shared staged bf16 V tile + OUR
692/// split partition/combine. NEW NUMERIC CONFIG (int8 Q quantization changes the K.Q accumulation
693/// vs the bf16-roundtrip FMA chain) — own argmax baseline; eager decode, the spec-verify rows
694/// path AND the graph _dc path switch TOGETHER (the spec-exactness law). Read per call so the
695/// gate battery can A/B within one process (the MEMRA_FA_V2 pattern).
696fn fa_v3_on() -> bool {
697    // DEFAULT ON since 2026-07-09 (MEMRA_FA_V3=0 reverts to v2): dp4a-K hybrid FA decode —
698    // fa kernel -21-23% at depth (micro), 35B spec p3 +5% (190->200, the last spec cell),
699    // d6257 +1.7%. Own numeric config; full battery green on 35B+9B incl graph bit-identity.
700    std::env::var("MEMRA_FA_V3").map(|v| v != "0").unwrap_or(true)
701}
702
703/// The v3 dp4a K path reads RAW q8_0 bytes (34B blocks) and stages q5_1 V verbatim — it is only
704/// correct on the DEFAULT KV formats — and needs dpl % 4 == 0 consecutive quants per lane
705/// (head_dim % 128 == 0; both daily models are hd256). All three dispatch sites share this
706/// predicate so the twins can never diverge.
707fn fa_v4_mode() -> &'static str {
708    static M: std::sync::OnceLock<String> = std::sync::OnceLock::new();
709    M.get_or_init(|| std::env::var("MEMRA_FA_V4").unwrap_or_default())
710}
711fn fa_v4_on() -> bool { fa_v4_mode() != "0" }   // DEFAULT ON 2026-07-10 (MEMRA_FA_V4=0 rollback)
712/// t_kv-conditional v4 pick (gemma depth lesson 2026-07-10: v4's key-per-lane pipeline starves
713/// at the 1024-window with short splits — MEMRA_FA_V4=0 measured depth plain 158.0 vs 156.7).
714/// Threshold MEMRA_FA_V4_MAX (default usize::MAX = unchanged behavior; gemma sets 1024 at load
715/// via FA_V4_MAX_DEFAULT). Applied at EVERY dispatch site (eager, rows, rows_w, dc) so verify
716/// stays kernel-family-identical to decode at the same t_kv.
717/// Per-model deep-ctx smem floor default (MEMRA_FA_SMEM_TKV env overrides): gemma pushes it
718/// above the 1024 window so the windowed decode + verify rows share the REGISTER family.
719pub static FA_SMEM_TKV_DEFAULT: std::sync::atomic::AtomicUsize =
720    std::sync::atomic::AtomicUsize::new(1024);
721pub static FA_V4_MAX_DEFAULT: std::sync::atomic::AtomicUsize =
722    std::sync::atomic::AtomicUsize::new(usize::MAX);
723pub fn fa_v4_at_pub(t_kv: usize) -> bool { fa_v4_at(t_kv) }
724fn fa_v4_at(t_kv: usize) -> bool {
725    static M: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
726    let mx = *M.get_or_init(|| std::env::var("MEMRA_FA_V4_MAX").ok()
727        .and_then(|v| v.parse().ok())
728        .unwrap_or_else(|| FA_V4_MAX_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)));
729    fa_v4_on() && t_kv < mx
730}
731/// FA-DEEP gate (2026-08-02, lane fa-decode-deep): deep-ctx v4 twins
732/// (fa_decode_vec_q_v4_deep / _deep_dc) — the depth-decode lane's priced fix. Unlike
733/// v2/v3/v4 this is NOT a numeric config: the deep twins run the v4 program VERBATIM
734/// (same split partition, same softmax/accumulation order, same partials/combine) and only
735/// move the smem physical layout (bank de-conflict row pads) + the load schedule (next-tile
736/// L2 prefetch) — kernel-check pins bitdiff==0 vs the v4 twins across depths, so eager /
737/// rows-verify / graph / seqs stay mutually bit-identical wherever the threshold falls.
738/// Engages at t_kv >= MEMRA_FA_DEEP_MIN. The swept floor is 0 = ALWAYS ON where v4 ran
739/// (fa-deep-bench fine grid 96..6144, 2026-08-02: deep flat-or-better at EVERY depth,
740/// 1.01-1.26x, no losing cell — so there is no engagement boundary and no new
741/// capture-recapture edge; the env stays as a sweep/diagnostic seam only).
742/// MEMRA_FA_DEEP=0 is the rollback seam. Read per call so the battery + bench can A/B
743/// within one process (the v2/v3 pattern).
744pub const FA_DEEP_MIN_DEFAULT: usize = 0;
745fn fa_deep_at(t_kv: usize) -> bool {
746    if std::env::var("MEMRA_FA_DEEP").as_deref() == Ok("0") { return false; }
747    let min = std::env::var("MEMRA_FA_DEEP_MIN").ok().and_then(|v| v.parse().ok())
748        .unwrap_or(FA_DEEP_MIN_DEFAULT);
749    t_kv >= min
750}
751/// Public twin (kernel-check builds the deep-vs-v4 bit pin; bench sweeps the floor).
752pub fn fa_deep_at_pub(t_kv: usize) -> bool { fa_deep_at(t_kv) }
753
754fn fa_v3_active(head_dim: usize) -> bool {
755    // v3's dp4a-K walk reads raw q8_0 K bytes — no e4m3 arm; the fp8-KV arm (MEMRA_KV_FP8)
756    // must fall back like any non-default KV format (the rows_dc stream path asserts on it).
757    fa_v3_on() && head_dim % 128 == 0 && kv_cache_formats() == ("q8_0", "q5_1")
758        && !Engine::kv_fp8_on()
759}
760
761/// BATCHED-TICK increment 2 (2026-08-01): true iff a row at this t_kv would take the v4
762/// eager arm in `fa_decode_kvmod`'s dispatch — the exact precondition for the z-batched
763/// `fa_decode_vec_q_seqs_v4` twin to reproduce its per-seq program bit-identically.
764/// Mirrors the kvmod predicates: vec on + above the vec floor + hd256 + inside the v4
765/// window + the PRODUCTION v4 body (the noB3/stage phase probes are wrong-output) + the
766/// default flash module (no fp8-KV g-module). Callers must ALSO group rows on one
767/// `fa_split_keys` rung (the rows-twins' straddle law) before batching.
768pub fn fa_seqs_eligible(t_kv: usize, head_dim: usize) -> bool {
769    std::env::var("MEMRA_NO_FA_VEC").is_err()
770        && t_kv >= fa_vec_min_tkv()
771        && head_dim == 256
772        && fa_v4_at(t_kv)
773        && !matches!(fa_v4_mode(), "noB3" | "stage")
774        && !Engine::kv_fp8_on()
775}
776/// Public twin of the crate-private split ladder (kernel-check builds the seqs-vs-loop pin).
777pub fn fa_split_keys_pub(t_kv: usize, n_head_kv: usize) -> usize { fa_split_keys(t_kv, n_head_kv) }
778
779/// A raw pinned (page-locked, CACHEABLE — flags=0, not write-combined) host allocation for
780/// DtoH staging. cudarc's `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED, which is right for
781/// HtoD streams but pathologically slow for host READS — the router readback is host-read-heavy,
782/// so we allocate through `result::malloc_host` with flags=0 directly.
783struct PinnedStage {
784    ptr: *mut u8,
785    cap: usize,
786}
787unsafe impl Send for PinnedStage {}
788impl PinnedStage {
789    fn new(cap: usize) -> Result<Self, Box<dyn std::error::Error>> {
790        let ptr = unsafe { cudarc::driver::result::malloc_host(cap, 0)? } as *mut u8;
791        Ok(PinnedStage { ptr, cap })
792    }
793}
794impl Drop for PinnedStage {
795    fn drop(&mut self) {
796        let _ = unsafe { cudarc::driver::result::free_host(self.ptr as _) };
797    }
798}
799
800/// Number of pass-1 blocks for the parallel argmax (fan-out across SMs to saturate HBM). 256 blocks
801/// x 256 threads = 65536 threads covering the 248K-vocab scan in ~4 strided loads/thread.
802pub const ARGMAX_NB: usize = 256;
803
804/// crate-visible alias for the batched FA3 shim entry (hybrid_forward's batch arm).
805pub(crate) use memra_fa3_vl as fa3_vl_raw;
806
807unsafe extern "C" {
808    /// FA3 v10 shim (cu/fa3_prefill.cu): TMA-swizzled wgmma FA, fresh causal hd256.
809    fn memra_fa3_prefill(q16: *const core::ffi::c_void, k16: *const core::ffi::c_void,
810                        v16: *const core::ffi::c_void, o: *mut f32,
811                        t: i32, h: i32, hkv: i32, d: i32, scale: f32,
812                        stream: *mut core::ffi::c_void) -> i32;
813    /// batched varlen twin: host arrays of device pointers per seq (B <= 8).
814    pub(crate) fn memra_fa3_vl(q16s: *const *const core::ffi::c_void, k16s: *const *const core::ffi::c_void,
815                   v16s: *const *const core::ffi::c_void, os: *const *mut f32,
816                   ts: *const i32, b: i32, h: i32, hkv: i32, d: i32, scale: f32,
817                   stream: *mut core::ffi::c_void) -> i32;
818}
819
820/// STAGE-2 GROUPED DECODE: 8 expert weight-block device pointers passed BY VALUE as one kernel
821/// param (matches the CUDA `wptr8_t` struct: 8x 64-bit pointers, `#[repr(C)]` => identical
822/// layout). The pointers are SLRU cache-slot base addresses — fixed for the engine's lifetime
823/// (slots are never re-allocated), so passing raw values is stable across the launch.
824#[repr(C)]
825#[derive(Clone, Copy)]
826pub struct WPtr8(pub [u64; 8]);
827unsafe impl cudarc::driver::DeviceRepr for WPtr8 {}
828
829/// task #18 varlen GDN: per-seq args for gdn_chunk_{state,output}_mma_vl — one launch
830/// runs all B<=8 sequences' K4/K5 (CUDA `gdnseq_t`/`gdnvl_t`, layout-identical repr(C)).
831/// Raw addresses are valid for the launch: every referenced buffer outlives the call and
832/// all work is on the single compute stream (same discipline as the f16 GEMM FFI).
833#[repr(C)]
834#[derive(Clone, Copy, Default)]
835pub struct GdnSeqVl {
836    pub kb16: u64, pub gcum: u64, pub beta: u64, pub u: u64, pub wb16: u64,
837    pub y: u64, pub ssnap: u64, pub state_in: u64, pub state_out: u64,
838    pub q: u64, pub p: u64, pub o: u64,
839    pub k: u64, pub v: u64, pub g: u64, pub a: u64, pub w: u64,
840    pub t: i32, pub nc: i32,
841}
842unsafe impl cudarc::driver::DeviceRepr for GdnSeqVl {}
843#[repr(C)]
844#[derive(Clone, Copy)]
845pub struct GdnVl8(pub [GdnSeqVl; 8]);
846unsafe impl cudarc::driver::DeviceRepr for GdnVl8 {}
847
848/// task #22: per-seq wgmma-fused extras (CUDA `gdnw_t`/`gdnwvl_t`) — qb16 mirror +
849/// pre-masked Pb16, riding NEXT TO GdnSeqVl so the base struct stays untouched.
850#[repr(C)]
851#[derive(Clone, Copy, Default)]
852pub struct GdnWVl { pub qb16: u64, pub pb16: u64 }
853unsafe impl cudarc::driver::DeviceRepr for GdnWVl {}
854#[repr(C)]
855#[derive(Clone, Copy)]
856pub struct GdnWVl8(pub [GdnWVl; 8]);
857unsafe impl cudarc::driver::DeviceRepr for GdnWVl8 {}
858
859/// task #18 increment 3: per-seq PREP/TAIL args (CUDA `gdnprep_t`/`gdnprepvl_t`).
860#[repr(C)]
861#[derive(Clone, Copy, Default)]
862pub struct GdnPrepVl {
863    pub qkv: u64, pub conv_state: u64, pub conv_out: u64,
864    pub q_g: u64, pub k_g: u64, pub v_g: u64,
865    pub q_l2: u64, pub k_l2: u64,
866    pub beta_raw: u64, pub alpha: u64, pub beta: u64, pub g_log: u64,
867    pub o: u64, pub z: u64, pub gn: u64, pub gn16: u64,
868    pub kb16: u64,
869    pub qb16: u64,
870    pub t: i32, pub pad: i32,
871}
872unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl {}
873#[repr(C)]
874#[derive(Clone, Copy)]
875pub struct GdnPrepVl8(pub [GdnPrepVl; 8]);
876unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl8 {}
877
878/// task #18 (attn side): per-seq varlen FA args (CUDA `faseq_t`/`favl_t`).
879#[repr(C)]
880#[derive(Clone, Copy, Default)]
881pub struct FaSeqVl {
882    pub q: u64, pub k16: u64, pub v16: u64, pub o: u64, pub kf: u64, pub vf: u64,
883    pub t: i32, pub pad: i32,
884}
885unsafe impl cudarc::driver::DeviceRepr for FaSeqVl {}
886#[repr(C)]
887#[derive(Clone, Copy)]
888pub struct FaVl8(pub [FaSeqVl; 8]);
889unsafe impl cudarc::driver::DeviceRepr for FaVl8 {}
890
891/// task #18 (attn pre-FA): per-seq split/norm/rope/append args (CUDA `attnpre_t`).
892#[repr(C)]
893#[derive(Clone, Copy, Default)]
894pub struct AttnPreVl {
895    pub qf: u64, pub kf: u64, pub vf: u64,
896    pub q: u64, pub gate: u64, pub qn: u64, pub kn: u64,
897    pub kc: u64, pub vc: u64,
898    pub t: i32, pub pad: i32,
899}
900unsafe impl cudarc::driver::DeviceRepr for AttnPreVl {}
901#[repr(C)]
902#[derive(Clone, Copy)]
903pub struct AttnPreVl8(pub [AttnPreVl; 8]);
904unsafe impl cudarc::driver::DeviceRepr for AttnPreVl8 {}
905
906/// task #18 increment 2: one sequence's FULL chunk-buffer set (alloc-only; the
907/// varlen K1-K5 chain fills them).
908pub struct GdnChunkBufs {
909    pub gcum: CudaSlice<f32>,
910    pub a: CudaSlice<f32>,
911    pub p: CudaSlice<f32>,
912    pub u: CudaSlice<f32>,
913    pub w: CudaSlice<f32>,
914    pub kb16: CudaSlice<u8>,
915    pub wb16: CudaSlice<u8>,
916    pub y16: CudaSlice<u8>,
917    pub ssnap16: CudaSlice<u8>,
918    pub qb16: CudaSlice<u8>,
919    pub pb16: CudaSlice<u8>,
920    pub o: CudaSlice<f32>,
921    pub t: usize,
922    pub nc: usize,
923}
924
925/// STAGE-2 GROUPED DECODE: the 8 routed-expert weights by value (CUDA `f32x8_t`).
926#[repr(C)]
927#[derive(Clone, Copy)]
928pub struct F32x8(pub [f32; 8]);
929unsafe impl cudarc::driver::DeviceRepr for F32x8 {}
930
931/// Harness timing contract: wall nanos of the LAST generate/generate_spec prompt prime on this
932/// process. Bench binaries read it right after the call to print gen-only throughput without the
933/// prime-subtraction hack (which amplifies prime jitter into the gen number at long prompts).
934pub static PRIME_NANOS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
935
936impl Engine {
937    pub fn new(ordinal: usize) -> Result<Self, Box<dyn std::error::Error>> {
938        let gpu = memra_runtime::Gpu::new(ordinal)?;
939        // ARCH GUARD (unified dual-arch engine): the fatbins carry single-arch SASS, so a
940        // binary/device mismatch otherwise dies at first module load with an opaque CUDA
941        // error. Fail early with the rebuild hint instead. MEMRA_ARCH_CHECK=0 skips.
942        if std::env::var("MEMRA_ARCH_CHECK").as_deref() != Ok("0") {
943            use cudarc::driver::sys::CUdevice_attribute_enum as A;
944            let (maj, min) = cudarc::driver::result::device::get(ordinal as i32)
945                .and_then(|d| unsafe { Ok((
946                    cudarc::driver::result::device::get_attribute(d, A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR)?,
947                    cudarc::driver::result::device::get_attribute(d, A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR)?)) })
948                .unwrap_or((0, 0));
949            let built = env!("MEMRA_BUILT_CUDA_ARCH");
950            let ok = matches!((built, maj, min),
951                ("120a", 12, 0) | ("120a", 12, 1) | ("100a", 10, 0) | ("90a", 9, 0) | ("89", 8, 9));
952            if !ok {
953                return Err(format!(
954                    "memra was built for sm_{built} but device {ordinal} reports compute \
955                     capability {maj}.{min}. Rebuild on this machine (MEMRA_CUDA_ARCH \
956                     auto-detects the GPU) or set MEMRA_ARCH_CHECK=0 to bypass.").into());
957            }
958        }
959        // Default async-pool RELEASE_THRESHOLD is 0: freed blocks return to the OS at every
960        // sync, so cuMemAllocAsync NODES inside captured graphs re-map memory on EVERY
961        // cuGraphLaunch (measured 226us/launch on the gemma graph door, 2026-07-23 osrt).
962        // Pinning the threshold keeps the pool cached -> alloc nodes become pointer bumps.
963        unsafe {
964            use cudarc::driver::sys;
965            let dev: sys::CUdevice = ordinal as sys::CUdevice;
966            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
967            if sys::cuDeviceGetDefaultMemPool(&mut pool, dev) == sys::CUresult::CUDA_SUCCESS {
968                let mut thresh: u64 = u64::MAX;
969                let _ = sys::cuMemPoolSetAttribute(
970                    pool,
971                    sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
972                    &mut thresh as *mut u64 as *mut core::ffi::c_void,
973                );
974            }
975        }
976        let module = gpu.ctx.load_module(Ptx::from_binary(FATBIN.to_vec()))?;
977        let hybrid = gpu.ctx.load_module(Ptx::from_binary(HYBRID_FATBIN.to_vec()))?;
978        let qmatvec = gpu.ctx.load_module(Ptx::from_binary(QMATVEC_FATBIN.to_vec()))?;
979        let flash = gpu.ctx.load_module(Ptx::from_binary(flash_fatbin_bytes().to_vec()))?;
980        let gemm = gpu.ctx.load_module(Ptx::from_binary(gemm_fatbin_bytes().into_owned()))?;
981        let router = gpu.ctx.load_module(Ptx::from_binary(ROUTER_FATBIN.to_vec()))?;
982        let sample = gpu.ctx.load_module(Ptx::from_binary(SAMPLE_FATBIN.to_vec()))?;
983        let copy_stream = gpu.ctx.new_stream()?;
984        // DECODE EVENT-TRACKING ELISION — DEFAULT ON (2026-07-05; MEMRA_EVT=1 = escape hatch).
985        // cudarc is in multi-stream mode (main stream +
986        // copy_stream are both created streams), so with tracking on EVERY launch arg records a
987        // read/write CudaEvent and inserts cuStreamWaitEvent on prior events. On the 35B MoE decode
988        // that is ~19k cuStreamWaitEvent + ~9k cuEventRecord + ~6k event create/destroy per token
989        // (~7 ms/tok host time, measured nsys 2026-07-04 g7e), and +4.6% measured on 27B decode —
990        // protecting NOTHING: every hot-path kernel/memcpy runs on the ONE gpu.stream.
991        // CROSS-STREAM HAZARD AUDIT: MoeSlotCache in-memory prefetch uses copy_stream. Every
992        // overwrite explicitly records the prior compute point and makes copy_stream wait; every
993        // consumer explicitly waits for the copy completion event. The opt-in positioned-read
994        // proof stays on gpu.stream and retains an explicit event solely to guard pinned-source
995        // reuse. Graph-capture sites use only gpu.stream, so these handoffs never rely on cudarc's
996        // implicit event tracking.
997        // SAFETY: single-stream ordering is total; the runtime mem-pool is configured with
998        // internal-dependency reuse (memra-runtime), so alloc reuse is stream-ordered too.
999        if std::env::var("MEMRA_EVT").map(|v| v == "1").unwrap_or(false) {
1000            // escape hatch: keep cudarc's implicit cross-stream event tracking.
1001        } else {
1002            unsafe { gpu.ctx.disable_event_tracking(); }
1003        }
1004        Ok(Self { gpu, module, hybrid, qmatvec, flash, flash_g: std::sync::OnceLock::new(), gemm, router, sample,
1005                  moe_cache: Mutex::new(None),
1006                  moe_cache_layout: Mutex::new(None),
1007                  copy_stream,
1008                  capture_keep_on: std::sync::atomic::AtomicBool::new(false),
1009                  verify_exact: std::sync::atomic::AtomicBool::new(false),
1010                  capture_keep: Mutex::new(Vec::new()),
1011                  argmax_partials: Mutex::new(None),
1012                  prime_deqw_ws: Mutex::new(None),
1013                  router_stage: Mutex::new(None),
1014                  fp8_scratch: Mutex::new(None),
1015                  fa_vf16_scratch: Mutex::new(None),
1016                  fa_part_pool: Mutex::new(None),
1017                  fa_part_retired: Mutex::new(Vec::new()),
1018                  fn_cache: Mutex::new(Default::default()),
1019                  f16_scratch: Mutex::new(None),
1020                  #[cfg(memra_cutlass)]
1021                  cutlass_scratch: Mutex::new(None) })
1022    }
1023
1024    pub fn ctx(&self) -> &Arc<CudaContext> { &self.gpu.ctx }
1025
1026    /// Bytes the async pool holds MAPPED but NOT LIVE (reserved - used), i.e. freed blocks
1027    /// parked in the pool because `Engine::new` pins RELEASE_THRESHOLD to u64::MAX above.
1028    ///
1029    /// Why this is a public engine surface: `mem_get_info`'s `free` DOES NOT SEE these bytes —
1030    /// they are mapped to this process, so `free` counts them as gone, yet the very next
1031    /// `alloc_u8` is satisfied from them without touching `free` at all. Any admission or
1032    /// budget decision that reads `free` alone therefore under-counts real headroom by exactly
1033    /// this amount. Effective allocatable headroom is `free + pool_cached_bytes()`.
1034    ///
1035    /// MEASURED SIZE (c=64 serve burst, 9B NVFP4 + draft, 24GB card, 2026-08-06): 34-89 MB
1036    /// during the burst — SMALL. The admission gate adds it because a term that can only ever
1037    /// under-count headroom does not belong in a gate that queues real work, but the honest
1038    /// reading of this number is that pool caching is NOT where a long-running server's VRAM
1039    /// hides on this path: reserved ~= used throughout, so the memory the driver reports as
1040    /// gone is genuinely LIVE (see `pool_reserved_used` for the diagnostic pair).
1041    ///
1042    /// Returns 0 if the pool cannot be queried (never a false-positive headroom claim).
1043    pub fn pool_cached_bytes(&self) -> usize {
1044        let (reserved, used) = self.pool_reserved_used();
1045        reserved.saturating_sub(used)
1046    }
1047
1048    /// Raw async-pool occupancy: (RESERVED_MEM_CURRENT, USED_MEM_CURRENT) in bytes. Reserved is
1049    /// what the pool has mapped from the driver; used is what is live inside it. Exposed for
1050    /// admission/VRAM diagnostics — the pair distinguishes "memory is parked in the pool and
1051    /// `free` cannot see it" (reserved >> used) from "memory is genuinely held live by some
1052    /// owner" (reserved ~= used), which are opposite bugs with opposite fixes.
1053    /// (0, 0) if the pool cannot be queried.
1054    pub fn pool_reserved_used(&self) -> (usize, usize) {
1055        use cudarc::driver::sys;
1056        unsafe {
1057            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1058            if sys::cuDeviceGetDefaultMemPool(&mut pool, self.gpu.ctx.ordinal() as sys::CUdevice)
1059                != sys::CUresult::CUDA_SUCCESS
1060            {
1061                return (0, 0);
1062            }
1063            let (mut reserved, mut used) = (0u64, 0u64);
1064            if sys::cuMemPoolGetAttribute(
1065                pool,
1066                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
1067                &mut reserved as *mut u64 as *mut core::ffi::c_void,
1068            ) != sys::CUresult::CUDA_SUCCESS {
1069                return (0, 0);
1070            }
1071            if sys::cuMemPoolGetAttribute(
1072                pool,
1073                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
1074                &mut used as *mut u64 as *mut core::ffi::c_void,
1075            ) != sys::CUresult::CUDA_SUCCESS {
1076                return (0, 0);
1077            }
1078            (reserved as usize, used as usize)
1079        }
1080    }
1081
1082    /// Ambient stream (by value since M1-PP2 increment 2): the thread's pp2 stage stream
1083    /// when a stage scope is active, else the main compute stream — see `Gpu::stream`.
1084    pub fn stream(&self) -> Arc<CudaStream> { self.gpu.stream() }
1085    /// FP8-GLOBALS switch (MEMRA_GEMMA_GKV, default ON): gemma global (hd512) layers keep
1086    /// their KV in e4m3 — the dequant-latency arc (HANDOVER). Windowed layers stay q8_0/q5_1.
1087    pub fn gkv_on() -> bool {
1088        memra_kv::gkv_on()
1089    }
1090
1091    /// FP8-WINDOWED switch (MEMRA_GEMMA_WKV — measured 2026-07-12 in a validity-gated
1092    /// window: 1.7k 174.1-174.4 vs 168.6-169.4 default (+3%), 4.9k 158.7-160.4; vs llama
1093    /// same-window 159.5-160.2 / 140.6 = 1.09x / 1.13x): gemma windowed (hd256 SWA)
1094    /// layers hold e4m3 KV and ride the format-aware v4 lane from the kf8vf8 module.
1095    /// SERVING-MODE DEFAULT (2026-07-12, the 31B spec unlock): fp8-windowed KV GUTS the
1096    /// MTP drafter's acceptance — its single swa attention reads the windowed cache and
1097    /// e4m3 noise flips its argmaxes (31B short accept .758 -> 1.000 with q8/q5, spec 88
1098    /// -> 122.7 vs llama-mtp 112; depth .59 -> .78; 26B depth .57 -> .89). So the default
1099    /// keys on serving intent: SPEC serving (MEMRA_DRAFT set) -> OFF, plain -> ON (its
1100    /// depth-plain +3% stands). Explicit MEMRA_GEMMA_WKV always wins. GKV (globals) stays
1101    /// ON for both — no acceptance cost measured.
1102    pub fn wkv_on() -> bool {
1103        memra_kv::wkv_on()
1104    }
1105
1106    /// QWEN FP8-KV switch (MEMRA_KV_FP8 explicit; else the per-model KV_FP8_FORCE door set
1107    /// at model load; else OFF). Non-gemma full-attn layers hold e4m3 K/V via the kf8vf8
1108    /// module. Per-model verdict 2026-07-12: 9B +0.7-4% scaling with depth, 27B flat,
1109    /// 35B −2% (fp8 format-gates its v3 dp4a lane) — so the 9B class defaults ON
1110    /// (adopted 2026-07-28 with the deferred acceptance battery), others stay OFF.
1111    pub fn kv_fp8_on() -> bool {
1112        memra_kv::kv_fp8_on()
1113    }
1114
1115    /// fa kernel routed by head_dim: hd512 (gemma globals) resolves from the kf8vf8 module
1116    /// when the fp8-globals arm is on; everything else from the default flash module.
1117    fn fa_func(&self, name: &str, head_dim: usize) -> CudaFunction {
1118        if head_dim == 512 && Self::gkv_on() { self.func_g(name) } else { self.func(name) }
1119    }
1120
1121    /// Kernel from the FP8-GLOBALS (kf8vf8) flash module — gemma global-layer arm only.
1122    /// Format-AGNOSTIC kernels (e.g. fa_decode_combine_f32) are not compiled into the
1123    /// per-format fatbins; fall back to the base modules for those.
1124    fn func_g(&self, name: &str) -> CudaFunction {
1125        let m = self.flash_g.get_or_init(|| {
1126            self.gpu.ctx.load_module(cudarc::nvrtc::Ptx::from_binary(FLASH_FATBIN_KF8VF8.to_vec()))
1127                .expect("load kf8vf8 flash fatbin (fp8-globals arm)")
1128        });
1129        let key = format!("g:{name}");
1130        if let Some(f) = self.fn_cache.lock().unwrap().get(&key) { return f.clone(); }
1131        let f = match m.load_function(name) {
1132            Ok(f) => f,
1133            Err(_) => self.func(name),
1134        };
1135        self.fn_cache.lock().unwrap().insert(key, f.clone());
1136        f
1137    }
1138
1139    fn func(&self, name: &str) -> CudaFunction {
1140        // Resolution cache: cuModuleGetFunction fails inside a CUDA-graph capture region,
1141        // so capture-time lookups MUST be host-memory hits (warmups populate the cache).
1142        if let Some(f) = self.fn_cache.lock().unwrap().get(name) { return f.clone(); }
1143        let f = self.module.load_function(name)
1144            .or_else(|_| self.hybrid.load_function(name))
1145            .or_else(|_| self.qmatvec.load_function(name))
1146            .or_else(|_| self.flash.load_function(name))
1147            .or_else(|_| self.gemm.load_function(name))
1148            .or_else(|_| self.router.load_function(name))
1149            .or_else(|_| self.sample.load_function(name))
1150            .unwrap_or_else(|_| panic!("kernel {name} not in any fatbin"));
1151        self.fn_cache.lock().unwrap().insert(name.to_string(), f.clone());
1152        f
1153    }
1154
1155    /// Scatter trimmed draft logits into full-vocab space: dst = -inf everywhere, then
1156    /// dst[d2t[i]] = src[i]. Two launches (fill, scatter) — no grid-wide sync needed.
1157    pub fn scatter_trim_logits(&self, src: &CudaSlice<f32>, d2t: &CudaSlice<u32>,
1158                               dst: &mut CudaSlice<f32>, d_vocab: usize, n_vocab: usize)
1159                               -> Result<(), Box<dyn std::error::Error>> {
1160        let f1 = self.func("scatter_trim_logits_f32");
1161        let f2 = self.func("scatter_trim_logits_pass2_f32");
1162        let (dv, nv) = (d_vocab as i32, n_vocab as i32);
1163        let cfg1 = LaunchConfig { grid_dim: (256, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1164        let __s_b1 = self.gpu.stream();
1165        let mut b1 = __s_b1.launch_builder(&f1);
1166        b1.arg(src).arg(d2t).arg(&mut *dst).arg(&dv).arg(&nv);
1167        unsafe { b1.launch(cfg1)?; }
1168        let cfg2 = LaunchConfig { grid_dim: (d_vocab.div_ceil(256) as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1169        let __s_b2 = self.gpu.stream();
1170        let mut b2 = __s_b2.launch_builder(&f2);
1171        b2.arg(src).arg(d2t).arg(&mut *dst).arg(&dv);
1172        unsafe { b2.launch(cfg2)?; }
1173        Ok(())
1174    }
1175
1176    // ---- FILTERED-SPEC (feat/filtered-spec): top-k/p/min-p transforms applied symmetrically
1177    // to p and q — rejection sampling stays distribution-exact for the filtered target. ----
1178
1179    /// Per-row filtered-softmax stats: out[r] = (threshold_e, renorm_mass_e, row_max) for the
1180    /// filter (top_k, top_p, min_p) at `temp`. Rows index into x with row_stride f32s.
1181    #[allow(clippy::too_many_arguments)]
1182    pub fn filter_stats(&self, x: &CudaSlice<f32>, row_stride: usize, rows: &CudaSlice<i32>,
1183                        out_th: &mut CudaSlice<f32>, out_z: &mut CudaSlice<f32>,
1184                        out_max: &mut CudaSlice<f32>, n: usize, nrow: usize,
1185                        temp: f32, top_k: i32, top_p: f32, min_p: f32)
1186                        -> Result<(), Box<dyn std::error::Error>> {
1187        let f = self.func("filter_stats_f32");
1188        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1189        let cfg = LaunchConfig { grid_dim: (nrow as u32, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
1190        let __s_b = self.gpu.stream();
1191        let mut b = __s_b.launch_builder(&f);
1192        b.arg(x).arg(&rs).arg(rows).arg(&mut *out_th).arg(&mut *out_z).arg(&mut *out_max)
1193         .arg(&ni).arg(&nr).arg(&temp).arg(&top_k).arg(&top_p).arg(&min_p);
1194        unsafe { b.launch(cfg)?; }
1195        Ok(())
1196    }
1197
1198    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1199    #[allow(clippy::too_many_arguments)]
1200    pub fn softmax_gather_filtered(&self, x: &CudaSlice<f32>, row_stride: usize,
1201                                   ids: &CudaSlice<u32>, rows: &CudaSlice<i32>,
1202                                   th: &CudaSlice<f32>, z: &CudaSlice<f32>,
1203                                   out: &mut CudaSlice<f32>, n: usize, npair: usize, temp: f32)
1204                                   -> Result<(), Box<dyn std::error::Error>> {
1205        let f = self.func("softmax_gather_filtered_f32");
1206        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1207        let cfg = LaunchConfig { grid_dim: (npair as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1208        let __s_b = self.gpu.stream();
1209        let mut b = __s_b.launch_builder(&f);
1210        b.arg(x).arg(&rs).arg(ids).arg(rows).arg(th).arg(z).arg(&mut *out).arg(&ni).arg(&np).arg(&temp);
1211        unsafe { b.launch(cfg)?; }
1212        Ok(())
1213    }
1214
1215    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1216    #[allow(clippy::too_many_arguments)]
1217    pub fn residual_sample_filtered(&self, p: &CudaSlice<f32>, q: Option<&CudaSlice<f32>>, n: usize,
1218                                    temp: f32, seed: u64, stream_pos: u32,
1219                                    p_stats: (f32, f32, f32), q_stats: (f32, f32, f32),
1220                                    out_tok: &mut CudaSlice<u32>)
1221                                    -> Result<(), Box<dyn std::error::Error>> {
1222        let f = self.func("residual_sample_filtered_f32");
1223        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1224        let has_q: i32 = q.is_some() as i32;
1225        let qbuf = q.unwrap_or(p);
1226        let (pm, pth, pz) = p_stats; let (qm, qth, qz) = q_stats;
1227        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
1228        let __s_b = self.gpu.stream();
1229        let mut b = __s_b.launch_builder(&f);
1230        b.arg(p).arg(qbuf).arg(&has_q).arg(&ni).arg(&temp).arg(&slo).arg(&shi).arg(&stream_pos)
1231         .arg(&pm).arg(&pth).arg(&pz).arg(&qm).arg(&qth).arg(&qz).arg(&mut *out_tok);
1232        unsafe { b.launch(cfg)?; }
1233        Ok(())
1234    }
1235
1236    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1237    #[allow(clippy::too_many_arguments)]
1238    pub fn gumbel_perturb_filtered(&self, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, n: usize,
1239                                   seed: u64, stream_pos: u32, temp: f32, row_max: f32, th: f32)
1240                                   -> Result<(), Box<dyn std::error::Error>> {
1241        let f = self.func("gumbel_perturb_filtered_f32");
1242        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1243        let cfg = LaunchConfig { grid_dim: (n.div_ceil(256) as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1244        let __s_b = self.gpu.stream();
1245        let mut b = __s_b.launch_builder(&f);
1246        b.arg(x).arg(&mut *y).arg(&ni).arg(&slo).arg(&shi).arg(&stream_pos).arg(&temp).arg(&row_max).arg(&th);
1247        unsafe { b.launch(cfg)?; }
1248        Ok(())
1249    }
1250
1251    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1252    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1253    /// filtered rejection sampling exact for the penalized target.
1254    #[allow(clippy::too_many_arguments)]
1255    pub fn penalize_logits(&self, x: &mut CudaSlice<f32>, hist: &CudaSlice<u32>, n_hist: usize,
1256                           rep: f32, freq: f32, present: f32, n: usize)
1257                           -> Result<(), Box<dyn std::error::Error>> {
1258        if n_hist == 0 { return Ok(()); }
1259        let f = self.func("penalize_logits_f32");
1260        let (nh, ni) = (n_hist as i32, n as i32);
1261        let cfg = LaunchConfig { grid_dim: (n_hist.div_ceil(128) as u32, 1, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
1262        let __s_b = self.gpu.stream();
1263        let mut b = __s_b.launch_builder(&f);
1264        b.arg(&mut *x).arg(hist).arg(&nh).arg(&rep).arg(&freq).arg(&present).arg(&ni);
1265        unsafe { b.launch(cfg)?; }
1266        Ok(())
1267    }
1268
1269    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1270    #[allow(clippy::too_many_arguments)]
1271    pub fn penalize_logits_rows(&self, x: &mut CudaSlice<f32>, hist: &CudaSlice<u32>, n_hist: usize,
1272                                rep: f32, freq: f32, present: f32, n: usize, nrow: usize)
1273                                -> Result<(), Box<dyn std::error::Error>> {
1274        if n_hist == 0 || nrow == 0 { return Ok(()); }
1275        let f = self.func("penalize_logits_rows_f32");
1276        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1277        let cfg = LaunchConfig { grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
1278        let __s_b = self.gpu.stream();
1279        let mut b = __s_b.launch_builder(&f);
1280        b.arg(&mut *x).arg(hist).arg(&nh).arg(&rep).arg(&freq).arg(&present).arg(&ni).arg(&nr);
1281        unsafe { b.launch(cfg)?; }
1282        Ok(())
1283    }
1284
1285    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1286    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1287    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1288    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1289    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1290    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1291    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1292    pub fn wpf_level() -> u32 {
1293        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1294        *ON.get_or_init(|| std::env::var("MEMRA_WPF").ok()
1295            .and_then(|v| v.parse().ok()).unwrap_or(1))
1296    }
1297
1298    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1299    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1300    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1301    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1302    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1303    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1304    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1305    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1306    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1307    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1308    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1309    pub fn set_verify_exact(&self, on: bool) {
1310        self.verify_exact.store(on, std::sync::atomic::Ordering::Relaxed);
1311    }
1312    pub(crate) fn verify_exact_on(&self) -> bool {
1313        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1314    }
1315
1316    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1317    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
1318    pub fn qkv_append_on() -> bool {
1319        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1320        *ON.get_or_init(|| std::env::var("MEMRA_QKV_APPEND").map(|v| v != "0").unwrap_or(true))
1321    }
1322
1323    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
1324    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
1325    pub fn pdl_wb_on() -> bool {
1326        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1327        *ON.get_or_init(|| std::env::var("MEMRA_PDL_WB").map(|v| v != "0").unwrap_or(true))
1328    }
1329
1330    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
1331    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
1332    /// per-model no-harm bisect knob.
1333    pub fn pdl_mmvq_on() -> bool {
1334        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1335        *ON.get_or_init(|| std::env::var("MEMRA_PDL_MMVQ").map(|v| v != "0").unwrap_or(true))
1336    }
1337
1338    pub fn pdl_on() -> bool {
1339        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1340        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
1341    }
1342
1343    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
1344    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
1345    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
1346    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
1347    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
1348    fn q40_mr1_on() -> bool {
1349        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
1350        match *Q40MR.get_or_init(|| std::env::var("MEMRA_Q40_MR").ok()
1351            .and_then(|v| v.parse().ok())) {
1352            Some(v) => v == 1,
1353            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1354        }
1355    }
1356
1357    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
1358    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
1359    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
1360    /// writes wrong bytes silently.
1361    fn pdl_func_flash(&self, g: bool, name: &'static str)
1362        -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1363        use cudarc::driver::sys as cu;
1364        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
1365        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
1366        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
1367        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
1368        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
1369        // this engine's CUcontext; single-context runs behave exactly as before.
1370        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
1371            std::sync::Mutex::new(None);
1372        static FNS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool, &'static str), usize>>> =
1373            std::sync::Mutex::new(None);
1374        let ctx_key = self.ctx().cu_ctx() as usize;
1375        if let Some(&f) = FNS.lock().unwrap().get_or_insert_with(Default::default)
1376            .get(&(ctx_key, g, name)) { return Ok(f as cu::CUfunction); }
1377        let module = {
1378            let mut mods = MODS.lock().unwrap();
1379            let map = mods.get_or_insert_with(Default::default);
1380            match map.get(&(ctx_key, g)) {
1381                Some(&m) => m,
1382                None => {
1383                    let m = self.pdl_load_module_in_ctx(
1384                        if g { FLASH_FATBIN_KF8VF8 } else { FLASH_FATBIN })?;
1385                    map.insert((ctx_key, g), m);
1386                    m
1387                }
1388            }
1389        };
1390        let cname = std::ffi::CString::new(name)?;
1391        let mut f: cu::CUfunction = std::ptr::null_mut();
1392        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1393        if r != cu::CUresult::CUDA_SUCCESS { return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into()); }
1394        FNS.lock().unwrap().get_or_insert_with(Default::default)
1395            .insert((ctx_key, g, name), f as usize);
1396        Ok(f)
1397    }
1398
1399    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
1400    /// the module to the thread's CURRENT context — a remote-stage engine must not
1401    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
1402    /// current context before returning.
1403    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
1404        use cudarc::driver::sys as cu;
1405        let mut prev: cu::CUcontext = std::ptr::null_mut();
1406        unsafe { cu::cuCtxGetCurrent(&mut prev).result()?; }
1407        self.ctx().bind_to_thread()?;
1408        let mut m: cu::CUmodule = std::ptr::null_mut();
1409        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
1410        let restore = if prev.is_null() { cu::CUresult::CUDA_SUCCESS }
1411                      else { unsafe { cu::cuCtxSetCurrent(prev) } };
1412        if r != cu::CUresult::CUDA_SUCCESS {
1413            return Err(format!("pdl module load: {r:?}").into());
1414        }
1415        if restore != cu::CUresult::CUDA_SUCCESS {
1416            return Err(format!("pdl module load: ctx restore {restore:?}").into());
1417        }
1418        Ok(m as usize)
1419    }
1420
1421    fn pdl_func(&self, name: &'static str) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1422        use cudarc::driver::sys as cu;
1423        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
1424        // are context-scoped; key everything by this engine's CUcontext).
1425        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1426            std::sync::Mutex::new(None);
1427        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
1428        // duplicate module, loaded lazily on the first kernels-module miss.
1429        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1430            std::sync::Mutex::new(None);
1431        static FNS: std::sync::Mutex<Option<std::collections::HashMap<(usize, &'static str), usize>>> =
1432            std::sync::Mutex::new(None);
1433        let ctx_key = self.ctx().cu_ctx() as usize;
1434        if let Some(&f) = FNS.lock().unwrap().get_or_insert_with(Default::default)
1435            .get(&(ctx_key, name)) { return Ok(f as cu::CUfunction); }
1436        let module = {
1437            let mut mods = MODULES.lock().unwrap();
1438            let map = mods.get_or_insert_with(Default::default);
1439            match map.get(&ctx_key) {
1440                Some(&m) => m,
1441                None => {
1442                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
1443                    map.insert(ctx_key, m);
1444                    m
1445                }
1446            }
1447        };
1448        let cname = std::ffi::CString::new(name)?;
1449        let mut f: cu::CUfunction = std::ptr::null_mut();
1450        let mut r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1451        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
1452            let qmodule = {
1453                let mut mods = QMODULES.lock().unwrap();
1454                let map = mods.get_or_insert_with(Default::default);
1455                match map.get(&ctx_key) {
1456                    Some(&m) => m,
1457                    None => {
1458                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
1459                        map.insert(ctx_key, m);
1460                        m
1461                    }
1462                }
1463            };
1464            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
1465        }
1466        if r != cu::CUresult::CUDA_SUCCESS { return Err(format!("pdl_func {name}: {r:?}").into()); }
1467        FNS.lock().unwrap().get_or_insert_with(Default::default)
1468            .insert((ctx_key, name), f as usize);
1469        Ok(f)
1470    }
1471
1472    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
1473    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
1474    ///
1475    /// # Safety
1476    /// `params` must match the kernel's exact parameter list (order, types, count) —
1477    /// a mismatch corrupts the launch silently.
1478    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
1479    /// builder path's fa_func/func_g choice exactly).
1480    ///
1481    /// # Safety
1482    /// Same contract as `launch_pdl`.
1483    unsafe fn launch_pdl_flash(&self, g: bool, name: &'static str, grid: (u32, u32, u32),
1484                               block: (u32, u32, u32), smem: u32,
1485                               params: &mut [*mut std::ffi::c_void])
1486                               -> Result<(), Box<dyn std::error::Error>> {
1487        use cudarc::driver::sys as cu;
1488        let f = self.pdl_func_flash(g, name)?;
1489        if smem > 0 {
1490            // mirror the builder path's opt-in ceiling (idempotent host-side set).
1491            let r = unsafe { cu::cuFuncSetAttribute(f,
1492                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
1493                smem as i32) };
1494            if r != cu::CUresult::CUDA_SUCCESS {
1495                return Err(format!("pdl smem attr {name}: {r:?}").into());
1496            }
1497        }
1498        let mut attr = cu::CUlaunchAttribute {
1499            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1500            pad: [0; 4],
1501            value: cu::CUlaunchAttributeValue { programmaticStreamSerializationAllowed: 1 },
1502        };
1503        let cfg = cu::CUlaunchConfig {
1504            gridDimX: grid.0, gridDimY: grid.1, gridDimZ: grid.2,
1505            blockDimX: block.0, blockDimY: block.1, blockDimZ: block.2,
1506            sharedMemBytes: smem, hStream: self.gpu.stream().cu_stream(),
1507            attrs: &mut attr, numAttrs: 1,
1508        };
1509        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1510        if r != cu::CUresult::CUDA_SUCCESS { return Err(format!("launch_pdl_flash {name}: {r:?}").into()); }
1511        Ok(())
1512    }
1513
1514    unsafe fn launch_pdl(&self, name: &'static str, grid: (u32, u32, u32), block: (u32, u32, u32),
1515                         params: &mut [*mut std::ffi::c_void])
1516                         -> Result<(), Box<dyn std::error::Error>> {
1517        use cudarc::driver::sys as cu;
1518        let f = self.pdl_func(name)?;
1519        let mut attr = cu::CUlaunchAttribute {
1520            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1521            pad: [0; 4],
1522            value: cu::CUlaunchAttributeValue { programmaticStreamSerializationAllowed: 1 },
1523        };
1524        let cfg = cu::CUlaunchConfig {
1525            gridDimX: grid.0, gridDimY: grid.1, gridDimZ: grid.2,
1526            blockDimX: block.0, blockDimY: block.1, blockDimZ: block.2,
1527            sharedMemBytes: 0, hStream: self.gpu.stream().cu_stream(),
1528            attrs: &mut attr, numAttrs: 1,
1529        };
1530        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1531        if r != cu::CUresult::CUDA_SUCCESS { return Err(format!("launch_pdl {name}: {r:?}").into()); }
1532        Ok(())
1533    }
1534
1535    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
1536    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
1537    pub fn prefetch_weight_l2(&self, w: &crate::model::GpuTensor)
1538                              -> Result<(), Box<dyn std::error::Error>> {
1539        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
1540            let p = rp4.as_ref().unwrap_or(bytes);
1541            self.prefetch_l2(p, p.len())?;
1542        }
1543        Ok(())
1544    }
1545
1546    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
1547    /// by the DEVICE token id at tok[idx] into f32.
1548    pub fn gather_row_bf16(&self, table: &CudaSlice<u8>, tok: &CudaSlice<u32>, idx: usize,
1549                           dst: &mut CudaSlice<f32>, ncols: usize)
1550                           -> Result<(), Box<dyn std::error::Error>> {
1551        let f = self.func("gather_row_bf16_f32");
1552        let cfg = LaunchConfig { grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
1553                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1554        let (nc, ix) = (ncols as i32, idx as i32);
1555        let __s_b = self.gpu.stream();
1556        let mut b = __s_b.launch_builder(&f);
1557        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
1558        unsafe { b.launch(cfg)?; }
1559        Ok(())
1560    }
1561
1562    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
1563    pub fn add_row_inplace(&self, logits: &mut CudaSlice<f32>, bias: &CudaSlice<f32>,
1564                           n: usize, row_off: usize)
1565                           -> Result<(), Box<dyn std::error::Error>> {
1566        let f = self.func("add_row_inplace_f32");
1567        let cfg = LaunchConfig { grid_dim: (n.div_ceil(256) as u32, 1, 1),
1568                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1569        let (ni, off) = (n as i32, row_off as i64);
1570        let __s_b = self.gpu.stream();
1571        let mut b = __s_b.launch_builder(&f);
1572        b.arg(logits).arg(bias).arg(&ni).arg(&off);
1573        unsafe { b.launch(cfg)?; }
1574        Ok(())
1575    }
1576
1577    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
1578    pub fn prefetch_l2(&self, p: &CudaSlice<u8>, n: usize) -> Result<(), Box<dyn std::error::Error>> {
1579        let f = self.func("prefetch_l2_bytes");
1580        let lines = n.div_ceil(128);
1581        let ni = n as i64;
1582        let cfg = LaunchConfig { grid_dim: (lines.div_ceil(256) as u32, 1, 1),
1583                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1584        let __s_b = self.gpu.stream();
1585        let mut b = __s_b.launch_builder(&f);
1586        b.arg(p).arg(&ni);
1587        unsafe { b.launch(cfg)?; }
1588        Ok(())
1589    }
1590
1591    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
1592    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
1593    pub fn router_gemv(&self, w: &CudaSlice<f32>, x: &CudaSlice<f32>, n_embd: usize,
1594                       n_experts: usize, t: usize)
1595                       -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1596        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
1597        // stream differs) — too small to justify a numeric config change; deleted.
1598        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
1599        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
1600        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
1601        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
1602            Ok("0") => false,
1603            Ok(_) => true,
1604            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1605        };
1606        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
1607        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
1608        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
1609        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
1610        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
1611        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
1612        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
1613        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
1614        // (perf-only, bits equal).
1615        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
1616        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
1617    }
1618
1619    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
1620    /// force both forms; `batch` requires `w8`).
1621    pub fn router_gemv_form(&self, w: &CudaSlice<f32>, x: &CudaSlice<f32>, n_embd: usize,
1622                            n_experts: usize, t: usize, w8: bool, batch: bool)
1623                            -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1624        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
1625        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
1626        let f = if batch { self.func("router_gemv_f32_w8_batch") }
1627                else if w8 { self.func("router_gemv_f32_w8") }
1628                else { self.func("router_gemv_f32") };
1629        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
1630        let cfg = if batch {
1631            LaunchConfig { grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
1632                           block_dim: (32, 8, 1), shared_mem_bytes: 0 }
1633        } else {
1634            LaunchConfig { grid_dim: (n_experts as u32, t as u32, 1),
1635                           block_dim: (32, if w8 { 8 } else { 1 }, 1), shared_mem_bytes: 0 }
1636        };
1637        let __s_b = self.gpu.stream();
1638        let mut b = __s_b.launch_builder(&f);
1639        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
1640        unsafe { b.launch(cfg)?; }
1641        Ok(y)
1642    }
1643
1644    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
1645    pub fn rows_permute(&self, src: &CudaSlice<f32>, idx: &CudaSlice<i32>, nrows: usize,
1646                        ncols: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1647        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
1648        let f = self.func("rows_permute_f32");
1649        let (nc, nr) = (ncols as i32, nrows as i32);
1650        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (256, 1, 1),
1651                                 shared_mem_bytes: 0 };
1652        let __s_b = self.gpu.stream();
1653        let mut b = __s_b.launch_builder(&f);
1654        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
1655        unsafe { b.launch(cfg)?; }
1656        Ok(dst)
1657    }
1658
1659    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
1660    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
1661    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
1662    /// decode chain and the small-t spec-verify chain match per row by construction.
1663    pub fn sigmoid_dot_rows(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, n_embd: usize,
1664                            t: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1665        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
1666        // config; same class as MEMRA_ROUTER_V2).
1667        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1668        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
1669            let gs = self.linear(x, w, t, n_embd, 1)?;
1670            let mut g = self.uninit(t)?;
1671            self.sigmoid(&gs, &mut g, t)?;
1672            return Ok(g);
1673        }
1674        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
1675        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
1676        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
1677        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
1678        // flags doctrine; this per-token form serves every t.
1679        let mut g = self.alloc_uninit::<f32>(t)?;
1680        let f = self.func("sigmoid_dot_rows_f32");
1681        let (ne, ti) = (n_embd as i32, t as i32);
1682        let cfg = LaunchConfig { grid_dim: (t as u32, 1, 1), block_dim: (32, 8, 1),
1683                                 shared_mem_bytes: 0 };
1684        let __s_b = self.gpu.stream();
1685        let mut b = __s_b.launch_builder(&f);
1686        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
1687        unsafe { b.launch(cfg)?; }
1688        Ok(g)
1689    }
1690
1691    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
1692    pub fn spec_rollback_stream(&self, len_ptrs: &CudaSlice<u64>, pos_start: &CudaSlice<i32>,
1693                                acc: &CudaSlice<u32>, base: usize, n_rows: usize)
1694                                -> Result<(), Box<dyn std::error::Error>> {
1695        let f = self.func("spec_rollback_stream");
1696        let (b, nr) = (base as i32, n_rows as i32);
1697        let cfg = LaunchConfig { grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
1698                                 block_dim: (64, 1, 1), shared_mem_bytes: 0 };
1699        let __s_bl = self.gpu.stream();
1700        let mut bl = __s_bl.launch_builder(&f);
1701        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
1702        unsafe { bl.launch(cfg)?; }
1703        Ok(())
1704    }
1705
1706    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
1707    pub fn plain_tok_ring(&self, vam: &CudaSlice<u32>, pos_start: &CudaSlice<i32>,
1708                          base: usize, ring: &mut CudaSlice<u32>)
1709                          -> Result<(), Box<dyn std::error::Error>> {
1710        let f = self.func("plain_tok_ring");
1711        let (b, cap) = (base as i32, ring.len() as i32);
1712        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1713        let __s_bl = self.gpu.stream();
1714        let mut bl = __s_bl.launch_builder(&f);
1715        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
1716        unsafe { bl.launch(cfg)?; }
1717        Ok(())
1718    }
1719
1720    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
1721    pub fn spec_ring_commit(&self, vtok: &CudaSlice<u32>, acc: &CudaSlice<u32>,
1722                            brk: &CudaSlice<u32>, ring: &mut CudaSlice<u32>,
1723                            pend: &mut CudaSlice<u32>)
1724                            -> Result<(), Box<dyn std::error::Error>> {
1725        let f = self.func("spec_ring_commit");
1726        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1727        let __s_b = self.gpu.stream();
1728        let mut b = __s_b.launch_builder(&f);
1729        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
1730        unsafe { b.launch(cfg)?; }
1731        Ok(())
1732    }
1733    pub fn i32_copy_add(&self, src: &CudaSlice<i32>, dst: &mut CudaSlice<i32>, delta: i32)
1734                        -> Result<(), Box<dyn std::error::Error>> {
1735        let f = self.func("i32_copy_add");
1736        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1737        let __s_b = self.gpu.stream();
1738        let mut b = __s_b.launch_builder(&f);
1739        b.arg(src).arg(dst).arg(&delta);
1740        unsafe { b.launch(cfg)?; }
1741        Ok(())
1742    }
1743    pub fn u32_copy(&self, src: &CudaSlice<u32>, dst: &mut CudaSlice<u32>)
1744                    -> Result<(), Box<dyn std::error::Error>> {
1745        let f = self.func("u32_copy");
1746        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1747        let __s_b = self.gpu.stream();
1748        let mut b = __s_b.launch_builder(&f);
1749        b.arg(src).arg(dst);
1750        unsafe { b.launch(cfg)?; }
1751        Ok(())
1752    }
1753
1754    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
1755    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
1756    /// caps acceptance exactly like drafting fewer tokens).
1757    pub fn spec_adapt_k(&self, acc: &CudaSlice<u32>, brk: &mut CudaSlice<u32>,
1758                        floor: usize, cap: usize)
1759                        -> Result<(), Box<dyn std::error::Error>> {
1760        let f = self.func("spec_adapt_k");
1761        let (fl, cp) = (floor as i32, cap as i32);
1762        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1763        let __s_b = self.gpu.stream();
1764        let mut b = __s_b.launch_builder(&f);
1765        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
1766        unsafe { b.launch(cfg)?; }
1767        Ok(())
1768    }
1769
1770    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
1771    pub fn spec_accept_greedy_dc(&self, preds: &CudaSlice<u32>, vtok: &CudaSlice<u32>,
1772                                 last_pred: &CudaSlice<u32>, brk: &CudaSlice<u32>,
1773                                 out: &mut CudaSlice<u32>)
1774                                 -> Result<(), Box<dyn std::error::Error>> {
1775        let f = self.func("spec_accept_greedy_dc");
1776        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1777        let __s_b = self.gpu.stream();
1778        let mut b = __s_b.launch_builder(&f);
1779        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
1780        unsafe { b.launch(cfg)?; }
1781        Ok(())
1782    }
1783
1784    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
1785    pub fn pos_iota(&self, pos0: &CudaSlice<i32>, out: &mut CudaSlice<i32>, t: usize)
1786                    -> Result<(), Box<dyn std::error::Error>> {
1787        let f = self.func("pos_iota_i32");
1788        let ti = t as i32;
1789        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (t.max(1) as u32, 1, 1),
1790                                 shared_mem_bytes: 0 };
1791        let __s_b = self.gpu.stream();
1792        let mut b = __s_b.launch_builder(&f);
1793        b.arg(pos0).arg(out).arg(&ti);
1794        unsafe { b.launch(cfg)?; }
1795        Ok(())
1796    }
1797    #[allow(clippy::too_many_arguments)]
1798    pub fn append_kv_quantized_rows_dc(&self, k_rows: &CudaSlice<f32>, v_rows: &CudaSlice<f32>,
1799                                       kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>,
1800                                       t0_dev: &CudaSlice<i32>, t: usize,
1801                                       kv_dim_k: usize, kv_dim_v: usize,
1802                                       k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
1803                                       -> Result<(), Box<dyn std::error::Error>> {
1804        let f = if g { self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc") }
1805                else { self.func("append_quantize_kv_q8_0_q5_1_rows_dc") };
1806        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
1807        let cfg = LaunchConfig { grid_dim: (nblk, t as u32, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1808        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
1809        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
1810        let __s_b = self.gpu.stream();
1811        let mut b = __s_b.launch_builder(&f);
1812        b.arg(k_rows).arg(v_rows).arg(kc).arg(vc).arg(t0_dev).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
1813        unsafe { b.launch(cfg)?; }
1814        Ok(())
1815    }
1816
1817    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
1818    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
1819    #[allow(clippy::too_many_arguments)]
1820    pub fn append_kv_quantized_row_dc_inc(&self, k_row: &CudaSlice<f32>, v_row: &CudaSlice<f32>,
1821                                          kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>,
1822                                          t0_dev: &mut CudaSlice<i32>,
1823                                          kv_dim_k: usize, kv_dim_v: usize,
1824                                          k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
1825                                          -> Result<(), Box<dyn std::error::Error>> {
1826        let f = if g { self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc") }
1827                else { self.func("append_quantize_kv_q8_0_q5_1_dc_inc") };
1828        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
1829        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (nthreads, 1, 1),
1830                                 shared_mem_bytes: 0 };
1831        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
1832        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
1833        let __s_b = self.gpu.stream();
1834        let mut b = __s_b.launch_builder(&f);
1835        b.arg(k_row).arg(v_row).arg(kc).arg(vc).arg(t0_dev).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
1836        unsafe { b.launch(cfg)?; }
1837        Ok(())
1838    }
1839
1840    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
1841    pub fn pack_tok_p(&self, tok: &CudaSlice<u32>, p: &CudaSlice<f32>, out: &mut CudaSlice<u32>,
1842                      slot: usize) -> Result<(), Box<dyn std::error::Error>> {
1843        let f = self.func("pack_tok_p");
1844        let sl = slot as i32;
1845        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1846        let __s_b = self.gpu.stream();
1847        let mut b = __s_b.launch_builder(&f);
1848        b.arg(tok).arg(p).arg(out).arg(&sl);
1849        unsafe { b.launch(cfg)?; }
1850        Ok(())
1851    }
1852    pub fn tok_map_u32(&self, tok: &mut CudaSlice<u32>, map: &CudaSlice<u32>)
1853                       -> Result<(), Box<dyn std::error::Error>> {
1854        let f = self.func("tok_map_u32");
1855        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1856        let __s_b = self.gpu.stream();
1857        let mut b = __s_b.launch_builder(&f);
1858        b.arg(tok).arg(map);
1859        unsafe { b.launch(cfg)?; }
1860        Ok(())
1861    }
1862
1863    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
1864    #[allow(clippy::too_many_arguments)]
1865    pub fn spec_assemble_verify(&self, tokp: &CudaSlice<u32>, pend: &CudaSlice<u32>,
1866                                d2t: Option<&CudaSlice<u32>>, vtok: &mut CudaSlice<u32>,
1867                                brk: &mut CudaSlice<u32>, p_min: f32, k: usize, pmin0: bool)
1868                                -> Result<(), Box<dyn std::error::Error>> {
1869        let f = self.func("spec_assemble_verify");
1870        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
1871        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
1872        let __s_b = self.gpu.stream();
1873        let mut b = __s_b.launch_builder(&f);
1874        match d2t {
1875            Some(m) => { b.arg(tokp).arg(pend).arg(m).arg(vtok).arg(brk).arg(&p_min).arg(&ki).arg(&pm);
1876                         unsafe { b.launch(cfg)?; } }
1877            None => { let null: u64 = 0;
1878                      b.arg(tokp).arg(pend).arg(&null).arg(vtok).arg(brk).arg(&p_min).arg(&ki).arg(&pm);
1879                      unsafe { b.launch(cfg)?; } }
1880        }
1881        Ok(())
1882    }
1883
1884    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
1885    #[allow(clippy::too_many_arguments)]
1886    pub fn ssm_conv_ring_rebuild_dc(&self, qkv_tm: &CudaSlice<f32>, ring_old: &CudaSlice<f32>,
1887                                    conv_state: &mut CudaSlice<f32>, conv_dim: usize,
1888                                    acc: &CudaSlice<u32>, base: usize, t_v: usize, d_conv: usize)
1889                                    -> Result<(), Box<dyn std::error::Error>> {
1890        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
1891        let n = conv_dim * (d_conv - 1);
1892        let cfg = LaunchConfig::for_num_elems(n as u32);
1893        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
1894        let __s_b = self.gpu.stream();
1895        let mut b = __s_b.launch_builder(&f);
1896        b.arg(qkv_tm).arg(ring_old).arg(conv_state).arg(&cd).arg(acc).arg(&b0).arg(&tv).arg(&dc);
1897        unsafe { b.launch(cfg)?; }
1898        Ok(())
1899    }
1900    #[allow(clippy::too_many_arguments)]
1901    pub fn gdn_scan_s128_dc(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
1902                            g: &CudaSlice<f32>, beta: &CudaSlice<f32>, state_in: &CudaSlice<f32>,
1903                            state_out: &mut CudaSlice<f32>, o: &mut CudaSlice<f32>,
1904                            n_head: usize, acc: &CudaSlice<u32>, base: usize, t_v: usize,
1905                            scale: f32)
1906                            -> Result<(), Box<dyn std::error::Error>> {
1907        let f = self.func("gdn_scan_s128_dc");
1908        const S_V: u32 = 128; const WARP: u32 = 32; const COLS_PER_BLOCK: u32 = 4;
1909        let cfg = LaunchConfig {
1910            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
1911            block_dim: (WARP, COLS_PER_BLOCK, 1),
1912            shared_mem_bytes: 0,
1913        };
1914        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
1915        let __s_b = self.gpu.stream();
1916        let mut b = __s_b.launch_builder(&f);
1917        b.arg(q).arg(k).arg(v).arg(g).arg(beta).arg(state_in).arg(state_out).arg(o)
1918         .arg(&h).arg(acc).arg(&b0).arg(&tv).arg(&scale);
1919        unsafe { b.launch(cfg)?; }
1920        Ok(())
1921    }
1922
1923    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
1924    pub fn spec_rollback_kv(&self, len_ptrs: &CudaSlice<u64>, saved: &CudaSlice<i32>,
1925                            acc: &CudaSlice<u32>, base: usize, n_layer: usize)
1926                            -> Result<(), Box<dyn std::error::Error>> {
1927        let f = self.func("spec_rollback_kv");
1928        let (b, nl) = (base as i32, n_layer as i32);
1929        let cfg = LaunchConfig { grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
1930                                 block_dim: (64, 1, 1), shared_mem_bytes: 0 };
1931        let __s_bl = self.gpu.stream();
1932        let mut bl = __s_bl.launch_builder(&f);
1933        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
1934        unsafe { bl.launch(cfg)?; }
1935        Ok(())
1936    }
1937
1938    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
1939    pub fn spec_fork_valid(&self, acc: &CudaSlice<u32>, optimistic_pending: u32,
1940                           valid: &mut CudaSlice<u32>)
1941                           -> Result<(), Box<dyn std::error::Error>> {
1942        let f = self.func("spec_fork_valid");
1943        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1, 1, 1),
1944                                 shared_mem_bytes: 0 };
1945        let __s_bl = self.gpu.stream();
1946        let mut bl = __s_bl.launch_builder(&f);
1947        bl.arg(acc).arg(&optimistic_pending).arg(valid);
1948        unsafe { bl.launch(cfg)?; }
1949        Ok(())
1950    }
1951
1952    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
1953    pub fn spec_fork_reconcile_kv(&self, len_ptrs: &CudaSlice<u64>, saved: &CudaSlice<i32>,
1954                                  acc: &CudaSlice<u32>, valid: &CudaSlice<u32>, base: usize,
1955                                  n_layer: usize)
1956                                  -> Result<(), Box<dyn std::error::Error>> {
1957        let f = self.func("spec_fork_reconcile_kv");
1958        let (b, nl) = (base as i32, n_layer as i32);
1959        let cfg = LaunchConfig { grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
1960                                 block_dim: (64, 1, 1), shared_mem_bytes: 0 };
1961        let __s_bl = self.gpu.stream();
1962        let mut bl = __s_bl.launch_builder(&f);
1963        bl.arg(len_ptrs).arg(saved).arg(acc).arg(valid).arg(&b).arg(&nl);
1964        unsafe { bl.launch(cfg)?; }
1965        Ok(())
1966    }
1967
1968    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
1969    pub fn spec_fork_restore_f32(&self, snapshot: &CudaSlice<f32>, state: &mut CudaSlice<f32>,
1970                                 valid: &CudaSlice<u32>)
1971                                 -> Result<(), Box<dyn std::error::Error>> {
1972        assert_eq!(snapshot.len(), state.len(), "fork recurrent snapshot shape mismatch");
1973        let f = self.func("spec_fork_restore_f32");
1974        let n = state.len() as i32;
1975        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
1976        let cfg = LaunchConfig { grid_dim: (blocks, 1, 1), block_dim: (256, 1, 1),
1977                                 shared_mem_bytes: 0 };
1978        let __s_bl = self.gpu.stream();
1979        let mut bl = __s_bl.launch_builder(&f);
1980        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
1981        unsafe { bl.launch(cfg)?; }
1982        Ok(())
1983    }
1984
1985    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
1986    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
1987    pub fn spec_seed_gather(&self, vx: &CudaSlice<f32>, fill_prev: &CudaSlice<f32>,
1988                            acc: &CudaSlice<u32>, h_seed: &mut CudaSlice<f32>,
1989                            base: usize, n_embd: usize)
1990                            -> Result<(), Box<dyn std::error::Error>> {
1991        let f = self.func("spec_seed_gather");
1992        let (b, ne) = (base as i32, n_embd as i32);
1993        let cfg = LaunchConfig { grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
1994                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
1995        let __s_bl = self.gpu.stream();
1996        let mut bl = __s_bl.launch_builder(&f);
1997        bl.arg(vx).arg(fill_prev).arg(acc).arg(h_seed).arg(&b).arg(&ne);
1998        unsafe { bl.launch(cfg)?; }
1999        Ok(())
2000    }
2001
2002
2003    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
2004    pub fn spec_accept_greedy(&self, preds: &CudaSlice<u32>, draft: &CudaSlice<u32>,
2005                              last_pred: u32, base: usize, k_round: usize,
2006                              out: &mut CudaSlice<u32>)
2007                              -> Result<(), Box<dyn std::error::Error>> {
2008        let f = self.func("spec_accept_greedy");
2009        let (b, k) = (base as i32, k_round as i32);
2010        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2011        let __s_bl = self.gpu.stream();
2012        let mut bl = __s_bl.launch_builder(&f);
2013        bl.arg(preds).arg(draft).arg(&last_pred).arg(&b).arg(&k).arg(out);
2014        unsafe { bl.launch(cfg)?; }
2015        Ok(())
2016    }
2017
2018    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
2019    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
2020    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
2021
2022    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
2023    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
2024    pub fn gumbel_perturb(&self, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, n: usize,
2025                          seed: u64, stream_pos: u32, temp: f32)
2026                          -> Result<(), Box<dyn std::error::Error>> {
2027        let f = self.func("gumbel_perturb_f32");
2028        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2029        let cfg = LaunchConfig { grid_dim: (n.div_ceil(256) as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2030        let __s_b = self.gpu.stream();
2031        let mut b = __s_b.launch_builder(&f);
2032        b.arg(x).arg(&mut *y).arg(&ni).arg(&slo).arg(&shi).arg(&stream_pos).arg(&temp);
2033        unsafe { b.launch(cfg)?; }
2034        Ok(())
2035    }
2036
2037    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
2038    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
2039    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
2040    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
2041    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
2042    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
2043    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
2044    pub fn mask_logits_col(&self, logits: &mut CudaSlice<f32>, mask: &CudaSlice<u32>,
2045                           col: usize, n: usize, mask_words: usize)
2046                           -> Result<(), Box<dyn std::error::Error>> {
2047        let f = self.func("mask_logits_f32");
2048        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
2049        let cfg = LaunchConfig { grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
2050                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2051        let __s_b = self.gpu.stream();
2052        let mut b = __s_b.launch_builder(&f);
2053        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
2054        unsafe { b.launch(cfg)?; }
2055        Ok(())
2056    }
2057
2058    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
2059    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
2060    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
2061    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
2062    /// (the lane index is the in-row position; `col` only moves the input pointer). That
2063    /// pointer-invariance IS the serving isolation contract for sampled rows.
2064    pub fn gumbel_perturb_col(&self, x: &CudaSlice<f32>, col: usize, y: &mut CudaSlice<f32>,
2065                              n: usize, seed: u64, stream_pos: u32, temp: f32)
2066                              -> Result<(), Box<dyn std::error::Error>> {
2067        let f = self.func("gumbel_perturb_f32");
2068        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2069        let col_view = x.slice(col * n..(col + 1) * n);
2070        let cfg = LaunchConfig { grid_dim: (n.div_ceil(256) as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2071        let __s_b = self.gpu.stream();
2072        let mut b = __s_b.launch_builder(&f);
2073        b.arg(&col_view).arg(&mut *y).arg(&ni).arg(&slo).arg(&shi).arg(&stream_pos).arg(&temp);
2074        unsafe { b.launch(cfg)?; }
2075        Ok(())
2076    }
2077
2078    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
2079    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
2080    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
2081    /// reads it (counter is data, not state — graph-replay-safe).
2082    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
2083        let f = self.func("memra_sctr_inc");
2084        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1, 1, 1), shared_mem_bytes: 0 };
2085        let __s_b = self.gpu.stream();
2086        let mut b = __s_b.launch_builder(&f);
2087        b.arg(&mut *ctr);
2088        unsafe { b.launch(cfg)?; }
2089        Ok(())
2090    }
2091
2092    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
2093    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
2094    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
2095    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
2096    pub fn gumbel_perturb_ctr(&self, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, n: usize,
2097                              seed: u64, ctr: &CudaSlice<u32>, temp: f32)
2098                              -> Result<(), Box<dyn std::error::Error>> {
2099        let f = self.func("gumbel_perturb_ctr_f32");
2100        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2101        let cfg = LaunchConfig { grid_dim: (n.div_ceil(256) as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2102        let __s_b = self.gpu.stream();
2103        let mut b = __s_b.launch_builder(&f);
2104        b.arg(x).arg(&mut *y).arg(&ni).arg(&slo).arg(&shi).arg(ctr).arg(&temp);
2105        unsafe { b.launch(cfg)?; }
2106        Ok(())
2107    }
2108
2109    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
2110    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
2111    /// (smallest-index tie-break — matches the argmax-gate contract).
2112    pub fn softmax_gather(&self, x: &CudaSlice<f32>, row_stride: usize,
2113                          ids: &CudaSlice<u32>, rows: &CudaSlice<i32>,
2114                          out: &mut CudaSlice<f32>, n: usize, npair: usize, temp: f32)
2115                          -> Result<(), Box<dyn std::error::Error>> {
2116        let f = self.func("softmax_gather_f32");
2117        let (ni, rs) = (n as i32, row_stride as i64);
2118        let np = npair as i32;
2119        let cfg = LaunchConfig { grid_dim: (npair as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2120        let __s_b = self.gpu.stream();
2121        let mut b = __s_b.launch_builder(&f);
2122        b.arg(x).arg(&rs).arg(ids).arg(rows).arg(&mut *out).arg(&ni).arg(&np).arg(&temp);
2123        unsafe { b.launch(cfg)?; }
2124        Ok(())
2125    }
2126
2127    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
2128    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
2129    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
2130    pub fn residual_sample(&self, p: &CudaSlice<f32>, q: Option<&CudaSlice<f32>>, n: usize,
2131                           temp: f32, seed: u64, stream_pos: u32,
2132                           out_tok: &mut CudaSlice<u32>)
2133                           -> Result<(), Box<dyn std::error::Error>> {
2134        let f = self.func("residual_sample_f32");
2135        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2136        let nth = 1024u32;
2137        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (nth, 1, 1), shared_mem_bytes: 0 };
2138        let has_q: i32 = q.is_some() as i32;
2139        let qbuf = q.unwrap_or(p);   // dummy when absent; kernel gates on has_q
2140        let __s_b = self.gpu.stream();
2141        let mut b = __s_b.launch_builder(&f);
2142        b.arg(p).arg(qbuf).arg(&has_q).arg(&ni).arg(&temp).arg(&slo).arg(&shi).arg(&stream_pos)
2143         .arg(&mut *out_tok);
2144        unsafe { b.launch(cfg)?; }
2145        Ok(())
2146    }
2147
2148    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
2149    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
2150    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
2151    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
2152    pub fn with_moe_cache<R>(&self, max_block_bytes: usize,
2153                             f: impl FnOnce(&mut crate::moe_cache::MoeSlotCache, &Engine) -> Result<R, Box<dyn std::error::Error>>)
2154                             -> Result<R, Box<dyn std::error::Error>> {
2155        let mut guard = self.moe_cache.lock().unwrap();
2156        if guard.is_none() {
2157            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
2158        }
2159        let cache = guard.as_mut().unwrap();
2160                f(cache, self)
2161    }
2162
2163    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
2164    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
2165    pub fn freeze_moe_cache(&self) {
2166        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
2167            cache.freeze();
2168        }
2169    }
2170
2171    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
2172    /// Never constructs a cache.
2173    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
2174        self.moe_cache
2175            .lock()
2176            .unwrap()
2177            .as_ref()
2178            .map(crate::moe_cache::MoeSlotCache::export_residency)
2179    }
2180
2181    pub(crate) fn moe_cache_frozen(&self) -> bool {
2182        self.moe_cache
2183            .lock()
2184            .unwrap()
2185            .as_ref()
2186            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
2187    }
2188
2189    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
2190    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
2191    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
2192    /// while leaving the profiling warmup's established batched behavior untouched.
2193    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
2194    /// tokenwise arm anyway.)
2195    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
2196        crate::cpu_experts::configured()
2197            && self.moe_cache_frozen()
2198            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
2199    }
2200
2201    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
2202    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
2203        assert!(
2204            self.moe_cache.lock().unwrap().is_none(),
2205            "MoE cache layout configured after cache construction"
2206        );
2207        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
2208    }
2209
2210    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
2211        self.moe_cache_layout.lock().unwrap().clone()
2212    }
2213
2214    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
2215    pub fn moe_cache_enabled() -> bool {
2216        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
2217 }
2218
2219    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
2220    /// Returns None if the cache was never built (disabled or no MoE forward ran).
2221    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
2222        let guard = self.moe_cache.lock().unwrap();
2223        guard.as_ref()            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
2224    }
2225
2226    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
2227    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
2228    /// callers compare a before/after snapshot around a decode window.
2229    pub fn cpu_expert_stats(
2230        &self,
2231    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
2232        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
2233    }
2234
2235    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
2236    /// the backend tail that resident-GPU expert work did not hide.
2237    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
2238        crate::cpu_experts::predictor_stats()
2239    }
2240
2241    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
2242        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
2243    }
2244
2245    /// CPU-routed expert selections grouped by how many of their three projections were already
2246    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
2247    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
2248        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
2249    }
2250
2251    /// Positioned-read proof-backend counters:
2252    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
2253    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
2254
2255        let guard = self.moe_cache.lock().unwrap();
2256        guard.as_ref().and_then(|cache| cache.pread_stats()).map(|stats| (
2257            stats.reads,
2258            stats.bytes,
2259            stats.read_errors,
2260            stats.short_reads,
2261            stats.fallbacks,
2262            stats.buffer_waits,
2263            stats.ring_full,
2264        ))
2265    }
2266
2267    /// Spill configuration values that warned and substituted their documented defaults.
2268    pub fn spill_config_fallbacks(&self) -> u64 {
2269        crate::spill_pread::config_fallbacks()
2270    }
2271
2272    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
2273    pub fn moe_cache_reset_counters(&self) {
2274        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() { c.reset_counters(); }
2275    }
2276
2277    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2278        Ok(self.gpu.stream().clone_htod(v)?)
2279    }
2280
2281    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
2282    /// past the final q4_0 block through their aligned window — the bytes never reach a
2283    /// result (funnelshift discards them) but must be mapped memory.
2284    pub fn htod_bytes_padded(&self, v: &[u8], pad: usize)
2285                             -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2286        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
2287        {
2288            let mut view = d.slice_mut(0..v.len());
2289            self.gpu.stream().memcpy_htod(v, &mut view)?;
2290        }
2291        Ok(d)
2292    }
2293
2294    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
2295    pub fn copy_into(&self, dst: &mut CudaSlice<f32>, off: usize, src: &CudaSlice<f32>, len: usize)
2296                     -> Result<(), Box<dyn std::error::Error>> {
2297        let mut view = dst.slice_mut(off..off + len);
2298        self.gpu.stream().memcpy_dtod(&src.slice(0..len), &mut view)?;
2299        Ok(())
2300    }
2301
2302    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
2303    /// u8 twin of copy_into (D2D byte-range copy at an offset).
2304    pub fn copy_u8_into(&self, dst: &mut CudaSlice<u8>, off: usize, src: &CudaSlice<u8>, len: usize)
2305                        -> Result<(), Box<dyn std::error::Error>> {
2306        let mut view = dst.slice_mut(off..off + len);
2307        self.gpu.stream().memcpy_dtod(&src.slice(0..len), &mut view)?;
2308        Ok(())
2309    }
2310
2311    /// D2D byte-range copy with explicit source and destination offsets.
2312    pub fn copy_u8_range_into(
2313        &self,
2314        dst: &mut CudaSlice<u8>,
2315        dst_off: usize,
2316        src: &CudaSlice<u8>,
2317        src_off: usize,
2318        len: usize,
2319    ) -> Result<(), Box<dyn std::error::Error>> {
2320        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
2321        self.gpu
2322            .stream()
2323            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
2324        Ok(())
2325    }
2326
2327    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
2328    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
2329    /// keeping the audited attention range contiguous without changing its absolute start.
2330    pub fn prepare_kv_append(
2331        &self,
2332        kv: &mut crate::cache::KvLayer,
2333        retain_from: usize,
2334        append_rows: usize,
2335    ) -> Result<usize, Box<dyn std::error::Error>> {
2336        let Some(plan) = kv
2337            .ring
2338            .as_ref()
2339            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
2340            .transpose()?
2341        else {
2342            return Ok(kv.len);
2343        };
2344        match plan {
2345            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
2346            crate::cache::KvRingAppend::Rebase {
2347                src_row,
2348                keep_rows,
2349                new_base,
2350                write_row,
2351            } => {
2352                if keep_rows > 0 {
2353                    let k_len = keep_rows * kv.k_tok_bytes;
2354                    let v_len = keep_rows * kv.v_tok_bytes;
2355                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
2356                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
2357                    self.copy_u8_range_into(
2358                        &mut k_tmp,
2359                        0,
2360                        &kv.k,
2361                        src_row * kv.k_tok_bytes,
2362                        k_len,
2363                    )?;
2364                    self.copy_u8_range_into(
2365                        &mut v_tmp,
2366                        0,
2367                        &kv.v,
2368                        src_row * kv.v_tok_bytes,
2369                        v_len,
2370                    )?;
2371                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
2372                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
2373                }
2374                kv.ring.as_mut().unwrap().apply_rebase(new_base);
2375                Ok(write_row)
2376            }
2377        }
2378    }
2379
2380    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
2381    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
2382    pub fn htod_u8_into(&self, dst: &mut CudaSlice<u8>, off: usize, src: &[u8])
2383                        -> Result<(), Box<dyn std::error::Error>> {
2384        let mut view = dst.slice_mut(off..off + src.len());
2385        self.gpu.stream().memcpy_htod(src, &mut view)?;
2386        Ok(())
2387    }
2388
2389    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
2390        b.slice(0..len)
2391    }
2392
2393    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
2394    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
2395    pub fn view_u8_range<'a>(&self, b: &'a CudaSlice<u8>, start: usize, end: usize)
2396                             -> cudarc::driver::CudaView<'a, u8> {
2397        b.slice(start..end)
2398    }
2399    pub fn view_u8<'a>(&self, b: &'a CudaSlice<u8>, len: usize) -> cudarc::driver::CudaView<'a, u8> {
2400        b.slice(0..len)
2401    }
2402
2403    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
2404    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
2405    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
2406    pub fn append_kv_quantized(&self, k_row: &CudaSlice<f32>, v_row: &CudaSlice<f32>,
2407                               kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>, t: usize,
2408                               kv_dim_k: usize, kv_dim_v: usize,
2409                               k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
2410                               -> Result<(), Box<dyn std::error::Error>> {
2411        let f = if g { self.func_g("append_quantize_kv_q8_0_q5_1") } else { self.func("append_quantize_kv_q8_0_q5_1") };
2412        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2413        let cfg = LaunchConfig { grid_dim: (nblk, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2414        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
2415        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2416        let __s_b = self.gpu.stream();
2417        let mut b = __s_b.launch_builder(&f);
2418        b.arg(k_row).arg(v_row).arg(kc).arg(vc).arg(&ti).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
2419        unsafe { b.launch(cfg)?; }
2420        Ok(())
2421    }
2422
2423    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
2424    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
2425    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
2426    pub fn append_kv_quantized_dc(&self, k_row: &CudaSlice<f32>, v_row: &CudaSlice<f32>,
2427                                  kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>, t_dev: &CudaSlice<i32>,
2428                                  kv_dim_k: usize, kv_dim_v: usize,
2429                                  k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
2430                               -> Result<(), Box<dyn std::error::Error>> {
2431        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2432        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2433        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2434        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
2435        if Self::pdl_on() && Self::pdl_wb_on() {
2436            use cudarc::driver::{DevicePtr, DevicePtrMut};
2437            let s = &self.gpu.stream();
2438            let (pk, _g0) = k_row.device_ptr(s); let (pv, _g1) = v_row.device_ptr(s);
2439            let (pkc, _g2) = kc.device_ptr_mut(s); let (pvc, _g3) = vc.device_ptr_mut(s);
2440            let (pt, _g4) = t_dev.device_ptr(s);
2441            let mut ps = [
2442                &pk as *const _ as *mut std::ffi::c_void, &pv as *const _ as *mut _,
2443                &pkc as *const _ as *mut _, &pvc as *const _ as *mut _,
2444                &pt as *const _ as *mut _, &kdk as *const _ as *mut _,
2445                &kdv as *const _ as *mut _, &ktb as *const _ as *mut _,
2446                &vtb as *const _ as *mut _,
2447            ];
2448            unsafe { self.launch_pdl_flash(g, "append_quantize_kv_q8_0_q5_1_dc",
2449                                           (nblk, 1, 1), (32, 1, 1), 0, &mut ps)?; }
2450            return Ok(());
2451        }
2452        let f = if g { self.func_g("append_quantize_kv_q8_0_q5_1_dc") } else { self.func("append_quantize_kv_q8_0_q5_1_dc") };
2453        let cfg = LaunchConfig { grid_dim: (nblk, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2454        let __s_b = self.gpu.stream();
2455        let mut b = __s_b.launch_builder(&f);
2456        b.arg(k_row).arg(v_row).arg(kc).arg(vc).arg(t_dev).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
2457        unsafe { b.launch(cfg)?; }
2458        Ok(())
2459    }
2460
2461    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
2462    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
2463    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
2464    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
2465    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
2466    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
2467    #[allow(clippy::too_many_arguments)]
2468    pub fn append_kv_quantized_rows(&self, k_rows: &CudaSlice<f32>, v_rows: &CudaSlice<f32>,
2469                                    kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>,
2470                                    t0: usize, t: usize, kv_dim_k: usize, kv_dim_v: usize,
2471                                    k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
2472                               -> Result<(), Box<dyn std::error::Error>> {
2473        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
2474            for i in 0..t {
2475                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
2476                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
2477                self.append_kv_quantized_view(&k_row, &v_row, kc, vc, t0 + i,
2478                                              kv_dim_k, kv_dim_v, k_tok_bytes, v_tok_bytes, g)?;
2479            }
2480            return Ok(());
2481        }
2482        let f = if g { self.func_g("append_quantize_kv_q8_0_q5_1_rows") } else { self.func("append_quantize_kv_q8_0_q5_1_rows") };
2483        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2484        let cfg = LaunchConfig { grid_dim: (nblk, t as u32, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2485        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
2486        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2487        let __s_b = self.gpu.stream();
2488        let mut b = __s_b.launch_builder(&f);
2489        b.arg(k_rows).arg(v_rows).arg(kc).arg(vc).arg(&t0i).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
2490        unsafe { b.launch(cfg)?; }
2491        Ok(())
2492    }
2493
2494    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
2495    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
2496    /// later, inside a captured graph) without a host round-trip.
2497    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
2498        let f = self.func("inc_i32");
2499        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1, 1, 1), shared_mem_bytes: 0 };
2500        let __s_b = self.gpu.stream();
2501        let mut b = __s_b.launch_builder(&f);
2502        b.arg(p);
2503        unsafe { b.launch(cfg)?; }
2504        Ok(())
2505    }
2506
2507    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
2508    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
2509    pub fn append_kv_quantized_view(&self, k_row: &cudarc::driver::CudaView<f32>,
2510                                    v_row: &cudarc::driver::CudaView<f32>,
2511                                    kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>, t: usize,
2512                                    kv_dim_k: usize, kv_dim_v: usize,
2513                                    k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
2514                                    -> Result<(), Box<dyn std::error::Error>> {
2515        let f = if g { self.func_g("append_quantize_kv_q8_0_q5_1") }
2516                else { self.func("append_quantize_kv_q8_0_q5_1") };
2517        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2518        let cfg = LaunchConfig { grid_dim: (nblk, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2519        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
2520        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2521        let __s_b = self.gpu.stream();
2522        let mut b = __s_b.launch_builder(&f);
2523        b.arg(k_row).arg(v_row).arg(kc).arg(vc).arg(&ti).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
2524        unsafe { b.launch(cfg)?; }
2525        Ok(())
2526    }
2527
2528    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
2529    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
2530    pub fn copy_view_into(&self, dst: &mut CudaSlice<f32>, off: usize,
2531                          src: &cudarc::driver::CudaView<f32>, len: usize)
2532                          -> Result<(), Box<dyn std::error::Error>> {
2533        let mut view = dst.slice_mut(off..off + len);
2534        self.gpu.stream().memcpy_dtod(&src.slice(0..len), &mut view)?;
2535        Ok(())
2536    }
2537
2538    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
2539    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
2540    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
2541    pub fn clone_dtod(&self, src: &CudaSlice<f32>) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2542        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
2543        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
2544        Ok(dst)
2545    }
2546
2547    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
2548    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
2549    pub fn dtod_copy_view(&self, src: &cudarc::driver::CudaView<f32>, dst: &mut CudaSlice<f32>)
2550                          -> Result<(), Box<dyn std::error::Error>> {
2551        self.gpu.stream().memcpy_dtod(src, dst)?;
2552        Ok(())
2553    }
2554
2555    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
2556    pub fn dtod_copy_view_i8(&self, src: &cudarc::driver::CudaView<i8>, dst: &mut CudaSlice<i8>)
2557                             -> Result<(), Box<dyn std::error::Error>> {
2558        self.gpu.stream().memcpy_dtod(src, dst)?;
2559        Ok(())
2560    }
2561
2562    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
2563    pub fn dtod_copy_into(&self, src: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, offset: usize)
2564                          -> Result<(), Box<dyn std::error::Error>> {
2565        let n = src.len();
2566        let mut dv = dst.slice_mut(offset..offset + n);
2567        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
2568        Ok(())
2569    }
2570
2571    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
2572    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
2573        self.alloc_uninit::<i8>(n)
2574    }
2575
2576    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
2577    pub fn qmatvec(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize, out_f: usize,
2578                   qtype: i32, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2579        let f = self.func("qmatvec_f32");
2580        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
2581        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2582        let (inf, outf, mi, qt, rb) = (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
2583        let __s_b = self.gpu.stream();
2584        let mut b = __s_b.launch_builder(&f);
2585        b.arg(w).arg(x).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&qt).arg(&rb);
2586        unsafe { b.launch(cfg)?; }
2587        Ok(y)
2588    }
2589
2590    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
2591    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2592        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
2593        self.keep_if_capturing(&s);
2594        Ok(s)
2595    }
2596
2597    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
2598    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
2599    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
2600    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
2601        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
2602        self.keep_if_capturing(&s);
2603        Ok(s)
2604    }
2605
2606    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
2607    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
2608    pub fn memset_zeros_view(&self, dst: &mut cudarc::driver::CudaViewMut<f32>)
2609                             -> Result<(), Box<dyn std::error::Error>> {
2610        self.gpu.stream().memset_zeros(dst)?;
2611        Ok(())
2612    }
2613
2614    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
2615    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
2616    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
2617    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
2618    /// stream would require an event).
2619    pub fn stage_expert(&self, host_bytes: &[u8], scratch: &mut CudaSlice<u8>, off: usize)
2620                        -> Result<(), Box<dyn std::error::Error>> {
2621        let mut dst = scratch.slice_mut(off..off + host_bytes.len());  // CudaViewMut<u8>
2622        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?;            // accepts &[u8] HostSlice src
2623        Ok(())
2624    }
2625
2626    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
2627    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
2628    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
2629    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
2630    /// One CTA per token row, 256 threads (one per expert).
2631    pub fn moe_router_topk(&self, logits: &CudaSlice<f32>, t: usize, n_expert: usize, n_used: usize)
2632                           -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2633        let f = self.func("moe_router_topk_f32");
2634        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;  // kernel fully overwrites
2635        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;    // kernel fully overwrites
2636        let cfg = LaunchConfig { grid_dim: (t as u32, 1, 1), block_dim: (n_expert as u32, 1, 1),
2637                                 shared_mem_bytes: 0 };
2638        let (ne, nu) = (n_expert as i32, n_used as i32);
2639        let __s_b = self.gpu.stream();
2640        let mut b = __s_b.launch_builder(&f);
2641        b.arg(logits).arg(&mut sel_idx).arg(&mut sel_w).arg(&ne).arg(&nu);
2642        unsafe { b.launch(cfg)?; }
2643        Ok((sel_idx, sel_w))
2644    }
2645
2646    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
2647    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
2648    pub fn moe_router_topk_scaled(&self, logits: &CudaSlice<f32>, t: usize, n_expert: usize,
2649                                  n_used: usize, ex_scale: &CudaSlice<f32>)
2650                                  -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2651        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
2652        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
2653        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
2654        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
2655        let f = self.func("moe_router_topk_scaled_f32");
2656        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
2657        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
2658        let cfg = LaunchConfig { grid_dim: (t as u32, 1, 1), block_dim: (n_expert as u32, 1, 1),
2659                                 shared_mem_bytes: 0 };
2660        let (ne, nu) = (n_expert as i32, n_used as i32);
2661        let __s_b = self.gpu.stream();
2662        let mut b = __s_b.launch_builder(&f);
2663        b.arg(logits).arg(&mut sel_idx).arg(&mut sel_w).arg(&ne).arg(&nu).arg(ex_scale);
2664        unsafe { b.launch(cfg)?; }
2665        Ok((sel_idx, sel_w))
2666    }
2667
2668    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
2669    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
2670    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
2671    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
2672    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
2673    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
2674    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
2675    pub fn moe_router_topk_host(&self, logits: &CudaSlice<f32>, t: usize, n_expert: usize, n_used: usize)
2676                                -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
2677        let f = self.func("moe_router_topk_f32");
2678        let n = t * n_used;
2679        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
2680        let mut sel_w = self.alloc_uninit::<f32>(n)?;
2681        let cfg = LaunchConfig { grid_dim: (t as u32, 1, 1), block_dim: (n_expert as u32, 1, 1),
2682                                 shared_mem_bytes: 0 };
2683        let (ne, nu) = (n_expert as i32, n_used as i32);
2684        let __s_b = self.gpu.stream();
2685        let mut b = __s_b.launch_builder(&f);
2686        b.arg(logits).arg(&mut sel_idx).arg(&mut sel_w).arg(&ne).arg(&nu);
2687        unsafe { b.launch(cfg)?; }
2688        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
2689        let bytes = n * 8;
2690        let mut guard = self.router_stage.lock().unwrap();
2691        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
2692            *guard = Some(PinnedStage::new(bytes.max(4096))?);
2693        }
2694        let stage = guard.as_mut().unwrap();
2695        let (si, sw) = unsafe {
2696            (std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
2697             std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n))
2698        };
2699        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;   // async (pinned dst)
2700        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;     // async (pinned dst)
2701        self.gpu.stream().synchronize()?;               // ONE sync for both
2702        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
2703    }
2704
2705    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
2706    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
2707    /// original expert ids before top-k. Exact key ties choose the smaller original id.
2708    #[allow(clippy::too_many_arguments)]
2709    pub fn moe_router_sigmoid_topk(&self, logits: &CudaSlice<f32>, t: usize, n_expert: usize,
2710                                    n_used: usize, active_count: usize,
2711                                    correction_bias: &CudaSlice<f32>,
2712                                    active: &CudaSlice<u8>, scaling_factor: f32, route_norm: bool)
2713                                    -> Result<(CudaSlice<i32>, CudaSlice<f32>),
2714                                              Box<dyn std::error::Error>> {
2715        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
2716        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
2717            return Err(format!(
2718                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
2719            ).into());
2720        }
2721        if logits.len() < t * n_expert || correction_bias.len() != n_expert
2722            || active.len() != n_expert {
2723            return Err(format!(
2724                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
2725                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
2726            ).into());
2727        }
2728        let f = self.func("moe_router_sigmoid_topk_f32");
2729        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
2730        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
2731        let threads = n_expert.div_ceil(32) * 32;
2732        let cfg = LaunchConfig { grid_dim: (t as u32, 1, 1), block_dim: (threads as u32, 1, 1),
2733                                 shared_mem_bytes: 0 };
2734        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
2735        let __s_b = self.gpu.stream();
2736        let mut b = __s_b.launch_builder(&f);
2737        b.arg(logits).arg(correction_bias).arg(active).arg(&mut sel_idx).arg(&mut sel_w)
2738         .arg(&ne).arg(&nu).arg(&scaling_factor).arg(&rn);
2739        unsafe { b.launch(cfg)?; }
2740        Ok((sel_idx, sel_w))
2741    }
2742
2743    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
2744    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
2745    #[allow(clippy::too_many_arguments)]
2746    pub fn moe_router_sigmoid_topk_host(
2747        &self,
2748        logits: &CudaSlice<f32>,
2749        t: usize,
2750        n_expert: usize,
2751        n_used: usize,
2752        active_count: usize,
2753        correction_bias: &CudaSlice<f32>,
2754        active: &CudaSlice<u8>,
2755        scaling_factor: f32,
2756        route_norm: bool,
2757    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
2758        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
2759            logits, t, n_expert, n_used, active_count, correction_bias, active, scaling_factor,
2760            route_norm,
2761        )?;
2762        let n = t * n_used;
2763        let bytes = n * 8;
2764        let mut guard = self.router_stage.lock().unwrap();
2765        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
2766            *guard = Some(PinnedStage::new(bytes.max(4096))?);
2767        }
2768        let stage = guard.as_mut().unwrap();
2769        let (si, sw) = unsafe {
2770            (std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
2771             std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n))
2772        };
2773        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
2774        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
2775        self.gpu.stream().synchronize()?;
2776        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
2777    }
2778
2779    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
2780    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
2781    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
2782    pub fn stage_expert_async(&self, host_bytes: &[u8], scratch: &mut CudaSlice<u8>, off: usize)
2783                              -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
2784        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
2785        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
2786        Ok(self.copy_stream.record_event(None)?)
2787    }
2788
2789    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
2790    pub fn compute_wait(&self, ev: &cudarc::driver::CudaEvent) -> Result<(), Box<dyn std::error::Error>> {
2791        self.gpu.stream().wait(ev)?;
2792        Ok(())
2793    }
2794
2795    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
2796    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
2797    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
2798    /// CudaView base+offset pointer is honored by the launch arg.
2799    pub fn qmatvec_view(&self, w: &CudaSlice<u8>, range: std::ops::Range<usize>,
2800                        x: &cudarc::driver::CudaView<f32>, m: usize, in_f: usize, out_f: usize,
2801                        qtype: i32, row_bytes: usize)
2802                        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2803        let f = self.func("qmatvec_f32");
2804        let wv = w.slice(range);  // CudaView<u8>, offset honored
2805        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
2806        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2807        let (inf, outf, mi, qt, rb) = (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
2808        let __s_b = self.gpu.stream();
2809        let mut b = __s_b.launch_builder(&f);
2810        b.arg(&wv).arg(x).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&qt).arg(&rb);
2811        unsafe { b.launch(cfg)?; }
2812        Ok(y)
2813    }
2814
2815    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
2816    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
2817    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
2818    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
2819    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
2820    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
2821    #[allow(clippy::too_many_arguments)]
2822    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
2823    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
2824    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
2825    pub fn moe_gate_up_silu8_q8(&self, gp: WPtr8, up: WPtr8,
2826                                aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
2827                                in_f: usize, n_ff: usize, n_used: usize, qt_g: i32, qt_u: i32,
2828                                rb_g: usize, rb_u: usize)
2829                                -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2830        let f = self.func("moe_gate_up_silu8_q8");
2831        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
2832        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
2833                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2834        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
2835        let __s_b = self.gpu.stream();
2836        let mut b = __s_b.launch_builder(&f);
2837        b.arg(&gp).arg(&up).arg(aq).arg(ad).arg(&mut act)
2838         .arg(&inf).arg(&nff).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu);
2839        unsafe { b.launch(cfg)?; }
2840        Ok(act)
2841    }
2842
2843    #[allow(clippy::too_many_arguments)]
2844    pub fn moe_down8_fma_q8(&self, dp: WPtr8, w: F32x8,
2845                            aq2: &CudaSlice<i8>, ad2: &CudaSlice<f32>,
2846                            dst: &mut cudarc::driver::CudaViewMut<f32>,
2847                            in_f: usize, out_f: usize, n_used: usize, qt: i32, rb: usize)
2848                            -> Result<(), Box<dyn std::error::Error>> {
2849        let f = self.func("moe_down8_fma_q8");
2850        let cfg = LaunchConfig { grid_dim: (out_f as u32, 1, 1),
2851                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
2852        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
2853        let __s_b = self.gpu.stream();
2854        let mut b = __s_b.launch_builder(&f);
2855        b.arg(&dp).arg(&w).arg(aq2).arg(ad2).arg(dst)
2856         .arg(&inf).arg(&outf).arg(&nu).arg(&qt).arg(&rbi);
2857        unsafe { b.launch(cfg)?; }
2858        Ok(())
2859    }
2860
2861    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
2862    pub fn qmatvec_expert_q8(&self, w: &CudaSlice<u8>, range: std::ops::Range<usize>,
2863                             aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize,
2864                             in_f: usize, out_f: usize, qtype: i32, row_bytes: usize)
2865                             -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2866        let f = self.func("qmatvec_expert_q8");
2867        let wv = w.slice(range);
2868        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
2869        const ROWS: u32 = 4;   // MEMRA_MMVQ_ROWS
2870        let cfg = LaunchConfig { grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
2871                                 block_dim: (32, ROWS, 1), shared_mem_bytes: 0 };
2872        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
2873        let __s_b = self.gpu.stream();
2874        let mut b = __s_b.launch_builder(&f);
2875        b.arg(&wv).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&qtype).arg(&rbi);
2876        unsafe { b.launch(cfg)?; }
2877        Ok(y)
2878    }
2879
2880    pub fn moe_gate_up_silu8(&self, gp: WPtr8, up: WPtr8, x: &cudarc::driver::CudaView<f32>,
2881                             in_f: usize, n_ff: usize, n_used: usize, qt_g: i32, qt_u: i32,
2882                             rb_g: usize, rb_u: usize)
2883                             -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2884        let f = self.func("moe_gate_up_silu8_f32");
2885        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;  // fully overwritten
2886        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
2887                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2888        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
2889        let __s_b = self.gpu.stream();
2890        let mut b = __s_b.launch_builder(&f);
2891        b.arg(&gp).arg(&up).arg(x).arg(&mut act)
2892         .arg(&inf).arg(&nff).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu);
2893        unsafe { b.launch(cfg)?; }
2894        Ok(act)
2895    }
2896
2897    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
2898    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
2899    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
2900    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
2901    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
2902    #[allow(clippy::too_many_arguments)]
2903    pub fn moe_down8_fma_into(&self, dp: WPtr8, w: F32x8, act: &CudaSlice<f32>,
2904                              dst: &mut cudarc::driver::CudaViewMut<f32>,
2905                              in_f: usize, out_f: usize, n_used: usize, qt: i32, rb: usize)
2906                              -> Result<(), Box<dyn std::error::Error>> {
2907        let f = self.func("moe_down8_fma_f32");
2908        let cfg = LaunchConfig { grid_dim: (out_f as u32, 1, 1),
2909                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
2910        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
2911        let __s_b = self.gpu.stream();
2912        let mut b = __s_b.launch_builder(&f);
2913        b.arg(&dp).arg(&w).arg(act).arg(dst).arg(&inf).arg(&outf).arg(&nu).arg(&qt).arg(&rbv);
2914        unsafe { b.launch(cfg)?; }
2915        Ok(())
2916    }
2917
2918    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
2919    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
2920    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
2921    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
2922    #[allow(clippy::too_many_arguments)]
2923    /// dp4a q8 twin of the _dev pair (resident-experts arc).
2924    ///
2925    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
2926    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
2927    /// down's FMA chain stays slot-ordered serial). Seams:
2928    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
2929    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
2930    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
2931    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
2932    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
2933    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
2934    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
2935    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
2936    ///                       only) | w8h2 (h2 x slot-parallel)
2937    #[allow(clippy::too_many_arguments)]
2938    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
2939    #[allow(clippy::too_many_arguments)]
2940    pub fn moe_pairs_matvec_q8(&self, table: &CudaSlice<u64>, proj: i32,
2941                               pair_tok: &CudaSlice<i32>, pair_ex: &CudaSlice<i32>,
2942                               aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
2943                               in_f: usize, out_f: usize, n_expert: usize, n_pairs: usize,
2944                               qtype: i32, row_bytes: usize)
2945                               -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2946        let f = self.func("moe_pairs_matvec_q8");
2947        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2948        const ROWS: u32 = 4;
2949        let cfg = LaunchConfig { grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
2950                                 block_dim: (32, ROWS, 1), shared_mem_bytes: 0 };
2951        let (inf, outf, ne, np, rbi) = (in_f as i32, out_f as i32, n_expert as i32,
2952                                        n_pairs as i32, row_bytes as i64);
2953        let __s_b = self.gpu.stream();
2954        let mut b = __s_b.launch_builder(&f);
2955        b.arg(table).arg(&proj).arg(pair_tok).arg(pair_ex).arg(aq).arg(ad).arg(&mut y)
2956         .arg(&inf).arg(&outf).arg(&ne).arg(&np).arg(&qtype).arg(&rbi);
2957        unsafe { b.launch(cfg)?; }
2958        Ok(y)
2959    }
2960
2961    /// Expert-major pair matvec (weight-reuse across each expert's token group).
2962    #[allow(clippy::too_many_arguments)]
2963    pub fn moe_pairs_matvec_q8_em(&self, table: &CudaSlice<u64>, proj: i32,
2964                                  ex_ids: &CudaSlice<i32>, ex_off: &CudaSlice<i32>,
2965                                  ex_pairs: &CudaSlice<i32>, pair_tok: &CudaSlice<i32>,
2966                                  aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
2967                                  in_f: usize, out_f: usize, n_expert: usize, n_active: usize,
2968                                  n_pairs: usize, qtype: i32, row_bytes: usize)
2969                                  -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2970        let f = self.func("moe_pairs_matvec_q8_em");
2971        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2972        const ROWS: u32 = 4;
2973        let cfg = LaunchConfig { grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
2974                                 block_dim: (32, ROWS, 1), shared_mem_bytes: 0 };
2975        let (inf, outf, ne, na, rbi) = (in_f as i32, out_f as i32, n_expert as i32,
2976                                        n_active as i32, row_bytes as i64);
2977        let __s_b = self.gpu.stream();
2978        let mut b = __s_b.launch_builder(&f);
2979        b.arg(table).arg(&proj).arg(ex_ids).arg(ex_off).arg(ex_pairs).arg(pair_tok)
2980         .arg(aq).arg(ad).arg(&mut y)
2981         .arg(&inf).arg(&outf).arg(&ne).arg(&na).arg(&qtype).arg(&rbi);
2982        unsafe { b.launch(cfg)?; }
2983        Ok(y)
2984    }
2985
2986    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
2987    // weight group once per (row,group) then dp4a's across the expert's token group.
2988    #[allow(clippy::too_many_arguments)]
2989    pub fn moe_pairs_matvec_q8_dec(&self, table: &CudaSlice<u64>, proj: i32,
2990                                   ex_ids: &CudaSlice<i32>, ex_off: &CudaSlice<i32>,
2991                                   ex_pairs: &CudaSlice<i32>, pair_tok: &CudaSlice<i32>,
2992                                   aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
2993                                   in_f: usize, out_f: usize, n_expert: usize, n_active: usize,
2994                                   n_pairs: usize, qtype: i32, row_bytes: usize)
2995                                   -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2996        let f = self.func("moe_pairs_matvec_q8_dec");
2997        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
2998        const ROWS: u32 = 4;
2999        let cfg = LaunchConfig { grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
3000                                 block_dim: (32, ROWS, 1), shared_mem_bytes: 0 };
3001        let (inf, outf, ne, na, rbi) = (in_f as i32, out_f as i32, n_expert as i32,
3002                                        n_active as i32, row_bytes as i64);
3003        let __s_b = self.gpu.stream();
3004        let mut b = __s_b.launch_builder(&f);
3005        b.arg(table).arg(&proj).arg(ex_ids).arg(ex_off).arg(ex_pairs).arg(pair_tok)
3006         .arg(aq).arg(ad).arg(&mut y)
3007         .arg(&inf).arg(&outf).arg(&ne).arg(&na).arg(&qtype).arg(&rbi);
3008        unsafe { b.launch(cfg)?; }
3009        Ok(y)
3010    }
3011
3012    pub fn moe_pairs_gelu_mul(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, n: usize)
3013                              -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3014        let f = self.func("moe_pairs_gelu_mul");
3015        let mut act = self.alloc_uninit::<f32>(n)?;
3016        let cfg = LaunchConfig::for_num_elems(n as u32);
3017        let nl = n as i64;
3018        let __s_b = self.gpu.stream();
3019        let mut b = __s_b.launch_builder(&f);
3020        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
3021        unsafe { b.launch(cfg)?; }
3022        Ok(act)
3023    }
3024
3025    pub fn moe_pairs_silu_mul(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, n: usize)
3026                              -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3027        let f = self.func("moe_pairs_silu_mul");
3028        let mut act = self.alloc_uninit::<f32>(n)?;
3029        let cfg = LaunchConfig::for_num_elems(n as u32);
3030        let nl = n as i64;
3031        let __s_b = self.gpu.stream();
3032        let mut b = __s_b.launch_builder(&f);
3033        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
3034        unsafe { b.launch(cfg)?; }
3035        Ok(act)
3036    }
3037
3038    #[allow(clippy::too_many_arguments)]
3039    pub fn moe_pairs_scatter(&self, y_down: &CudaSlice<f32>, pair_w: &CudaSlice<f32>,
3040                             tok_pair_off: &CudaSlice<i32>, tok_pair_ids: &CudaSlice<i32>,
3041                             moe_out: &mut CudaSlice<f32>, t: usize, n_embd: usize)
3042                             -> Result<(), Box<dyn std::error::Error>> {
3043        let f = self.func("moe_pairs_scatter");
3044        let cfg = LaunchConfig { grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
3045                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3046        let ne = n_embd as i32;
3047        let __s_b = self.gpu.stream();
3048        let mut b = __s_b.launch_builder(&f);
3049        b.arg(y_down).arg(pair_w).arg(tok_pair_off).arg(tok_pair_ids).arg(moe_out).arg(&ne);
3050        unsafe { b.launch(cfg)?; }
3051        Ok(())
3052    }
3053
3054    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
3055    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
3056    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
3057    #[allow(clippy::too_many_arguments)]
3058    pub fn moe_gate_up_gelu8_dev_q8(&self, table: &CudaSlice<u64>, sel: &cudarc::driver::CudaView<i32>,
3059                                    aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
3060                                    in_f: usize, n_ff: usize, n_used: usize, n_expert: usize,
3061                                    qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize)
3062                                    -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3063        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
3064        let (inf, nff, ne, rbg, rbu) = (in_f as i32, n_ff as i32, n_expert as i32,
3065                                        rb_g as i64, rb_u as i64);
3066        let f = self.func("moe_gate_up_gelu8_dev_q8");
3067        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3068                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3069        let __s_b = self.gpu.stream();
3070        let mut b = __s_b.launch_builder(&f);
3071        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3072         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu);
3073        unsafe { b.launch(cfg)?; }
3074        Ok(act)
3075    }
3076
3077    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
3078    #[allow(clippy::too_many_arguments)]
3079    pub fn moe_gate_up_gelu8_dev_q8_rows(&self, table: &CudaSlice<u64>, sel: &CudaSlice<i32>,
3080                                         aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, t: usize,
3081                                         in_f: usize, n_ff: usize, n_used: usize, n_expert: usize,
3082                                         qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize)
3083                                         -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3084        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
3085        let (inf, nff, ne, rbg, rbu, nu) = (in_f as i32, n_ff as i32, n_expert as i32,
3086                                            rb_g as i64, rb_u as i64, n_used as i32);
3087        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
3088        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, t as u32),
3089                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3090        let __s_b = self.gpu.stream();
3091        let mut b = __s_b.launch_builder(&f);
3092        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3093         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu).arg(&nu);
3094        unsafe { b.launch(cfg)?; }
3095        Ok(act)
3096    }
3097
3098    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
3099    #[allow(clippy::too_many_arguments)]
3100    pub fn moe_gate_up_gelu8_dev_q8_csr(&self, table: &CudaSlice<u64>, sel: &CudaSlice<i32>,
3101                                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, n_pairs: usize,
3102                                        in_f: usize, n_ff: usize, n_used: usize, n_expert: usize,
3103                                        qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize)
3104                                        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3105        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
3106        let (inf, nff, ne, rbg, rbu, nu, npi) = (in_f as i32, n_ff as i32, n_expert as i32,
3107                                                 rb_g as i64, rb_u as i64, n_used as i32,
3108                                                 n_pairs as i32);
3109        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
3110        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_pairs as u32, 1),
3111                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3112        let __s_b = self.gpu.stream();
3113        let mut b = __s_b.launch_builder(&f);
3114        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3115         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu).arg(&nu).arg(&npi);
3116        unsafe { b.launch(cfg)?; }
3117        Ok(act)
3118    }
3119
3120    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
3121    #[allow(clippy::too_many_arguments)]
3122    pub fn moe_down8_fma_dev_q8_rows_g(&self, table: &CudaSlice<u64>, sel: &CudaSlice<i32>,
3123                                       w: &CudaSlice<f32>, aq2: &CudaSlice<i8>, ad2: &CudaSlice<f32>,
3124                                       dst: &mut CudaSlice<f32>, t: usize,
3125                                       in_f: usize, out_f: usize, n_used: usize, n_expert: usize,
3126                                       qt: i32, rb: usize)
3127                                       -> Result<(), Box<dyn std::error::Error>> {
3128        let (inf, outf, nu, ne, rbi) = (in_f as i32, out_f as i32, n_used as i32,
3129                                        n_expert as i32, rb as i64);
3130        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
3131        // eight warps, then replay the original slot-ordered FMA chain. Every
3132        // other shape retains the generic one-warp rows kernel.
3133        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096
3134            && n_used == 8 && qt == QT_IQ4_XS;
3135        let f = self.func(if step_b1_w8 {
3136            "moe_down8_fma_dev_q8_rows_w8"
3137        } else {
3138            "moe_down8_fma_dev_q8_rows_g"
3139        });
3140        let cfg = LaunchConfig { grid_dim: (out_f as u32, 1, t as u32),
3141                                 block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
3142                                 shared_mem_bytes: 0 };
3143        let __s_b = self.gpu.stream();
3144        let mut b = __s_b.launch_builder(&f);
3145        b.arg(table).arg(sel).arg(w).arg(aq2).arg(ad2).arg(dst)
3146         .arg(&inf).arg(&outf).arg(&nu).arg(&ne).arg(&qt).arg(&rbi);
3147        unsafe { b.launch(cfg)?; }
3148        Ok(())
3149    }
3150
3151    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
3152    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
3153    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
3154    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
3155        let (out_f, in_f) = (2048usize, 2816usize);
3156        let nblk = in_f / 32;
3157        let mut seed = 0x9E3779B97F4A7C15u64;
3158        let mut rng = move || { seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); (seed >> 33) as u8 };
3159        let mut w = vec![0u8; out_f * nblk * 18];
3160        for b in w.iter_mut() { *b = rng(); }
3161        for r in 0..out_f {
3162            for g in 0..nblk {
3163                let off = (r * nblk + g) * 18;
3164                w[off] = 0x00; w[off + 1] = 0x2C;   // sane half d
3165            }
3166        }
3167        let qplane = out_f * nblk * 16;
3168        let mut wrp = vec![0u8; w.len()];
3169        for r in 0..out_f {
3170            for g in 0..nblk {
3171                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
3172                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
3173                    .copy_from_slice(&src[0..2]);
3174                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
3175            }
3176        }
3177        let w_d = self.htod_bytes(&w)?;
3178        let wrp_d = self.htod_bytes(&wrp)?;
3179        let mut aq = vec![0i8; m * in_f];
3180        for v in aq.iter_mut() { *v = rng() as i8; }
3181        let aq_d = self.htod_i8(&aq)?;
3182        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
3183        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
3184        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
3185        const RPB: u32 = 4;
3186        let cfg = LaunchConfig { grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
3187                                 block_dim: (32, RPB, 1), shared_mem_bytes: 0 };
3188        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
3189        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
3190        let fb = self.func("qmatvec_q4_0_mmvq_b4");
3191        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
3192        {
3193            let __s_b = self.gpu.stream();
3194            let mut b = __s_b.launch_builder(&fb);
3195            b.arg(&w_d).arg(&aq_d).arg(&ad_d).arg(&mut y0).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
3196            unsafe { b.launch(cfg)?; }
3197            let __s_b = self.gpu.stream();
3198            let mut b = __s_b.launch_builder(&fr);
3199            b.arg(&wrp_d).arg(&aq_d).arg(&ad_d).arg(&mut y1).arg(&inf).arg(&outf).arg(&mi).arg(&qp);
3200            unsafe { b.launch(cfg)?; }
3201        }
3202        self.gpu.stream().synchronize()?;
3203        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
3204        let nd = h0.iter().zip(&h1).filter(|(a, b)| a.to_bits() != b.to_bits()).count();
3205        if nd != 0 { return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into()); }
3206        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
3207            self.gpu.stream().synchronize()?;
3208            let t0 = std::time::Instant::now();
3209            for _ in 0..500 {
3210                if rp {
3211                    let __s_b = self.gpu.stream();
3212                    let mut b = __s_b.launch_builder(&fr);
3213                    b.arg(&wrp_d).arg(&aq_d).arg(&ad_d).arg(&mut y1)
3214                     .arg(&inf).arg(&outf).arg(&mi).arg(&qp);
3215                    unsafe { b.launch(cfg)?; }
3216                } else {
3217                    let __s_b = self.gpu.stream();
3218                    let mut b = __s_b.launch_builder(&fb);
3219                    b.arg(&w_d).arg(&aq_d).arg(&ad_d).arg(&mut y0)
3220                     .arg(&inf).arg(&outf).arg(&mi).arg(&rb);
3221                    unsafe { b.launch(cfg)?; }
3222                }
3223            }
3224            self.gpu.stream().synchronize()?;
3225            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
3226        };
3227        let _ = time(false)?; let _ = time(true)?;   // warm
3228        Ok((time(false)?, time(true)?))
3229    }
3230
3231    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
3232    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
3233    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
3234    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
3235    pub fn build_q4_rp4(&self, t: &mut crate::model::GpuTensor)
3236                        -> Result<(), Box<dyn std::error::Error>> {
3237        use crate::model::GpuTensor;
3238        let GpuTensor::Quant { bytes, qtype, row_bytes, ne, rp4, .. } = t else { return Ok(()) };
3239        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 { return Ok(()); }
3240        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
3241        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 { return Ok(()); }
3242        let nblk = in_f / 32;
3243        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
3244        let f = self.func("q4_0_split_rp_build");
3245        let n = (out_f * nblk) as i32;
3246        let cfg = LaunchConfig { grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
3247                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3248        let (of, nb) = (out_f as i32, nblk as i32);
3249        let _ = n;
3250        let __s_b = self.gpu.stream();
3251        let mut b = __s_b.launch_builder(&f);
3252        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
3253        unsafe { b.launch(cfg)?; }
3254        *rp4 = Some(dst);
3255        Ok(())
3256    }
3257
3258    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
3259    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
3260    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
3261    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
3262    pub fn build_q8_rp4(&self, t: &mut crate::model::GpuTensor)
3263                        -> Result<(), Box<dyn std::error::Error>> {
3264        use crate::model::GpuTensor;
3265        let GpuTensor::Quant { bytes, qtype, row_bytes, ne, rp4, .. } = t else { return Ok(()) };
3266        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 { return Ok(()); }
3267        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
3268        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 { return Ok(()); }
3269        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
3270        Ok(())
3271    }
3272
3273    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
3274    /// mirror without a GpuTensor (same kernel the loader path above uses).
3275    pub fn build_q8_rp4_raw(&self, bytes: &CudaSlice<u8>, in_f: usize, out_f: usize)
3276                            -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3277        assert!(in_f % 32 == 0);
3278        let nblk = in_f / 32;
3279        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
3280        let f = self.func("q8_0_split_rp_build");
3281        let cfg = LaunchConfig { grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
3282                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3283        let (of, nb) = (out_f as i32, nblk as i32);
3284        let __s_b = self.gpu.stream();
3285        let mut b = __s_b.launch_builder(&f);
3286        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
3287        unsafe { b.launch(cfg)?; }
3288        Ok(dst)
3289    }
3290
3291    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
3292    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
3293    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
3294    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
3295    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
3296    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
3297    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
3298    pub fn build_q4k_rp4(&self, t: &mut crate::model::GpuTensor)
3299                         -> Result<(), Box<dyn std::error::Error>> {
3300        use crate::model::GpuTensor;
3301        let GpuTensor::Quant { bytes, qtype, row_bytes, ne, rp4, .. } = t else { return Ok(()) };
3302        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 { return Ok(()); }
3303        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
3304        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 { return Ok(()); }
3305        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
3306        Ok(())
3307    }
3308
3309    pub fn build_q6k_rp4(&self, t: &mut crate::model::GpuTensor)
3310                         -> Result<(), Box<dyn std::error::Error>> {
3311        use crate::model::GpuTensor;
3312        let GpuTensor::Quant { bytes, qtype, row_bytes, ne, rp4, .. } = t else { return Ok(()) };
3313        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 { return Ok(()); }
3314        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
3315        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 { return Ok(()); }
3316        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
3317        Ok(())
3318    }
3319
3320    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
3321    pub fn build_kq_rp4_raw(&self, bytes: &CudaSlice<u8>, in_f: usize, out_f: usize, qtype: i32)
3322                            -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3323        assert!(in_f % 256 == 0);
3324        let nsbk = in_f / 256;
3325        let (sb_bytes, kname) = match qtype {
3326            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
3327            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
3328            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
3329        };
3330        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
3331        let f = self.func(kname);
3332        let cfg = LaunchConfig { grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
3333                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3334        let (of, nb) = (out_f as i32, nsbk as i32);
3335        let __s_b = self.gpu.stream();
3336        let mut b = __s_b.launch_builder(&f);
3337        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
3338        unsafe { b.launch(cfg)?; }
3339        Ok(dst)
3340    }
3341
3342    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
3343    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
3344    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
3345    pub fn kqrp_enabled() -> bool {
3346        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3347        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
3348            Ok("0") => false,
3349            Ok(_) => true,
3350            Err(_) => cfg!(memra_hopper_mma),
3351        })
3352    }
3353
3354    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
3355    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
3356    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
3357    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
3358    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
3359    pub fn build_q4_rp_swap(&self, t: &mut crate::model::GpuTensor)
3360                            -> Result<bool, Box<dyn std::error::Error>> {
3361        self.build_q4_rp4(t)?;
3362        self.gpu.stream().synchronize()?;   // build kernel reads the GGUF bytes — drain BEFORE dropping them
3363        use crate::model::GpuTensor;
3364        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else { return Ok(false) };
3365        match rp4.take() {
3366            Some(split) => {
3367                *bytes = split;   // the GGUF-layout buffer drops here
3368                *rp = true;
3369                Ok(true)
3370            }
3371            None => Ok(false),
3372        }
3373    }
3374
3375    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
3376    pub fn q4rp_enabled() -> bool {
3377        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3378        *ON.get_or_init(|| std::env::var("MEMRA_Q4RP").map(|v| v != "0").unwrap_or(true))
3379    }
3380
3381    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
3382    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
3383    pub fn copy_rows_strided(&self, src: &CudaSlice<f32>, dst: &mut CudaSlice<f32>,
3384                             row_elems: usize, n_rows: usize, src_stride: usize, src_off: usize)
3385                             -> Result<(), Box<dyn std::error::Error>> {
3386        let f = self.func("copy_rows_strided_f32");
3387        let cfg = LaunchConfig { grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
3388                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3389        let (re, nr) = (row_elems as i32, n_rows as i32);
3390        let (st, off) = (src_stride as i64, src_off as i64);
3391        let __s_b = self.gpu.stream();
3392        let mut b = __s_b.launch_builder(&f);
3393        b.arg(src).arg(&mut *dst).arg(&re).arg(&nr).arg(&st).arg(&off);
3394        unsafe { b.launch(cfg)?; }
3395        Ok(())
3396    }
3397
3398    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
3399    pub fn u32_set_k(&self, dst: &mut CudaSlice<u32>, v: u32, idx: usize)
3400                     -> Result<(), Box<dyn std::error::Error>> {
3401        let f = self.func("u32_set_k");
3402        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1, 1, 1), shared_mem_bytes: 0 };
3403        let ii = idx as i32;
3404        let __s_b = self.gpu.stream();
3405        let mut b = __s_b.launch_builder(&f);
3406        b.arg(dst).arg(&v).arg(&ii);
3407        unsafe { b.launch(cfg)?; }
3408        Ok(())
3409    }
3410
3411    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
3412    pub fn i32_add_k(&self, d: &mut CudaSlice<i32>, v: i32) -> Result<(), Box<dyn std::error::Error>> {
3413        let f = self.func("i32_add_k");
3414        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3415        let __s_b = self.gpu.stream();
3416        let mut b = __s_b.launch_builder(&f);
3417        b.arg(d).arg(&v);
3418        unsafe { b.launch(cfg)?; }
3419        Ok(())
3420    }
3421
3422    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
3423    pub fn i32_iota_from(&self, ctr: &CudaSlice<i32>, dst: &mut CudaSlice<i32>, n: usize)
3424                         -> Result<(), Box<dyn std::error::Error>> {
3425        let f = self.func("i32_iota_from");
3426        let cfg = LaunchConfig::for_num_elems(n as u32);
3427        let ni = n as i32;
3428        let __s_b = self.gpu.stream();
3429        let mut b = __s_b.launch_builder(&f);
3430        b.arg(ctr).arg(dst).arg(&ni);
3431        unsafe { b.launch(cfg)?; }
3432        Ok(())
3433    }
3434
3435    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
3436    pub fn u32_map_k(&self, buf: &mut CudaSlice<u32>, map: &CudaSlice<u32>, idx: usize)
3437                     -> Result<(), Box<dyn std::error::Error>> {
3438        let f = self.func("u32_map_k");
3439        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1, 1, 1), shared_mem_bytes: 0 };
3440        let ii = idx as i32;
3441        let __s_b = self.gpu.stream();
3442        let mut b = __s_b.launch_builder(&f);
3443        b.arg(buf).arg(map).arg(&ii);
3444        unsafe { b.launch(cfg)?; }
3445        Ok(())
3446    }
3447
3448    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
3449    #[allow(clippy::too_many_arguments)]
3450    pub fn u32_pack2(&self, a: &CudaSlice<u32>, off_a: usize, n1: usize,
3451                     b_in: &CudaSlice<u32>, n2: usize, out: &mut CudaSlice<u32>)
3452                     -> Result<(), Box<dyn std::error::Error>> {
3453        let f = self.func("u32_pack2");
3454        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
3455        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
3456        let __s_b = self.gpu.stream();
3457        let mut b = __s_b.launch_builder(&f);
3458        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
3459        unsafe { b.launch(cfg)?; }
3460        Ok(())
3461    }
3462
3463    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
3464    pub fn moe_w_exscale(&self, w: &mut CudaSlice<f32>, sel: &CudaSlice<i32>,
3465                         s: &CudaSlice<f32>, n: usize) -> Result<(), Box<dyn std::error::Error>> {
3466        let f = self.func("moe_w_exscale");
3467        let cfg = LaunchConfig::for_num_elems(n as u32);
3468        let ni = n as i32;
3469        let __s_b = self.gpu.stream();
3470        let mut b = __s_b.launch_builder(&f);
3471        b.arg(w).arg(sel).arg(s).arg(&ni);
3472        unsafe { b.launch(cfg)?; }
3473        Ok(())
3474    }
3475
3476    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
3477    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
3478    pub fn moe_w_scale_by_expert(&self, w: &mut CudaSlice<f32>, sel: &CudaSlice<i32>,
3479                                 macros: &CudaSlice<f32>, n_expert: usize, n: usize)
3480                                 -> Result<(), Box<dyn std::error::Error>> {
3481        let f = self.func("moe_w_scale_by_expert");
3482        let cfg = LaunchConfig { grid_dim: (n.div_ceil(64) as u32, 1, 1),
3483                                 block_dim: (64, 1, 1), shared_mem_bytes: 0 };
3484        let (ne, nn) = (n_expert as i32, n as i32);
3485        let __s_b = self.gpu.stream();
3486        let mut b = __s_b.launch_builder(&f);
3487        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
3488        unsafe { b.launch(cfg)?; }
3489        Ok(())
3490    }
3491
3492    pub fn moe_gate_up_silu8_dev_q8(&self, table: &CudaSlice<u64>, sel: &cudarc::driver::CudaView<i32>,
3493                                    aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
3494                                    in_f: usize, n_ff: usize, n_used: usize, n_expert: usize,
3495                                    qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize,
3496                                    macros: &CudaSlice<f32>)
3497                                    -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3498        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
3499        let (mode, wpb) = GU.get_or_init(|| {
3500            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
3501            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB").ok()
3502                .and_then(|v| v.parse().ok()).unwrap_or(4u32).clamp(1, 16);
3503            (mode, wpb)
3504        });
3505        let (mode, wpb) = (mode.as_str(), *wpb);
3506        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
3507        let (inf, nff, ne, rbg, rbu) = (in_f as i32, n_ff as i32, n_expert as i32,
3508                                        rb_g as i64, rb_u as i64);
3509        let (f, cfg) = match mode {
3510            "1" | "2" | "4" => {
3511                let rpw: u32 = mode.parse().unwrap();
3512                let f = self.func(match rpw { 1 => "moe_gate_up_silu8_dev_q8_r1",
3513                                              2 => "moe_gate_up_silu8_dev_q8_r2",
3514                                              _ => "moe_gate_up_silu8_dev_q8_r4" });
3515                let rows_per_block = (rpw * wpb) as usize;
3516                let gx = n_ff.div_ceil(rows_per_block) as u32;
3517                (f, LaunchConfig { grid_dim: (gx, n_used as u32, 1),
3518                                   block_dim: (32, wpb, 1), shared_mem_bytes: 0 })
3519            }
3520            "j8" if n_used <= 32 => (self.func("moe_gate_up_silu8_dev_q8_j8"),
3521                     LaunchConfig { grid_dim: (n_ff as u32, 1, 1),
3522                                    block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3523            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
3524            "vsm2" => {
3525                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
3526                let sh = (rb_g + rb_u) as u32;
3527                use cudarc::driver::sys::CUfunction_attribute_enum as A;
3528                f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
3529                (f, LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3530                                   block_dim: (32, 1, 1), shared_mem_bytes: sh })
3531            }
3532            "vsm" => {
3533                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
3534                let sh = (rb_g + rb_u) as u32;
3535                use cudarc::driver::sys::CUfunction_attribute_enum as A;
3536                f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
3537                (f, LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3538                                   block_dim: (32, 1, 1), shared_mem_bytes: sh })
3539            }
3540            "sg" => (self.func("moe_gate_up_silu8_dev_q8_sg"),
3541                     LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3542                                    block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3543            "j8sg" if n_used <= 32 => (self.func("moe_gate_up_silu8_dev_q8_j8sg"),
3544                     LaunchConfig { grid_dim: (n_ff as u32, 1, 1),
3545                                    block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3546            "u64" if in_f == 2048 => (self.func("moe_gate_up_silu8_dev_q8_u64"),
3547                     LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3548                                    block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3549            "gs4" if in_f == 2048 => (self.func("moe_gate_up_silu8_dev_q8_gs4"),
3550                     LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3551                                    block_dim: (32, 4, 1), shared_mem_bytes: 0 }),
3552            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
3553            "v" | "" => (self.func("moe_gate_up_silu8_dev_q8_v"),
3554                    LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3555                                   block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3556            "s2" => (self.func("moe_gate_up_silu8_dev_q8_s2"),
3557                     LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3558                                    block_dim: (32, 2, 1), shared_mem_bytes: 0 }),
3559            "s2z" => {
3560                let rz = wpb.min(16);        // s2z smem tile is [16][2]
3561                (self.func("moe_gate_up_silu8_dev_q8_s2z"),
3562                 LaunchConfig { grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
3563                                block_dim: (32, 2, rz), shared_mem_bytes: 0 })
3564            }
3565            _ => (self.func("moe_gate_up_silu8_dev_q8"),
3566                  LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3567                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3568        };
3569        let __s_b = self.gpu.stream();
3570        let mut b = __s_b.launch_builder(&f);
3571        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3572         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu).arg(macros);
3573        unsafe { b.launch(cfg)?; }
3574        Ok(act)
3575    }
3576
3577    #[allow(clippy::too_many_arguments)]
3578    pub fn moe_down8_fma_dev_q8(&self, table: &CudaSlice<u64>, sel: &cudarc::driver::CudaView<i32>,
3579                                w: &cudarc::driver::CudaView<f32>,
3580                                aq2: &CudaSlice<i8>, ad2: &CudaSlice<f32>,
3581                                dst: &mut cudarc::driver::CudaViewMut<f32>,
3582                                in_f: usize, out_f: usize, n_used: usize, n_expert: usize,
3583                                qt: i32, rb: usize)
3584                                -> Result<(), Box<dyn std::error::Error>> {
3585        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
3586        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
3587        let (inf, outf, nu, ne, rbi) = (in_f as i32, out_f as i32, n_used as i32,
3588                                        n_expert as i32, rb as i64);
3589        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
3590        // the h2 twins are nsb==16 (in_f==512) shape-gated.
3591        let (f, cfg) = match mode.as_str() {
3592            m @ ("1" | "2" | "4") if n_used <= 8 => {
3593                let rpw: usize = m.parse().unwrap();
3594                let f = self.func(match rpw { 1 => "moe_down8_fma_dev_q8_w8r1",
3595                                              2 => "moe_down8_fma_dev_q8_w8r2",
3596                                              _ => "moe_down8_fma_dev_q8_w8r4" });
3597                (f, LaunchConfig { grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
3598                                   block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 })
3599            }
3600            "h2" if in_f == 512 => (self.func("moe_down8_fma_dev_q8_h2"),
3601                LaunchConfig { grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
3602                               block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3603            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
3604            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
3605            "" if in_f == 704 && n_used <= 8 =>
3606                (self.func("moe_down8_fma_dev_q8_w8r2"),
3607                 LaunchConfig { grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
3608                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3609            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
3610            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
3611            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
3612            "w8h2v" | "" if in_f == 512 && n_used <= 8 =>
3613                (self.func("moe_down8_fma_dev_q8_w8h2v"),
3614                 LaunchConfig { grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
3615                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3616            "w8h2r2v" if in_f == 512 && n_used <= 8 =>
3617                (self.func("moe_down8_fma_dev_q8_w8h2r2v"),
3618                 LaunchConfig { grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
3619                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3620            "w8h2r2" if in_f == 512 && n_used <= 8 =>
3621                (self.func("moe_down8_fma_dev_q8_w8h2r2"),
3622                 LaunchConfig { grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
3623                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3624            "w8h2" if in_f == 512 && n_used <= 8 =>
3625                (self.func("moe_down8_fma_dev_q8_w8h2"),
3626                 LaunchConfig { grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
3627                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 }),
3628            _ => (self.func("moe_down8_fma_dev_q8"),
3629                  LaunchConfig { grid_dim: (out_f as u32, 1, 1),
3630                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3631        };
3632        let __s_b = self.gpu.stream();
3633        let mut b = __s_b.launch_builder(&f);
3634        b.arg(table).arg(sel).arg(w).arg(aq2).arg(ad2).arg(dst)
3635         .arg(&inf).arg(&outf).arg(&nu).arg(&ne).arg(&qt).arg(&rbi);
3636        unsafe { b.launch(cfg)?; }
3637        Ok(())
3638    }
3639
3640    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
3641    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
3642    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
3643    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
3644    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
3645    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
3646    #[allow(clippy::too_many_arguments)]
3647    pub fn moe_gate_up_silu8_dev_q8_rows(&self, table: &CudaSlice<u64>, sel: &CudaSlice<i32>,
3648                                         aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, t: usize,
3649                                         in_f: usize, n_ff: usize, n_used: usize, n_expert: usize,
3650                                         qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize,
3651                                         macros: &CudaSlice<f32>)
3652                                         -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3653        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
3654        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
3655        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, t as u32),
3656                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3657        let (inf, nff, ne, nu, rbg, rbu) = (in_f as i32, n_ff as i32, n_expert as i32,
3658                                            n_used as i32, rb_g as i64, rb_u as i64);
3659        let __s_b = self.gpu.stream();
3660        let mut b = __s_b.launch_builder(&f);
3661        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3662         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu).arg(&nu).arg(macros);
3663        unsafe { b.launch(cfg)?; }
3664        Ok(act)
3665    }
3666
3667    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
3668    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
3669    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
3670    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
3671    #[allow(clippy::too_many_arguments)]
3672    pub fn moe_down8_fma_dev_q8_rows(&self, table: &CudaSlice<u64>, sel: &CudaSlice<i32>,
3673                                     w: &CudaSlice<f32>, aq2: &CudaSlice<i8>, ad2: &CudaSlice<f32>,
3674                                     dst: &mut CudaSlice<f32>, t: usize,
3675                                     in_f: usize, out_f: usize, n_used: usize, n_expert: usize,
3676                                     qt: i32, rb: usize)
3677                                     -> Result<(), Box<dyn std::error::Error>> {
3678        assert!(in_f == 512 && n_used <= 8, "down rows twin is w8h2v shape-gated");
3679        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
3680        let cfg = LaunchConfig { grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
3681                                 block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 };
3682        let (inf, outf, nu, ne, rbi) = (in_f as i32, out_f as i32, n_used as i32,
3683                                        n_expert as i32, rb as i64);
3684        let __s_b = self.gpu.stream();
3685        let mut b = __s_b.launch_builder(&f);
3686        b.arg(table).arg(sel).arg(w).arg(aq2).arg(ad2).arg(dst)
3687         .arg(&inf).arg(&outf).arg(&nu).arg(&ne).arg(&qt).arg(&rbi);
3688        unsafe { b.launch(cfg)?; }
3689        Ok(())
3690    }
3691
3692    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
3693    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
3694    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
3695    #[allow(clippy::too_many_arguments)]
3696    pub fn moe_gate_up_silu8_dev_q8_csr(&self, table: &CudaSlice<u64>, sel: &CudaSlice<i32>,
3697                                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
3698                                        n_pairs: usize, in_f: usize, n_ff: usize, n_used: usize,
3699                                        n_expert: usize, qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize)
3700                                        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3701        let f = self.func("moe_gate_up_silu8_dev_q8_csr_iq4");
3702        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
3703        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_pairs as u32, 1),
3704                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3705        let (inf, nff, ne, nu, npi, rbg, rbu) = (in_f as i32, n_ff as i32, n_expert as i32,
3706                                                 n_used as i32, n_pairs as i32, rb_g as i64, rb_u as i64);
3707        let __s_b = self.gpu.stream();
3708        let mut b = __s_b.launch_builder(&f);
3709        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3710         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu).arg(&nu).arg(&npi);
3711        unsafe { b.launch(cfg)?; }
3712        Ok(act)
3713    }
3714
3715
3716    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
3717    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
3718    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
3719    #[allow(clippy::too_many_arguments)]
3720    pub fn moe_down8_fma_dev_q8_variant(&self, variant: &str, table: &CudaSlice<u64>,
3721                                        sel: &cudarc::driver::CudaView<i32>,
3722                                        w: &cudarc::driver::CudaView<f32>,
3723                                        aq2: &CudaSlice<i8>, ad2: &CudaSlice<f32>,
3724                                        dst: &mut cudarc::driver::CudaViewMut<f32>,
3725                                        in_f: usize, out_f: usize, n_used: usize, n_expert: usize,
3726                                        qt: i32, rb: usize)
3727                                        -> Result<(), Box<dyn std::error::Error>> {
3728        let (inf, outf, nu, ne, rbi) = (in_f as i32, out_f as i32, n_used as i32,
3729                                        n_expert as i32, rb as i64);
3730        let (f, cfg) = match variant {
3731            "w8h2" | "w8h2v" => {
3732                (self.func(if variant == "w8h2" { "moe_down8_fma_dev_q8_w8h2" }
3733                           else { "moe_down8_fma_dev_q8_w8h2v" }),
3734                 LaunchConfig { grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
3735                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 })
3736            }
3737            "w8h2r2" | "w8h2r2v" => {
3738                (self.func(if variant == "w8h2r2" { "moe_down8_fma_dev_q8_w8h2r2" }
3739                           else { "moe_down8_fma_dev_q8_w8h2r2v" }),
3740                 LaunchConfig { grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
3741                                block_dim: (32, n_used as u32, 1), shared_mem_bytes: 0 })
3742            }
3743            _ => (self.func("moe_down8_fma_dev_q8"),
3744                  LaunchConfig { grid_dim: (out_f as u32, 1, 1),
3745                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 }),
3746        };
3747        let __s_b = self.gpu.stream();
3748        let mut b = __s_b.launch_builder(&f);
3749        b.arg(table).arg(sel).arg(w).arg(aq2).arg(ad2).arg(dst)
3750         .arg(&inf).arg(&outf).arg(&nu).arg(&ne).arg(&qt).arg(&rbi);
3751        unsafe { b.launch(cfg)?; }
3752        Ok(())
3753    }
3754
3755    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
3756    #[allow(clippy::too_many_arguments)]
3757    pub fn moe_gate_up_silu8_dev_q8_variant(&self, variant: &str, table: &CudaSlice<u64>,
3758                                            sel: &cudarc::driver::CudaView<i32>,
3759                                            aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
3760                                            in_f: usize, n_ff: usize, n_used: usize,
3761                                            n_expert: usize, qt_g: i32, qt_u: i32,
3762                                            rb_g: usize, rb_u: usize)
3763                                            -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3764        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
3765        let (inf, nff, ne, rbg, rbu) = (in_f as i32, n_ff as i32, n_expert as i32,
3766                                        rb_g as i64, rb_u as i64);
3767        let f = self.func(if variant == "v" { "moe_gate_up_silu8_dev_q8_v" }
3768                          else { "moe_gate_up_silu8_dev_q8" });
3769        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3770                                 block_dim: (32, 1, 1), shared_mem_bytes: 0 };
3771        let __s_b = self.gpu.stream();
3772        let mut b = __s_b.launch_builder(&f);
3773        b.arg(table).arg(sel).arg(aq).arg(ad).arg(&mut act)
3774         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu);
3775        unsafe { b.launch(cfg)?; }
3776        Ok(act)
3777    }
3778
3779    pub fn moe_gate_up_silu8_dev(&self, table: &CudaSlice<u64>, sel: &cudarc::driver::CudaView<i32>,
3780                                 x: &cudarc::driver::CudaView<f32>,
3781                                 in_f: usize, n_ff: usize, n_used: usize, n_expert: usize,
3782                                 qt_g: i32, qt_u: i32, rb_g: usize, rb_u: usize,
3783                                 macros: &CudaSlice<f32>)
3784                                 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3785        let f = self.func("moe_gate_up_silu8_dev");
3786        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;  // fully overwritten
3787        let cfg = LaunchConfig { grid_dim: (n_ff as u32, n_used as u32, 1),
3788                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3789        let (inf, nff, ne, rbg, rbu) = (in_f as i32, n_ff as i32, n_expert as i32,
3790                                        rb_g as i64, rb_u as i64);
3791        let __s_b = self.gpu.stream();
3792        let mut b = __s_b.launch_builder(&f);
3793        b.arg(table).arg(sel).arg(x).arg(&mut act)
3794         .arg(&inf).arg(&nff).arg(&ne).arg(&qt_g).arg(&qt_u).arg(&rbg).arg(&rbu).arg(macros);
3795        unsafe { b.launch(cfg)?; }
3796        Ok(act)
3797    }
3798
3799    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
3800    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
3801    #[allow(clippy::too_many_arguments)]
3802    pub fn moe_down8_fma_dev(&self, table: &CudaSlice<u64>, sel: &cudarc::driver::CudaView<i32>,
3803                             w: &cudarc::driver::CudaView<f32>, act: &CudaSlice<f32>,
3804                             dst: &mut cudarc::driver::CudaViewMut<f32>,
3805                             in_f: usize, out_f: usize, n_used: usize, n_expert: usize,
3806                             qt: i32, rb: usize)
3807                             -> Result<(), Box<dyn std::error::Error>> {
3808        let f = self.func("moe_down8_fma_dev");
3809        let cfg = LaunchConfig { grid_dim: (out_f as u32, 1, 1),
3810                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
3811        let (inf, outf, nu, ne, rbv) = (in_f as i32, out_f as i32, n_used as i32,
3812                                        n_expert as i32, rb as i64);
3813        let __s_b = self.gpu.stream();
3814        let mut b = __s_b.launch_builder(&f);
3815        b.arg(table).arg(sel).arg(w).arg(act).arg(dst)
3816         .arg(&inf).arg(&outf).arg(&nu).arg(&ne).arg(&qt).arg(&rbv);
3817        unsafe { b.launch(cfg)?; }
3818        Ok(())
3819    }
3820
3821    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
3822    pub fn axpy_into(&self, src: &CudaSlice<f32>, alpha: f32,
3823                     dst: &mut cudarc::driver::CudaViewMut<f32>, n: usize)
3824                     -> Result<(), Box<dyn std::error::Error>> {
3825        let f = self.func("axpy_f32");
3826        let cfg = LaunchConfig::for_num_elems(n as u32);
3827        let (a, ni) = (alpha, n as i32);
3828        let __s_b = self.gpu.stream();
3829        let mut b = __s_b.launch_builder(&f);
3830        b.arg(src).arg(dst).arg(&a).arg(&ni);
3831        unsafe { b.launch(cfg)?; }
3832        Ok(())
3833    }
3834
3835    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
3836    pub fn add_scaled_rows(&self, src: &CudaSlice<f32>, scale: &CudaSlice<f32>,
3837                           dst: &mut CudaSlice<f32>, ncols: usize, nrows: usize)
3838                           -> Result<(), Box<dyn std::error::Error>> {
3839        let f = self.func("add_scaled_rows_f32");
3840        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
3841        let (nc, nr) = (ncols as i32, nrows as i32);
3842        let __s_b = self.gpu.stream();
3843        let mut b = __s_b.launch_builder(&f);
3844        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
3845        unsafe { b.launch(cfg)?; }
3846        Ok(())
3847    }
3848
3849    // ======== A2 GROUPED MoE PREFILL KERNELS ========
3850
3851    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
3852    pub fn gather_rows(&self, src: &CudaSlice<f32>, idx: &CudaSlice<i32>,
3853                       dst: &mut CudaSlice<f32>, ncols: usize, m_e: usize)
3854                       -> Result<(), Box<dyn std::error::Error>> {
3855        let f = self.func("gather_rows_f32");
3856        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
3857        let (nc, me) = (ncols as i32, m_e as i32);
3858        let __s_b = self.gpu.stream();
3859        let mut b = __s_b.launch_builder(&f);
3860        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
3861        unsafe { b.launch(cfg)?; }
3862        Ok(())
3863    }
3864
3865    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
3866    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
3867    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
3868    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
3869    pub fn scatter_slot(&self, src: &CudaSlice<f32>, tok_idx: &CudaSlice<i32>,
3870                        slot_idx: &CudaSlice<i32>, weight: &CudaSlice<f32>,
3871                        dst: &mut CudaSlice<f32>, wbuf: &mut CudaSlice<f32>,
3872                        ncols: usize, n_used: usize, m_e: usize)
3873                        -> Result<(), Box<dyn std::error::Error>> {
3874        let f = self.func("scatter_add_slot_f32");
3875        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
3876        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
3877        let __s_b = self.gpu.stream();
3878        let mut b = __s_b.launch_builder(&f);
3879        b.arg(src).arg(tok_idx).arg(slot_idx).arg(weight).arg(dst).arg(wbuf).arg(&nc).arg(&nu).arg(&me);
3880        unsafe { b.launch(cfg)?; }
3881        Ok(())
3882    }
3883
3884    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
3885    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
3886    /// Uses FMA for bit-identity with the sequential axpy path.
3887    pub fn reduce_slots(&self, slots: &CudaSlice<f32>, wbuf: &CudaSlice<f32>,
3888                        dst: &mut CudaSlice<f32>, ncols: usize, n_used: usize, t: usize)
3889                        -> Result<(), Box<dyn std::error::Error>> {
3890        let f = self.func("reduce_slots_f32");
3891        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
3892        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
3893        let __s_b = self.gpu.stream();
3894        let mut b = __s_b.launch_builder(&f);
3895        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
3896        unsafe { b.launch(cfg)?; }
3897        Ok(())
3898    }
3899
3900    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
3901    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
3902    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
3903    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
3904    /// GPU time, ~half of it redundant re-quantization of the same row.
3905    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
3906    pub fn quantize_q8_1_view(&self, x: &cudarc::driver::CudaView<f32>, m: usize, in_f: usize)
3907                     -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3908        let f = self.func("quantize_q8_1");
3909        let nblk = in_f / 32;
3910        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
3911        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
3912        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
3913        let (inf, mi) = (in_f as i32, m as i32);
3914        let __s_b = self.gpu.stream();
3915        let mut b = __s_b.launch_builder(&f);
3916        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
3917        unsafe { b.launch(cfg)?; }
3918        Ok((q, d))
3919    }
3920
3921    pub fn quantize_q8_1(&self, x: &CudaSlice<f32>, m: usize, in_f: usize)
3922                     -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3923        let nblk = in_f / 32;
3924        let mut q = self.alloc_uninit::<i8>(m * in_f)?;  // full-overwrite output: skip memset
3925        let mut d = self.alloc_uninit::<f32>(m * nblk)?;  // full-overwrite output: skip memset
3926        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
3927        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
3928        let (inf, mi) = (in_f as i32, m as i32);
3929        if Self::pdl_on() && Self::pdl_wb_on() {
3930            {
3931            use cudarc::driver::{DevicePtr, DevicePtrMut};
3932            let s = &self.gpu.stream();
3933            let (px, _g0) = x.device_ptr(s);
3934            let (pq, _g1) = q.device_ptr_mut(s); let (pd, _g2) = d.device_ptr_mut(s);
3935            let mut ps = [
3936                &px as *const _ as *mut std::ffi::c_void, &pq as *const _ as *mut _,
3937                &pd as *const _ as *mut _, &inf as *const _ as *mut _,
3938                &mi as *const _ as *mut _,
3939            ];
3940            unsafe { self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?; }
3941            }
3942            return Ok((q, d));
3943        }
3944        let f = self.func("quantize_q8_1");
3945        let __s_b = self.gpu.stream();
3946        let mut b = __s_b.launch_builder(&f);
3947        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
3948        unsafe { b.launch(cfg)?; }
3949        Ok((q, d))
3950    }
3951
3952    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
3953    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
3954    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
3955    pub fn quantize_fp4_act(&self, x: &CudaSlice<f32>, m: usize, in_f: usize)
3956                     -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
3957        let f = self.func("quantize_fp4_act");
3958        let nb16 = in_f / 16;
3959        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?;  // full-overwrite output: skip memset
3960        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?;  // full-overwrite output: skip memset
3961        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
3962        let (inf, mi) = (in_f as i32, m as i32);
3963        let __s_b = self.gpu.stream();
3964        let mut b = __s_b.launch_builder(&f);
3965        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
3966        unsafe { b.launch(cfg)?; }
3967        Ok((aq4, ad4))
3968    }
3969
3970    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
3971    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
3972    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
3973    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
3974    pub fn qmatvec_gemm_nvfp4_fp4(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize,
3975                                  in_f: usize, out_f: usize, row_bytes: usize, scale: f32)
3976                                  -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3977        assert!(in_f % 64 == 0, "FP4 GEMM requires in_f % 64 == 0, got {in_f}");
3978        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
3979        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
3980        if scale != 1.0 { self.scale_inplace(&mut y, scale, m * out_f)?; }
3981        Ok(y)
3982    }
3983
3984    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
3985    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
3986    fn fp4_gemm_launch(&self, bytes: &CudaSlice<u8>, aq4: &CudaSlice<u32>, ad4: &CudaSlice<u8>,
3987                       m: usize, in_f: usize, out_f: usize, row_bytes: usize)
3988                       -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3989        let f = self.func("qmatvec_gemm_nvfp4_fp4");
3990        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
3991        const BM: u32 = 64; const BN: u32 = 256;
3992        let cfg = LaunchConfig {
3993            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
3994            block_dim: (32, 4, 1), shared_mem_bytes: 0,
3995        };
3996        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
3997        let __s_b = self.gpu.stream();
3998        let mut b = __s_b.launch_builder(&f);
3999        b.arg(bytes).arg(aq4).arg(ad4).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
4000        unsafe { b.launch(cfg)?; }
4001        Ok(y)
4002    }
4003
4004    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
4005    pub fn qmatvec_gemm_nvfp4_fp4_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize,
4006                                      in_f: usize, out_f: usize, row_bytes: usize)
4007                                      -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4008        assert!(in_f % 64 == 0, "FP4 GEMM requires in_f % 64 == 0, got {in_f}");
4009        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
4010        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
4011    }
4012
4013    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
4014    pub fn qmatvec_q8_0_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4015                             out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4016        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
4017        let f = self.func("qmatvec_q8_0_dp4a");
4018        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
4019        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
4020        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4021        let __s_b = self.gpu.stream();
4022        let mut b = __s_b.launch_builder(&f);
4023        b.arg(w).arg(&aq).arg(&ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
4024        unsafe { b.launch(cfg)?; }
4025        Ok(y)
4026    }
4027
4028    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
4029    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
4030    pub fn qmatvec_q4_K_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4031                             out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4032        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
4033        let f = self.func("qmatvec_q4_K_dp4a");
4034        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
4035        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
4036        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4037        let __s_b = self.gpu.stream();
4038        let mut b = __s_b.launch_builder(&f);
4039        b.arg(w).arg(&aq).arg(&ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
4040        unsafe { b.launch(cfg)?; }
4041        Ok(y)
4042    }
4043
4044    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
4045    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
4046    pub fn qmatvec_q6_K_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4047                             out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4048        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
4049        let f = self.func("qmatvec_q6_K_dp4a");
4050        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
4051        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
4052        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4053        let __s_b = self.gpu.stream();
4054        let mut b = __s_b.launch_builder(&f);
4055        b.arg(w).arg(&aq).arg(&ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
4056        unsafe { b.launch(cfg)?; }
4057        Ok(y)
4058    }
4059
4060    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
4061    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
4062    pub fn qmatvec_q5_K_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4063                             out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4064        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
4065    }
4066    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
4067    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
4068    pub fn qmatvec_q3_K_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4069                             out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4070        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
4071    }
4072    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
4073    pub fn qmatvec_nvfp4_fast_rp(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4074                                 out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4075        assert!(in_f % 64 == 0, "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}");
4076        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
4077    }
4078    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
4079    pub fn qmatvec_nvfp4_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4080                              out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4081        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
4082        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
4083        assert!(in_f % 64 == 0, "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}");
4084        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
4085    }
4086    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
4087    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
4088    pub fn qmatvec_iq4_XS_fast(&self, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
4089                               out_f: usize, row_bytes: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4090        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
4091    }
4092
4093    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
4094    fn qmatvec_dp4a_named(&self, name: &str, w: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize,
4095                          in_f: usize, out_f: usize, row_bytes: usize)
4096                          -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4097        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
4098        let f = self.func(name);
4099        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite output: skip memset
4100        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
4101        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4102        let __s_b = self.gpu.stream();
4103        let mut b = __s_b.launch_builder(&f);
4104        b.arg(w).arg(&aq).arg(&ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
4105        unsafe { b.launch(cfg)?; }
4106        Ok(y)
4107    }
4108
4109    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4110        Ok(self.gpu.stream().clone_htod(v)?)
4111    }
4112    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
4113        Ok(self.gpu.stream().clone_htod(v)?)
4114    }
4115    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
4116    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
4117        Ok(self.gpu.stream().clone_htod(v)?)
4118    }
4119    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
4120        Ok(self.gpu.stream().clone_htod(v)?)
4121    }
4122    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
4123    pub fn dtoh_view(&self, d: &cudarc::driver::CudaView<f32>)
4124                     -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4125        let v = self.gpu.stream().clone_dtoh(d)?;
4126        self.gpu.stream().synchronize()?;
4127        Ok(v)
4128    }
4129    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
4130        let v = self.gpu.stream().clone_dtoh(d)?;
4131                self.gpu.stream().synchronize()?;
4132        Ok(v)
4133    }
4134    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
4135    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
4136    /// issuing them together avoids a second stream synchronization in every trunk layer.
4137    pub fn dtoh_pair(
4138        &self,
4139        a: &CudaSlice<f32>,
4140        b: &CudaSlice<f32>,
4141    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
4142        let av = self.gpu.stream().clone_dtoh(a)?;
4143        let bv = self.gpu.stream().clone_dtoh(b)?;
4144        self.gpu.stream().synchronize()?;
4145        Ok((av, bv))
4146    }
4147    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
4148    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
4149        let v = self.gpu.stream().clone_dtoh(d)?;
4150        self.gpu.stream().synchronize()?;
4151        Ok(v)
4152    }
4153    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
4154    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
4155        let v = self.gpu.stream().clone_dtoh(d)?;
4156        self.gpu.stream().synchronize()?;
4157        Ok(v)
4158    }
4159    pub fn dtoh_u8_view(&self, d: &cudarc::driver::CudaView<u8>)
4160                        -> Result<Vec<u8>, Box<dyn std::error::Error>> {
4161        let v = self.gpu.stream().clone_dtoh(d)?;
4162        self.gpu.stream().synchronize()?;
4163        Ok(v)
4164    }
4165    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4166        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
4167        self.keep_if_capturing(&s);
4168        Ok(s)
4169    }
4170
4171    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
4172    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
4173    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
4174    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
4175    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
4176    /// back (or kept resident for graph replay). Returns the device token buffer.
4177    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
4178    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
4179    pub fn prob_of_token_device(&self, logits: &CudaSlice<f32>, tok: &CudaSlice<u32>, n_vocab: usize)
4180                                -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4181        let nb = ARGMAX_NB;
4182        let mut part = self.alloc_uninit::<f32>(nb)?;
4183        let mut p = self.alloc_uninit::<f32>(1)?;
4184        let f1 = self.func("prob_of_token_partial_f32");
4185        let cfg1 = LaunchConfig { grid_dim: (nb as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4186        let nv = n_vocab as i32;
4187        let __s_b1 = self.gpu.stream();
4188        let mut b1 = __s_b1.launch_builder(&f1);
4189        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
4190        unsafe { b1.launch(cfg1)?; }
4191        let f2 = self.func("prob_of_token_final_f32");
4192        let cfg2 = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4193        let nbi = nb as i32;
4194        let __s_b2 = self.gpu.stream();
4195        let mut b2 = __s_b2.launch_builder(&f2);
4196        b2.arg(&part).arg(&mut p).arg(&nbi);
4197        unsafe { b2.launch(cfg2)?; }
4198        Ok(p)
4199    }
4200
4201    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
4202    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
4203    /// where the host reads the p-min confidence between replays. Same kernels, same math.
4204    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
4205    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
4206    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
4207    pub fn prob_of_token_device_col(&self, logits: &CudaSlice<f32>,
4208                                    tok_all: &CudaSlice<u32>, tok_idx: usize,
4209                                    p_out: &mut CudaSlice<f32>, p_idx: usize, n_vocab: usize)
4210                                    -> Result<(), Box<dyn std::error::Error>> {
4211        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
4212        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
4213        let nb = ARGMAX_NB;
4214        let mut part = self.alloc_uninit::<f32>(nb)?;
4215        let f1 = self.func("prob_of_token_partial_f32");
4216        let cfg1 = LaunchConfig { grid_dim: (nb as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4217        let nv = n_vocab as i32;
4218        let __s_b1 = self.gpu.stream();
4219        let mut b1 = __s_b1.launch_builder(&f1);
4220        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
4221        unsafe { b1.launch(cfg1)?; }
4222        let f2 = self.func("prob_of_token_final_f32");
4223        let cfg2 = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4224        let nbi = nb as i32;
4225        let __s_b2 = self.gpu.stream();
4226        let mut b2 = __s_b2.launch_builder(&f2);
4227        b2.arg(&part).arg(&mut p_v).arg(&nbi);
4228        unsafe { b2.launch(cfg2)?; }
4229        Ok(())
4230    }
4231
4232    pub fn prob_of_token_device_into(&self, logits: &CudaSlice<f32>, tok: &CudaSlice<u32>,
4233                                     p_out: &mut CudaSlice<f32>, n_vocab: usize)
4234                                     -> Result<(), Box<dyn std::error::Error>> {
4235        let nb = ARGMAX_NB;
4236        let mut part = self.alloc_uninit::<f32>(nb)?;
4237        let f1 = self.func("prob_of_token_partial_f32");
4238        let cfg1 = LaunchConfig { grid_dim: (nb as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4239        let nv = n_vocab as i32;
4240        let __s_b1 = self.gpu.stream();
4241        let mut b1 = __s_b1.launch_builder(&f1);
4242        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
4243        unsafe { b1.launch(cfg1)?; }
4244        let f2 = self.func("prob_of_token_final_f32");
4245        let cfg2 = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4246        let nbi = nb as i32;
4247        let __s_b2 = self.gpu.stream();
4248        let mut b2 = __s_b2.launch_builder(&f2);
4249        b2.arg(&part).arg(p_out).arg(&nbi);
4250        unsafe { b2.launch(cfg2)?; }
4251        Ok(())
4252    }
4253
4254    pub fn argmax_token_device(&self, logits: &CudaSlice<f32>, n_vocab: usize)
4255                               -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
4256        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
4257        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
4258        Ok(tok)
4259    }
4260    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
4261    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
4262    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
4263    /// pointer is baked once and the token id never round-trips to host inside steady state. The
4264    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
4265    /// captured passes bake fixed addresses.
4266    pub fn argmax_token_device_into(&self, logits: &CudaSlice<f32>, tok: &mut CudaSlice<u32>,
4267                                    n_vocab: usize) -> Result<(), Box<dyn std::error::Error>> {
4268        let nb = ARGMAX_NB;
4269        let f1 = self.func("argmax_partial_f32");
4270        let f2 = self.func("argmax_final_f32");
4271        let mut guard = self.argmax_partials.lock().unwrap();
4272        if guard.is_none() {
4273            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
4274            // buffers carry no cudarc events (illegal inside capture).
4275            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
4276            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
4277            *guard = Some((pv, pi));
4278        }
4279        let (part_v, part_i) = guard.as_mut().unwrap();
4280        let nv = n_vocab as i32;
4281        let nbi = nb as i32;
4282        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
4283        let cfg1 = LaunchConfig { grid_dim: (nb as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4284        let __s_b1 = self.gpu.stream();
4285        let mut b1 = __s_b1.launch_builder(&f1);
4286        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
4287        unsafe { b1.launch(cfg1)?; }
4288        // pass 2: one block reduces NB partials -> token_out[0].
4289        let cfg2 = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4290        let __s_b2 = self.gpu.stream();
4291        let mut b2 = __s_b2.launch_builder(&f2);
4292        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
4293        unsafe { b2.launch(cfg2)?; }
4294        Ok(())
4295    }
4296    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
4297    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
4298    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
4299    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
4300    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
4301    pub fn argmax_token_device_col(&self, logits: &CudaSlice<f32>, col: usize, n_vocab: usize,
4302                                   toks: &mut CudaSlice<u32>, out_idx: usize)
4303                                   -> Result<(), Box<dyn std::error::Error>> {
4304        let nb = ARGMAX_NB;
4305        let f1 = self.func("argmax_partial_f32");
4306        let f2 = self.func("argmax_final_f32");
4307        let mut guard = self.argmax_partials.lock().unwrap();
4308        if guard.is_none() {
4309            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
4310            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
4311            *guard = Some((pv, pi));
4312        }
4313        let (part_v, part_i) = guard.as_mut().unwrap();
4314        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
4315        let nv = n_vocab as i32;
4316        let nbi = nb as i32;
4317        let cfg1 = LaunchConfig { grid_dim: (nb as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4318        let __s_b1 = self.gpu.stream();
4319        let mut b1 = __s_b1.launch_builder(&f1);
4320        b1.arg(&col_view).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
4321        unsafe { b1.launch(cfg1)?; }
4322        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
4323        let cfg2 = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4324        let __s_b2 = self.gpu.stream();
4325        let mut b2 = __s_b2.launch_builder(&f2);
4326        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
4327        unsafe { b2.launch(cfg2)?; }
4328        Ok(())
4329    }
4330    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
4331    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
4332        Ok(self.gpu.stream().clone_htod(v)?)
4333    }
4334    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
4335        let v = self.gpu.stream().clone_dtoh(d)?;
4336        self.gpu.stream().synchronize()?;
4337        Ok(v)
4338    }
4339    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
4340    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
4341    /// contents change every step, the address must not, so a captured graph can read it).
4342    pub fn htod_u32_into(&self, dst: &mut CudaSlice<u32>, src: &[u32])
4343                         -> Result<(), Box<dyn std::error::Error>> {
4344        let mut view = dst.slice_mut(0..src.len());
4345        self.gpu.stream().memcpy_htod(src, &mut view)?;
4346        Ok(())
4347    }
4348
4349    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
4350    /// table without changing the device address its reconcile kernel consumes.
4351    pub fn htod_i32_into(&self, dst: &mut CudaSlice<i32>, src: &[i32])
4352                         -> Result<(), Box<dyn std::error::Error>> {
4353        let mut view = dst.slice_mut(0..src.len());
4354        self.gpu.stream().memcpy_htod(src, &mut view)?;
4355        Ok(())
4356    }
4357
4358    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
4359        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
4360        self.keep_if_capturing(&s);
4361        Ok(s)
4362    }
4363    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
4364    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
4365    pub fn embed_gather_device_into(&self, embd: &CudaSlice<u8>, token_d: &CudaSlice<u32>,
4366                                    x_out: &mut CudaSlice<f32>, n_embd: usize, qtype: i32,
4367                                    row_bytes: usize) -> Result<(), Box<dyn std::error::Error>> {
4368        let f = self.func("embed_gather_u32");
4369        let cfg = LaunchConfig { grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
4370                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4371        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
4372        let __s_b = self.gpu.stream();
4373        let mut b = __s_b.launch_builder(&f);
4374        b.arg(embd).arg(token_d).arg(x_out).arg(&ne).arg(&qt).arg(&rb);
4375        unsafe { b.launch(cfg)?; }
4376        Ok(())
4377    }
4378    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
4379    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
4380        let v = self.gpu.stream().clone_dtoh(d)?;
4381        self.gpu.stream().synchronize()?;
4382        Ok(v[0])
4383    }
4384    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
4385    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
4386    /// the counter value after the throwaway capture warmups corrupt it.
4387    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
4388    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
4389    /// copy (fine at stream-idle boundaries, poison mid-round).
4390    pub fn i32_set_k(&self, dst: &mut CudaSlice<i32>, v: i32)
4391                     -> Result<(), Box<dyn std::error::Error>> {
4392        let f = self.func("i32_set_k");
4393        let cfg = LaunchConfig { grid_dim: (1, 1, 1), block_dim: (1, 1, 1), shared_mem_bytes: 0 };
4394        let idx = 0i32;
4395        let __s_b = self.gpu.stream();
4396        let mut b = __s_b.launch_builder(&f);
4397        b.arg(dst).arg(&v).arg(&idx);
4398        unsafe { b.launch(cfg)?; }
4399        Ok(())
4400    }
4401
4402    pub fn set_i32_one(&self, d: &mut CudaSlice<i32>, v: i32) -> Result<(), Box<dyn std::error::Error>> {
4403        self.gpu.stream().memcpy_htod(&[v], d)?;
4404        Ok(())
4405    }
4406    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
4407    /// during priming / capture-state restore.
4408    pub fn set_u32_one(&self, d: &mut CudaSlice<u32>, v: u32) -> Result<(), Box<dyn std::error::Error>> {
4409        self.gpu.stream().memcpy_htod(&[v], d)?;
4410        Ok(())
4411    }
4412    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
4413    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
4414        let v = self.gpu.stream().clone_dtoh(d)?;
4415        self.gpu.stream().synchronize()?;
4416        Ok(v[0])
4417    }
4418    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
4419    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
4420        Ok(self.gpu.stream().clone_htod(bytes)?)
4421    }
4422    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
4423    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
4424    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
4425    pub fn embed_gather_device(&self, embd: &CudaSlice<u8>, token_d: &CudaSlice<u32>,
4426                               n_embd: usize, qtype: i32, row_bytes: usize)
4427                               -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4428        let f = self.func("embed_gather_u32");
4429        let mut x = self.alloc_uninit::<f32>(n_embd)?;
4430        let cfg = LaunchConfig { grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
4431                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4432        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
4433        let __s_b = self.gpu.stream();
4434        let mut b = __s_b.launch_builder(&f);
4435        b.arg(embd).arg(token_d).arg(&mut x).arg(&ne).arg(&qt).arg(&rb);
4436        unsafe { b.launch(cfg)?; }
4437        Ok(x)
4438    }
4439
4440
4441    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
4442    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
4443    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
4444    pub fn embed_gather_device_t(&self, embd: &CudaSlice<u8>, tokens: &[u32],
4445                                 n_embd: usize, qtype: i32, row_bytes: usize)
4446                                 -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4447        let t = tokens.len();
4448        let tok_d = self.gpu.stream().clone_htod(tokens)?;
4449        let f = self.func("embed_gather_u32_t");
4450        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
4451        let cfg = LaunchConfig { grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
4452                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4453        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
4454        let __s_b = self.gpu.stream();
4455        let mut b = __s_b.launch_builder(&f);
4456        b.arg(embd).arg(&tok_d).arg(&mut x).arg(&ne).arg(&qt).arg(&rb).arg(&ti);
4457        unsafe { b.launch(cfg)?; }
4458        Ok(x)
4459    }
4460
4461    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
4462    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
4463    /// as embed_gather_device_t — bit-identical rows.
4464    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
4465    pub fn embed_gather_device_tv(&self, embd: &CudaSlice<u8>, tok_v: &cudarc::driver::CudaView<u32>,
4466                                  t: usize, n_embd: usize, qtype: i32, row_bytes: usize)
4467                                  -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4468        let f = self.func("embed_gather_u32_t");
4469        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
4470        let cfg = LaunchConfig { grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
4471                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4472        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
4473        let __s_b = self.gpu.stream();
4474        let mut b = __s_b.launch_builder(&f);
4475        b.arg(embd).arg(tok_v).arg(&mut x).arg(&ne).arg(&qt).arg(&rb).arg(&ti);
4476        unsafe { b.launch(cfg)?; }
4477        Ok(x)
4478    }
4479
4480    pub fn embed_gather_device_td(&self, embd: &CudaSlice<u8>, tok_d: &CudaSlice<u32>, t: usize,
4481                                  n_embd: usize, qtype: i32, row_bytes: usize)
4482                                  -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4483        let f = self.func("embed_gather_u32_t");
4484        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
4485        let cfg = LaunchConfig { grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
4486                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
4487        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
4488        let __s_b = self.gpu.stream();
4489        let mut b = __s_b.launch_builder(&f);
4490        b.arg(embd).arg(tok_d).arg(&mut x).arg(&ne).arg(&qt).arg(&rb).arg(&ti);
4491        unsafe { b.launch(cfg)?; }
4492        Ok(x)
4493    }
4494
4495    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
4496    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
4497    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
4498    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
4499    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
4500    #[inline]
4501    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
4502    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
4503        if self.capture_keep_on.load(std::sync::atomic::Ordering::Relaxed) {
4504            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
4505        }
4506    }
4507
4508    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, n: usize)
4509            -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
4510        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
4511        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
4512        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
4513        // not cover engine-internal buffers). Debug-only: massive launch overhead.
4514        {
4515            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4516            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
4517                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
4518                use cudarc::driver::DevicePtrMut;
4519                let n_bytes = s.len() * std::mem::size_of::<T>();
4520                let stream = self.gpu.stream();
4521                let (p_, _g) = s.device_ptr_mut(&stream);
4522                unsafe {
4523                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
4524                        .result()?;
4525                }
4526            }
4527        }
4528        self.keep_if_capturing(&s);
4529        Ok(s)
4530    }
4531
4532    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
4533    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
4534    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
4535    /// consumers alloc through this (m=1 decode arms).
4536    pub fn uninit_q8_pair(&self, n: usize)
4537        -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4538        Ok((self.alloc_uninit::<i8>(n)?, self.alloc_uninit::<f32>(n / 32)?))
4539    }
4540
4541    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4542        self.alloc_uninit::<f32>(n)
4543    }
4544
4545    /// i8 uninitialized scratch (same contract as `uninit`).
4546    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
4547        self.alloc_uninit::<i8>(n)
4548    }
4549
4550    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
4551    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
4552    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
4553    #[allow(clippy::too_many_arguments)]
4554    pub fn rms_norm3(&self, x: &CudaSlice<f32>, w0: &CudaSlice<f32>, w1: &CudaSlice<f32>,
4555                     w2: &CudaSlice<f32>, d0: &mut CudaSlice<f32>, d1: &mut CudaSlice<f32>,
4556                     d2: &mut CudaSlice<f32>, ncols: usize, nrows: usize, eps: f32)
4557                     -> Result<(), Box<dyn std::error::Error>> {
4558        let f = self.func("rms_norm3_f32");
4559        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4560        let (nc, e) = (ncols as i32, eps);
4561        let __s_b = self.gpu.stream();
4562        let mut b = __s_b.launch_builder(&f);
4563        b.arg(x).arg(w0).arg(w1).arg(w2).arg(d0).arg(d1).arg(d2).arg(&nc).arg(&e);
4564        unsafe { b.launch(cfg)?; }
4565        Ok(())
4566    }
4567
4568    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
4569    #[allow(clippy::too_many_arguments)]
4570    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
4571    /// piggybacks on the same conditions.
4572    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
4573        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4574        *WARP_ON.get_or_init(|| {
4575            std::env::var("MEMRA_QKVNORM_W").map(|v| v != "0").unwrap_or(true)
4576        }) && ncols % 4 == 0 && rows >= 64
4577    }
4578
4579    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
4580    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
4581    #[allow(clippy::too_many_arguments)]
4582    pub fn rms_norm_qkv_w4b(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
4583                        wq: &CudaSlice<f32>, wk: &CudaSlice<f32>, wv: &CudaSlice<f32>,
4584                        dq: &mut CudaSlice<f32>, dk: &mut CudaSlice<f32>, dv: &mut CudaSlice<f32>,
4585                        dvb: &mut CudaSlice<u8>,
4586                        ncols: usize, rq: usize, rk: usize, eps: f32, vf16: bool)
4587                        -> Result<(), Box<dyn std::error::Error>> {
4588        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
4589        let f = self.func("rms_norm_qkv_w4b_f32");
4590        let rows = (rq + 2 * rk) as u32;
4591        let cfg = LaunchConfig {
4592            grid_dim: (rows.div_ceil(8), 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0,
4593        };
4594        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
4595        let vf = vf16 as i32;
4596        let __s_b = self.gpu.stream();
4597        let mut b = __s_b.launch_builder(&f);
4598        b.arg(q).arg(k).arg(v).arg(wq).arg(wk).arg(wv).arg(dq).arg(dk).arg(dv).arg(&mut *dvb)
4599         .arg(&nc).arg(&rqi).arg(&rki).arg(&rvi).arg(&e).arg(&vf);
4600        unsafe { b.launch(cfg)?; }
4601        Ok(())
4602    }
4603
4604    pub fn rms_norm_qkv(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
4605                        wq: &CudaSlice<f32>, wk: &CudaSlice<f32>, wv: &CudaSlice<f32>,
4606                        dq: &mut CudaSlice<f32>, dk: &mut CudaSlice<f32>, dv: &mut CudaSlice<f32>,
4607                        ncols: usize, rq: usize, rk: usize, eps: f32)
4608                        -> Result<(), Box<dyn std::error::Error>> {
4609        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
4610        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
4611        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
4612        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4613        let warp_on = *WARP_ON.get_or_init(|| {
4614            std::env::var("MEMRA_QKVNORM_W").map(|v| v != "0").unwrap_or(true)
4615        });
4616        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
4617        // replay numerics are untouched on every model; only prefill depth takes the new config.
4618        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
4619            let f = self.func("rms_norm_qkv_w4_f32");
4620            let rows = (rq + 2 * rk) as u32;
4621            let cfg = LaunchConfig {
4622                grid_dim: (rows.div_ceil(8), 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0,
4623            };
4624            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
4625            let __s_b = self.gpu.stream();
4626            let mut b = __s_b.launch_builder(&f);
4627            b.arg(q).arg(k).arg(v).arg(wq).arg(wk).arg(wv).arg(dq).arg(dk).arg(dv)
4628             .arg(&nc).arg(&rqi).arg(&rki).arg(&rvi).arg(&e);
4629            unsafe { b.launch(cfg)?; }
4630            return Ok(());
4631        }
4632        let f = self.func("rms_norm_qkv_f32");
4633        let grid = (rq + 2 * rk) as u32;
4634        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4635        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
4636        let __s_b = self.gpu.stream();
4637        let mut b = __s_b.launch_builder(&f);
4638        b.arg(q).arg(k).arg(v).arg(wq).arg(wk).arg(wv).arg(dq).arg(dk).arg(dv)
4639         .arg(&nc).arg(&rqi).arg(&rki).arg(&e);
4640        unsafe { b.launch(cfg)?; }
4641        Ok(())
4642    }
4643
4644    /// gemma4 fused pair of rms_norms over two different inputs (same width).
4645    #[allow(clippy::too_many_arguments)]
4646    pub fn rms_norm2x(&self, a: &CudaSlice<f32>, bb: &CudaSlice<f32>, wa: &CudaSlice<f32>,
4647                      wb: &CudaSlice<f32>, da: &mut CudaSlice<f32>, db: &mut CudaSlice<f32>,
4648                      ncols: usize, nrows: usize, eps: f32)
4649                      -> Result<(), Box<dyn std::error::Error>> {
4650        let f = self.func("rms_norm2x_f32");
4651        let cfg = LaunchConfig { grid_dim: (2 * nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4652        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
4653        let __s_b = self.gpu.stream();
4654        let mut b = __s_b.launch_builder(&f);
4655        b.arg(a).arg(bb).arg(wa).arg(wb).arg(da).arg(db).arg(&nc).arg(&nr).arg(&e);
4656        unsafe { b.launch(cfg)?; }
4657        Ok(())
4658    }
4659
4660    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
4661    pub fn softcap(&self, y: &mut CudaSlice<f32>, cap: f32, n: usize)
4662                   -> Result<(), Box<dyn std::error::Error>> {
4663        let f = self.func("softcap_f32");
4664        let cfg = LaunchConfig::for_num_elems(n as u32);
4665        let ni = n as i32;
4666        let __s_b = self.gpu.stream();
4667        let mut b = __s_b.launch_builder(&f);
4668        b.arg(y).arg(&cap).arg(&ni);
4669        unsafe { b.launch(cfg)?; }
4670        Ok(())
4671    }
4672
4673    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
4674    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
4675    pub fn mask_ids_rows(&self, y: &mut CudaSlice<f32>, ids: &CudaSlice<i32>, n_ids: usize,
4676                         n_vocab: usize, t: usize)
4677                         -> Result<(), Box<dyn std::error::Error>> {
4678        let f = self.func("mask_ids_rows_f32");
4679        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
4680        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
4681        let __s_b = self.gpu.stream();
4682        let mut b = __s_b.launch_builder(&f);
4683        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
4684        unsafe { b.launch(cfg)?; }
4685        Ok(())
4686    }
4687
4688    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
4689    #[allow(clippy::too_many_arguments)]
4690    pub fn add_scale_rms_norm(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, c: f32,
4691                              w: &CudaSlice<f32>, res: &mut CudaSlice<f32>, dst: &mut CudaSlice<f32>,
4692                              ncols: usize, nrows: usize, eps: f32)
4693                              -> Result<(), Box<dyn std::error::Error>> {
4694        let f = self.func("add_scale_rms_norm_f32");
4695        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4696        let (nc, e2) = (ncols as i32, eps);
4697        let __s_b = self.gpu.stream();
4698        let mut b = __s_b.launch_builder(&f);
4699        b.arg(a).arg(b_in).arg(&c).arg(w).arg(res).arg(dst).arg(&nc).arg(&e2);
4700        unsafe { b.launch(cfg)?; }
4701        Ok(())
4702    }
4703
4704    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
4705    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
4706    #[allow(clippy::too_many_arguments)]
4707    pub fn add_scale_rms_norm_q8_1(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, c: f32,
4708                                   w: &CudaSlice<f32>, res: &mut CudaSlice<f32>,
4709                                   ncols: usize, nrows: usize, eps: f32)
4710                                   -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4711        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
4712        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
4713        let (nc, e2) = (ncols as i32, eps);
4714        if Self::pdl_on() && Self::pdl_wb_on() {
4715            {
4716            use cudarc::driver::{DevicePtr, DevicePtrMut};
4717            let s = &self.gpu.stream();
4718            let (pa, _g0) = a.device_ptr(s); let (pb, _g1) = b_in.device_ptr(s);
4719            let (pw, _g2) = w.device_ptr(s); let (pr, _g3) = res.device_ptr_mut(s);
4720            let (pq, _g4) = out_q.device_ptr_mut(s); let (pd, _g5) = out_d.device_ptr_mut(s);
4721            let mut ps = [
4722                &pa as *const _ as *mut std::ffi::c_void, &pb as *const _ as *mut _,
4723                &c as *const _ as *mut _, &pw as *const _ as *mut _,
4724                &pr as *const _ as *mut _, &pq as *const _ as *mut _,
4725                &pd as *const _ as *mut _, &nc as *const _ as *mut _,
4726                &e2 as *const _ as *mut _,
4727            ];
4728            unsafe { self.launch_pdl("add_scale_rms_norm_q8_1", (nrows as u32, 1, 1),
4729                                     (rms_block(), 1, 1), &mut ps)?; }
4730            }
4731            return Ok((out_q, out_d));
4732        }
4733        let f = self.func("add_scale_rms_norm_q8_1");
4734        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4735        let __s_b = self.gpu.stream();
4736        let mut b = __s_b.launch_builder(&f);
4737        b.arg(a).arg(b_in).arg(&c).arg(w).arg(res).arg(&mut out_q).arg(&mut out_d).arg(&nc).arg(&e2);
4738        unsafe { b.launch(cfg)?; }
4739        Ok((out_q, out_d))
4740    }
4741
4742    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
4743    #[allow(clippy::too_many_arguments)]
4744    pub fn add_scale_rms_norm_q8_1_into(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, c: f32,
4745                                        w: &CudaSlice<f32>, res: &mut CudaSlice<f32>,
4746                                        ncols: usize, nrows: usize, eps: f32,
4747                                        out_q: &mut CudaSlice<i8>, out_d: &mut CudaSlice<f32>)
4748                                        -> Result<(), Box<dyn std::error::Error>> {
4749        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
4750        let (nc, e2) = (ncols as i32, eps);
4751        if Self::pdl_on() && Self::pdl_wb_on() {
4752            use cudarc::driver::{DevicePtr, DevicePtrMut};
4753            let s = &self.gpu.stream();
4754            let (pa, _g0) = a.device_ptr(s); let (pb, _g1) = b_in.device_ptr(s);
4755            let (pw, _g2) = w.device_ptr(s); let (pr, _g3) = res.device_ptr_mut(s);
4756            let (pq, _g4) = out_q.device_ptr_mut(s); let (pd, _g5) = out_d.device_ptr_mut(s);
4757            let mut ps = [
4758                &pa as *const _ as *mut std::ffi::c_void, &pb as *const _ as *mut _,
4759                &c as *const _ as *mut _, &pw as *const _ as *mut _,
4760                &pr as *const _ as *mut _, &pq as *const _ as *mut _,
4761                &pd as *const _ as *mut _, &nc as *const _ as *mut _,
4762                &e2 as *const _ as *mut _,
4763            ];
4764            unsafe { self.launch_pdl("add_scale_rms_norm_q8_1", (nrows as u32, 1, 1),
4765                                     (rms_block(), 1, 1), &mut ps)?; }
4766            return Ok(());
4767        }
4768        let f = self.func("add_scale_rms_norm_q8_1");
4769        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4770        let __s_b = self.gpu.stream();
4771        let mut b = __s_b.launch_builder(&f);
4772        b.arg(a).arg(b_in).arg(&c).arg(w).arg(res).arg(&mut *out_q).arg(&mut *out_d).arg(&nc).arg(&e2);
4773        unsafe { b.launch(cfg)?; }
4774        Ok(())
4775    }
4776
4777    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
4778    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
4779    #[allow(clippy::too_many_arguments)]
4780    pub fn rms_pre_add_scale_rms_norm_q8_1(&self, a: &CudaSlice<f32>, wa: &CudaSlice<f32>,
4781                                           b_in: &CudaSlice<f32>, c: f32,
4782                                           w: &CudaSlice<f32>, res: &mut CudaSlice<f32>,
4783                                           ncols: usize, nrows: usize, eps: f32)
4784                                           -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4785        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
4786        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
4787        let (nc, e2) = (ncols as i32, eps);
4788        if Self::pdl_on() {
4789            {
4790            use cudarc::driver::{DevicePtr, DevicePtrMut};
4791            let s = &self.gpu.stream();
4792            let (pa, _g0) = a.device_ptr(s); let (pwa, _g1) = wa.device_ptr(s);
4793            let (pb, _g2) = b_in.device_ptr(s); let (pw, _g3) = w.device_ptr(s);
4794            let (pr, _g4) = res.device_ptr_mut(s);
4795            let (pq, _g5) = out_q.device_ptr_mut(s); let (pd, _g6) = out_d.device_ptr_mut(s);
4796            let mut ps = [
4797                &pa as *const _ as *mut std::ffi::c_void, &pwa as *const _ as *mut _,
4798                &pb as *const _ as *mut _, &c as *const _ as *mut _,
4799                &pw as *const _ as *mut _, &pr as *const _ as *mut _,
4800                &pq as *const _ as *mut _, &pd as *const _ as *mut _,
4801                &nc as *const _ as *mut _, &e2 as *const _ as *mut _,
4802            ];
4803            unsafe { self.launch_pdl("rms_pre_add_scale_rms_norm_q8_1", (nrows as u32, 1, 1),
4804                                     (rms_block(), 1, 1), &mut ps)?; }
4805            }
4806            return Ok((out_q, out_d));
4807        }
4808        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
4809        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4810        let __s_b = self.gpu.stream();
4811        let mut b = __s_b.launch_builder(&f);
4812        b.arg(a).arg(wa).arg(b_in).arg(&c).arg(w).arg(res).arg(&mut out_q).arg(&mut out_d).arg(&nc).arg(&e2);
4813        unsafe { b.launch(cfg)?; }
4814        Ok((out_q, out_d))
4815    }
4816
4817    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
4818    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
4819    pub fn gelu_tanh_mul_q8_1(&self, gate: &CudaSlice<f32>, up: &cudarc::driver::CudaView<f32>,
4820                              act: &mut CudaSlice<f32>, ncols: usize, nrows: usize)
4821                              -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4822        debug_assert!(ncols % 128 == 0);
4823        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
4824        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
4825        let nc = ncols as i32;
4826        if Self::pdl_on() {
4827            {
4828            use cudarc::driver::{DevicePtr, DevicePtrMut};
4829            let s = &self.gpu.stream();
4830            let (pg, _g0) = gate.device_ptr(s); let (pu, _g1) = up.device_ptr(s);
4831            let (pact, _g2) = act.device_ptr_mut(s);
4832            let (pq, _g3) = out_q.device_ptr_mut(s); let (pd, _g4) = out_d.device_ptr_mut(s);
4833            let mut ps = [
4834                &pg as *const _ as *mut std::ffi::c_void, &pu as *const _ as *mut _,
4835                &pact as *const _ as *mut _, &pq as *const _ as *mut _,
4836                &pd as *const _ as *mut _, &nc as *const _ as *mut _,
4837            ];
4838            unsafe { self.launch_pdl("gelu_tanh_mul_q8_1", (nrows as u32, 1, 1),
4839                                     (rms_block(), 1, 1), &mut ps)?; }
4840            }
4841            return Ok((out_q, out_d));
4842        }
4843        let f = self.func("gelu_tanh_mul_q8_1");
4844        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4845        let __s_b = self.gpu.stream();
4846        let mut b = __s_b.launch_builder(&f);
4847        b.arg(gate).arg(up).arg(act).arg(&mut out_q).arg(&mut out_d).arg(&nc);
4848        unsafe { b.launch(cfg)?; }
4849        Ok((out_q, out_d))
4850    }
4851
4852    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
4853    #[allow(clippy::too_many_arguments)]
4854    pub fn gelu_tanh_mul_q8_1_into(&self, gate: &CudaSlice<f32>, up: &cudarc::driver::CudaView<f32>,
4855                                   act: &mut CudaSlice<f32>, ncols: usize, nrows: usize,
4856                                   out_q: &mut CudaSlice<i8>, out_d: &mut CudaSlice<f32>)
4857                                   -> Result<(), Box<dyn std::error::Error>> {
4858        debug_assert!(ncols % 128 == 0);
4859        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
4860        let nc = ncols as i32;
4861        if Self::pdl_on() {
4862            use cudarc::driver::{DevicePtr, DevicePtrMut};
4863            let s = &self.gpu.stream();
4864            let (pg, _g0) = gate.device_ptr(s); let (pu, _g1) = up.device_ptr(s);
4865            let (pact, _g2) = act.device_ptr_mut(s);
4866            let (pq, _g3) = out_q.device_ptr_mut(s); let (pd, _g4) = out_d.device_ptr_mut(s);
4867            let mut ps = [
4868                &pg as *const _ as *mut std::ffi::c_void, &pu as *const _ as *mut _,
4869                &pact as *const _ as *mut _, &pq as *const _ as *mut _,
4870                &pd as *const _ as *mut _, &nc as *const _ as *mut _,
4871            ];
4872            unsafe { self.launch_pdl("gelu_tanh_mul_q8_1", (nrows as u32, 1, 1),
4873                                     (rms_block(), 1, 1), &mut ps)?; }
4874            return Ok(());
4875        }
4876        let f = self.func("gelu_tanh_mul_q8_1");
4877        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4878        let __s_b = self.gpu.stream();
4879        let mut b = __s_b.launch_builder(&f);
4880        b.arg(gate).arg(up).arg(&mut *act).arg(&mut *out_q).arg(&mut *out_d).arg(&nc);
4881        unsafe { b.launch(cfg)?; }
4882        Ok(())
4883    }
4884
4885    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
4886    #[allow(clippy::too_many_arguments)]
4887    pub fn add_rms_norm3_q8z(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>,
4888                             w0: &CudaSlice<f32>, w1: &CudaSlice<f32>, w2: &CudaSlice<f32>,
4889                             res: &mut CudaSlice<f32>, out1: &mut CudaSlice<f32>,
4890                             ncols: usize, nrows: usize, eps: f32)
4891                             -> Result<((CudaSlice<i8>, CudaSlice<f32>), (CudaSlice<i8>, CudaSlice<f32>)), Box<dyn std::error::Error>> {
4892        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
4893        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
4894        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
4895        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
4896        let f = self.func("add_rms_norm3_q8z_f32");
4897        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4898        let (nc, e2) = (ncols as i32, eps);
4899        let __s_b = self.gpu.stream();
4900        let mut b = __s_b.launch_builder(&f);
4901        b.arg(a).arg(b_in).arg(w0).arg(w1).arg(w2).arg(res)
4902         .arg(&mut q0).arg(&mut d0).arg(out1).arg(&mut q2).arg(&mut d2).arg(&nc).arg(&e2);
4903        unsafe { b.launch(cfg)?; }
4904        Ok(((q0, d0), (q2, d2)))
4905    }
4906
4907    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
4908    #[allow(clippy::too_many_arguments)]
4909    pub fn add_rms_norm3(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>,
4910                         w0: &CudaSlice<f32>, w1: &CudaSlice<f32>, w2: &CudaSlice<f32>,
4911                         res: &mut CudaSlice<f32>, d0: &mut CudaSlice<f32>, d1: &mut CudaSlice<f32>,
4912                         d2: &mut CudaSlice<f32>, ncols: usize, nrows: usize, eps: f32)
4913                         -> Result<(), Box<dyn std::error::Error>> {
4914        let f = self.func("add_rms_norm3_f32");
4915        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4916        let (nc, e2) = (ncols as i32, eps);
4917        let __s_b = self.gpu.stream();
4918        let mut b = __s_b.launch_builder(&f);
4919        b.arg(a).arg(b_in).arg(w0).arg(w1).arg(w2).arg(res).arg(d0).arg(d1).arg(d2).arg(&nc).arg(&e2);
4920        unsafe { b.launch(cfg)?; }
4921        Ok(())
4922    }
4923
4924    /// dst = (a + b) * c (residual add + layer scale, one launch).
4925    pub fn add_scale(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, c: f32,
4926                     dst: &mut CudaSlice<f32>, n: usize) -> Result<(), Box<dyn std::error::Error>> {
4927        let f = self.func("add_scale_f32");
4928        let cfg = LaunchConfig::for_num_elems(n as u32);
4929        let ni = n as i32;
4930        let __s_b = self.gpu.stream();
4931        let mut b = __s_b.launch_builder(&f);
4932        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
4933        unsafe { b.launch(cfg)?; }
4934        Ok(())
4935    }
4936
4937    pub fn rms_norm(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, dst: &mut CudaSlice<f32>,
4938                    ncols: usize, nrows: usize, eps: f32) -> Result<(), Box<dyn std::error::Error>> {
4939        let (nc, e) = (ncols as i32, eps);
4940        if Self::pdl_on() && Self::pdl_wb_on() {
4941            use cudarc::driver::{DevicePtr, DevicePtrMut};
4942            let s = &self.gpu.stream();
4943            let (px, _g0) = x.device_ptr(s); let (pw, _g1) = w.device_ptr(s);
4944            let (pd, _g2) = dst.device_ptr_mut(s);
4945            let mut ps = [
4946                &px as *const _ as *mut std::ffi::c_void, &pw as *const _ as *mut _,
4947                &pd as *const _ as *mut _, &nc as *const _ as *mut _,
4948                &e as *const _ as *mut _,
4949            ];
4950            unsafe { self.launch_pdl("rms_norm_f32", (nrows as u32, 1, 1),
4951                                     (rms_block(), 1, 1), &mut ps)?; }
4952            return Ok(());
4953        }
4954        let f = self.func("rms_norm_f32");
4955        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
4956        let __s_b = self.gpu.stream();
4957        let mut b = __s_b.launch_builder(&f);
4958        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
4959        unsafe { b.launch(cfg)?; }
4960        Ok(())
4961    }
4962
4963    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
4964    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
4965    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
4966    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
4967    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
4968    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
4969    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
4970    pub fn rms_norm_decode(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, dst: &mut CudaSlice<f32>,
4971                           ncols: usize, nrows: usize, eps: f32) -> Result<(), Box<dyn std::error::Error>> {
4972        let f = self.func("rms_norm_f32");
4973        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
4974        let (nc, e) = (ncols as i32, eps);
4975        let __s_b = self.gpu.stream();
4976        let mut b = __s_b.launch_builder(&f);
4977        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
4978        unsafe { b.launch(cfg)?; }
4979        Ok(())
4980    }
4981
4982    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
4983    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
4984    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
4985    pub fn rms_norm_q8_1(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, ncols: usize, nrows: usize,
4986                         eps: f32) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4987        let nblk = ncols / 32;
4988        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
4989        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
4990        let (nc, e) = (ncols as i32, eps);
4991        if Self::pdl_on() {
4992            {
4993            use cudarc::driver::{DevicePtr, DevicePtrMut};
4994            let s = &self.gpu.stream();
4995            let (px, _g0) = x.device_ptr(s); let (pw, _g1) = w.device_ptr(s);
4996            let (pq, _g2) = q.device_ptr_mut(s); let (pd, _g3) = d.device_ptr_mut(s);
4997            let mut ps = [
4998                &px as *const _ as *mut std::ffi::c_void, &pw as *const _ as *mut _,
4999                &pq as *const _ as *mut _, &pd as *const _ as *mut _,
5000                &nc as *const _ as *mut _, &e as *const _ as *mut _,
5001            ];
5002            unsafe { self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1),
5003                                     &mut ps)?; }
5004            }
5005            return Ok((q, d));
5006        }
5007        let f = self.func("rms_norm_q8_1");
5008        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
5009        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
5010        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
5011        let __s_b = self.gpu.stream();
5012        let mut b = __s_b.launch_builder(&f);
5013        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
5014        unsafe { b.launch(cfg)?; }
5015        Ok((q, d))
5016    }
5017
5018    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
5019    /// PDL arm), caller-owned outputs.
5020    pub fn rms_norm_q8_1_into(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, ncols: usize,
5021                              nrows: usize, eps: f32,
5022                              q: &mut CudaSlice<i8>, d: &mut CudaSlice<f32>)
5023                              -> Result<(), Box<dyn std::error::Error>> {
5024        let nblk = ncols / 32;
5025        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
5026        let (nc, e) = (ncols as i32, eps);
5027        if Self::pdl_on() {
5028            use cudarc::driver::{DevicePtr, DevicePtrMut};
5029            let s = &self.gpu.stream();
5030            let (px, _g0) = x.device_ptr(s); let (pw, _g1) = w.device_ptr(s);
5031            let (pq, _g2) = q.device_ptr_mut(s); let (pd, _g3) = d.device_ptr_mut(s);
5032            let mut ps = [
5033                &px as *const _ as *mut std::ffi::c_void, &pw as *const _ as *mut _,
5034                &pq as *const _ as *mut _, &pd as *const _ as *mut _,
5035                &nc as *const _ as *mut _, &e as *const _ as *mut _,
5036            ];
5037            unsafe { self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1),
5038                                     &mut ps)?; }
5039            return Ok(());
5040        }
5041        let f = self.func("rms_norm_q8_1");
5042        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
5043        let __s_b = self.gpu.stream();
5044        let mut b = __s_b.launch_builder(&f);
5045        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
5046        unsafe { b.launch(cfg)?; }
5047        Ok(())
5048    }
5049
5050    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
5051    pub fn quantize_q8_1_into(&self, x: &CudaSlice<f32>, m: usize, in_f: usize,
5052                              q: &mut CudaSlice<i8>, d: &mut CudaSlice<f32>)
5053                              -> Result<(), Box<dyn std::error::Error>> {
5054        let nblk = in_f / 32;
5055        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
5056        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
5057        let (inf, mi) = (in_f as i32, m as i32);
5058        if Self::pdl_on() && Self::pdl_wb_on() {
5059            use cudarc::driver::{DevicePtr, DevicePtrMut};
5060            let s = &self.gpu.stream();
5061            let (px, _g0) = x.device_ptr(s);
5062            let (pq, _g1) = q.device_ptr_mut(s); let (pd, _g2) = d.device_ptr_mut(s);
5063            let mut ps = [
5064                &px as *const _ as *mut std::ffi::c_void, &pq as *const _ as *mut _,
5065                &pd as *const _ as *mut _, &inf as *const _ as *mut _,
5066                &mi as *const _ as *mut _,
5067            ];
5068            unsafe { self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?; }
5069            return Ok(());
5070        }
5071        let f = self.func("quantize_q8_1");
5072        let __s_b = self.gpu.stream();
5073        let mut b = __s_b.launch_builder(&f);
5074        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
5075        unsafe { b.launch(cfg)?; }
5076        Ok(())
5077    }
5078
5079    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
5080    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
5081    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
5082    pub fn add_rms_norm_q8_1(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, w: &CudaSlice<f32>,
5083                             res: &mut CudaSlice<f32>, ncols: usize, nrows: usize, eps: f32)
5084                             -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
5085        let nblk = ncols / 32;
5086        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
5087        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
5088        let f = self.func("add_rms_norm_q8_1");
5089        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
5090        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
5091        let (nc, e) = (ncols as i32, eps);
5092        let __s_bld = self.gpu.stream();
5093        let mut bld = __s_bld.launch_builder(&f);
5094        bld.arg(a).arg(b_in).arg(w).arg(res).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
5095        unsafe { bld.launch(cfg)?; }
5096        Ok((q, d))
5097    }
5098
5099    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
5100    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
5101    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
5102    pub fn add_rms_norm(&self, a: &CudaSlice<f32>, b: &CudaSlice<f32>, w: &CudaSlice<f32>,
5103                        res: &mut CudaSlice<f32>, dst: &mut CudaSlice<f32>, ncols: usize, nrows: usize,
5104                        eps: f32) -> Result<(), Box<dyn std::error::Error>> {
5105        let (nc, e) = (ncols as i32, eps);
5106        if Self::pdl_on() && Self::pdl_wb_on() {
5107            use cudarc::driver::{DevicePtr, DevicePtrMut};
5108            let s = &self.gpu.stream();
5109            let (pa, _g0) = a.device_ptr(s); let (pb, _g1) = b.device_ptr(s);
5110            let (pw, _g2) = w.device_ptr(s);
5111            let (pr, _g3) = res.device_ptr_mut(s); let (pd, _g4) = dst.device_ptr_mut(s);
5112            let mut ps = [
5113                &pa as *const _ as *mut std::ffi::c_void, &pb as *const _ as *mut _,
5114                &pw as *const _ as *mut _, &pr as *const _ as *mut _,
5115                &pd as *const _ as *mut _, &nc as *const _ as *mut _,
5116                &e as *const _ as *mut _,
5117            ];
5118            unsafe { self.launch_pdl("add_rms_norm_f32", (nrows as u32, 1, 1),
5119                                     (rms_block(), 1, 1), &mut ps)?; }
5120            return Ok(());
5121        }
5122        let f = self.func("add_rms_norm_f32");
5123        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5124        let __s_b2 = self.gpu.stream();
5125        let mut b2 = __s_b2.launch_builder(&f);
5126        b2.arg(a).arg(b).arg(w).arg(&mut *res).arg(&mut *dst).arg(&nc).arg(&e);
5127        unsafe { b2.launch(cfg)?; }
5128        Ok(())
5129    }
5130
5131    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
5132    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
5133    #[allow(clippy::too_many_arguments)]
5134    pub fn rms_pre_add_rms_norm(&self, a: &CudaSlice<f32>, wa: &CudaSlice<f32>,
5135                                b: &CudaSlice<f32>, w: &CudaSlice<f32>,
5136                                res: &mut CudaSlice<f32>, dst: &mut CudaSlice<f32>,
5137                                ncols: usize, nrows: usize, eps: f32)
5138                                -> Result<(), Box<dyn std::error::Error>> {
5139        let f = self.func("rms_pre_add_rms_norm_f32");
5140        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5141        let (nc, e) = (ncols as i32, eps);
5142        let __s_b2 = self.gpu.stream();
5143        let mut b2 = __s_b2.launch_builder(&f);
5144        b2.arg(a).arg(wa).arg(b).arg(w).arg(&mut *res).arg(&mut *dst).arg(&nc).arg(&e);
5145        unsafe { b2.launch(cfg)?; }
5146        Ok(())
5147    }
5148
5149    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
5150    #[allow(clippy::too_many_arguments)]
5151    pub fn rms_pre_add_rms_norm_q8z(&self, a: &CudaSlice<f32>, wa: &CudaSlice<f32>,
5152                                    b: &CudaSlice<f32>, w: &CudaSlice<f32>,
5153                                    res: &mut CudaSlice<f32>, dst: &mut CudaSlice<f32>,
5154                                    ncols: usize, nrows: usize, eps: f32)
5155                                    -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
5156        debug_assert!(ncols % 128 == 0);
5157        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
5158        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
5159        let (nc, e) = (ncols as i32, eps);
5160        if Self::pdl_on() {
5161            {
5162            use cudarc::driver::{DevicePtr, DevicePtrMut};
5163            let s = &self.gpu.stream();
5164            let (pa, _g0) = a.device_ptr(s); let (pwa, _g1) = wa.device_ptr(s);
5165            let (pb, _g2) = b.device_ptr(s); let (pw, _g3) = w.device_ptr(s);
5166            let (pr, _g4) = res.device_ptr_mut(s); let (pdst, _g5) = dst.device_ptr_mut(s);
5167            let (pq, _g6) = out_q.device_ptr_mut(s); let (pd, _g7) = out_d.device_ptr_mut(s);
5168            let mut ps = [
5169                &pa as *const _ as *mut std::ffi::c_void, &pwa as *const _ as *mut _,
5170                &pb as *const _ as *mut _, &pw as *const _ as *mut _,
5171                &pr as *const _ as *mut _, &pdst as *const _ as *mut _,
5172                &pq as *const _ as *mut _, &pd as *const _ as *mut _,
5173                &nc as *const _ as *mut _, &e as *const _ as *mut _,
5174            ];
5175            unsafe { self.launch_pdl("rms_pre_add_rms_norm_q8z_f32", (nrows as u32, 1, 1),
5176                                     (rms_block(), 1, 1), &mut ps)?; }
5177            }
5178            return Ok((out_q, out_d));
5179        }
5180        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
5181        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5182        let __s_b2 = self.gpu.stream();
5183        let mut b2 = __s_b2.launch_builder(&f);
5184        b2.arg(a).arg(wa).arg(b).arg(w).arg(&mut *res).arg(&mut *dst)
5185          .arg(&mut out_q).arg(&mut out_d).arg(&nc).arg(&e);
5186        unsafe { b2.launch(cfg)?; }
5187        Ok((out_q, out_d))
5188    }
5189
5190    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
5191    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
5192    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
5193    pub fn build_q4_out_concat3(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
5194                                w2: &crate::model::GpuTensor)
5195                                -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
5196        use crate::model::GpuTensor;
5197        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
5198            match w {
5199                GpuTensor::Quant { qtype, row_bytes, rp, .. }
5200                    if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
5201                _ => None,
5202            }
5203        };
5204        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
5205        else { return Ok(None) };
5206        if rb0 != rb1 || rb0 != rb2
5207            || w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
5208            return Ok(None);
5209        }
5210        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
5211            match w { crate::model::GpuTensor::Quant { bytes, .. } => bytes, _ => unreachable!() }
5212        }
5213        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
5214        let total = rb0 * (o0 + o1 + o2);
5215        let mut cat = self.alloc_u8(total)?;
5216        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
5217        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
5218        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
5219        Ok(Some(GpuTensor::Quant {
5220            bytes: cat, qtype: QT_Q4_0, row_bytes: rb0,
5221            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64], scale: 1.0, rp: false,
5222            #[cfg(memra_cutlass)]
5223            cutlass: None,
5224            fp8: None, blk: None, rp4: None, f16: None,
5225        }))
5226    }
5227
5228    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
5229    #[allow(clippy::too_many_arguments)]
5230    pub fn rms_norm_qkv_rope_cat(&self, qkv: &CudaSlice<f32>,
5231                                 wq: &CudaSlice<f32>, wk: &CudaSlice<f32>, wv: &CudaSlice<f32>,
5232                                 q: &mut CudaSlice<f32>, k: &mut CudaSlice<f32>, v: &mut CudaSlice<f32>,
5233                                 head_dim: usize, rq: usize, rk: usize,
5234                                 pos: &CudaSlice<i32>, nh_q: usize, nh_k: usize,
5235                                 base: f32, freq_scale: f32, ff: Option<&CudaSlice<f32>>, eps: f32)
5236                                 -> Result<(), Box<dyn std::error::Error>> {
5237        let rows = rq + rk + rk;
5238        let theta_scale = base.powf(-2.0 / head_dim as f32);
5239        let (nc, rqi, rki, nhq, nhk) = (head_dim as i32, rq as i32, rk as i32, nh_q as i32, nh_k as i32);
5240        if Self::pdl_on() {
5241            use cudarc::driver::{DevicePtr, DevicePtrMut};
5242            let s = &self.gpu.stream();
5243            let (pqkv, _g0) = qkv.device_ptr(s);
5244            let (pwq, _g1) = wq.device_ptr(s); let (pwk, _g2) = wk.device_ptr(s);
5245            let (pwv, _g3) = wv.device_ptr(s);
5246            let (pq, _g4) = q.device_ptr_mut(s); let (pk, _g5) = k.device_ptr_mut(s);
5247            let (pv, _g6) = v.device_ptr_mut(s);
5248            let (ppos, _g7) = pos.device_ptr(s);
5249            let (pff, _g8) = match ff {
5250                Some(t) => { let (p, g) = t.device_ptr(s); (p, Some(g)) }
5251                None => (0, None),
5252            };
5253            let mut ps = [
5254                &pqkv as *const _ as *mut std::ffi::c_void,
5255                &pwq as *const _ as *mut _, &pwk as *const _ as *mut _,
5256                &pwv as *const _ as *mut _,
5257                &pq as *const _ as *mut _, &pk as *const _ as *mut _,
5258                &pv as *const _ as *mut _,
5259                &nc as *const _ as *mut _, &rqi as *const _ as *mut _,
5260                &rki as *const _ as *mut _, &ppos as *const _ as *mut _,
5261                &nhq as *const _ as *mut _, &nhk as *const _ as *mut _,
5262                &theta_scale as *const _ as *mut _, &freq_scale as *const _ as *mut _,
5263                &pff as *const _ as *mut _, &eps as *const _ as *mut _,
5264            ];
5265            unsafe { self.launch_pdl("rms_norm_qkv_rope_cat_f32", (rows as u32, 1, 1),
5266                                     (rms_block(), 1, 1), &mut ps)?; }
5267            return Ok(());
5268        }
5269        let f = self.func("rms_norm_qkv_rope_cat_f32");
5270        let cfg = LaunchConfig { grid_dim: (rows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5271        let __s_b = self.gpu.stream();
5272        let mut b = __s_b.launch_builder(&f);
5273        match ff {
5274            Some(t) => { b.arg(qkv).arg(wq).arg(wk).arg(wv)
5275                          .arg(&mut *q).arg(&mut *k).arg(&mut *v)
5276                          .arg(&nc).arg(&rqi).arg(&rki).arg(pos).arg(&nhq).arg(&nhk)
5277                          .arg(&theta_scale).arg(&freq_scale).arg(t).arg(&eps);
5278                         unsafe { b.launch(cfg)?; } }
5279            None => { let null: u64 = 0;
5280                      b.arg(qkv).arg(wq).arg(wk).arg(wv)
5281                       .arg(&mut *q).arg(&mut *k).arg(&mut *v)
5282                       .arg(&nc).arg(&rqi).arg(&rki).arg(pos).arg(&nhq).arg(&nhk)
5283                       .arg(&theta_scale).arg(&freq_scale).arg(&null).arg(&eps);
5284                      unsafe { b.launch(cfg)?; } }
5285        }
5286        Ok(())
5287    }
5288
5289    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
5290    #[allow(clippy::too_many_arguments)]
5291    pub fn rms_norm_qkv_rope(&self, q0: &CudaSlice<f32>, k0: &CudaSlice<f32>, v0: &CudaSlice<f32>,
5292                             wq: &CudaSlice<f32>, wk: &CudaSlice<f32>, wv: &CudaSlice<f32>,
5293                             q: &mut CudaSlice<f32>, k: &mut CudaSlice<f32>, v: &mut CudaSlice<f32>,
5294                             head_dim: usize, rq: usize, rk: usize,
5295                             pos: &CudaSlice<i32>, nh_q: usize, nh_k: usize,
5296                             base: f32, freq_scale: f32, ff: Option<&CudaSlice<f32>>, eps: f32)
5297                             -> Result<(), Box<dyn std::error::Error>> {
5298        let f = self.func("rms_norm_qkv_rope_f32");
5299        let rows = rq + rk + rk;   // q rows + k rows + v rows (rk == rv)
5300        let cfg = LaunchConfig { grid_dim: (rows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5301        let theta_scale = base.powf(-2.0 / head_dim as f32);
5302        let (nc, rqi, rki, nhq, nhk) = (head_dim as i32, rq as i32, rk as i32, nh_q as i32, nh_k as i32);
5303        let __s_b = self.gpu.stream();
5304        let mut b = __s_b.launch_builder(&f);
5305        match ff {
5306            Some(t) => { b.arg(q0).arg(k0).arg(v0).arg(wq).arg(wk).arg(wv)
5307                          .arg(&mut *q).arg(&mut *k).arg(&mut *v)
5308                          .arg(&nc).arg(&rqi).arg(&rki).arg(pos).arg(&nhq).arg(&nhk)
5309                          .arg(&theta_scale).arg(&freq_scale).arg(t).arg(&eps);
5310                         unsafe { b.launch(cfg)?; } }
5311            None => { let null: u64 = 0;
5312                      b.arg(q0).arg(k0).arg(v0).arg(wq).arg(wk).arg(wv)
5313                       .arg(&mut *q).arg(&mut *k).arg(&mut *v)
5314                       .arg(&nc).arg(&rqi).arg(&rki).arg(pos).arg(&nhq).arg(&nhk)
5315                       .arg(&theta_scale).arg(&freq_scale).arg(&null).arg(&eps);
5316                      unsafe { b.launch(cfg)?; } }
5317        }
5318        Ok(())
5319    }
5320
5321    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
5322    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
5323    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
5324    #[allow(clippy::too_many_arguments)]
5325    pub fn rms_norm_qkv_rope_append_dc(&self, q0: &CudaSlice<f32>, k0: &CudaSlice<f32>,
5326                             v0: &CudaSlice<f32>,
5327                             wq: &CudaSlice<f32>, wk: &CudaSlice<f32>, wv: &CudaSlice<f32>,
5328                             q: &mut CudaSlice<f32>, k: &mut CudaSlice<f32>, v: &mut CudaSlice<f32>,
5329                             head_dim: usize, rq: usize, rk: usize,
5330                             pos: &CudaSlice<i32>, nh_q: usize, nh_k: usize,
5331                             base: f32, freq_scale: f32, ff: Option<&CudaSlice<f32>>, eps: f32,
5332                             kc: &mut CudaSlice<u8>, vc: &mut CudaSlice<u8>,
5333                             t_dev: &CudaSlice<i32>, k_tok_bytes: usize, v_tok_bytes: usize,
5334                             g: bool)
5335                             -> Result<(), Box<dyn std::error::Error>> {
5336        let rows = rq + rk + rk;
5337        let theta_scale = base.powf(-2.0 / head_dim as f32);
5338        let (nc, rqi, rki, nhq, nhk) = (head_dim as i32, rq as i32, rk as i32, nh_q as i32, nh_k as i32);
5339        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
5340        if Self::pdl_on() && Self::pdl_wb_on() {
5341            use cudarc::driver::{DevicePtr, DevicePtrMut};
5342            let s = &self.gpu.stream();
5343            let (p0, _a0) = q0.device_ptr(s); let (p1, _a1) = k0.device_ptr(s);
5344            let (p2, _a2) = v0.device_ptr(s);
5345            let (pwq, _a3) = wq.device_ptr(s); let (pwk, _a4) = wk.device_ptr(s);
5346            let (pwv, _a5) = wv.device_ptr(s);
5347            let (pq, _a6) = q.device_ptr_mut(s); let (pk, _a7) = k.device_ptr_mut(s);
5348            let (pv, _a8) = v.device_ptr_mut(s);
5349            let (pp, _a9) = pos.device_ptr(s);
5350            let pff: u64 = match ff { Some(t) => { let (p, _gg) = t.device_ptr(s); p as u64 }
5351                                      None => 0 };
5352            let (pkc, _a10) = kc.device_ptr_mut(s); let (pvc, _a11) = vc.device_ptr_mut(s);
5353            let (pt, _a12) = t_dev.device_ptr(s);
5354            let mut ps = [
5355                &p0 as *const _ as *mut std::ffi::c_void, &p1 as *const _ as *mut _,
5356                &p2 as *const _ as *mut _, &pwq as *const _ as *mut _,
5357                &pwk as *const _ as *mut _, &pwv as *const _ as *mut _,
5358                &pq as *const _ as *mut _, &pk as *const _ as *mut _,
5359                &pv as *const _ as *mut _, &nc as *const _ as *mut _,
5360                &rqi as *const _ as *mut _, &rki as *const _ as *mut _,
5361                &pp as *const _ as *mut _, &nhq as *const _ as *mut _,
5362                &nhk as *const _ as *mut _, &theta_scale as *const _ as *mut _,
5363                &freq_scale as *const _ as *mut _, &pff as *const _ as *mut _,
5364                &eps as *const _ as *mut _, &pkc as *const _ as *mut _,
5365                &pvc as *const _ as *mut _, &pt as *const _ as *mut _,
5366                &ktb as *const _ as *mut _, &vtb as *const _ as *mut _,
5367            ];
5368            unsafe { self.launch_pdl_flash(g, "rms_norm_qkv_rope_append_dc_f32",
5369                                           (rows as u32, 1, 1), (rms_block(), 1, 1), 0, &mut ps)?; }
5370            return Ok(());
5371        }
5372        let f = if g { self.func_g("rms_norm_qkv_rope_append_dc_f32") }
5373                else { self.func("rms_norm_qkv_rope_append_dc_f32") };
5374        let cfg = LaunchConfig { grid_dim: (rows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5375        let __s_b = self.gpu.stream();
5376        let mut b = __s_b.launch_builder(&f);
5377        match ff {
5378            Some(t) => { b.arg(q0).arg(k0).arg(v0).arg(wq).arg(wk).arg(wv)
5379                          .arg(&mut *q).arg(&mut *k).arg(&mut *v)
5380                          .arg(&nc).arg(&rqi).arg(&rki).arg(pos).arg(&nhq).arg(&nhk)
5381                          .arg(&theta_scale).arg(&freq_scale).arg(t).arg(&eps)
5382                          .arg(&mut *kc).arg(&mut *vc).arg(t_dev).arg(&ktb).arg(&vtb);
5383                         unsafe { b.launch(cfg)?; } }
5384            None => { let null: u64 = 0;
5385                      b.arg(q0).arg(k0).arg(v0).arg(wq).arg(wk).arg(wv)
5386                       .arg(&mut *q).arg(&mut *k).arg(&mut *v)
5387                       .arg(&nc).arg(&rqi).arg(&rki).arg(pos).arg(&nhq).arg(&nhk)
5388                       .arg(&theta_scale).arg(&freq_scale).arg(&null).arg(&eps)
5389                       .arg(&mut *kc).arg(&mut *vc).arg(t_dev).arg(&ktb).arg(&vtb);
5390                      unsafe { b.launch(cfg)?; } }
5391        }
5392        Ok(())
5393    }
5394
5395    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
5396    pub fn add_q8_1(&self, a: &CudaSlice<f32>, b: &CudaSlice<f32>, res: &mut CudaSlice<f32>,
5397                    ncols: usize, nrows: usize)
5398                    -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
5399        debug_assert!(ncols % 128 == 0);
5400        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
5401        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
5402        let f = self.func("add_q8_1_f32");
5403        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
5404        let nc = ncols as i32;
5405        let __s_b2 = self.gpu.stream();
5406        let mut b2 = __s_b2.launch_builder(&f);
5407        b2.arg(a).arg(b).arg(&mut *res).arg(&mut out_q).arg(&mut out_d).arg(&nc);
5408        unsafe { b2.launch(cfg)?; }
5409        Ok((out_q, out_d))
5410    }
5411
5412    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
5413    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
5414    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
5415    pub fn rms_pre_add_q8_1(&self, a: &CudaSlice<f32>, wa: &CudaSlice<f32>, b: &CudaSlice<f32>,
5416                            res: &mut CudaSlice<f32>, ncols: usize, nrows: usize, eps: f32)
5417                            -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
5418        debug_assert!(ncols % 128 == 0);
5419        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
5420        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
5421        let f = self.func("rms_pre_add_q8_1_f32");
5422        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1),
5423                                 shared_mem_bytes: 0 };
5424        let (nc, ep) = (ncols as i32, eps);
5425        let __s_b2 = self.gpu.stream();
5426        let mut b2 = __s_b2.launch_builder(&f);
5427        b2.arg(a).arg(wa).arg(b).arg(&mut *res).arg(&mut out_q).arg(&mut out_d).arg(&nc).arg(&ep);
5428        unsafe { b2.launch(cfg)?; }
5429        Ok((out_q, out_d))
5430    }
5431
5432    /// L2 norm per row (head_dim), no weight.
5433    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
5434    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
5435    pub fn l2_v2_on(ncols: usize) -> bool {
5436        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
5437    }
5438
5439    pub fn l2_norm_pp(&self, x: &CudaSlice<f32>, dst: &mut CudaSlice<f32>,
5440                      dst16: Option<&mut CudaSlice<u8>>, ncols: usize, nrows: usize,
5441                      eps: f32) -> Result<(), Box<dyn std::error::Error>> {
5442        if Self::l2_v2_on(ncols) {
5443            let f = self.func("l2_norm_pp_v2_f32");
5444            let rows_per_block = 8u32;   // 256 threads = 8 warps = 8 rows
5445            let cfg = LaunchConfig { grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
5446            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
5447            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
5448            let d16: u64 = match dst16 { Some(d) => self.addr_u8(d), None => 0 };
5449            let __s_b = self.gpu.stream();
5450            let mut b = __s_b.launch_builder(&f);
5451            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
5452            unsafe { b.launch(cfg)?; }
5453            return Ok(());
5454        }
5455        self.l2_norm(x, dst, ncols, nrows, eps)
5456    }
5457
5458    pub fn l2_norm(&self, x: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, ncols: usize, nrows: usize,
5459                   eps: f32) -> Result<(), Box<dyn std::error::Error>> {
5460        let f = self.func("l2_norm_f32");
5461        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
5462        let (nc, e) = (ncols as i32, eps);
5463        let __s_b = self.gpu.stream();
5464        let mut b = __s_b.launch_builder(&f);
5465        b.arg(x).arg(dst).arg(&nc).arg(&e);
5466        unsafe { b.launch(cfg)?; }
5467        Ok(())
5468    }
5469
5470    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
5471    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
5472    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
5473    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
5474    /// propagate through gdn_scan and flip argmax on marginal logits.
5475    pub fn l2_norm_decode(&self, x: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, ncols: usize,
5476                          nrows: usize, eps: f32) -> Result<(), Box<dyn std::error::Error>> {
5477        let f = self.func("l2_norm_f32");
5478        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
5479        let (nc, e) = (ncols as i32, eps);
5480        let __s_b = self.gpu.stream();
5481        let mut b = __s_b.launch_builder(&f);
5482        b.arg(x).arg(dst).arg(&nc).arg(&e);
5483        unsafe { b.launch(cfg)?; }
5484        Ok(())
5485    }
5486
5487    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
5488    pub fn rope_neox(&self, x: &mut CudaSlice<f32>, pos: &CudaSlice<i32>, head_dim: usize,
5489                     n_dims: usize, n_heads: usize, n_tokens: usize, freq_base: f32, freq_scale: f32)
5490                     -> Result<(), Box<dyn std::error::Error>> {
5491        let f = self.func("rope_neox_f32");
5492        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
5493        let grid = (n_heads * n_tokens) as u32;
5494        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: ((head_dim / 2) as u32, 1, 1), shared_mem_bytes: 0 };
5495        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
5496        let __s_b = self.gpu.stream();
5497        let mut b = __s_b.launch_builder(&f);
5498        b.arg(x).arg(pos).arg(&hd).arg(&nd).arg(&nh).arg(&theta_scale).arg(&freq_scale);
5499        unsafe { b.launch(cfg)?; }
5500        Ok(())
5501    }
5502
5503    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
5504    pub fn rope_neox_ff(&self, x: &mut CudaSlice<f32>, pos: &CudaSlice<i32>, head_dim: usize,
5505                        n_dims: usize, n_heads: usize, n_tokens: usize, freq_base: f32,
5506                        freq_scale: f32, ff: &CudaSlice<f32>)
5507                        -> Result<(), Box<dyn std::error::Error>> {
5508        let f = self.func("rope_neox_ff_f32");
5509        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
5510        let grid = (n_heads * n_tokens) as u32;
5511        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: ((head_dim / 2) as u32, 1, 1), shared_mem_bytes: 0 };
5512        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
5513        let __s_b = self.gpu.stream();
5514        let mut b = __s_b.launch_builder(&f);
5515        b.arg(x).arg(pos).arg(&hd).arg(&nd).arg(&nh).arg(&theta_scale).arg(&freq_scale).arg(ff);
5516        unsafe { b.launch(cfg)?; }
5517        Ok(())
5518    }
5519
5520    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
5521    #[allow(clippy::too_many_arguments)]
5522    pub fn rope_neox2(&self, q: &mut CudaSlice<f32>, k: &mut CudaSlice<f32>,
5523                      pos: &CudaSlice<i32>, head_dim: usize, n_dims: usize,
5524                      nh_q: usize, nh_k: usize, n_tokens: usize, freq_base: f32,
5525                      freq_scale: f32, ff: Option<&CudaSlice<f32>>)
5526                      -> Result<(), Box<dyn std::error::Error>> {
5527        let f = self.func("rope_neox2_f32");
5528        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
5529        let grid = ((nh_q + nh_k) * n_tokens) as u32;
5530        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: ((head_dim / 2) as u32, 1, 1), shared_mem_bytes: 0 };
5531        let (hd, nd, nq, nk, nt) = (head_dim as i32, n_dims as i32, nh_q as i32, nh_k as i32, n_tokens as i32);
5532        let __s_b = self.gpu.stream();
5533        let mut b = __s_b.launch_builder(&f);
5534        b.arg(q).arg(k).arg(pos).arg(&hd).arg(&nd).arg(&nq).arg(&nk).arg(&nt)
5535         .arg(&theta_scale).arg(&freq_scale);
5536        match ff {
5537            Some(ffv) => { b.arg(ffv); unsafe { b.launch(cfg)?; } }
5538            None => {
5539                let null: u64 = 0;
5540                b.arg(&null);
5541                unsafe { b.launch(cfg)?; }
5542            }
5543        }
5544        Ok(())
5545    }
5546
5547    /// gemma4 R1: dst = GELU_tanh(gate) * up.
5548    pub fn gelu_tanh_mul(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, n: usize)
5549                         -> Result<(), Box<dyn std::error::Error>> {
5550        let f = self.func("gelu_tanh_mul_f32");
5551        let cfg = LaunchConfig::for_num_elems(n as u32);
5552        let ni = n as i32;
5553        let __s_b = self.gpu.stream();
5554        let mut b = __s_b.launch_builder(&f);
5555        b.arg(gate).arg(up).arg(dst).arg(&ni);
5556        unsafe { b.launch(cfg)?; }
5557        Ok(())
5558    }
5559
5560    pub fn silu_mul(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, n: usize)
5561                    -> Result<(), Box<dyn std::error::Error>> {
5562        let f = self.func("silu_mul_f32");
5563        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
5564        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
5565        let ni = n as i32;
5566        let __s_b = self.gpu.stream();
5567        let mut b = __s_b.launch_builder(&f);
5568        b.arg(gate).arg(up).arg(dst).arg(&ni);
5569        unsafe { b.launch(cfg)?; }
5570        Ok(())
5571    }
5572
5573    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
5574    /// for the down projection — kills the standalone convert pass. Bit-identical class.
5575    pub fn silu_mul_f16out(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>,
5576                           dst: &mut CudaSlice<f32>, dst16: &mut CudaSlice<u8>, n: usize)
5577                           -> Result<(), Box<dyn std::error::Error>> {
5578        let f = self.func("silu_mul_f16out_f32");
5579        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
5580        let ni = n as i32;
5581        let __s_b = self.gpu.stream();
5582        let mut b = __s_b.launch_builder(&f);
5583        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
5584        unsafe { b.launch(cfg)?; }
5585        Ok(())
5586    }
5587
5588    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
5589    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
5590    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
5591    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
5592    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
5593    /// launches per dense FFN layer (the gate+up post-matmul scales).
5594    pub fn silu_mul_scaled(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, gs: f32, us: f32,
5595                           dst: &mut CudaSlice<f32>, n: usize) -> Result<(), Box<dyn std::error::Error>> {
5596        let f = self.func("silu_mul_scaled_f32");
5597        let cfg = LaunchConfig::for_num_elems(n as u32);
5598        let ni = n as i32;
5599        let (gsf, usf) = (gs, us);
5600        let __s_b = self.gpu.stream();
5601        let mut b = __s_b.launch_builder(&f);
5602        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
5603        unsafe { b.launch(cfg)?; }
5604        Ok(())
5605    }
5606
5607    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
5608    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
5609    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
5610    #[allow(clippy::too_many_arguments)]
5611    pub fn swigluoai_mul_scaled(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, gs: f32, us: f32,
5612                                alpha: f32, limit: f32, dst: &mut CudaSlice<f32>, n: usize)
5613                                -> Result<(), Box<dyn std::error::Error>> {
5614        let f = self.func("swigluoai_mul_scaled_f32");
5615        let cfg = LaunchConfig::for_num_elems(n as u32);
5616        let ni = n as i32;
5617        let __s_b = self.gpu.stream();
5618        let mut b = __s_b.launch_builder(&f);
5619        b.arg(gate).arg(up).arg(&gs).arg(&us).arg(&alpha).arg(&limit).arg(dst).arg(&ni);
5620        unsafe { b.launch(cfg)?; }
5621        Ok(())
5622    }
5623
5624    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
5625    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
5626    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
5627    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
5628    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
5629    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
5630    /// n must be a multiple of 32 (n_ff always is).
5631    pub fn silu_mul_scaled_q8_1(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, gs: f32, us: f32,
5632                                n: usize)
5633                                -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
5634        let f = self.func("silu_mul_scaled_q8_1");
5635        let nblk = n / 32;
5636        let mut aq = self.alloc_uninit::<i8>(n)?;       // full-overwrite output
5637        let mut ad = self.alloc_uninit::<f32>(nblk)?;   // full-overwrite output
5638        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
5639        let cfg = LaunchConfig::for_num_elems(n as u32);
5640        let (gsf, usf, ni) = (gs, us, n as i32);
5641        let __s_b = self.gpu.stream();
5642        let mut b = __s_b.launch_builder(&f);
5643        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(&mut aq).arg(&mut ad).arg(&ni);
5644        unsafe { b.launch(cfg)?; }
5645        Ok((aq, ad))
5646    }
5647
5648    pub fn add(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, n: usize)
5649               -> Result<(), Box<dyn std::error::Error>> {
5650        let f = self.func("add_f32");
5651        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
5652        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
5653        let ni = n as i32;
5654        let __s_bld = self.gpu.stream();
5655        let mut bld = __s_bld.launch_builder(&f);
5656        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
5657        unsafe { bld.launch(cfg)?; }
5658        Ok(())
5659    }
5660
5661    pub fn mul(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, dst: &mut CudaSlice<f32>, n: usize)
5662               -> Result<(), Box<dyn std::error::Error>> {
5663        let f = self.func("mul_f32");
5664        let cfg = LaunchConfig::for_num_elems(n as u32);
5665        let ni = n as i32;
5666        let __s_bld = self.gpu.stream();
5667        let mut bld = __s_bld.launch_builder(&f);
5668        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
5669        unsafe { bld.launch(cfg)?; }
5670        Ok(())
5671    }
5672
5673    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
5674    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
5675    pub fn matmul(&self, w: &crate::model::GpuTensor, x: &CudaSlice<f32>, m: usize)
5676                  -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5677        use crate::model::GpuTensor;
5678        let in_f = w.in_features();
5679        let out_f = w.out_features();
5680        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
5681        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
5682        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
5683        // gives nothing). Quantize the activation once here then call the GEMM.
5684        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
5685        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
5686        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
5687        #[allow(non_snake_case)]
5688        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
5689        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
5690        let GEMM_M_THRESHOLD = if self.verify_exact_on() { usize::MAX } else { 16usize };
5691
5692        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
5693        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
5694        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
5695        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
5696        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
5697        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
5698        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
5699        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
5700        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
5701        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
5702        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
5703        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
5704        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
5705        const GEMM_MIN_OUT_F: usize = 128;   // 2*BM; below this the GEMM grid.x starves the 82 SMs
5706        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
5707        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
5708        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
5709        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
5710        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
5711        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
5712        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
5713        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
5714        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
5715        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
5716        if m >= GEMM_M_THRESHOLD {
5717            if let Some(y) = self.try_fp8_gemm(w, x, m)? { return Ok(y); }
5718            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
5719            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
5720            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
5721            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
5722            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
5723            // tile defaults differently by operand source.
5724            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? { return Ok(y); }
5725            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
5726            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
5727            if let Some(y) = self.try_f16_gemm(w, x, m)? { return Ok(y); }
5728        }
5729        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
5730        // m threshold the rest of this method uses:
5731        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
5732        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
5733        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
5734        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
5735        //     across every tier by construction with no batched twin needed.
5736        //
5737        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
5738        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
5739        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
5740        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
5741        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
5742        // arms is what makes sure it never gets there.
5743        if let GpuTensor::Quant { qtype, .. } = w {
5744            if *qtype == QT_F8_E4M3_BLK {
5745                if m >= GEMM_M_THRESHOLD {
5746                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? { return Ok(y); }
5747                }
5748                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
5749                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? { return Ok(y); }
5750            }
5751        }
5752        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
5753            return self.qmatvec_mmq(w, x, m);
5754        }
5755        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
5756            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
5757            return self.qmatvec_gemm(w, &aq, &ad, m);
5758        }
5759        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
5760        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
5761        if m >= GEMM_M_THRESHOLD {
5762            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? { return Ok(y); }
5763        }
5764        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
5765        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
5766        // to Stage-A f32-dequant (the correctness oracle path).
5767        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
5768        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
5769        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
5770        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
5771        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
5772        if m == 1 && fast {
5773            if let GpuTensor::Quant { bytes, qtype, row_bytes, rp, rp4, scale, .. } = w {
5774                if self.mmvq_supports(*qtype) {
5775                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
5776                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
5777                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
5778                    let (bytes, rp) = match rp4 { Some(m4) => (m4, true), None => (bytes, *rp) };
5779                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
5780                    return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp);
5781                }
5782            }
5783        }
5784        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
5785        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
5786        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
5787        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
5788        // block below. MEMRA_NO_BATCHED -> per-m path.
5789        //
5790        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
5791        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
5792        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
5793        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
5794        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
5795        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
5796        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
5797        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
5798        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
5799        if (2..=16).contains(&m) && fast && std::env::var("MEMRA_NO_BATCHED").is_err()
5800            && (m <= 4 || Self::b8_enabled()) {
5801            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
5802            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
5803            // is present (rp4) — the mirror pick below then routes to the _rp family.
5804            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
5805            // because the native e4m3 row layout is already aligned and needs no mirror.
5806            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
5807            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
5808            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
5809            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
5810            let m_ok = m <= 8 || matches!(w, GpuTensor::Quant { qtype, .. }
5811                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
5812                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
5813            if m_ok {
5814            if let GpuTensor::Quant { bytes, qtype, row_bytes, rp, rp4, .. } = w {
5815                if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
5816                    let (bytes, rp) = match rp4 { Some(m4) => (m4, true), None => (bytes, *rp) };
5817                    let mcols = Self::batched_mcols(m);
5818                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
5819                    let mut y = self.qmatvec_mmvq_batched(bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp)?;
5820                    if let GpuTensor::Quant { scale, .. } = w {
5821                        if *scale != 1.0 { self.scale_inplace(&mut y, *scale, m * out_f)?; }
5822                    }
5823                    return Ok(y);
5824                }
5825            }
5826        }
5827        }
5828        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
5829        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
5830        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
5831        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
5832        // for this dtype, so the generic match below must never see it under `fast`.
5833        if fast {
5834            if let GpuTensor::Quant { bytes, qtype, row_bytes, scale, .. } = w {
5835                if *qtype == QT_F8_E4M3 {
5836                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
5837                    return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes,
5838                                             *scale, false);
5839                }
5840            }
5841        }
5842        let mut y = match w {
5843            GpuTensor::Quant { bytes, qtype, row_bytes, .. } if fast && *qtype == QT_Q8_0 =>
5844                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?,
5845            GpuTensor::Quant { bytes, qtype, row_bytes, .. } if fast && *qtype == QT_Q4_K =>
5846                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?,
5847            GpuTensor::Quant { bytes, qtype, row_bytes, .. } if fast && *qtype == QT_Q6_K =>
5848                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?,
5849            GpuTensor::Quant { bytes, qtype, row_bytes, .. } if fast && *qtype == QT_Q5_K =>
5850                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?,
5851            GpuTensor::Quant { bytes, qtype, row_bytes, .. } if fast && *qtype == QT_Q3_K =>
5852                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?,
5853            GpuTensor::Quant { bytes, qtype, row_bytes, rp, .. } if fast && *qtype == QT_NVFP4 =>
5854                self.qmatvec_dp4a_named(
5855                    if *rp { "qmatvec_nvfp4_dp4a_rp" } else { "qmatvec_nvfp4_dp4a" },
5856                    bytes, x, m, in_f, out_f, *row_bytes)?,
5857            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
5858            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
5859            // anomaly (research/kat-anomaly-20260802/).
5860            GpuTensor::Quant { bytes, qtype, row_bytes, .. }
5861                if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() =>
5862                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?,
5863            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
5864            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
5865            // without first writing the matching kernel, or func() will panic
5866            // "kernel ... not in any fatbin".
5867            GpuTensor::Quant { bytes, qtype, row_bytes, rp, .. } =>
5868                // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
5869                // deq(row,j) form cannot address the planes; same value/product order).
5870                self.qmatvec(bytes, x, m, in_f, out_f,
5871                             if *rp && *qtype == QT_NVFP4 { QT_NVFP4_RP } else { *qtype },
5872                             *row_bytes)?,
5873            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
5874            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
5875            // cuBLASLt f32 GEMV as the Float arm.
5876            GpuTensor::FloatBf16 { data, .. } =>
5877                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?,
5878        };
5879        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
5880        if let GpuTensor::Quant { scale, .. } = w {
5881            if *scale != 1.0 { self.scale_inplace(&mut y, *scale, m * out_f)?; }
5882        }
5883        Ok(y)
5884    }
5885
5886    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
5887    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
5888    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
5889        use crate::model::GpuTensor;
5890        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") { return false; }
5891        match w {
5892            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
5893            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
5894            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
5895            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
5896            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
5897            // block class has no fused twin yet, so each of its projections takes its own launch.
5898            GpuTensor::Quant { qtype, .. } => matches!(*qtype,
5899                QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q3_K | QT_NVFP4 | QT_F8_E4M3
5900                | QT_F8_E4M3_BLK | QT_Q4_0)
5901                || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled()),
5902            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
5903        }
5904    }
5905
5906    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
5907    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
5908    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
5909    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
5910    pub fn matmul_pre(&self, w: &crate::model::GpuTensor, aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
5911                      x_fallback: &CudaSlice<f32>, m: usize)
5912                      -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5913        use crate::model::GpuTensor;
5914        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
5915        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
5916        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
5917        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
5918        // rc=30013 dig, 2026-07-31).
5919        let x_raw_ok = x_fallback.len() >= m * w.in_features();
5920        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
5921        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
5922        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
5923            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? { return Ok(y); }
5924            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
5925            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
5926            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? { return Ok(y); }
5927            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
5928            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? { return Ok(y); }
5929        }
5930        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
5931        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
5932        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
5933        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
5934        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
5935        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
5936            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? { return Ok(y); }
5937        }
5938        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? { return Ok(y); }
5939        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
5940        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
5941        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
5942        // aq/ad.
5943        if m >= 16 && w.out_features() >= 128 && self.mmq_supports(w) && !self.verify_exact_on()
5944            && x_raw_ok {
5945            return self.qmatvec_mmq(w, x_fallback, m);
5946        }
5947        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
5948        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
5949        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
5950            if let Some(y) = self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())? {
5951                return Ok(y);
5952            }
5953        }
5954        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
5955        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
5956        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
5957            return self.qmatvec_gemm(w, aq, ad, m);
5958        }
5959        if !self.uses_q8_1_fast(w) { return self.matmul(w, x_fallback, m); }
5960        let in_f = w.in_features();
5961        let out_f = w.out_features();
5962        let (bytes, qtype, row_bytes, scale, rp) = match w {
5963            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } => (bytes, *qtype, *row_bytes, *scale, *rp),
5964            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
5965        };
5966        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
5967        // the dp4a/oracle tails below keep the raw GGUF bytes.
5968        let (mbytes, mrp) = match w {
5969            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
5970            _ => (bytes, rp),
5971        };
5972        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
5973        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
5974        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
5975        if m == 1 && self.mmvq_supports(qtype) {
5976            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
5977        }
5978        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
5979        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
5980        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
5981        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
5982        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
5983        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
5984        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
5985        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
5986        // m=5..8 on the old per-m path (b8-tier-only seam).
5987        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
5988        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
5989        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
5990        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
5991            && std::env::var("MEMRA_NO_BATCHED").is_err()
5992            && (m <= 4 || Self::b8_enabled())
5993            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
5994            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
5995            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
5996            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
5997                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0) {
5998            let mcols = Self::batched_mcols(m);
5999            return self.qmatvec_mmvq_batched(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp);
6000        }
6001        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
6002        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
6003        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
6004        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
6005        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
6006        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
6007            let (b2, r2) = if qtype == QT_Q4_0 { (mbytes, mrp) } else { (bytes, rp) };
6008            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
6009        }
6010        let name = match qtype {
6011            QT_Q8_0 => "qmatvec_q8_0_dp4a", QT_Q4_K => "qmatvec_q4_K_dp4a",
6012            QT_Q6_K => "qmatvec_q6_K_dp4a", QT_Q5_K => "qmatvec_q5_K_dp4a",
6013            QT_Q3_K => "qmatvec_q3_K_dp4a",
6014            QT_NVFP4 => if rp { "qmatvec_nvfp4_dp4a_rp" } else { "qmatvec_nvfp4_dp4a" },
6015            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
6016            _ => unreachable!(),
6017        };
6018        let f = self.func(name);
6019        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite GEMM output: skip memset
6020        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
6021        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6022        let __s_b = self.gpu.stream();
6023        let mut b = __s_b.launch_builder(&f);
6024        b.arg(bytes).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
6025        unsafe { b.launch(cfg)?; }
6026        if scale != 1.0 { self.scale_inplace(&mut y, scale, m * out_f)?; }
6027        Ok(y)
6028    }
6029
6030    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
6031    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
6032    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
6033    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
6034    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
6035    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
6036    /// reduce as m=1); this method just forces that path unconditionally.
6037    pub fn matmul_decode_exact(&self, w: &crate::model::GpuTensor, x: &CudaSlice<f32>, m: usize)
6038                               -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6039        use crate::model::GpuTensor;
6040        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
6041        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
6042        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
6043        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
6044        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
6045        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
6046        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
6047        if let GpuTensor::Float { data, .. } = w {
6048            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
6049        }
6050        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
6051        // float linear (same n-independent reduction contract as the Float arm above).
6052        if let GpuTensor::FloatBf16 { data, .. } = w {
6053            let (in_f, out_f) = (w.in_features(), w.out_features());
6054            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
6055        }
6056        if !self.uses_q8_1_fast(w) { return self.matmul(w, x, m); }
6057        let in_f = w.in_features();
6058        let out_f = w.out_features();
6059        let (bytes, qtype, row_bytes, scale, rp) = match w {
6060            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } => (bytes, *qtype, *row_bytes, *scale, *rp),
6061            _ => return self.matmul(w, x, m),
6062        };
6063        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
6064        // which does its own mirror pick).
6065        let (bytes, rp) = match w {
6066            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
6067            _ => (bytes, rp),
6068        };
6069        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6070        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
6071        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
6072        // (token,row) by construction, which is exactly what this method exists to guarantee.
6073        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? { return Ok(y); }
6074        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
6075        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
6076        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
6077        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
6078        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
6079        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
6080        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
6081        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
6082        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
6083            && std::env::var("MEMRA_NO_BATCHED").is_err()
6084            && (m <= 4 || Self::b8_enabled())
6085            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
6086            // no mirror precondition, `rp` selects the layout only.
6087            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
6088                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0) {
6089            let mcols = Self::batched_mcols(m);
6090            return self.qmatvec_mmvq_batched(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp);
6091        }
6092        if self.mmvq_supports(qtype) {
6093            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
6094            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
6095            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
6096        }
6097        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
6098        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
6099        self.matmul_pre(w, &aq, &ad, x, m)
6100    }
6101
6102    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
6103    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
6104    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
6105    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
6106    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
6107    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
6108    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
6109    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
6110    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
6111    pub fn matmul_decode_exact_pre(&self, w: &crate::model::GpuTensor, aq: &CudaSlice<i8>,
6112                                   ad: &CudaSlice<f32>, m: usize)
6113                                   -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6114        use crate::model::GpuTensor;
6115        debug_assert!(self.uses_q8_1_fast(w),
6116                      "matmul_decode_exact_pre: caller must guarantee q8_1-fast");
6117        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
6118        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? { return Ok(y); }
6119        let in_f = w.in_features();
6120        let out_f = w.out_features();
6121        let (bytes, qtype, row_bytes, scale, rp) = match w {
6122            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } =>
6123                (bytes, *qtype, *row_bytes, *scale, *rp),
6124            _ => return Err("matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into()),
6125        };
6126        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
6127        let (bytes, rp) = match w {
6128            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
6129            _ => (bytes, rp),
6130        };
6131        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
6132        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
6133            && std::env::var("MEMRA_NO_BATCHED").is_err()
6134            && (m <= 4 || Self::b8_enabled())
6135            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
6136                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0) {
6137            let mcols = Self::batched_mcols(m);
6138            return self.qmatvec_mmvq_batched(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp);
6139        }
6140        if self.mmvq_supports(qtype) {
6141            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
6142        }
6143        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
6144        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
6145        let x0 = self.zeros(0)?;
6146        self.matmul_pre(w, aq, ad, &x0, m)
6147    }
6148
6149    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
6150    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
6151    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
6152    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
6153    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
6154    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
6155    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
6156    /// per-tensor path.
6157    pub fn matmul_decode_exact_dual_pre(&self, w0: &crate::model::GpuTensor,
6158                                        w1: &crate::model::GpuTensor,
6159                                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize)
6160        -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>> {
6161        use crate::model::GpuTensor;
6162        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6163        let on = *ON.get_or_init(|| {
6164            std::env::var("MEMRA_SPEC_DUAL_T").map(|v| v != "0").unwrap_or(true)
6165        });
6166        if !on || !(2..=7).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok()
6167            || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
6168            return Ok(None);
6169        }
6170        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
6171        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
6172        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
6173        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
6174        if !self.mmvq_supports(QT_NVFP4) { return Ok(None); }
6175        let (in_f, out_f) = (w0.in_features(), w0.out_features());
6176        if w1.in_features() != in_f || w1.out_features() != out_f {
6177            return Ok(None);
6178        }
6179        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
6180            (GpuTensor::Quant { bytes: b0, qtype: q0, row_bytes: rb0, scale: s0, rp: rp0, rp4: None, .. },
6181             GpuTensor::Quant { bytes: b1, qtype: q1, row_bytes: rb1, scale: s1, rp: rp1, rp4: None, .. })
6182                if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 =>
6183                (b0, b1, *rb0, *s0, *s1, *rp0),
6184            _ => return Ok(None),
6185        };
6186        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
6187        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
6188        if m > 4 && !(rp && Self::b8_enabled()
6189            && std::env::var("MEMRA_B567").as_deref() != Ok("0")) {
6190            return Ok(None);
6191        }
6192        let (y0, y1) = self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
6193        Ok(Some(((y0, s0), (y1, s1))))
6194    }
6195
6196    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
6197    /// launch computes both FFN projections of a verify batch — same activation, same shape,
6198    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
6199    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
6200    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
6201    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
6202    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
6203    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
6204    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
6205    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
6206    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
6207    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
6208    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
6209    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
6210    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
6211    pub fn matmul_decode_exact_dual(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6212                                    x: &CudaSlice<f32>, m: usize)
6213        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6214        use crate::model::GpuTensor;
6215        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6216        let on = *ON.get_or_init(|| {
6217            std::env::var("MEMRA_SPEC_DUAL_T").map(|v| v != "0").unwrap_or(true)
6218        });
6219        if !on || !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok()
6220            || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
6221            return Ok(None);
6222        }
6223        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
6224        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
6225        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
6226        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
6227        if !self.mmvq_supports(QT_NVFP4) { return Ok(None); }
6228        let (in_f, out_f) = (w0.in_features(), w0.out_features());
6229        if w1.in_features() != in_f || w1.out_features() != out_f {
6230            return Ok(None);
6231        }
6232        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
6233            (GpuTensor::Quant { bytes: b0, qtype: q0, row_bytes: rb0, scale: s0, rp: rp0, rp4: None, .. },
6234             GpuTensor::Quant { bytes: b1, qtype: q1, row_bytes: rb1, scale: s1, rp: rp1, rp4: None, .. })
6235                if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 =>
6236                (b0, b1, *rb0, *s0, *s1, *rp0),
6237            _ => return Ok(None),
6238        };
6239        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
6240        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
6241        if std::env::var("MEMRA_DEBUG").is_ok() {
6242            static ONCE: std::sync::Once = std::sync::Once::new();
6243            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
6244        }
6245        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6246        let (y0, y1) = self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
6247        let mut y0 = y0;
6248        let mut y1 = y1;
6249        if s0 != 1.0 { self.scale_inplace(&mut y0, s0, m * out_f)?; }
6250        if s1 != 1.0 { self.scale_inplace(&mut y1, s1, m * out_f)?; }
6251        Ok(Some((y0, y1)))
6252    }
6253
6254    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
6255    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
6256    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
6257    /// twins (both buffers must be the repacked layout).
6258    #[allow(clippy::too_many_arguments)]
6259    pub fn qmatvec_batched_dual_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
6260                                    aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
6261                                    m: usize, in_f: usize, out_f: usize, row_bytes: usize, rp: bool)
6262        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6263        const ROWS_PER_BLOCK: u32 = 4;
6264        let mcols = Self::batched_mcols(m);
6265        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
6266        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
6267        let tiny_rp1 = rp && mcols == 4 && out_f <= 128
6268            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
6269        let (name, rows_per_block) = if tiny_rp1 {
6270            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
6271        } else { match (mcols, rp, m) {
6272            (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
6273            (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
6274            (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
6275            (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
6276            (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
6277            (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
6278            (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
6279            _ => return Err(format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into()),
6280        }};
6281        let f = self.func(name);
6282        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
6283        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
6284        let cfg = LaunchConfig {
6285            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
6286            block_dim: (32, ROWS_PER_BLOCK, 1),
6287            shared_mem_bytes: 0,
6288        };
6289        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6290        let __s_b = self.gpu.stream();
6291        let mut b = __s_b.launch_builder(&f);
6292        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
6293            .arg(&inf).arg(&outf).arg(&mi).arg(&rb);
6294        unsafe { b.launch(cfg)?; }
6295        Ok((y0, y1))
6296    }
6297
6298    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
6299    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
6300    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
6301    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
6302    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
6303    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
6304    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
6305    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
6306    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
6307    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
6308    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
6309    pub fn matmul_pre_dual_noscale(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6310                                   aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize)
6311        -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>> {
6312        use crate::model::GpuTensor;
6313        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) { return Ok(None); }
6314        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
6315        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
6316        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
6317        // would mix dispatch families across the pair — the exact class `q8_fused_params`
6318        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
6319        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
6320        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
6321        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
6322        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
6323        if !self.mmvq_supports(QT_NVFP4) { return Ok(None); }
6324        let (in_f, out_f) = (w0.in_features(), w0.out_features());
6325        if w1.in_features() != in_f || w1.out_features() != out_f { return Ok(None); }
6326        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
6327        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
6328        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
6329        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
6330        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
6331        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
6332        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
6333        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
6334        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
6335        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
6336        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
6337        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
6338        let no_mirror = |w: &crate::model::GpuTensor| {
6339            !matches!(w, GpuTensor::Quant { rp4: Some(_), .. })
6340        };
6341        if self.q8_ffn_fuse2_on()
6342            && no_mirror(w0) && no_mirror(w1)
6343            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
6344        {
6345            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
6346            return Ok(Some(((y0, 1.0), (y1, 1.0))));
6347        }
6348        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
6349        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
6350        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
6351        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
6352        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
6353        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
6354        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
6355        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
6356        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
6357        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
6358            let (y0, y1) = self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2,
6359                                                 1.0, 1.0)?;
6360            return Ok(Some(((y0, p0.3), (y1, p1.3))));
6361        }
6362        let (b0, q0, rb0, s0, rp0) = match w0 {
6363            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } => (bytes, *qtype, *row_bytes, *scale, *rp),
6364            _ => return Ok(None),
6365        };
6366        let (b1, q1, rb1, s1, rp1) = match w1 {
6367            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } => (bytes, *qtype, *row_bytes, *scale, *rp),
6368            _ => return Ok(None),
6369        };
6370        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 { return Ok(None); }
6371        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
6372        const RPW: u32 = 2;
6373        let rows_per_block = ROWS_PER_BLOCK * RPW;
6374        let f = self.func(if rp0 { "qmatvec_nvfp4_mmvq_dual_mr2_rp" } else { "qmatvec_nvfp4_mmvq_dual_mr2" });
6375        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
6376        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
6377        let cfg = LaunchConfig {
6378            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
6379            block_dim: (32, ROWS_PER_BLOCK, 1), shared_mem_bytes: 0,
6380        };
6381        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
6382        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
6383        // yscale args stay 1.0 here (they exist for the single-tensor callers).
6384        let one = 1.0f32;
6385        let __s_b = self.gpu.stream();
6386        let mut b = __s_b.launch_builder(&f);
6387        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
6388         .arg(&inf).arg(&outf).arg(&mi).arg(&rb).arg(&one).arg(&one);
6389        unsafe { b.launch(cfg)?; }
6390        Ok(Some(((y0, s0), (y1, s1))))
6391    }
6392
6393    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
6394    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
6395    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
6396    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
6397    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
6398    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
6399    /// back to the per-tensor path.
6400    pub fn matmul_q8_fused2(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6401                            aq: &CudaSlice<i8>, ad: &CudaSlice<f32>)
6402        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6403        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
6404        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
6405        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
6406        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
6407        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
6408        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
6409            return Ok(Some(self.e4m3_fused2_core(p0.0, p1.0, aq, ad, w0.in_features(),
6410                                                 p0.1, p1.1, p0.2, p0.3, p1.3)?));
6411        }
6412        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else { return Ok(None) };
6413        Ok(Some(self.q8_fused2_core(p0.0, p1.0, aq, ad, w0.in_features(), p0.1, p1.1, p0.2)?))
6414    }
6415
6416    #[allow(clippy::too_many_arguments)]
6417    fn q8_fused2_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
6418                      aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
6419                      in_f: usize, out0: usize, out1: usize, row_bytes: usize)
6420        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6421        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
6422        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
6423        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
6424        let f = self.func("qmatvec_q8_0_mmvq_fused2");
6425        let mut y0 = self.alloc_uninit::<f32>(out0)?;
6426        let mut y1 = self.alloc_uninit::<f32>(out1)?;
6427        let cfg = LaunchConfig { grid_dim: (nb0 + nb1, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
6428                                 shared_mem_bytes: 0 };
6429        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
6430        let __s_b = self.gpu.stream();
6431        let mut b = __s_b.launch_builder(&f);
6432        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
6433         .arg(&inf).arg(&o0).arg(&o1).arg(&rbl);
6434        unsafe { b.launch(cfg)?; }
6435        Ok((y0, y1))
6436    }
6437
6438    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
6439    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
6440    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
6441    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
6442    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
6443    pub fn matmul_q8_fused2_x(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6444                              x: &CudaSlice<f32>)
6445        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6446        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) { return Ok(None); }
6447        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
6448            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
6449            return Ok(Some(self.e4m3_fused2_core(p0.0, p1.0, &aq, &ad, w0.in_features(),
6450                                                 p0.1, p1.1, p0.2, p0.3, p1.3)?));
6451        }
6452        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else { return Ok(None) };
6453        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
6454        Ok(Some(self.q8_fused2_core(p0.0, p1.0, &aq, &ad, w0.in_features(), p0.1, p1.1, p0.2)?))
6455    }
6456
6457    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
6458    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
6459    #[allow(clippy::too_many_arguments)]
6460    pub fn qmatvec_q8_fused2_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, x: &CudaSlice<f32>,
6461                                 in_f: usize, out0: usize, out1: usize, row_bytes: usize)
6462        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6463        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
6464        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
6465    }
6466
6467    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
6468    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
6469    /// (tensor,row) to three separate m=1 MMVQ launches.
6470    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
6471    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
6472    pub fn matmul_q4_fused3(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6473                            w2: &crate::model::GpuTensor,
6474                            aq: &CudaSlice<i8>, ad: &CudaSlice<f32>)
6475        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6476        use crate::model::GpuTensor;
6477        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
6478            match w {
6479                GpuTensor::Quant { qtype, row_bytes, .. } if *qtype == QT_Q4_0 =>
6480                    Some((*row_bytes, w.out_features())),
6481                _ => None,
6482            }
6483        };
6484        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2))
6485        else { return Ok(None) };
6486        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
6487            return Ok(None);
6488        }
6489        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
6490        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
6491        // the separate matvecs (each routes its own rp).
6492        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
6493            match w {
6494                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
6495                    Some(m) => (m, true),
6496                    None => (bytes, *rp),
6497                },
6498                _ => unreachable!(),
6499            }
6500        }
6501        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
6502        if rp0 != rp1 || rp1 != rp2 { return Ok(None); }
6503        let rp = rp0;
6504        let rpb: u32 = 4;
6505        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
6506        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
6507        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
6508        let mr1 = rp && Self::q40_mr1_on();
6509        let nb = |o: usize| if mr1 { (o as u32).div_ceil(rpb) }
6510                            else { (o as u32).div_ceil(2).div_ceil(rpb) };
6511        let grid = nb(o0) + nb(o1) + nb(o2);
6512        let mut y0 = self.alloc_uninit::<f32>(o0)?;
6513        let mut y1 = self.alloc_uninit::<f32>(o1)?;
6514        let mut y2 = self.alloc_uninit::<f32>(o2)?;
6515        let f = self.func(if mr1 { "qmatvec_q4_0_mmvq_fused3_mr1_rp" }
6516                          else if rp { "qmatvec_q4_0_mmvq_fused3_rp" }
6517                          else { "qmatvec_q4_0_mmvq_fused3" });
6518        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (32, rpb, 1), shared_mem_bytes: 0 };
6519        let inf = w0.in_features() as i32;
6520        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
6521        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
6522        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
6523        // variant may take the programmatic-serialization launch.
6524        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
6525            {
6526            use cudarc::driver::{DevicePtr, DevicePtrMut};
6527            let s = &self.gpu.stream();
6528            let (p0, _g0) = b0.device_ptr(s); let (p1, _g1) = b1.device_ptr(s);
6529            let (p2, _g2) = b2.device_ptr(s); let (paq, _g3) = aq.device_ptr(s);
6530            let (pad, _g4) = ad.device_ptr(s);
6531            let (py0, _g5) = y0.device_ptr_mut(s); let (py1, _g6) = y1.device_ptr_mut(s);
6532            let (py2, _g7) = y2.device_ptr_mut(s);
6533            let mut ps = [
6534                &p0 as *const _ as *mut std::ffi::c_void, &p1 as *const _ as *mut _,
6535                &p2 as *const _ as *mut _, &paq as *const _ as *mut _,
6536                &pad as *const _ as *mut _, &py0 as *const _ as *mut _,
6537                &py1 as *const _ as *mut _, &py2 as *const _ as *mut _,
6538                &inf as *const _ as *mut _, &oo0 as *const _ as *mut _,
6539                &oo1 as *const _ as *mut _, &oo2 as *const _ as *mut _,
6540                &r0 as *const _ as *mut _, &r1 as *const _ as *mut _,
6541                &r2 as *const _ as *mut _,
6542            ];
6543            unsafe { self.launch_pdl("qmatvec_q4_0_mmvq_fused3_mr1_rp",
6544                                     (grid, 1, 1), (32, rpb, 1), &mut ps)?; }
6545            }
6546            return Ok(Some((y0, y1, y2)));
6547        }
6548        let __s_b = self.gpu.stream();
6549        let mut b = __s_b.launch_builder(&f);
6550        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1).arg(&mut y2)
6551         .arg(&inf).arg(&oo0).arg(&oo1).arg(&oo2).arg(&r0).arg(&r1).arg(&r2);
6552        unsafe { b.launch(cfg)?; }
6553        Ok(Some((y0, y1, y2)))
6554    }
6555
6556    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
6557    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
6558    #[allow(clippy::too_many_arguments)]
6559    pub fn matmul_q4_fused3_into(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6560                                 w2: &crate::model::GpuTensor,
6561                                 aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
6562                                 y0: &mut CudaSlice<f32>, y1: &mut CudaSlice<f32>,
6563                                 y2: &mut CudaSlice<f32>)
6564        -> Result<bool, Box<dyn std::error::Error>> {
6565        use crate::model::GpuTensor;
6566        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
6567            match w {
6568                GpuTensor::Quant { qtype, row_bytes, .. } if *qtype == QT_Q4_0 =>
6569                    Some((*row_bytes, w.out_features())),
6570                _ => None,
6571            }
6572        };
6573        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2))
6574        else { return Ok(false) };
6575        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
6576            return Ok(false);
6577        }
6578        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
6579            match w {
6580                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
6581                    Some(m) => (m, true),
6582                    None => (bytes, *rp),
6583                },
6584                _ => unreachable!(),
6585            }
6586        }
6587        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
6588        if rp0 != rp1 || rp1 != rp2 { return Ok(false); }
6589        let rp = rp0;
6590        let rpb: u32 = 4;
6591        let mr1 = rp && Self::q40_mr1_on();
6592        let nb = |o: usize| if mr1 { (o as u32).div_ceil(rpb) }
6593                            else { (o as u32).div_ceil(2).div_ceil(rpb) };
6594        let grid = nb(o0) + nb(o1) + nb(o2);
6595        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
6596        let f = self.func(if mr1 { "qmatvec_q4_0_mmvq_fused3_mr1_rp" }
6597                          else if rp { "qmatvec_q4_0_mmvq_fused3_rp" }
6598                          else { "qmatvec_q4_0_mmvq_fused3" });
6599        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (32, rpb, 1), shared_mem_bytes: 0 };
6600        let inf = w0.in_features() as i32;
6601        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
6602        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
6603        // PDL wave-A: identical to the owned twin (capture-lane parity).
6604        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
6605            use cudarc::driver::{DevicePtr, DevicePtrMut};
6606            let s = &self.gpu.stream();
6607            let (p0, _g0) = b0.device_ptr(s); let (p1, _g1) = b1.device_ptr(s);
6608            let (p2, _g2) = b2.device_ptr(s); let (paq, _g3) = aq.device_ptr(s);
6609            let (pad, _g4) = ad.device_ptr(s);
6610            let (py0, _g5) = y0.device_ptr_mut(s); let (py1, _g6) = y1.device_ptr_mut(s);
6611            let (py2, _g7) = y2.device_ptr_mut(s);
6612            let mut ps = [
6613                &p0 as *const _ as *mut std::ffi::c_void, &p1 as *const _ as *mut _,
6614                &p2 as *const _ as *mut _, &paq as *const _ as *mut _,
6615                &pad as *const _ as *mut _, &py0 as *const _ as *mut _,
6616                &py1 as *const _ as *mut _, &py2 as *const _ as *mut _,
6617                &inf as *const _ as *mut _, &oo0 as *const _ as *mut _,
6618                &oo1 as *const _ as *mut _, &oo2 as *const _ as *mut _,
6619                &r0 as *const _ as *mut _, &r1 as *const _ as *mut _,
6620                &r2 as *const _ as *mut _,
6621            ];
6622            unsafe { self.launch_pdl("qmatvec_q4_0_mmvq_fused3_mr1_rp",
6623                                     (grid, 1, 1), (32, rpb, 1), &mut ps)?; }
6624            return Ok(true);
6625        }
6626        let __s_b = self.gpu.stream();
6627        let mut b = __s_b.launch_builder(&f);
6628        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut *y0).arg(&mut *y1).arg(&mut *y2)
6629         .arg(&inf).arg(&oo0).arg(&oo1).arg(&oo2).arg(&r0).arg(&r1).arg(&r2);
6630        unsafe { b.launch(cfg)?; }
6631        Ok(true)
6632    }
6633
6634    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
6635    pub fn matmul_q4_fused2(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6636                            aq: &CudaSlice<i8>, ad: &CudaSlice<f32>)
6637        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6638        use crate::model::GpuTensor;
6639        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
6640            match w {
6641                GpuTensor::Quant { qtype, row_bytes, .. } if *qtype == QT_Q4_0 =>
6642                    Some((*row_bytes, w.out_features())),
6643                _ => None,
6644            }
6645        };
6646        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else { return Ok(None) };
6647        if w0.in_features() != w1.in_features() { return Ok(None); }
6648        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
6649        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
6650            match w {
6651                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
6652                    Some(m) => (m, true),
6653                    None => (bytes, *rp),
6654                },
6655                _ => unreachable!(),
6656            }
6657        }
6658        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
6659        if rp0 != rp1 { return Ok(None); }
6660        let rp = rp0;
6661        let rpb: u32 = 4;
6662        // mr1 twin — see matmul_q4_fused3.
6663        let mr1 = rp && Self::q40_mr1_on();
6664        let nb = |o: usize| if mr1 { (o as u32).div_ceil(rpb) }
6665                            else { (o as u32).div_ceil(2).div_ceil(rpb) };
6666        let grid = nb(o0) + nb(o1);
6667        let mut y0 = self.alloc_uninit::<f32>(o0)?;
6668        let mut y1 = self.alloc_uninit::<f32>(o1)?;
6669        let f = self.func(if mr1 { "qmatvec_q4_0_mmvq_fused2_mr1_rp" }
6670                          else if rp { "qmatvec_q4_0_mmvq_fused2_rp" }
6671                          else { "qmatvec_q4_0_mmvq_fused2" });
6672        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (32, rpb, 1), shared_mem_bytes: 0 };
6673        let inf = w0.in_features() as i32;
6674        let (oo0, oo1) = (o0 as i32, o1 as i32);
6675        let (r0, r1) = (rb0 as i64, rb1 as i64);
6676        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
6677        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
6678            {
6679            use cudarc::driver::{DevicePtr, DevicePtrMut};
6680            let s = &self.gpu.stream();
6681            let (p0, _g0) = b0.device_ptr(s); let (p1, _g1) = b1.device_ptr(s);
6682            let (paq, _g2) = aq.device_ptr(s); let (pad, _g3) = ad.device_ptr(s);
6683            let (py0, _g4) = y0.device_ptr_mut(s); let (py1, _g5) = y1.device_ptr_mut(s);
6684            let mut ps = [
6685                &p0 as *const _ as *mut std::ffi::c_void, &p1 as *const _ as *mut _,
6686                &paq as *const _ as *mut _, &pad as *const _ as *mut _,
6687                &py0 as *const _ as *mut _, &py1 as *const _ as *mut _,
6688                &inf as *const _ as *mut _, &oo0 as *const _ as *mut _,
6689                &oo1 as *const _ as *mut _, &r0 as *const _ as *mut _,
6690                &r1 as *const _ as *mut _,
6691            ];
6692            unsafe { self.launch_pdl("qmatvec_q4_0_mmvq_fused2_mr1_rp",
6693                                     (grid, 1, 1), (32, rpb, 1), &mut ps)?; }
6694            }
6695            return Ok(Some((y0, y1)));
6696        }
6697        let __s_b = self.gpu.stream();
6698        let mut b = __s_b.launch_builder(&f);
6699        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
6700         .arg(&inf).arg(&oo0).arg(&oo1).arg(&r0).arg(&r1);
6701        unsafe { b.launch(cfg)?; }
6702        Ok(Some((y0, y1)))
6703    }
6704
6705    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
6706    pub fn matmul_q4_fused2_into(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6707                                 aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
6708                                 y0: &mut CudaSlice<f32>, y1: &mut CudaSlice<f32>)
6709        -> Result<bool, Box<dyn std::error::Error>> {
6710        use crate::model::GpuTensor;
6711        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
6712            match w {
6713                GpuTensor::Quant { qtype, row_bytes, .. } if *qtype == QT_Q4_0 =>
6714                    Some((*row_bytes, w.out_features())),
6715                _ => None,
6716            }
6717        };
6718        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else { return Ok(false) };
6719        if w0.in_features() != w1.in_features() { return Ok(false); }
6720        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
6721            match w {
6722                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
6723                    Some(m) => (m, true),
6724                    None => (bytes, *rp),
6725                },
6726                _ => unreachable!(),
6727            }
6728        }
6729        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
6730        if rp0 != rp1 { return Ok(false); }
6731        let rp = rp0;
6732        let rpb: u32 = 4;
6733        let mr1 = rp && Self::q40_mr1_on();
6734        let nb = |o: usize| if mr1 { (o as u32).div_ceil(rpb) }
6735                            else { (o as u32).div_ceil(2).div_ceil(rpb) };
6736        let grid = nb(o0) + nb(o1);
6737        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
6738        let f = self.func(if mr1 { "qmatvec_q4_0_mmvq_fused2_mr1_rp" }
6739                          else if rp { "qmatvec_q4_0_mmvq_fused2_rp" }
6740                          else { "qmatvec_q4_0_mmvq_fused2" });
6741        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (32, rpb, 1), shared_mem_bytes: 0 };
6742        let inf = w0.in_features() as i32;
6743        let (oo0, oo1) = (o0 as i32, o1 as i32);
6744        let (r0, r1) = (rb0 as i64, rb1 as i64);
6745        // PDL wave-A: identical to the owned twin (capture-lane parity).
6746        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
6747            use cudarc::driver::{DevicePtr, DevicePtrMut};
6748            let s = &self.gpu.stream();
6749            let (p0, _g0) = b0.device_ptr(s); let (p1, _g1) = b1.device_ptr(s);
6750            let (paq, _g2) = aq.device_ptr(s); let (pad, _g3) = ad.device_ptr(s);
6751            let (py0, _g4) = y0.device_ptr_mut(s); let (py1, _g5) = y1.device_ptr_mut(s);
6752            let mut ps = [
6753                &p0 as *const _ as *mut std::ffi::c_void, &p1 as *const _ as *mut _,
6754                &paq as *const _ as *mut _, &pad as *const _ as *mut _,
6755                &py0 as *const _ as *mut _, &py1 as *const _ as *mut _,
6756                &inf as *const _ as *mut _, &oo0 as *const _ as *mut _,
6757                &oo1 as *const _ as *mut _, &r0 as *const _ as *mut _,
6758                &r1 as *const _ as *mut _,
6759            ];
6760            unsafe { self.launch_pdl("qmatvec_q4_0_mmvq_fused2_mr1_rp",
6761                                     (grid, 1, 1), (32, rpb, 1), &mut ps)?; }
6762            return Ok(true);
6763        }
6764        let __s_b = self.gpu.stream();
6765        let mut b = __s_b.launch_builder(&f);
6766        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut *y0).arg(&mut *y1)
6767         .arg(&inf).arg(&oo0).arg(&oo1).arg(&r0).arg(&r1);
6768        unsafe { b.launch(cfg)?; }
6769        Ok(true)
6770    }
6771
6772    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
6773    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
6774    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
6775    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
6776    pub fn matmul_q4_fused2_batched(&self, w0: &crate::model::GpuTensor,
6777                                    w1: &crate::model::GpuTensor,
6778                                    aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize)
6779        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6780        use crate::model::GpuTensor;
6781        if m < 2 || m > 8 { return Ok(None); }
6782        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
6783            match w {
6784                GpuTensor::Quant { qtype, row_bytes, .. } if *qtype == QT_Q4_0 =>
6785                    Some((*row_bytes, w.out_features())),
6786                _ => None,
6787            }
6788        };
6789        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else { return Ok(None) };
6790        if w0.in_features() != w1.in_features() { return Ok(None); }
6791        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
6792            match w {
6793                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
6794                    Some(mr) => (mr, true),
6795                    None => (bytes, *rp),
6796                },
6797                _ => unreachable!(),
6798            }
6799        }
6800        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
6801        if !rp0 || !rp1 { return Ok(None); }
6802        let mcols = Self::batched_mcols(m);
6803        let rpb: u32 = 4;
6804        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
6805        let grid = nb(o0) + nb(o1);
6806        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
6807        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
6808        let f = self.func(match mcols { 2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
6809                                        4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
6810                                        _ => "qmatvec_q4_0_mmvq_b8_f2_rp" });
6811        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (32, rpb, 1),
6812                                 shared_mem_bytes: 0 };
6813        let inf = w0.in_features() as i32;
6814        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
6815        let rb = rb0 as i64;
6816        let __s_b = self.gpu.stream();
6817        let mut b = __s_b.launch_builder(&f);
6818        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
6819         .arg(&inf).arg(&oo0).arg(&oo1).arg(&mi).arg(&rb);
6820        unsafe { b.launch(cfg)?; }
6821        Ok(Some((y0, y1)))
6822    }
6823
6824    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
6825    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
6826    #[allow(clippy::too_many_arguments)]
6827    pub fn matmul_q4_fused3_batched(&self, w0: &crate::model::GpuTensor,
6828                                    w1: &crate::model::GpuTensor, w2: &crate::model::GpuTensor,
6829                                    aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize)
6830        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6831        use crate::model::GpuTensor;
6832        if m < 2 || m > 8 { return Ok(None); }
6833        let q4 = |w: &GpuTensor| -> Option<usize> {
6834            match w {
6835                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
6836                _ => None,
6837            }
6838        };
6839        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else { return Ok(None) };
6840        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
6841            return Ok(None);
6842        }
6843        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
6844            match w {
6845                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
6846                    Some(mr) => (mr, true),
6847                    None => (bytes, *rp),
6848                },
6849                _ => unreachable!(),
6850            }
6851        }
6852        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
6853        if !rp0 || !rp1 || !rp2 { return Ok(None); }
6854        let mcols = Self::batched_mcols(m);
6855        let rpb: u32 = 4;
6856        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
6857        let grid = nb(o0) + nb(o1) + nb(o2);
6858        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
6859        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
6860        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
6861        let f = self.func(match mcols { 2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
6862                                        4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
6863                                        _ => "qmatvec_q4_0_mmvq_b8_f3_rp" });
6864        let cfg = LaunchConfig { grid_dim: (grid, 1, 1), block_dim: (32, rpb, 1),
6865                                 shared_mem_bytes: 0 };
6866        let inf = w0.in_features() as i32;
6867        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
6868        let rb = 0i64;
6869        let __s_b = self.gpu.stream();
6870        let mut b = __s_b.launch_builder(&f);
6871        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1).arg(&mut y2)
6872         .arg(&inf).arg(&oo0).arg(&oo1).arg(&oo2).arg(&mi).arg(&rb);
6873        unsafe { b.launch(cfg)?; }
6874        Ok(Some((y0, y1, y2)))
6875    }
6876
6877    pub fn matmul_q8_fused3(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6878                            w2: &crate::model::GpuTensor,
6879                            aq: &CudaSlice<i8>, ad: &CudaSlice<f32>)
6880        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6881        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
6882        // are per-tensor FP8, so native residency without this arm meant three separate launches.
6883        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
6884            return Ok(Some(self.e4m3_fused3_core(p0.0, p1.0, p2.0, aq, ad, w0.in_features(),
6885                                                 p0.1, p1.1, p2.1, p0.2,
6886                                                 p0.3, p1.3, p2.3)?));
6887        }
6888        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else { return Ok(None) };
6889        Ok(Some(self.q8_fused3_core(p0.0, p1.0, p2.0, aq, ad, w0.in_features(),
6890                                    p0.1, p1.1, p2.1, p0.2)?))
6891    }
6892
6893    #[allow(clippy::too_many_arguments)]
6894    fn q8_fused3_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
6895                      aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
6896                      in_f: usize, out0: usize, out1: usize, out2: usize, row_bytes: usize)
6897        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6898        const ROWS_PER_BLOCK: u32 = 4;
6899        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
6900        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
6901        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
6902        let f = self.func("qmatvec_q8_0_mmvq_fused3");
6903        let mut y0 = self.alloc_uninit::<f32>(out0)?;
6904        let mut y1 = self.alloc_uninit::<f32>(out1)?;
6905        let mut y2 = self.alloc_uninit::<f32>(out2)?;
6906        let cfg = LaunchConfig { grid_dim: (nb0 + nb1 + nb2, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
6907                                 shared_mem_bytes: 0 };
6908        let (inf, o0, o1, o2, rbl) = (in_f as i32, out0 as i32, out1 as i32, out2 as i32, row_bytes as i64);
6909        let __s_b = self.gpu.stream();
6910        let mut b = __s_b.launch_builder(&f);
6911        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1).arg(&mut y2)
6912         .arg(&inf).arg(&o0).arg(&o1).arg(&o2).arg(&rbl);
6913        unsafe { b.launch(cfg)?; }
6914        Ok((y0, y1, y2))
6915    }
6916
6917    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
6918    #[allow(clippy::too_many_arguments)]
6919    pub fn qmatvec_q8_fused3_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
6920                                 x: &CudaSlice<f32>, in_f: usize, out0: usize, out1: usize,
6921                                 out2: usize, row_bytes: usize)
6922        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6923        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
6924        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
6925    }
6926
6927    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
6928    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
6929    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
6930    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
6931    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
6932    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
6933    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
6934    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
6935    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
6936    /// twin must not introduce a batched program the reference path would not run).
6937    pub fn matmul_q8_fused2_t(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6938                              aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize)
6939        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
6940        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
6941        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
6942        // fuses too — same template body, still bit-identical to the two _b8 launches.
6943        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() { return Ok(None); }
6944        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
6945        // so the fused b8 launch would introduce a batched program the reference path would not run.
6946        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
6947            if m > 4 && !Self::b8_enabled() { return Ok(None); }
6948            return Ok(Some(self.e4m3_fused2_t_core(p0.0, p1.0, aq, ad, m, w0.in_features(),
6949                                                   p0.1, p1.1, p0.2, p0.3, p1.3)?));
6950        }
6951        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else { return Ok(None) };
6952        Ok(Some(self.q8_fused2_t_core(p0.0, p1.0, aq, ad, m, w0.in_features(), p0.1, p1.1, p0.2)?))
6953    }
6954
6955    #[allow(clippy::too_many_arguments)]
6956    fn q8_fused2_t_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
6957                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize,
6958                        in_f: usize, out0: usize, out1: usize, row_bytes: usize)
6959        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6960        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
6961        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
6962        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
6963        let f = self.func(match Self::batched_mcols(m) {
6964            2 => "qmatvec_q8_0_mmvq_fused2_b2",
6965            4 => "qmatvec_q8_0_mmvq_fused2_b4",
6966            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
6967            _ => "qmatvec_q8_0_mmvq_fused2_b8",
6968        });
6969        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
6970        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
6971        let cfg = LaunchConfig { grid_dim: (nb0 + nb1, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
6972                                 shared_mem_bytes: 0 };
6973        let (inf, o0, o1, mi, rbl) = (in_f as i32, out0 as i32, out1 as i32, m as i32, row_bytes as i64);
6974        let __s_b = self.gpu.stream();
6975        let mut b = __s_b.launch_builder(&f);
6976        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
6977         .arg(&inf).arg(&o0).arg(&o1).arg(&mi).arg(&rbl);
6978        unsafe { b.launch(cfg)?; }
6979        Ok((y0, y1))
6980    }
6981
6982    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
6983    /// q8_1 quant of the [m, in_f] activation), no env gating.
6984    #[allow(clippy::too_many_arguments)]
6985    pub fn qmatvec_q8_fused2_t_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
6986                                   x: &CudaSlice<f32>, m: usize,
6987                                   in_f: usize, out0: usize, out1: usize, row_bytes: usize)
6988        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6989        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6990        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
6991    }
6992
6993    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
6994    /// `matmul_q8_fused2_t` with three ranges.
6995    #[allow(clippy::too_many_arguments)]
6996    pub fn matmul_q8_fused3_t(&self, w0: &crate::model::GpuTensor, w1: &crate::model::GpuTensor,
6997                              w2: &crate::model::GpuTensor,
6998                              aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize)
6999        -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
7000        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() { return Ok(None); }
7001        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
7002            return Ok(Some(self.e4m3_fused3_t_core(p0.0, p1.0, p2.0, aq, ad, m, w0.in_features(),
7003                                                   p0.1, p1.1, p2.1, p0.2,
7004                                                   p0.3, p1.3, p2.3)?));
7005        }
7006        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else { return Ok(None) };
7007        Ok(Some(self.q8_fused3_t_core(p0.0, p1.0, p2.0, aq, ad, m, w0.in_features(),
7008                                      p0.1, p1.1, p2.1, p0.2)?))
7009    }
7010
7011    #[allow(clippy::too_many_arguments)]
7012    fn q8_fused3_t_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
7013                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize,
7014                        in_f: usize, out0: usize, out1: usize, out2: usize, row_bytes: usize)
7015        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7016        const ROWS_PER_BLOCK: u32 = 4;
7017        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
7018        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
7019        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
7020        let f = self.func(if Self::batched_mcols(m) == 2 { "qmatvec_q8_0_mmvq_fused3_b2" }
7021                          else { "qmatvec_q8_0_mmvq_fused3_b4" });
7022        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
7023        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
7024        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
7025        let cfg = LaunchConfig { grid_dim: (nb0 + nb1 + nb2, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
7026                                 shared_mem_bytes: 0 };
7027        let (inf, o0, o1, o2, mi, rbl) = (in_f as i32, out0 as i32, out1 as i32, out2 as i32,
7028                                          m as i32, row_bytes as i64);
7029        let __s_b = self.gpu.stream();
7030        let mut b = __s_b.launch_builder(&f);
7031        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1).arg(&mut y2)
7032         .arg(&inf).arg(&o0).arg(&o1).arg(&o2).arg(&mi).arg(&rbl);
7033        unsafe { b.launch(cfg)?; }
7034        Ok((y0, y1, y2))
7035    }
7036
7037    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
7038    #[allow(clippy::too_many_arguments)]
7039    pub fn qmatvec_q8_fused3_t_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
7040                                   x: &CudaSlice<f32>, m: usize, in_f: usize, out0: usize,
7041                                   out1: usize, out2: usize, row_bytes: usize)
7042        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7043        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7044        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
7045    }
7046
7047    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
7048    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
7049    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
7050    pub fn q8_ffn_fuse2_on(&self) -> bool {
7051        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7052        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
7053    }
7054
7055    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
7056    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
7057    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
7058    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
7059    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
7060    #[allow(clippy::type_complexity)]
7061    fn q8_fused_params<'w, const N: usize>(&self, ws: &[&'w crate::model::GpuTensor; N])
7062        -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
7063        use crate::model::GpuTensor;
7064        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") { return None; }
7065        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") { return None; }
7066        let in_f = ws[0].in_features();
7067        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
7068        for (i, w) in ws.iter().enumerate() {
7069            match w {
7070                GpuTensor::Quant { bytes, qtype, row_bytes, scale, .. }
7071                    if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f =>
7072                        out[i] = Some((bytes, w.out_features(), *row_bytes)),
7073                _ => return None,
7074            }
7075        }
7076        Some(out.map(|o| o.unwrap()))
7077    }
7078
7079    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
7080    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
7081    pub fn e4m3_dual_on(&self) -> bool {
7082        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7083        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
7084    }
7085
7086    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
7087    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
7088    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
7089    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
7090    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
7091    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
7092    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
7093    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
7094    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
7095    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
7096    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
7097    #[allow(clippy::type_complexity)]
7098    fn e4m3_fused_params<'w, const N: usize>(&self, ws: &[&'w crate::model::GpuTensor; N])
7099        -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
7100        use crate::model::GpuTensor;
7101        if !self.e4m3_dual_on() { return None; }
7102        let in_f = ws[0].in_features();
7103        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
7104        for (i, w) in ws.iter().enumerate() {
7105            match w {
7106                GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, rp4, .. }
7107                    if *qtype == QT_F8_E4M3 && w.in_features() == in_f
7108                        && *row_bytes == in_f && !*rp && rp4.is_none() =>
7109                        out[i] = Some((bytes, w.out_features(), *row_bytes, *scale)),
7110                _ => return None,
7111            }
7112        }
7113        Some(out.map(|o| o.unwrap()))
7114    }
7115
7116    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
7117    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
7118    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
7119    #[allow(clippy::too_many_arguments)]
7120    fn e4m3_fused2_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
7121                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
7122                        in_f: usize, out0: usize, out1: usize, row_bytes: usize,
7123                        ws0: f32, ws1: f32)
7124        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7125        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
7126        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
7127        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
7128        let f = self.func("qmatvec_e4m3_mmvq_fused2");
7129        let mut y0 = self.alloc_uninit::<f32>(out0)?;
7130        let mut y1 = self.alloc_uninit::<f32>(out1)?;
7131        let cfg = LaunchConfig { grid_dim: (nb0 + nb1, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
7132                                 shared_mem_bytes: 0 };
7133        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
7134        let __s_b = self.gpu.stream();
7135        let mut b = __s_b.launch_builder(&f);
7136        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
7137         .arg(&inf).arg(&o0).arg(&o1).arg(&rbl).arg(&ws0).arg(&ws1);
7138        unsafe { b.launch(cfg)?; }
7139        Ok((y0, y1))
7140    }
7141
7142    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
7143    #[allow(clippy::too_many_arguments)]
7144    fn e4m3_fused3_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
7145                        aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
7146                        in_f: usize, out0: usize, out1: usize, out2: usize, row_bytes: usize,
7147                        ws0: f32, ws1: f32, ws2: f32)
7148        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7149        const ROWS_PER_BLOCK: u32 = 4;
7150        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
7151        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
7152        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
7153        let f = self.func("qmatvec_e4m3_mmvq_fused3");
7154        let mut y0 = self.alloc_uninit::<f32>(out0)?;
7155        let mut y1 = self.alloc_uninit::<f32>(out1)?;
7156        let mut y2 = self.alloc_uninit::<f32>(out2)?;
7157        let cfg = LaunchConfig { grid_dim: (nb0 + nb1 + nb2, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
7158                                 shared_mem_bytes: 0 };
7159        let (inf, o0, o1, o2, rbl) = (in_f as i32, out0 as i32, out1 as i32, out2 as i32,
7160                                      row_bytes as i64);
7161        let __s_b = self.gpu.stream();
7162        let mut b = __s_b.launch_builder(&f);
7163        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1).arg(&mut y2)
7164         .arg(&inf).arg(&o0).arg(&o1).arg(&o2).arg(&rbl).arg(&ws0).arg(&ws1).arg(&ws2);
7165        unsafe { b.launch(cfg)?; }
7166        Ok((y0, y1, y2))
7167    }
7168
7169    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
7170    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
7171    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
7172    #[allow(clippy::too_many_arguments)]
7173    fn e4m3_fused2_t_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
7174                          aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize,
7175                          in_f: usize, out0: usize, out1: usize, row_bytes: usize,
7176                          ws0: f32, ws1: f32)
7177        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7178        const ROWS_PER_BLOCK: u32 = 4;
7179        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
7180        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
7181        let f = self.func(match Self::batched_mcols(m) {
7182            2 => "qmatvec_e4m3_mmvq_fused2_b2",
7183            4 => "qmatvec_e4m3_mmvq_fused2_b4",
7184            _ => "qmatvec_e4m3_mmvq_fused2_b8",
7185        });
7186        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
7187        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
7188        let cfg = LaunchConfig { grid_dim: (nb0 + nb1, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
7189                                 shared_mem_bytes: 0 };
7190        let (inf, o0, o1, mi, rbl) = (in_f as i32, out0 as i32, out1 as i32, m as i32,
7191                                      row_bytes as i64);
7192        let __s_b = self.gpu.stream();
7193        let mut b = __s_b.launch_builder(&f);
7194        b.arg(b0).arg(b1).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1)
7195         .arg(&inf).arg(&o0).arg(&o1).arg(&mi).arg(&rbl);
7196        unsafe { b.launch(cfg)?; }
7197        if ws0 != 1.0 { self.scale_inplace(&mut y0, ws0, m * out0)?; }
7198        if ws1 != 1.0 { self.scale_inplace(&mut y1, ws1, m * out1)?; }
7199        Ok((y0, y1))
7200    }
7201
7202    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
7203    #[allow(clippy::too_many_arguments)]
7204    fn e4m3_fused3_t_core(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
7205                          aq: &CudaSlice<i8>, ad: &CudaSlice<f32>, m: usize,
7206                          in_f: usize, out0: usize, out1: usize, out2: usize, row_bytes: usize,
7207                          ws0: f32, ws1: f32, ws2: f32)
7208        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7209        const ROWS_PER_BLOCK: u32 = 4;
7210        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
7211        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
7212        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
7213        let f = self.func(if Self::batched_mcols(m) == 2 { "qmatvec_e4m3_mmvq_fused3_b2" }
7214                          else { "qmatvec_e4m3_mmvq_fused3_b4" });
7215        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
7216        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
7217        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
7218        let cfg = LaunchConfig { grid_dim: (nb0 + nb1 + nb2, 1, 1), block_dim: (32, ROWS_PER_BLOCK, 1),
7219                                 shared_mem_bytes: 0 };
7220        let (inf, o0, o1, o2, mi, rbl) = (in_f as i32, out0 as i32, out1 as i32, out2 as i32,
7221                                          m as i32, row_bytes as i64);
7222        let __s_b = self.gpu.stream();
7223        let mut b = __s_b.launch_builder(&f);
7224        b.arg(b0).arg(b1).arg(b2).arg(aq).arg(ad).arg(&mut y0).arg(&mut y1).arg(&mut y2)
7225         .arg(&inf).arg(&o0).arg(&o1).arg(&o2).arg(&mi).arg(&rbl);
7226        unsafe { b.launch(cfg)?; }
7227        if ws0 != 1.0 { self.scale_inplace(&mut y0, ws0, m * out0)?; }
7228        if ws1 != 1.0 { self.scale_inplace(&mut y1, ws1, m * out1)?; }
7229        if ws2 != 1.0 { self.scale_inplace(&mut y2, ws2, m * out2)?; }
7230        Ok((y0, y1, y2))
7231    }
7232
7233    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
7234    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
7235    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
7236    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
7237    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
7238    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
7239    ///
7240    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
7241    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
7242    pub fn qmatvec_e4m3_blk_mmvq(&self, bytes: &CudaSlice<u8>, aq: &CudaSlice<i8>,
7243                                 ad: &CudaSlice<f32>, scales: &CudaSlice<f32>,
7244                                 m: usize, in_f: usize, out_f: usize, row_bytes: usize,
7245                                 scale_cols: usize)
7246        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7247        let mut y = self.alloc_uninit::<f32>(m * out_f)?;   // full-overwrite output: skip memset
7248        self.qmatvec_e4m3_blk_mmvq_into(bytes, aq, ad, scales, m, in_f, out_f, row_bytes,
7249                                        scale_cols, &mut y)?;
7250        Ok(y)
7251    }
7252
7253    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
7254    #[allow(clippy::too_many_arguments)]
7255    pub fn qmatvec_e4m3_blk_mmvq_into(&self, bytes: &CudaSlice<u8>, aq: &CudaSlice<i8>,
7256                                      ad: &CudaSlice<f32>, scales: &CudaSlice<f32>,
7257                                      m: usize, in_f: usize, out_f: usize, row_bytes: usize,
7258                                      scale_cols: usize, y: &mut CudaSlice<f32>)
7259        -> Result<(), Box<dyn std::error::Error>> {
7260        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
7261        let f = self.func("qmatvec_e4m3_blk_mmvq");
7262        let cfg = LaunchConfig {
7263            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
7264            block_dim: (32, ROWS_PER_BLOCK, 1),   // warp-per-row
7265            shared_mem_bytes: 0,                  // warp-only reduce
7266        };
7267        let (inf, outf, mi, rb, sc) =
7268            (in_f as i32, out_f as i32, m as i32, row_bytes as i64, scale_cols as i32);
7269        let __s_b = self.gpu.stream();
7270        let mut b = __s_b.launch_builder(&f);
7271        b.arg(bytes).arg(aq).arg(ad).arg(scales).arg(&mut *y)
7272         .arg(&inf).arg(&outf).arg(&mi).arg(&rb).arg(&sc);
7273        unsafe { b.launch(cfg)?; }
7274        Ok(())
7275    }
7276
7277    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
7278    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
7279    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
7280    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
7281    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
7282    #[allow(clippy::too_many_arguments)]
7283    pub fn qmatvec_e4m3_blk_mmvq_batched(&self, bytes: &CudaSlice<u8>, aq: &CudaSlice<i8>,
7284                                         ad: &CudaSlice<f32>, scales: &CudaSlice<f32>,
7285                                         m: usize, in_f: usize, out_f: usize, row_bytes: usize,
7286                                         scale_cols: usize, mcols: usize)
7287        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7288        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
7289        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
7290        let name = match mcols {
7291            2 => "qmatvec_e4m3_blk_mmvq_b2",
7292            4 => "qmatvec_e4m3_blk_mmvq_b4",
7293            8 => "qmatvec_e4m3_blk_mmvq_b8",
7294            16 => "qmatvec_e4m3_blk_mmvq_b16",
7295            _ => return Err(format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into()),
7296        };
7297        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
7298        let f = self.func(name);
7299        let cfg = LaunchConfig {
7300            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
7301            block_dim: (32, ROWS_PER_BLOCK, 1),
7302            shared_mem_bytes: 0,
7303        };
7304        let (inf, outf, mi, rb, sc) =
7305            (in_f as i32, out_f as i32, m as i32, row_bytes as i64, scale_cols as i32);
7306        let __s_b = self.gpu.stream();
7307        let mut b = __s_b.launch_builder(&f);
7308        b.arg(bytes).arg(aq).arg(ad).arg(scales).arg(&mut y)
7309         .arg(&inf).arg(&outf).arg(&mi).arg(&rb).arg(&sc);
7310        unsafe { b.launch(cfg)?; }
7311        Ok(y)
7312    }
7313
7314    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
7315    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
7316    #[allow(clippy::too_many_arguments)]
7317    pub fn qmatvec_e4m3_blk_batched_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>,
7318                                        scales: &CudaSlice<f32>, m: usize, in_f: usize,
7319                                        out_f: usize, row_bytes: usize, scale_cols: usize,
7320                                        mcols: usize)
7321        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7322        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7323        self.qmatvec_e4m3_blk_mmvq_batched(bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes,
7324                                           scale_cols, mcols)
7325    }
7326
7327    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
7328    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
7329    #[allow(clippy::too_many_arguments)]
7330    pub fn qmatvec_e4m3_blk_mmvq_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>,
7331                                     scales: &CudaSlice<f32>, m: usize, in_f: usize, out_f: usize,
7332                                     row_bytes: usize, scale_cols: usize)
7333        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7334        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7335        self.qmatvec_e4m3_blk_mmvq(bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols)
7336    }
7337
7338    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
7339    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
7340    #[allow(clippy::too_many_arguments)]
7341    pub fn qmatvec_e4m3_fused2_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, x: &CudaSlice<f32>,
7342                                   in_f: usize, out0: usize, out1: usize, row_bytes: usize,
7343                                   ws0: f32, ws1: f32)
7344        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7345        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
7346        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
7347    }
7348
7349    #[allow(clippy::too_many_arguments)]
7350    pub fn qmatvec_e4m3_fused3_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>, b2: &CudaSlice<u8>,
7351                                   x: &CudaSlice<f32>, in_f: usize, out0: usize, out1: usize,
7352                                   out2: usize, row_bytes: usize, ws0: f32, ws1: f32, ws2: f32)
7353        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7354        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
7355        self.e4m3_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes,
7356                              ws0, ws1, ws2)
7357    }
7358
7359    #[allow(clippy::too_many_arguments)]
7360    pub fn qmatvec_e4m3_fused2_t_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
7361                                     x: &CudaSlice<f32>, m: usize, in_f: usize, out0: usize,
7362                                     out1: usize, row_bytes: usize, ws0: f32, ws1: f32)
7363        -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7364        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7365        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
7366    }
7367
7368    #[allow(clippy::too_many_arguments)]
7369    pub fn qmatvec_e4m3_fused3_t_raw(&self, b0: &CudaSlice<u8>, b1: &CudaSlice<u8>,
7370                                     b2: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize,
7371                                     in_f: usize, out0: usize, out1: usize, out2: usize,
7372                                     row_bytes: usize, ws0: f32, ws1: f32, ws2: f32)
7373        -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7374        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7375        self.e4m3_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes,
7376                                ws0, ws1, ws2)
7377    }
7378
7379    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
7380    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
7381    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
7382    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
7383    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
7384    ///
7385    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
7386    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
7387    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
7388    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
7389    fn try_e4m3_blk_pre(&self, w: &crate::model::GpuTensor, aq: &CudaSlice<i8>,
7390                        ad: &CudaSlice<f32>, m: usize)
7391        -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
7392        use crate::model::GpuTensor;
7393        if let GpuTensor::Quant { bytes, qtype, row_bytes, blk: Some(g), .. } = w {
7394            if *qtype == QT_F8_E4M3_BLK {
7395                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
7396                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
7397                // below, so the decode-exactness contract is preserved at every width. Gated by
7398                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
7399                // one rollback door covers every dtype's batched tier.
7400                if (2..=16).contains(&m) && std::env::var("MEMRA_NO_BATCHED").is_err()
7401                    && (m <= 4 || Self::b8_enabled()) {
7402                    let mcols = Self::batched_mcols(m);
7403                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
7404                        bytes, aq, ad, &g.scales, m, w.in_features(), w.out_features(),
7405                        *row_bytes, g.cols, mcols)?));
7406                }
7407                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
7408                    bytes, aq, ad, &g.scales, m, w.in_features(), w.out_features(),
7409                    *row_bytes, g.cols)?));
7410            }
7411        }
7412        Ok(None)
7413    }
7414
7415    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
7416    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
7417    ///
7418    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
7419    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
7420    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
7421    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
7422    /// prefill keeps the floor's arithmetic and the floor's kernels.
7423    ///
7424    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
7425    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
7426    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
7427    /// (projection, prefill call) and frees immediately.
7428    ///
7429    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
7430    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
7431    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
7432    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
7433    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
7434    /// single-variable comparison instead of a two-variable one.
7435    ///
7436    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
7437    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
7438    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
7439    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
7440    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
7441    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
7442    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
7443    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
7444    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
7445    ///
7446    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
7447    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
7448    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
7449    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
7450    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
7451    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
7452    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
7453    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
7454    /// because v2's denominator had its slab already resident while this class's floor must build it
7455    /// every call; same tile, opposite sign, because the question changed.
7456    ///
7457    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
7458    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
7459    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
7460    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
7461    fn try_e4m3_blk_prefill(&self, w: &crate::model::GpuTensor, x: &CudaSlice<f32>, m: usize)
7462        -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
7463        use crate::model::GpuTensor;
7464        let GpuTensor::Quant { bytes, qtype, blk: Some(g), .. } = w else { return Ok(None) };
7465        if *qtype != QT_F8_E4M3_BLK { return Ok(None) }
7466        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
7467        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
7468        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
7469        // through to the dequant below when they do, never silently produce nothing.
7470        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? { return Ok(Some(y)); }
7471        let (in_f, out_f) = (w.in_features(), w.out_features());
7472        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
7473        let tmp = GpuTensor::Quant {
7474            bytes: slab,
7475            qtype: QT_Q8_0,
7476            row_bytes: in_f / 32 * 34,
7477            ne: vec![in_f as u64, out_f as u64],
7478            scale: 1.0,
7479            rp: false,
7480            #[cfg(memra_cutlass)]
7481            cutlass: None,
7482            fp8: None, blk: None, f16: None, rp4: None,
7483        };
7484        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
7485        Ok(Some(self.matmul(&tmp, x, m)?))
7486    }
7487
7488    pub fn matmul_pre_noscale(&self, w: &crate::model::GpuTensor, aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
7489                              m: usize) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
7490        use crate::model::GpuTensor;
7491        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
7492        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
7493        // rather than let the tail below refuse and cost the caller a re-dispatch.
7494        if m == 1 {
7495            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? { return Ok(Some((y, 1.0))); }
7496        }
7497        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
7498        if m != 1 || !self.uses_q8_1_fast(w) { return Ok(None); }
7499        let in_f = w.in_features();
7500        let out_f = w.out_features();
7501        let (bytes, qtype, row_bytes, scale, rp) = match w {
7502            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } => (bytes, *qtype, *row_bytes, *scale, *rp),
7503            _ => return Ok(None),
7504        };
7505        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
7506        if self.mmvq_supports(qtype) {
7507            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
7508            let (mbytes, mrp) = match w {
7509                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
7510                _ => (bytes, rp),
7511            };
7512            let y = self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp)?;
7513            return Ok(Some((y, scale)));
7514        }
7515        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
7516        let name = match qtype {
7517            QT_Q8_0 => "qmatvec_q8_0_dp4a", QT_Q4_K => "qmatvec_q4_K_dp4a",
7518            QT_Q6_K => "qmatvec_q6_K_dp4a", QT_Q5_K => "qmatvec_q5_K_dp4a",
7519            QT_Q3_K => "qmatvec_q3_K_dp4a",
7520            QT_NVFP4 => if rp { "qmatvec_nvfp4_dp4a_rp" } else { "qmatvec_nvfp4_dp4a" },
7521            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
7522            _ => return Ok(None),
7523        };
7524        let f = self.func(name);
7525        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
7526        let cfg = LaunchConfig { grid_dim: (out_f as u32, m as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
7527        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7528        let __s_b = self.gpu.stream();
7529        let mut b = __s_b.launch_builder(&f);
7530        b.arg(bytes).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
7531        unsafe { b.launch(cfg)?; }
7532        Ok(Some((y, scale)))
7533    }
7534
7535    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
7536    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
7537    pub fn mmvq_supports(&self, qtype: i32) -> bool {
7538        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
7539        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
7540        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
7541        // is a pure function of the dtype — the decode-parity law holds under every env.
7542        if qtype == QT_F8_E4M3 { return true; }
7543        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") { return false; }
7544        matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0)
7545    }
7546
7547    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
7548    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
7549    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
7550    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
7551    pub fn qmatvec_mmvq(&self, bytes: &CudaSlice<u8>, aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
7552                        m: usize, in_f: usize, out_f: usize, qtype: i32, row_bytes: usize, scale: f32,
7553                        rp: bool)
7554                        -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7555        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite GEMM output: skip memset
7556        self.qmatvec_mmvq_into(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y)?;
7557        Ok(y)
7558    }
7559
7560    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
7561    #[allow(clippy::too_many_arguments)]
7562    pub fn qmatvec_mmvq_into(&self, bytes: &CudaSlice<u8>, aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
7563                        m: usize, in_f: usize, out_f: usize, qtype: i32, row_bytes: usize, scale: f32,
7564                        rp: bool, y: &mut CudaSlice<f32>)
7565                        -> Result<(), Box<dyn std::error::Error>> {
7566        debug_assert!(y.len() >= m * out_f);
7567        const ROWS_PER_BLOCK: u32 = 4;   // matches MEMRA_MMVQ_ROWS in qmatvec.cu
7568        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
7569        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
7570        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
7571        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
7572        if qtype == QT_Q8_0 && rp && m == 1 && out_f >= 64
7573            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
7574            && {
7575                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7576                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
7577            }
7578        {
7579            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
7580            let cfg = LaunchConfig {
7581                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
7582                block_dim: (32, 2, 1),
7583                shared_mem_bytes: 0,
7584            };
7585            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
7586            let __s_b = self.gpu.stream();
7587            let mut b = __s_b.launch_builder(&f);
7588            b.arg(bytes).arg(aq).arg(ad).arg(&mut *y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
7589            unsafe { b.launch(cfg)?; }
7590            if scale != 1.0 { self.scale_inplace(y, scale, out_f)?; }
7591            return Ok(());
7592        }
7593        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
7594        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
7595        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
7596        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
7597        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
7598        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
7599        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
7600        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
7601        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) { 2 } else { 1 };
7602        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
7603        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
7604        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
7605        // valid-window interleaved, bit-identical per row — same dot program).
7606        if m == 1 && qtype == QT_Q4_0 {
7607            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
7608            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
7609            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
7610            mr = *Q40MR.get_or_init(|| std::env::var("MEMRA_Q40_MR").ok()
7611                .and_then(|v| v.parse().ok()).unwrap_or(1));
7612        }
7613        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
7614        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
7615        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
7616        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
7617        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
7618        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
7619        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
7620        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
7621        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
7622        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
7623        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
7624        let q5_force = q5_mode.as_deref() == Some("2");
7625        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
7626        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
7627        let q5_il = qtype == QT_Q5_K && m == 1
7628            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
7629        if q5_il && !q5_force && out_f > 65536 { mr = 1; }
7630        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
7631        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
7632        if qtype == QT_Q4_0 && rp && mr != 1 { mr = 2; }
7633        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
7634        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
7635        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
7636        if qtype == QT_Q8_0 && rp {
7637            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
7638            mr = *Q80MR.get_or_init(|| std::env::var("MEMRA_Q80_MR").ok()
7639                .and_then(|v| v.parse().ok()).unwrap_or(1));
7640        }
7641        let name = match (qtype, mr, rp) {
7642            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
7643            (QT_NVFP4, 2, true)  => "qmatvec_nvfp4_mmvq_mr2_rp",
7644            (QT_NVFP4, _, true)  => "qmatvec_nvfp4_mmvq_rp",
7645            (QT_Q4_0, 1, true)   => "qmatvec_q4_0_mmvq_rp",
7646            (QT_Q4_0, _, true)   => "qmatvec_q4_0_mmvq_mr2_rp",
7647            (QT_Q5_K, 2, _) => if q5_il { "qmatvec_q5_K_mmvq_mr2_il" } else { "qmatvec_q5_K_mmvq_mr2" },
7648            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
7649            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
7650            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
7651            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
7652            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
7653            (QT_Q8_0, _, true) if in_f % 1024 == 0 && {
7654                static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7655                *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
7656            } => "qmatvec_q8_0_mmvq_rpca",
7657            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
7658            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
7659            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
7660            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
7661            // reach a GGUF-layout kernel or vice versa.
7662            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
7663            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
7664            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
7665            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
7666            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
7667            (QT_Q5_K, _, _) => if q5_il { "qmatvec_q5_K_mmvq_il" } else { "qmatvec_q5_K_mmvq" },
7668            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
7669            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
7670            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
7671            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
7672        };
7673        let f = self.func(name);
7674        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
7675        let rows_per_block = ROWS_PER_BLOCK * mr;
7676        let cfg = LaunchConfig {
7677            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, m as u32, 1),
7678            block_dim: (32, ROWS_PER_BLOCK, 1),   // warp-per-row (x mr rows each)
7679            shared_mem_bytes: 0,                  // warp-only reduce at m=1
7680        };
7681        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7682        let __s_b = self.gpu.stream();
7683        let mut b = __s_b.launch_builder(&f);
7684        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
7685        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
7686        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
7687        // weight_scale). Other mmvq kernels keep the 8-arg signature.
7688        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
7689            b.arg(bytes).arg(aq).arg(ad).arg(&mut *y).arg(&inf).arg(&outf).arg(&mi).arg(&rb).arg(&scale);
7690            unsafe { b.launch(cfg)?; }
7691        } else if Self::pdl_on() && Self::pdl_mmvq_on()
7692            && matches!(name, "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq"
7693                              | "qmatvec_q6_K_mmvq_rp") {
7694            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
7695            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
7696            // names may take this launch (unmarked kernels would read unordered).
7697            {
7698            use cudarc::driver::{DevicePtr, DevicePtrMut};
7699            let s = &self.gpu.stream();
7700            let (pw, _g0) = bytes.device_ptr(s); let (paq, _g1) = aq.device_ptr(s);
7701            let (pad, _g2) = ad.device_ptr(s); let (py, _g3) = y.device_ptr_mut(s);
7702            let mut ps = [
7703                &pw as *const _ as *mut std::ffi::c_void, &paq as *const _ as *mut _,
7704                &pad as *const _ as *mut _, &py as *const _ as *mut _,
7705                &inf as *const _ as *mut _, &outf as *const _ as *mut _,
7706                &mi as *const _ as *mut _, &rb as *const _ as *mut _,
7707            ];
7708            unsafe { self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?; }
7709            }
7710            if scale != 1.0 { self.scale_inplace(y, scale, m * out_f)?; }
7711        } else {
7712            b.arg(bytes).arg(aq).arg(ad).arg(&mut *y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
7713            unsafe { b.launch(cfg)?; }
7714            if scale != 1.0 { self.scale_inplace(y, scale, m * out_f)?; }
7715        }
7716        Ok(())
7717    }
7718
7719    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
7720    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
7721    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
7722    pub fn qmatvec_mmvq_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
7723                            out_f: usize, qtype: i32, row_bytes: usize, rp: bool)
7724                            -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7725        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7726        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
7727    }
7728
7729    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
7730    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
7731    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
7732    pub fn batched_supports(&self, qtype: i32) -> bool {
7733        matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0)
7734    }
7735
7736    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
7737    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
7738    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
7739    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
7740    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
7741    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
7742    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
7743    pub fn iq_fast_enabled() -> bool {
7744        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7745        *ON.get_or_init(|| std::env::var("MEMRA_IQ_FAST").map(|v| v != "0").unwrap_or(true))
7746    }
7747
7748    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
7749    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
7750    pub fn b8_enabled() -> bool {
7751        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7752        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
7753    }
7754
7755    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
7756    pub fn batched_mcols(m: usize) -> usize {
7757        if m == 2 { 2 } else if m <= 4 { 4 } else if m <= 8 { 8 } else { 16 }
7758    }
7759
7760    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
7761    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
7762    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
7763    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
7764    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
7765        Some(match (qtype, mcols) {
7766            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2", (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
7767            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
7768            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
7769            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
7770            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
7771            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
7772            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
7773            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
7774            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2", (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
7775            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
7776            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
7777            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
7778            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
7779            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2", (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
7780            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
7781            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
7782            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
7783            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
7784            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2", (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
7785            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8", (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
7786            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2", (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
7787            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
7788            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
7789            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
7790            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
7791            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
7792            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2", (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
7793            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
7794            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
7795            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
7796            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
7797            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
7798            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2", (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
7799            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8", (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
7800            _ => return None,
7801        })
7802    }
7803
7804    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
7805    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
7806    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
7807    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
7808    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
7809    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
7810    ///
7811    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
7812    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
7813    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
7814    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
7815    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
7816    /// msweep on all six 27B shapes (2026-07-03):
7817    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
7818    ///          it applies for b4 (-3..-14%), never loses;
7819    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
7820    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
7821    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
7822    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
7823    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
7824    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
7825    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
7826    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
7827    /// b2: in_f>=6144 -> r2, else base.
7828    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
7829    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
7830    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
7831    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
7832    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
7833    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
7834    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
7835    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
7836    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
7837    /// Device SM count (cached) — grid-fill policy input.
7838    pub fn sm_count(&self) -> i32 {
7839        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
7840        *SMS.get_or_init(|| {
7841            use cudarc::driver::sys::CUdevice_attribute_enum as A;
7842            self.gpu.ctx.attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT).unwrap_or(82)
7843        })
7844    }
7845
7846    pub fn batched_variant(&self, _m: usize, in_f: usize, out_f: usize, qtype: i32,
7847                           row_bytes: usize, mcols: usize, rp: bool) -> &'static str {
7848        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
7849        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
7850        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
7851        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
7852        if qtype == QT_Q8_0 {
7853            return if rp { "rp" } else { "base" };
7854        }
7855        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
7856        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
7857            Ok("base") => "base", Ok("pf") => "pf", Ok("r2") => "r2", Ok("r2w8") => "r2w8",
7858            Ok("pfr2") => "pfr2", Ok("ca") => "ca", Ok("car2") => "car2",
7859            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
7860            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
7861            Ok("rp") => "rp", Ok("rpr2") => "rpr2", Ok("rpr2w8") => "rpr2w8",
7862            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
7863            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
7864            Ok("rpca") => "rpca", Ok("rpcar2") => "rpcar2",
7865            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
7866            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
7867            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
7868            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
7869            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
7870            // bit-identical to the decode path — measurement corpus ONLY, never auto).
7871            Ok("rpsc") => "rpsc", Ok("rpms") => "rpms", Ok("rpmsc") => "rpmsc",
7872            Ok("rpks") => "rpks", Ok("rpksc") => "rpksc",
7873            _ => "auto",
7874        });
7875        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
7876        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
7877        // shapes qualify; anything else falls back to the register variants.
7878        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
7879        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
7880        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
7881        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
7882        // forced MEMRA_MMVQ_BV values still work).
7883        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7884        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
7885        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
7886        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
7887        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
7888        let sms = *SMS.get_or_init(|| {
7889            use cudarc::driver::sys::CUdevice_attribute_enum as A;
7890            self.gpu.ctx.attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT).unwrap_or(82)
7891        });
7892        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
7893        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
7894        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
7895        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
7896        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
7897        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
7898        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
7899        // AUTO RULE = the measured winners table (differs from NVFP4's!):
7900        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
7901        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
7902        //     r2 1258us) — kernels kept behind the force seam for the corpus;
7903        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
7904        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
7905        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
7906        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
7907        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
7908        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
7909        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
7910        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
7911        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
7912        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
7913        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
7914        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
7915        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
7916            Ok("base") => "base", Ok("r2") => "r2", Ok("r2w8") => "r2w8",
7917            _ => "auto",
7918        });
7919        let variant: &'static str = if qtype == QT_Q4_0 {
7920            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
7921            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
7922            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
7923            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
7924            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
7925                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
7926                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
7927                // + syncs cost more than the stalls, bank-pad made no difference);
7928                // register load-ahead flat (nvcc already reorders). The b-tier limiter
7929                // is still unidentified — see the jsonl row.
7930                Ok("base") => "base", Ok("r2") => "r2", Ok("ms") => "ms", Ok("sm") => "sm",
7931                Ok("la") => "la", _ => "auto",
7932            });
7933            let v = if q40 != "auto" { q40 }
7934            else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 { "r2" } else { "base" };
7935            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
7936            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
7937            // and the limiter is the per-column activation load chain (long_scoreboard
7938            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
7939            if rp { match v { "ms" => "r2ms_rp", "sm" => "r2sm_rp", "la" => "r2la_rp",
7940                              "r2" => "r2_rp", _ => "rp" } }
7941            else if matches!(v, "ms" | "sm" | "la") { "r2" } else { v }
7942        } else if qtype != QT_NVFP4 && !kq_r2 {
7943            "base"
7944        } else if kq_r2 && rp {
7945            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
7946            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
7947            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
7948            "rp"
7949        } else if kq_r2 {
7950            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
7951            // mcols != 4 forced r2w8 falls to unbounded r2.
7952            if kq_bv != "auto" {
7953                if kq_bv == "r2w8" && mcols != 4 { "r2" } else { kq_bv }
7954            } else if bv != "auto" {
7955                match bv {
7956                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
7957                    "r2w8" | "rpr2w8" => if mcols != 4 { "r2" } else { "r2w8" },
7958                    _ => "base",   // base/pf/ca/rp forced -> base (no such k-quant kernels)
7959                }
7960            } else {
7961                let blocks = (out_f + 7) / 8;
7962                let waves = blocks as f64 / (7 * sms as usize) as f64;
7963                let filled = blocks >= 4 * sms as usize;
7964                let use_r2 = if qtype == QT_Q4_K { filled } else { waves >= 2.0 };
7965                if use_r2 { "r2" } else { "base" }
7966            }
7967        } else if bv != "auto" {
7968            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
7969            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
7970            // unsupported (shape, mcols) combos fall back to pf/r2.
7971            // On rp buffers, forced legacy names map to their rp twins (layout law).
7972            let v = if bv == "r2w8" && mcols == 2 { "r2" }
7973                else if bv == "ca" && (!ca_ok || mcols == 8) { "pf" }
7974                else if bv == "car2" && (!ca_ok || mcols == 8) { "r2" }
7975                else if bv == "pfr2" && mcols == 8 { "r2" }
7976                else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 { "rpr2" }
7977                // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
7978                else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
7979                    if mcols == 8 { "rpr2w8" } else { "rpr2" }
7980                }
7981                else if bv == "rpcar2" && mcols == 2 { "rpca" }
7982                // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
7983                // (rpms has no smem and no alignment need — always valid on rp buffers).
7984                else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok { "rpr2" }
7985                else if (bv == "rpks" || bv == "rpksc") && !ks_ok { "rpr2" }
7986                else { bv };
7987            if rp {
7988                match v {
7989                    "base" | "pf" | "ca" | "rp" => "rp",
7990                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
7991                    "r2w8" | "rpr2w8" => if mcols == 2 { "rpr2" } else { "rpr2w8" },
7992                    other => other,   // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
7993                }
7994            } else { v }
7995        } else if mcols == 8 {
7996            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
7997            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
7998            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
7999            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
8000            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
8001            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
8002            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
8003            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
8004            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
8005            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
8006            if rp { if sc_ok { "rpsc" } else { "rpr2w8" } } else { "r2w8" }
8007        } else if mcols >= 4 {
8008            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
8009            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
8010            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
8011            let blocks = (out_f + 7) / 8;
8012            let r7 = 7 * sms as usize;
8013            let r8 = 8 * sms as usize;
8014            let waves = blocks as f64 / r7 as f64;
8015            let filled = blocks >= 4 * sms as usize;
8016            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
8017            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
8018            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
8019            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
8020                // the extra residency drops the INTEGER wave count -> the straggler wave a
8021                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
8022                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
8023                if rp { "rpr2w8" } else { "r2w8" }
8024            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
8025                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
8026                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
8027                if rp { "rpr2" } else { "r2" }
8028            } else {
8029                // fractional straggler-wave window with no crossing, or grid too small to fill
8030                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
8031                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
8032                if rp { "rp" } else { "pf" }
8033            }
8034        } else if in_f >= 6144 {
8035            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
8036            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
8037            // stays.
8038            if rp { "rpr2" } else { "r2" }
8039        }
8040        else if rp {
8041            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
8042            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
8043            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
8044            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
8045            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
8046            if sc_ok && waves >= 0.9 && waves <= 1.1 { "rpsc" } else { "rp" }
8047        } else { "base" };
8048        variant
8049    }
8050
8051    pub fn qmatvec_mmvq_batched(&self, bytes: &CudaSlice<u8>, aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
8052                                m: usize, in_f: usize, out_f: usize, qtype: i32, row_bytes: usize,
8053                                mcols: usize, scale: f32, rp: bool)
8054                                -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8055        const ROWS_PER_BLOCK: u32 = 4;
8056        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
8057        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
8058        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
8059        // weight keeps its rp-layout kernel family regardless of the override.
8060        let forced: Option<&'static str> = {
8061            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
8062            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
8063                .as_deref()
8064                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
8065        };
8066        let variant = match forced {
8067            Some(v) if !rp || v.contains("rp") => v,
8068            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
8069        };
8070        let base_name = Self::batched_kernel_name(qtype, mcols)
8071            .ok_or_else(|| format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}"))?;
8072        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
8073        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
8074        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
8075        let variant = if mcols == 16 { if rp { "rp" } else { "base" } } else { variant };
8076        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
8077        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
8078        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
8079        // per-(token,row) chain (columns c >= m never execute in either form) ->
8080        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
8081        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
8082        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8083        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
8084        if b567 && qtype == QT_NVFP4 && rp && mcols == 8 && (5..=7).contains(&m)
8085            && matches!(variant, "rpsc" | "rpr2w8") {
8086            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
8087            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
8088            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
8089            let cfg = LaunchConfig {
8090                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
8091                block_dim: (32, ROWS_PER_BLOCK, 1), shared_mem_bytes: 0 };
8092            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
8093            let __s_b = self.gpu.stream();
8094            let mut b = __s_b.launch_builder(&f);
8095            b.arg(bytes).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
8096            unsafe { b.launch(cfg)?; }
8097            if scale != 1.0 { self.scale_inplace(&mut y, scale, m * out_f)?; }
8098            return Ok(y);
8099        }
8100        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
8101            "base" => (base_name.into(), ROWS_PER_BLOCK),
8102            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
8103            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
8104            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
8105            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
8106            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
8107            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
8108            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
8109            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
8110            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
8111            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
8112            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
8113            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
8114            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
8115            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
8116        };
8117        debug_assert!(!rp || name.contains("_rp"), "rp weight dispatched to a GGUF-layout kernel");
8118        let f = self.func(&name);
8119        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
8120        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
8121        let smem = if name.contains("_r2sm_rp") { (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32 }
8122                   else { 0 };
8123        let cfg = LaunchConfig {
8124            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
8125            block_dim: (32, ROWS_PER_BLOCK, 1), shared_mem_bytes: smem };
8126        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
8127        let __s_b = self.gpu.stream();
8128        let mut b = __s_b.launch_builder(&f);
8129        b.arg(bytes).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
8130        unsafe { b.launch(cfg)?; }
8131        if scale != 1.0 { self.scale_inplace(&mut y, scale, m * out_f)?; }
8132        Ok(y)
8133    }
8134
8135    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
8136    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
8137    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
8138    pub fn qmatvec_batched_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize,
8139                               in_f: usize, out_f: usize, qtype: i32, row_bytes: usize, mcols: usize,
8140                               rp: bool)
8141                               -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8142        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
8143        self.qmatvec_mmvq_batched(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp)
8144    }
8145
8146    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
8147    pub fn qmatvec_nvfp4_batched_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize,
8148                                     in_f: usize, out_f: usize, row_bytes: usize, mcols: usize,
8149                                     rp: bool)
8150                                     -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8151        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
8152    }
8153
8154    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
8155    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
8156    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
8157    fn try_fp4_gemm(&self, w: &crate::model::GpuTensor, x: &CudaSlice<f32>, m: usize,
8158                    in_f: usize, out_f: usize)
8159                    -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
8160        use crate::model::GpuTensor;
8161        if cfg!(memra_portable_cuda) { return Ok(None); }
8162        if std::env::var("MEMRA_FP4").is_err() { return Ok(None); }
8163        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
8164        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
8165        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
8166        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
8167        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
8168        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
8169        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
8170        // for the common no-macro-scale case.
8171        #[cfg(memra_cutlass)]
8172        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
8173            if let GpuTensor::Quant { bytes, qtype, scale, row_bytes, cutlass, .. } = w {
8174                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
8175                    if let Some(cw) = cutlass {
8176                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
8177                        let y = self.cutlass_fp4_gemm(&cw.b_packed, &cw.sfb_swizzled, x, *scale,
8178                                                      m, out_f, in_f)?;
8179                        return Ok(Some(y));
8180                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
8181                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
8182                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
8183                        // (the load-time repack ~doubles it) — needed for models that don't fit the
8184                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
8185                        let (b_packed, sfb_sw) = self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
8186                        let y = self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
8187                        return Ok(Some(y));
8188                    }
8189                }
8190            }
8191        }
8192        if let GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } = w {
8193            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
8194            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
8195            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
8196                let y = self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
8197                return Ok(Some(y));
8198            }
8199        }
8200        Ok(None)
8201    }
8202
8203    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
8204    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
8205    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
8206    pub fn rms_norm_f16out(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>,
8207                           dst: &mut CudaSlice<f32>, dst16: &mut CudaSlice<u8>,
8208                           ncols: usize, nrows: usize, eps: f32)
8209                           -> Result<(), Box<dyn std::error::Error>> {
8210        let f = self.func("rms_norm_f16out_f32");
8211        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
8212        let (nc, e) = (ncols as i32, eps);
8213        let __s_b = self.gpu.stream();
8214        let mut b = __s_b.launch_builder(&f);
8215        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
8216        unsafe { b.launch(cfg)?; }
8217        Ok(())
8218    }
8219
8220    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
8221    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
8222    #[allow(clippy::too_many_arguments)]
8223    pub fn add_rms_norm_f16out(&self, a: &CudaSlice<f32>, b: &CudaSlice<f32>, w: &CudaSlice<f32>,
8224                               res: &mut CudaSlice<f32>, dst: &mut CudaSlice<f32>,
8225                               dst16: &mut CudaSlice<u8>, ncols: usize, nrows: usize, eps: f32)
8226                               -> Result<(), Box<dyn std::error::Error>> {
8227        let f = self.func("add_rms_norm_f16out_f32");
8228        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
8229        let (nc, e) = (ncols as i32, eps);
8230        let __s_lb = self.gpu.stream();
8231        let mut lb = __s_lb.launch_builder(&f);
8232        lb.arg(a).arg(b).arg(w).arg(res).arg(dst).arg(dst16).arg(&nc).arg(&e);
8233        unsafe { lb.launch(cfg)?; }
8234        Ok(())
8235    }
8236
8237    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
8238    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
8239    pub fn matmul_group_xh(&self, ws: &[&crate::model::GpuTensor], x: &CudaSlice<f32>,
8240                           xh: &CudaSlice<u8>, m: usize)
8241                           -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
8242        let mut out = Vec::with_capacity(ws.len());
8243        let in_f = ws[0].in_features();
8244        for w in ws {
8245            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
8246                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
8247                    out.push(y);
8248                    continue;
8249                }
8250            }
8251            out.push(self.matmul(w, x, m)?);
8252        }
8253        Ok(out)
8254    }
8255
8256    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
8257    /// GDN steps). Layouts [T, H].
8258    pub fn gdn_pad_mask(&self, beta: &mut CudaSlice<f32>, g_log: &mut CudaSlice<f32>,
8259                        len_d: &CudaSlice<i32>, h: usize, t: usize)
8260                        -> Result<(), Box<dyn std::error::Error>> {
8261        let f = self.func("gdn_pad_mask_f32");
8262        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
8263        let (hi, ti) = (h as i32, t as i32);
8264        let __s_b = self.gpu.stream();
8265        let mut b = __s_b.launch_builder(&f);
8266        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
8267        unsafe { b.launch(cfg)?; }
8268        Ok(())
8269    }
8270
8271    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
8272    /// gather for the padded prime graph's h_seed/hlast.
8273    pub fn row_gather_dev(&self, src: &CudaSlice<f32>, dst: &mut CudaSlice<f32>,
8274                          len_d: &CudaSlice<i32>, ncols: usize)
8275                          -> Result<(), Box<dyn std::error::Error>> {
8276        let f = self.func("row_gather_dev_f32");
8277        let cfg = LaunchConfig::for_num_elems(ncols as u32);
8278        let nc = ncols as i32;
8279        let __s_b = self.gpu.stream();
8280        let mut b = __s_b.launch_builder(&f);
8281        b.arg(src).arg(dst).arg(len_d).arg(&nc);
8282        unsafe { b.launch(cfg)?; }
8283        Ok(())
8284    }
8285
8286    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
8287    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
8288    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
8289    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
8290    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
8291    /// different in_f) falls back to its own `matmul` — behavior unchanged.
8292    pub fn matmul_group(&self, ws: &[&crate::model::GpuTensor], x: &CudaSlice<f32>, m: usize)
8293                        -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
8294        use crate::model::GpuTensor;
8295        let mut out = Vec::with_capacity(ws.len());
8296        let any_mirror = ws.iter().any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
8297        if m >= 16 && any_mirror && !self.verify_exact_on() {
8298            let in_f = ws[0].in_features();
8299            let xh = self.f16_act(x, m * in_f, in_f)?;
8300            for w in ws {
8301                if w.in_features() == in_f {
8302                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
8303                        out.push(y);
8304                        continue;
8305                    }
8306                }
8307                out.push(self.matmul(w, x, m)?);
8308            }
8309            return Ok(out);
8310        }
8311        for w in ws {
8312            out.push(self.matmul(w, x, m)?);
8313        }
8314        Ok(out)
8315    }
8316
8317    /// Cross-request grouped matmul (task #13): run ONE projection group over the
8318    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
8319    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
8320    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
8321    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
8322    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
8323    pub fn matmul_group_multi(&self, ws: &[&crate::model::GpuTensor],
8324                              xs: &[&CudaSlice<f32>], ms: &[usize])
8325                              -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
8326        assert_eq!(xs.len(), ms.len());
8327        let in_f = ws[0].in_features();
8328        let total: usize = ms.iter().sum();
8329        let mut xcat = self.uninit(total * in_f)?;
8330        let mut off = 0usize;
8331        for (x, &m) in xs.iter().zip(ms) {
8332            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
8333            off += m;
8334        }
8335        let ys = self.matmul_group(ws, &xcat, total)?;
8336        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
8337        for (w, y) in ws.iter().zip(ys) {
8338            let out_f = w.out_features();
8339            let mut off = 0usize;
8340            for (s, &m) in ms.iter().enumerate() {
8341                let mut ys_s = self.uninit(m * out_f)?;
8342                let src = y.slice(off * out_f..(off + m) * out_f);
8343                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
8344                out[s].push(ys_s);
8345                off += m;
8346            }
8347        }
8348        Ok(out)
8349    }
8350
8351    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
8352    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
8353    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
8354    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
8355    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
8356    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
8357    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
8358    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
8359    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
8360    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
8361        use crate::model::GpuTensor;
8362        if !legacy_quant_gemm_allowed(
8363            cfg!(memra_portable_cuda),
8364            cfg!(memra_hopper_mma),
8365            std::env::var_os("MEMRA_NO_GEMM").is_some(),
8366        ) {
8367            return false;
8368        }
8369        match w {
8370            GpuTensor::Quant { qtype, .. } =>
8371                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
8372                || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0),
8373            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
8374        }
8375    }
8376
8377    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
8378    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
8379    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
8380    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
8381    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
8382    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
8383    pub fn qmatvec_gemm(&self, w: &crate::model::GpuTensor, aq: &CudaSlice<i8>, ad: &CudaSlice<f32>,
8384                        m: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8385        use crate::model::GpuTensor;
8386        let in_f = w.in_features();
8387        let out_f = w.out_features();
8388        let (bytes, qtype, row_bytes, scale, rp) = match w {
8389            GpuTensor::Quant { bytes, qtype, row_bytes, scale, rp, .. } => (bytes, *qtype, *row_bytes, *scale, *rp),
8390            _ => unreachable!("gemm_supports guaranteed Quant"),
8391        };
8392        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
8393        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
8394        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
8395        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
8396        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
8397        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
8398            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
8399                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
8400                if scale != 1.0 { self.scale_inplace(&mut y, scale, m * out_f)?; }
8401                return Ok(y);
8402            }
8403        }
8404        let name = match qtype {
8405            QT_Q8_0 => "qmatvec_gemm_q8_0", QT_Q4_K => "qmatvec_gemm_q4_K",
8406            QT_Q4_0 => if rp { "qmatvec_gemm_q4_0_rp" } else { "qmatvec_gemm_q4_0" },
8407            QT_Q5_K => "qmatvec_gemm_q5_K",
8408            QT_Q6_K => "qmatvec_gemm_q6_K",
8409            QT_NVFP4 => if rp { "qmatvec_gemm_nvfp4_rp" } else { "qmatvec_gemm_nvfp4" },
8410            _ => unreachable!(),
8411        };
8412        let f = self.func(name);
8413        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite GEMM output: skip memset
8414        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
8415        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
8416        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
8417        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
8418        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
8419        let k1_tile = if is_k1 { k1_launch_override().unwrap_or((128, 128, 8)) } else { (128, 128, 8) };
8420        let (bm, bn): (u32, u32) = if is_k1 { (k1_tile.0, k1_tile.1) } else { (64, 256) };
8421        let warps: u32 = if is_k1 { k1_tile.2 } else {
8422            match qtype { QT_NVFP4 => 8, _ => 4 }
8423        };
8424        let cfg = LaunchConfig {
8425            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
8426            block_dim: (32, warps, 1),
8427            shared_mem_bytes: 0,
8428        };
8429        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
8430        let __s_b = self.gpu.stream();
8431        let mut b = __s_b.launch_builder(&f);
8432        b.arg(bytes).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
8433        unsafe { b.launch(cfg)?; }
8434        if scale != 1.0 { self.scale_inplace(&mut y, scale, m * out_f)?; }
8435        Ok(y)
8436    }
8437
8438    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
8439    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
8440    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
8441    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
8442    pub fn qmatvec_gemm_raw(&self, bytes: &CudaSlice<u8>, x: &CudaSlice<f32>, m: usize, in_f: usize,
8443                            out_f: usize, qtype: i32, row_bytes: usize)
8444                            -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8445        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
8446        let name = match qtype {
8447            QT_Q8_0 => "qmatvec_gemm_q8_0", QT_Q4_K => "qmatvec_gemm_q4_K",
8448            QT_Q4_0 => "qmatvec_gemm_q4_0",
8449            QT_Q5_K => "qmatvec_gemm_q5_K",
8450            QT_Q6_K => "qmatvec_gemm_q6_K", QT_NVFP4 => "qmatvec_gemm_nvfp4",
8451            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
8452            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
8453        };
8454        let f = self.func(name);
8455        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite GEMM output: skip memset
8456        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
8457        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
8458        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
8459        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
8460        let k1_tile = if is_k1 { k1_launch_override().unwrap_or((128, 128, 8)) } else { (128, 128, 8) };
8461        let (bm, bn): (u32, u32) = if is_k1 { (k1_tile.0, k1_tile.1) } else { (64, 256) };
8462        let warps: u32 = if is_k1 { k1_tile.2 } else {
8463            match qtype { QT_NVFP4 | QT_NVFP4_RP => 8, _ => 4 }
8464        };
8465        let cfg = LaunchConfig {
8466            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
8467            block_dim: (32, warps, 1), shared_mem_bytes: 0,
8468        };
8469        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
8470        let __s_b = self.gpu.stream();
8471        let mut b = __s_b.launch_builder(&f);
8472        b.arg(bytes).arg(&aq).arg(&ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi).arg(&rb);
8473        unsafe { b.launch(cfg)?; }
8474        Ok(y)
8475    }
8476
8477    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
8478    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
8479    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
8480    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
8481    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
8482    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
8483    pub fn qmatvec_gemm_q8_0_wgmma_raw(&self, rp4: &CudaSlice<u8>, aq: &CudaSlice<i8>,
8484                                       ad: &CudaSlice<f32>, m: usize, in_f: usize, out_f: usize)
8485                                       -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8486        assert!(out_f % 64 == 0 && in_f % 32 == 0, "wgmma GEMM needs out_f%64==0, in_f%32==0");
8487        let f = self.func("qmatvec_gemm_q8_0_wgmma");
8488        let mut y = self.alloc_uninit::<f32>(m * out_f)?;  // full-overwrite GEMM output
8489        let cfg = LaunchConfig {
8490            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
8491            block_dim: (128, 1, 1), shared_mem_bytes: 0,
8492        };
8493        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
8494        let __s_b = self.gpu.stream();
8495        let mut b = __s_b.launch_builder(&f);
8496        b.arg(rp4).arg(aq).arg(ad).arg(&mut y).arg(&inf).arg(&outf).arg(&mi);
8497        unsafe { b.launch(cfg)?; }
8498        Ok(y)
8499    }
8500
8501    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
8502    pub fn scale_inplace(&self, y: &mut CudaSlice<f32>, s: f32, n: usize)
8503                         -> Result<(), Box<dyn std::error::Error>> {
8504        let f = self.func("scale_f32");
8505        let cfg = LaunchConfig::for_num_elems(n as u32);
8506        let (sf, ni) = (s, n as i32);
8507        let __s_b = self.gpu.stream();
8508        let mut b = __s_b.launch_builder(&f);
8509        b.arg(y).arg(&sf).arg(&ni);
8510        unsafe { b.launch(cfg)?; }
8511        Ok(())
8512    }
8513
8514    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
8515    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
8516    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
8517    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
8518    pub fn bf16_to_f32(&self, data: &cudarc::driver::CudaView<'_, u8>, n: usize)
8519                       -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8520        let mut out = self.alloc_uninit::<f32>(n)?;
8521        let f = self.func("bf16_to_f32");
8522        let cfg = LaunchConfig::for_num_elems(n as u32);
8523        let ni = n as i32;
8524        let __s_b = self.gpu.stream();
8525        let mut b = __s_b.launch_builder(&f);
8526        b.arg(data).arg(&mut out).arg(&ni);
8527        unsafe { b.launch(cfg)?; }
8528        Ok(out)
8529    }
8530
8531    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
8532    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
8533    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
8534    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
8535    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
8536    /// calls, the spec-verify contract) vs plain linear.
8537    fn linear_bf16_chunked(&self, x: &CudaSlice<f32>, data: &CudaSlice<u8>, m: usize,
8538                           in_f: usize, out_f: usize, exact: bool)
8539                           -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8540        const CHUNK_BYTES: usize = 256 << 20;
8541        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
8542        if chunk_rows >= out_f {
8543            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
8544            return if exact { self.linear_decode_exact(x, &wf32, m, in_f, out_f) }
8545                   else { self.linear(x, &wf32, m, in_f, out_f) };
8546        }
8547        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
8548        let mut r0 = 0usize;
8549        while r0 < out_f {
8550            let rows = chunk_rows.min(out_f - r0);
8551            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
8552            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
8553            let yc = if exact { self.linear_decode_exact(x, &wf32, m, in_f, rows)? }
8554                     else { self.linear(x, &wf32, m, in_f, rows)? };
8555            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
8556            for mi in 0..m {
8557                let src = yc.slice(mi * rows..(mi + 1) * rows);
8558                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
8559                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
8560            }
8561            r0 += rows;
8562        }
8563        Ok(y)
8564    }
8565
8566    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
8567    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
8568    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
8569    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
8570    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
8571    /// router/shexp sites and matmul_decode_exact's Float arm.
8572    pub fn linear_decode_exact(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, m_tokens: usize,
8573                               in_f: usize, out_f: usize)
8574                               -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8575        if m_tokens == 1 { return self.linear(x, w, 1, in_f, out_f); }
8576        let xv = self.view(x, m_tokens * in_f);
8577        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
8578        for t in 0..m_tokens {
8579            let row = xv.slice(t * in_f..(t + 1) * in_f);
8580            let mut xr = self.alloc_uninit::<f32>(in_f)?;
8581            self.copy_view_into(&mut xr, 0, &row, in_f)?;
8582            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
8583            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
8584        }
8585        Ok(y)
8586    }
8587
8588    pub fn linear(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, m_tokens: usize, in_f: usize, out_f: usize)
8589                  -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8590        use cudarc::cublaslt::{Matmul, MatmulConfig};
8591        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?;  // cuBLASLt beta=0: C fully written
8592        let cfg = MatmulConfig {
8593            transa: true, transb: false, transc: false,
8594            m: out_f as u64, n: m_tokens as u64, k: in_f as u64,
8595            alpha: 1.0, lda: in_f as i64, ldb: in_f as i64, beta: 0.0, ldc: out_f as i64,
8596            stride_a: None, stride_b: None, stride_c: None, stride_bias: None, batch_size: None,
8597        };
8598        unsafe { self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?; }
8599        Ok(c)
8600    }
8601
8602    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
8603    pub fn sdpa_naive(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
8604                      o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize, n_head_kv: usize,
8605                      t: usize, t_kv: usize, scale: f32, causal: bool)
8606                      -> Result<(), Box<dyn std::error::Error>> {
8607        let f = self.func("sdpa_naive_f32");
8608        let cfg = LaunchConfig {
8609            grid_dim: (n_head as u32, t as u32, 1),
8610            block_dim: (128, 1, 1),
8611            shared_mem_bytes: (t_kv * 4) as u32,
8612        };
8613        let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
8614        let __s_b = self.gpu.stream();
8615        let mut b = __s_b.launch_builder(&f);
8616        b.arg(q).arg(k).arg(v).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi).arg(&scale).arg(&cz);
8617        unsafe { b.launch(cfg)?; }
8618        Ok(())
8619    }
8620
8621    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
8622    #[allow(clippy::too_many_arguments)]
8623    pub fn sdpa_naive_w(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
8624                        o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize, n_head_kv: usize,
8625                        t: usize, t_kv: usize, scale: f32, causal: bool, window: usize)
8626                        -> Result<(), Box<dyn std::error::Error>> {
8627        let f = self.func("sdpa_naive_w_f32");
8628        let cfg = LaunchConfig {
8629            grid_dim: (n_head as u32, t as u32, 1),
8630            block_dim: (128, 1, 1),
8631            shared_mem_bytes: (t_kv * 4) as u32,
8632        };
8633        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32, n_head_kv as i32,
8634                                                t as i32, t_kv as i32, causal as i32, window as i32);
8635        let __s_b = self.gpu.stream();
8636        let mut b = __s_b.launch_builder(&f);
8637        b.arg(q).arg(k).arg(v).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
8638         .arg(&scale).arg(&cz).arg(&wi);
8639        unsafe { b.launch(cfg)?; }
8640        Ok(())
8641    }
8642
8643    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
8644    pub fn sdpa_naive_view(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<f32>,
8645                           v: &cudarc::driver::CudaView<f32>, o: &mut CudaSlice<f32>,
8646                           head_dim: usize, n_head: usize, n_head_kv: usize, t: usize, t_kv: usize,
8647                           scale: f32, causal: bool) -> Result<(), Box<dyn std::error::Error>> {
8648        let f = self.func("sdpa_naive_f32");
8649        let cfg = LaunchConfig {
8650            grid_dim: (n_head as u32, t as u32, 1), block_dim: (128, 1, 1),
8651            shared_mem_bytes: (t_kv * 4) as u32,
8652        };
8653        let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
8654        let __s_b = self.gpu.stream();
8655        let mut b = __s_b.launch_builder(&f);
8656        b.arg(q).arg(k).arg(v).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi).arg(&scale).arg(&cz);
8657        unsafe { b.launch(cfg)?; }
8658        Ok(())
8659    }
8660
8661    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
8662    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
8663    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
8664    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
8665    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
8666    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
8667    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
8668    #[allow(clippy::too_many_arguments)]
8669    pub fn fa_dequant_kv_view_f32(&self, k: &cudarc::driver::CudaView<u8>,
8670                                  v: &cudarc::driver::CudaView<u8>,
8671                                  kf: &mut CudaSlice<f32>, vf: &mut CudaSlice<f32>,
8672                                  kv_dim_k: usize, kv_dim_v: usize, t_kv: usize,
8673                                  k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
8674                                  -> Result<(), Box<dyn std::error::Error>> {
8675        let f = if g { self.func_g("fa_dequant_kv_ws_f32") } else { self.func("fa_dequant_kv_ws_f32") };
8676        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
8677        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
8678        let cfg = LaunchConfig { grid_dim: (nblk.max(1), 1, 1), block_dim: (256, 1, 1),
8679                                 shared_mem_bytes: 0 };
8680        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
8681        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
8682        let __s_b = self.gpu.stream();
8683        let mut b = __s_b.launch_builder(&f);
8684        b.arg(k).arg(v).arg(&mut *kf).arg(&mut *vf).arg(&kdk).arg(&kdv).arg(&tkvi).arg(&ktb).arg(&vtb);
8685        unsafe { b.launch(cfg)?; }
8686        Ok(())
8687    }
8688
8689    #[allow(clippy::too_many_arguments)]
8690    pub fn sdpa_naive_quantized_view(
8691        &self,
8692        q: &CudaSlice<f32>,
8693        k: &cudarc::driver::CudaView<u8>,
8694        v: &cudarc::driver::CudaView<u8>,
8695        o: &mut CudaSlice<f32>,
8696        head_dim: usize,
8697        n_head: usize,
8698        n_head_kv: usize,
8699        t: usize,
8700        t_kv: usize,
8701        scale: f32,
8702        causal: bool,
8703        k_tok_bytes: usize,
8704        v_tok_bytes: usize,
8705    ) -> Result<(), Box<dyn std::error::Error>> {
8706        let kv_dim = n_head_kv * head_dim;
8707        let mut kf = self.uninit(t_kv * kv_dim)?;
8708        let mut vf = self.uninit(t_kv * kv_dim)?;
8709        let f = self.func("fa_dequant_kv_ws_f32");
8710        let total = (2 * t_kv * kv_dim) as u64;
8711        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
8712        let cfg = LaunchConfig {
8713            grid_dim: (nblk.max(1), 1, 1),
8714            block_dim: (256, 1, 1),
8715            shared_mem_bytes: 0,
8716        };
8717        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
8718        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
8719        let __s_b = self.gpu.stream();
8720        let mut b = __s_b.launch_builder(&f);
8721        b.arg(k)
8722            .arg(v)
8723            .arg(&mut kf)
8724            .arg(&mut vf)
8725            .arg(&kv_dim_i)
8726            .arg(&kv_dim_i)
8727            .arg(&t_kv_i)
8728            .arg(&k_tok_bytes_i)
8729            .arg(&v_tok_bytes_i);
8730        unsafe { b.launch(cfg)? };
8731        self.sdpa_naive(
8732            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
8733        )
8734    }
8735
8736    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
8737    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
8738    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
8739    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
8740    /// unwindowed function above and produces bit-identical output at window == 0.
8741    ///
8742    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
8743    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
8744    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
8745    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
8746    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
8747    #[allow(clippy::too_many_arguments)]
8748    pub fn sdpa_naive_w_quantized_view(
8749        &self,
8750        q: &CudaSlice<f32>,
8751        k: &cudarc::driver::CudaView<u8>,
8752        v: &cudarc::driver::CudaView<u8>,
8753        o: &mut CudaSlice<f32>,
8754        head_dim: usize,
8755        n_head: usize,
8756        n_head_kv: usize,
8757        t: usize,
8758        t_kv: usize,
8759        scale: f32,
8760        causal: bool,
8761        window: usize,
8762        k_tok_bytes: usize,
8763        v_tok_bytes: usize,
8764    ) -> Result<(), Box<dyn std::error::Error>> {
8765        let kv_dim = n_head_kv * head_dim;
8766        let mut kf = self.uninit(t_kv * kv_dim)?;
8767        let mut vf = self.uninit(t_kv * kv_dim)?;
8768        let f = self.func("fa_dequant_kv_ws_f32");
8769        let total = (2 * t_kv * kv_dim) as u64;
8770        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
8771        let cfg = LaunchConfig {
8772            grid_dim: (nblk.max(1), 1, 1),
8773            block_dim: (256, 1, 1),
8774            shared_mem_bytes: 0,
8775        };
8776        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
8777        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
8778        let __s_b = self.gpu.stream();
8779        let mut b = __s_b.launch_builder(&f);
8780        b.arg(k)
8781            .arg(v)
8782            .arg(&mut kf)
8783            .arg(&mut vf)
8784            .arg(&kv_dim_i)
8785            .arg(&kv_dim_i)
8786            .arg(&t_kv_i)
8787            .arg(&k_tok_bytes_i)
8788            .arg(&v_tok_bytes_i);
8789        unsafe { b.launch(cfg)? };
8790        self.sdpa_naive_w(
8791            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
8792        )
8793    }
8794
8795    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
8796    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
8797    /// Q/K/V/O [head_dim, n_head(_kv), T].
8798    pub fn fa_prefill(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
8799                      o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize, n_head_kv: usize,
8800                      t: usize, t_kv: usize, scale: f32, causal: bool)
8801                      -> Result<(), Box<dyn std::error::Error>> {
8802        if portable_mma_gated() {
8803            return self.sdpa_naive(q, k, v, o, head_dim, n_head, n_head_kv,
8804                                   t, t_kv, scale, causal);
8805        }
8806        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
8807        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
8808        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
8809        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
8810        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
8811        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
8812        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
8813        let fa3_on = head_dim == 256 && causal && t == t_kv
8814            && match std::env::var("MEMRA_FA3").as_deref() {
8815                Ok("0") => false,
8816                Ok("1") => true,
8817                _ => cfg!(memra_hopper_mma),
8818            };
8819        if fa3_on {
8820            let n = t * n_head * head_dim;
8821            let nkv = t * n_head_kv * head_dim;
8822            let mut q16 = self.alloc_u8_uninit(n * 2)?;
8823            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
8824            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
8825            self.f32_to_bf16_into(q, &mut q16, n)?;
8826            self.f32_to_bf16_into(k, &mut k16, nkv)?;
8827            self.f32_to_bf16_into(v, &mut v16, nkv)?;
8828            let rc = {
8829                use cudarc::driver::{DevicePtr, DevicePtrMut};
8830                let stream = self.gpu.stream();
8831                let (qp, _g1) = q16.device_ptr(&stream);
8832                let (kp, _g2) = k16.device_ptr(&stream);
8833                let (vp, _g3) = v16.device_ptr(&stream);
8834                let (op, _g4) = o.device_ptr_mut(&stream);
8835                unsafe {
8836                    memra_fa3_prefill(qp as *const core::ffi::c_void,
8837                                     kp as *const core::ffi::c_void,
8838                                     vp as *const core::ffi::c_void,
8839                                     op as *mut f32,
8840                                     t as i32, n_head as i32, n_head_kv as i32,
8841                                     head_dim as i32, scale,
8842                                     stream.cu_stream() as *mut core::ffi::c_void)
8843                }
8844            };
8845            if rc != 0 {
8846                return Err(format!("memra_fa3_prefill rc={rc}").into());
8847            }
8848            return Ok(());
8849        }
8850        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
8851        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
8852        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
8853        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
8854        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8855        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
8856        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
8857            const BLOCK_Q: usize = 64; const BKX: usize = 32;
8858            let f = self.func("fa_prefill_bf16_p1");
8859            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
8860                       + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
8861            use cudarc::driver::sys::CUfunction_attribute_enum as A;
8862            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
8863            let cfg = LaunchConfig {
8864                grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
8865                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
8866            };
8867            let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32,
8868                n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
8869            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
8870            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
8871            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
8872            let __s_b = self.gpu.stream();
8873            let mut b = __s_b.launch_builder(&f);
8874            b.arg(&qb).arg(&kb).arg(&vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti)
8875             .arg(&tkvi).arg(&scale).arg(&cz);
8876            unsafe { b.launch(cfg)?; }
8877            return Ok(());
8878        }
8879        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
8880        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
8881        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
8882        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
8883        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
8884        const BK: usize = 32;
8885        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
8886        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
8887        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
8888        let (block_q, warps, w2_sfx): (usize, u32, &str) =
8889            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
8890        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
8891        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
8892        // other head_dims to sdpa_naive before reaching here.
8893        let hd_sfx = fa_hd_suffix(head_dim)?;
8894        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
8895        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
8896        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
8897        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
8898        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
8899        let bf16kv = !floor && !w2
8900            && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
8901        let (kb16, vb16) = if bf16kv {
8902            let n = t_kv * n_head_kv * head_dim;
8903            let mut kb = self.alloc_u8_uninit(n * 2)?;
8904            let mut vb = self.alloc_u8_uninit(n * 2)?;
8905            let fcv = self.func("f32_to_bf16_bulk");
8906            let ni = n as i64;
8907            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
8908            let __s_b = self.gpu.stream();
8909            let mut b = __s_b.launch_builder(&fcv);
8910            b.arg(k).arg(&mut kb).arg(&ni);
8911            unsafe { b.launch(cfgc)?; }
8912            let __s_b = self.gpu.stream();
8913            let mut b = __s_b.launch_builder(&fcv);
8914            b.arg(v).arg(&mut vb).arg(&ni);
8915            unsafe { b.launch(cfgc)?; }
8916            (Some(kb), Some(vb))
8917        } else {
8918            (None, None)
8919        };
8920        let f = self.func(&if bf16kv {
8921            format!("fa_prefill_bf16kv_pp{hd_sfx}")
8922        } else {
8923            format!("fa_prefill_f32{}{}{hd_sfx}",
8924                    if floor { "" } else { "_pp" },
8925                    if floor { "" } else { w2_sfx })
8926        });
8927        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
8928        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
8929        let kv_stages = if bf16kv { 2 } else { 1 };
8930        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
8931                   + 4 * (block_q * BK + 2 * block_q)) as u32;
8932        use cudarc::driver::sys::CUfunction_attribute_enum as A;
8933        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
8934        let cfg = LaunchConfig {
8935            grid_dim: ((t as u32 + block_q as u32 - 1) / block_q as u32, n_head as u32, 1),
8936            block_dim: (32, warps, 1), shared_mem_bytes: shmem,
8937        };
8938        let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
8939        let __s_b = self.gpu.stream();
8940        let mut b = __s_b.launch_builder(&f);
8941        b.arg(q);
8942        match (&kb16, &vb16) {
8943            (Some(kb), Some(vb)) => { b.arg(kb).arg(vb); }
8944            _ => { b.arg(k).arg(v); }
8945        }
8946        b.arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi).arg(&scale).arg(&cz);
8947        unsafe { b.launch(cfg)?; }
8948        Ok(())
8949    }
8950
8951    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
8952    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
8953    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
8954    #[allow(clippy::too_many_arguments)]
8955    pub fn fa_prefill_w(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
8956                        o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize, n_head_kv: usize,
8957                        t: usize, t_kv: usize, scale: f32, causal: bool, window: usize)
8958                        -> Result<(), Box<dyn std::error::Error>> {
8959        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
8960        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
8961        if portable_mma_gated() {
8962            return self.sdpa_naive_w(q, k, v, o, head_dim, n_head, n_head_kv,
8963                                     t, t_kv, scale, causal, window);
8964        }
8965        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
8966        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
8967        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
8968        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8969        let faw_f32 = *FAW_F32.get_or_init(|| {
8970            std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32")
8971        });
8972        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
8973        self.fa_prefill_w_arm(q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
8974                              window, floor || faw_f32, floor)
8975    }
8976
8977    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
8978    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
8979    #[allow(clippy::too_many_arguments)]
8980    pub fn fa_prefill_w_pre(&self, qb: &CudaSlice<u8>, kb: &CudaSlice<u8>, vb: &CudaSlice<u8>,
8981                            o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize,
8982                            n_head_kv: usize, t: usize, t_kv: usize, scale: f32, causal: bool,
8983                            window: usize, v_f16: bool)
8984                            -> Result<(), Box<dyn std::error::Error>> {
8985        const BLOCK_Q: usize = 64; const BK: usize = 32;
8986        debug_assert_eq!(head_dim, 256);
8987        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0
8988            && (n_head / n_head_kv) % 2 == 0;
8989        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
8990        if hp {
8991            const BLOCK_QH: usize = 32;
8992            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
8993            // else re-encode through the pooled scratch (stream-ordered reuse).
8994            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
8995            let vh: &CudaSlice<u8> = if v_f16 { vb } else {
8996                let n = t_kv * n_head_kv * head_dim;
8997                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
8998                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
8999                }
9000                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
9001                vguard.as_ref().unwrap()
9002            };
9003            let f = self.func("fa_prefill_w_bf16_p1h2");
9004            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK)
9005                       + 4 * (2 * BLOCK_QH)) as u32;
9006            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9007            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9008            let cfg = LaunchConfig {
9009                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
9010                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9011            };
9012            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32,
9013                n_head_kv as i32, t as i32, t_kv as i32, causal as i32, window as i32);
9014            let __s_b = self.gpu.stream();
9015            let mut b = __s_b.launch_builder(&f);
9016            b.arg(qb).arg(kb).arg(vh).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9017             .arg(&scale).arg(&cz).arg(&wi);
9018            unsafe { b.launch(cfg)?; }
9019            return Ok(());
9020        }
9021        let f = self.func("fa_prefill_w_bf16_p1");
9022        let shmem = (2 * (2 * BK * head_dim + BLOCK_Q * BK)
9023                   + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
9024        use cudarc::driver::sys::CUfunction_attribute_enum as A;
9025        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9026        let cfg = LaunchConfig {
9027            grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
9028            block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9029        };
9030        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32,
9031            n_head_kv as i32, t as i32, t_kv as i32, causal as i32, window as i32);
9032        let __s_b = self.gpu.stream();
9033        let mut b = __s_b.launch_builder(&f);
9034        b.arg(qb).arg(kb).arg(vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9035         .arg(&scale).arg(&cz).arg(&wi);
9036        unsafe { b.launch(cfg)?; }
9037        Ok(())
9038    }
9039
9040    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
9041    #[allow(clippy::too_many_arguments)]
9042    pub fn fa_prefill_w_arm(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
9043                            o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize,
9044                            n_head_kv: usize, t: usize, t_kv: usize, scale: f32, causal: bool,
9045                            window: usize, f32_stage: bool, floor: bool)
9046                            -> Result<(), Box<dyn std::error::Error>> {
9047        const BLOCK_Q: usize = 64; const BK: usize = 32;
9048        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
9049        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
9050        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
9051        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
9052        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9053        let p1 = !floor && !f32_stage
9054            && *P1_ON.get_or_init(|| {
9055                std::env::var("MEMRA_FAW_P1").map(|v| v != "0").unwrap_or(true)
9056            });
9057        let hp = p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0
9058            && (n_head / n_head_kv) % 2 == 0;
9059        if hp {
9060            const BLOCK_QH: usize = 32;
9061            let f = self.func("fa_prefill_w_bf16_p1h2");
9062            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK)
9063                       + 4 * (2 * BLOCK_QH)) as u32;
9064            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9065            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9066            let cfg = LaunchConfig {
9067                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
9068                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9069            };
9070            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32,
9071                n_head_kv as i32, t as i32, t_kv as i32, causal as i32, window as i32);
9072            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
9073            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
9074            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
9075            let __s_b = self.gpu.stream();
9076            let mut b = __s_b.launch_builder(&f);
9077            b.arg(&qb).arg(&kb).arg(&vh).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9078             .arg(&scale).arg(&cz).arg(&wi);
9079            unsafe { b.launch(cfg)?; }
9080            return Ok(());
9081        }
9082        if p1 {
9083            let f = self.func("fa_prefill_w_bf16_p1");
9084            let shmem = (2 * (2 * BK * head_dim + BLOCK_Q * BK)
9085                       + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
9086            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9087            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9088            let cfg = LaunchConfig {
9089                grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
9090                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9091            };
9092            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32,
9093                n_head_kv as i32, t as i32, t_kv as i32, causal as i32, window as i32);
9094            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
9095            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
9096            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
9097            let __s_b = self.gpu.stream();
9098            let mut b = __s_b.launch_builder(&f);
9099            b.arg(&qb).arg(&kb).arg(&vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9100             .arg(&scale).arg(&cz).arg(&wi);
9101            unsafe { b.launch(cfg)?; }
9102            return Ok(());
9103        }
9104        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
9105        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
9106        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9107        let g4 = !floor && !f32_stage && n_head_kv == 1 && n_head % 4 == 0
9108            && *G4_ON.get_or_init(|| {
9109                std::env::var("MEMRA_FAW_G4").map(|v| v != "0").unwrap_or(true)
9110            });
9111        if g4 {
9112            const SP_M: usize = 16;
9113            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
9114            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
9115            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9116            let o2 = *O2_ON.get_or_init(|| {
9117                std::env::var("MEMRA_FAW_O2").map(|v| v != "0").unwrap_or(true)
9118            });
9119            let f = self.func(if o2 { "fa_prefill_w_bf16_g4o2" } else { "fa_prefill_w_bf16_g4" });
9120            let shmem = if o2 {
9121                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
9122            } else {
9123                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK)
9124                    + 4 * (4 * SP_M)) as u32
9125            };
9126            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9127            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9128            let cfg = LaunchConfig {
9129                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
9130                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9131            };
9132            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32,
9133                n_head_kv as i32, t as i32, t_kv as i32, causal as i32, window as i32);
9134            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
9135            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
9136            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
9137            let __s_b = self.gpu.stream();
9138            let mut b = __s_b.launch_builder(&f);
9139            b.arg(&qb).arg(&kb).arg(&vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9140             .arg(&scale).arg(&cz).arg(&wi);
9141            unsafe { b.launch(cfg)?; }
9142            return Ok(());
9143        }
9144        let f = self.func(if floor { "fa_prefill_w_f32" }
9145                          else if f32_stage { "fa_prefill_w_f32_pp" }
9146                          else { "fa_prefill_w_bf16_pp" });
9147        let shmem = (2 * (2 * BK * head_dim + BLOCK_Q * BK)
9148                   + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
9149        use cudarc::driver::sys::CUfunction_attribute_enum as A;
9150        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9151        let cfg = LaunchConfig {
9152            grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
9153            block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9154        };
9155        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (head_dim as i32, n_head as i32, n_head_kv as i32,
9156                                                t as i32, t_kv as i32, causal as i32, window as i32);
9157        if f32_stage {
9158            let __s_b = self.gpu.stream();
9159            let mut b = __s_b.launch_builder(&f);
9160            b.arg(q).arg(k).arg(v).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9161             .arg(&scale).arg(&cz).arg(&wi);
9162            unsafe { b.launch(cfg)?; }
9163        } else {
9164            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
9165            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
9166            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
9167            let __s_b = self.gpu.stream();
9168            let mut b = __s_b.launch_builder(&f);
9169            b.arg(&qb).arg(&kb).arg(&vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9170             .arg(&scale).arg(&cz).arg(&wi);
9171            unsafe { b.launch(cfg)?; }
9172        }
9173        Ok(())
9174    }
9175
9176    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
9177    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
9178    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
9179    #[allow(clippy::too_many_arguments)]
9180    pub fn fa_prefill_hd512(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
9181                            o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize,
9182                            n_head_kv: usize, t: usize, t_kv: usize, scale: f32, causal: bool)
9183                            -> Result<(), Box<dyn std::error::Error>> {
9184        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
9185        if portable_mma_gated() {
9186            return self.sdpa_naive(q, k, v, o, head_dim, n_head, n_head_kv,
9187                                   t, t_kv, scale, causal);
9188        }
9189        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
9190        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
9191        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
9192        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
9193        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
9194        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9195        let f32_stage = *F32_STAGE.get_or_init(|| {
9196            std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32")
9197        });
9198        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
9199        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
9200        // Own numeric config (partial-sum order) — battery-gated.
9201        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9202        let sp = !f32_stage
9203            && *SP_ON.get_or_init(|| {
9204                std::env::var("MEMRA_FA512_SP").map(|v| v != "0").unwrap_or(true)
9205            });
9206        self.fa_prefill_hd512_arm(q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale,
9207                                  causal, f32_stage, sp, sp && fa_f16pv_on())
9208    }
9209
9210    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
9211    #[allow(clippy::too_many_arguments)]
9212    pub fn fa_prefill_hd512_pre(&self, qb: &CudaSlice<u8>, kb: &CudaSlice<u8>, vb: &CudaSlice<u8>,
9213                                o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize,
9214                                n_head_kv: usize, t: usize, t_kv: usize, scale: f32, causal: bool,
9215                                v_f16: bool)
9216                                -> Result<(), Box<dyn std::error::Error>> {
9217        debug_assert_eq!(head_dim, 512);
9218        const SP_M: usize = 16; const BKS: usize = 32;
9219        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
9220        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
9221        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
9222        let f16pv = fa_f16pv_on();
9223        let nw = if f16pv { fa512_wide_warps() } else { 2 };
9224        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
9225        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
9226        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
9227        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
9228            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
9229            let n = t_kv * n_head_kv * head_dim;
9230            let need = n * 2;
9231            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
9232                *vguard = Some(self.alloc_uninit::<u8>(need)?);
9233            }
9234            let dst = vguard.as_mut().unwrap();
9235            self.bf16_to_f16_into(vb, n, dst)?;
9236            vguard.as_ref().unwrap()
9237        } else { vb };
9238        let f = self.func(if hp { "fa_prefill_bf16_hd512_sp16h2" }
9239                          else { match (f16pv, nw) {
9240                              (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
9241                              (true, _) => "fa_prefill_bf16_hd512_sp16",
9242                              _ => "fa_prefill_bf16_hd512_sp",
9243                          } });
9244        let (nwarp, npart) = if hp { (4usize, 4usize) } else if nw > 2 { (nw, nw) } else { (2, 1) };
9245        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
9246        let shmem = if hp {
9247            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
9248               + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
9249        } else {
9250            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
9251               + 4 * (npart * SP_M * BKS + SP_M)) as u32
9252        };
9253        use cudarc::driver::sys::CUfunction_attribute_enum as A;
9254        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9255        let grid_y = if hp { (n_head / 2) as u32 } else { n_head as u32 };
9256        let cfg = LaunchConfig {
9257            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
9258            block_dim: (32, nwarp as u32, 1), shared_mem_bytes: shmem,
9259        };
9260        let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32,
9261                                            t as i32, t_kv as i32, causal as i32);
9262        let __s_b = self.gpu.stream();
9263        let mut b = __s_b.launch_builder(&f);
9264        b.arg(qb).arg(kb).arg(vref).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9265         .arg(&scale).arg(&cz);
9266        unsafe { b.launch(cfg)?; }
9267        Ok(())
9268    }
9269
9270    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
9271    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
9272    #[allow(clippy::too_many_arguments)]
9273    pub fn fa_prefill_hd512_arm(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
9274                                o: &mut CudaSlice<f32>, head_dim: usize, n_head: usize,
9275                                n_head_kv: usize, t: usize, t_kv: usize, scale: f32, causal: bool,
9276                                f32_stage: bool, sp: bool, f16pv: bool)
9277                                -> Result<(), Box<dyn std::error::Error>> {
9278        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
9279        if sp && !f32_stage {
9280            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
9281            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
9282            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
9283            const SP_M: usize = 16; const BKS: usize = 32;
9284            let nw = if f16pv { fa512_wide_warps() } else { 2 };
9285            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
9286            let f = self.func(if hp { "fa_prefill_bf16_hd512_sp16h2" }
9287                              else { match (f16pv, nw) {
9288                                  (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
9289                                  (true, _) => "fa_prefill_bf16_hd512_sp16",
9290                                  _ => "fa_prefill_bf16_hd512_sp",
9291                              } });
9292            let (nwarp, npart) = if hp { (4usize, 4usize) } else if nw > 2 { (nw, nw) } else { (2, 1) };
9293            let shmem = if hp {
9294                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
9295                   + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
9296            } else {
9297                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
9298                   + 4 * (npart * SP_M * BKS + SP_M)) as u32
9299            };
9300            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9301            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9302            let grid_y = if hp { (n_head / 2) as u32 } else { n_head as u32 };
9303            let cfg = LaunchConfig {
9304                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
9305                block_dim: (32, nwarp as u32, 1), shared_mem_bytes: shmem,
9306            };
9307            let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32,
9308                                                t as i32, t_kv as i32, causal as i32);
9309            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
9310            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
9311            let vb = if f16pv { self.f32_to_f16(v, t_kv * n_head_kv * head_dim)? }
9312                     else { self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)? };
9313            let __s_b = self.gpu.stream();
9314            let mut b = __s_b.launch_builder(&f);
9315            b.arg(&qb).arg(&kb).arg(&vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9316             .arg(&scale).arg(&cz);
9317            unsafe { b.launch(cfg)?; }
9318            return Ok(());
9319        }
9320        const BLOCK_Q: usize = 32; const BK: usize = 32; const HALF: usize = 256;
9321        let f = self.func(if f32_stage { "fa_prefill_f32_hd512" } else { "fa_prefill_bf16_hd512" });
9322        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
9323        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
9324                   + 4 * BLOCK_Q) as u32;
9325        use cudarc::driver::sys::CUfunction_attribute_enum as A;
9326        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9327        let cfg = LaunchConfig {
9328            grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 2),
9329            block_dim: (32, 2, 1), shared_mem_bytes: shmem,
9330        };
9331        let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32,
9332                                            t as i32, t_kv as i32, causal as i32);
9333        if f32_stage {
9334            let __s_b = self.gpu.stream();
9335            let mut b = __s_b.launch_builder(&f);
9336            b.arg(q).arg(k).arg(v).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9337             .arg(&scale).arg(&cz);
9338            unsafe { b.launch(cfg)?; }
9339        } else {
9340            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
9341            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
9342            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
9343            let __s_b = self.gpu.stream();
9344            let mut b = __s_b.launch_builder(&f);
9345            b.arg(&qb).arg(&kb).arg(&vb).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi)
9346             .arg(&scale).arg(&cz);
9347            unsafe { b.launch(cfg)?; }
9348        }
9349        Ok(())
9350    }
9351
9352    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
9353    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
9354    /// separate f32_to_bf16 the FA entries would run).
9355    #[allow(clippy::too_many_arguments)]
9356    pub fn rope_neox2_bf16e(&self, q: &mut CudaSlice<f32>, k: &mut CudaSlice<f32>,
9357                            qb: &mut CudaSlice<u8>, kb: &mut CudaSlice<u8>,
9358                            pos: &CudaSlice<i32>, head_dim: usize, n_dims: usize,
9359                            nh_q: usize, nh_k: usize, n_tokens: usize, base: f32,
9360                            freq_scale: f32, ff: Option<&CudaSlice<f32>>)
9361                            -> Result<(), Box<dyn std::error::Error>> {
9362        let f = self.func("rope_neox2_bf16e_f32");
9363        let rows = ((nh_q + nh_k) * n_tokens) as u32;
9364        let cfg = LaunchConfig { grid_dim: (rows, 1, 1),
9365                                 block_dim: ((head_dim / 2) as u32, 1, 1), shared_mem_bytes: 0 };
9366        let theta_scale = base.powf(-2.0 / n_dims as f32);
9367        let (hd, nd, nhq, nhk, nt) = (head_dim as i32, n_dims as i32, nh_q as i32,
9368                                      nh_k as i32, n_tokens as i32);
9369        let __s_b = self.gpu.stream();
9370        let mut b = __s_b.launch_builder(&f);
9371        match ff {
9372            Some(t) => { b.arg(&mut *q).arg(&mut *k).arg(&mut *qb).arg(&mut *kb).arg(pos)
9373                          .arg(&hd).arg(&nd).arg(&nhq).arg(&nhk).arg(&nt)
9374                          .arg(&theta_scale).arg(&freq_scale).arg(t);
9375                         unsafe { b.launch(cfg)?; } }
9376            None => { let null: u64 = 0;
9377                      b.arg(&mut *q).arg(&mut *k).arg(&mut *qb).arg(&mut *kb).arg(pos)
9378                       .arg(&hd).arg(&nd).arg(&nhq).arg(&nhk).arg(&nt)
9379                       .arg(&theta_scale).arg(&freq_scale).arg(&null);
9380                      unsafe { b.launch(cfg)?; } }
9381        }
9382        Ok(())
9383    }
9384
9385    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
9386    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
9387    pub fn f32_to_bf16(&self, x: &CudaSlice<f32>, n: usize)
9388                       -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9389        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
9390        let mut y = self.alloc_uninit::<u8>(n * 2)?;
9391        let f = self.func("f32_to_bf16_flat");
9392        let n_i = n as i64;
9393        let cfg = LaunchConfig {
9394            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
9395            block_dim: (256, 1, 1), shared_mem_bytes: 0,
9396        };
9397        let __s_b = self.gpu.stream();
9398        let mut b = __s_b.launch_builder(&f);
9399        b.arg(x).arg(&mut y).arg(&n_i);
9400        unsafe { b.launch(cfg)?; }
9401        Ok(y)
9402    }
9403
9404    pub fn f32_to_f16(&self, x: &CudaSlice<f32>, n: usize)
9405                      -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9406        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
9407        let mut y = self.alloc_uninit::<u8>(n * 2)?;
9408        let f = self.func("f32_to_f16_flat");
9409        let n_i = n as i64;
9410        let cfg = LaunchConfig {
9411            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
9412            block_dim: (256, 1, 1), shared_mem_bytes: 0,
9413        };
9414        let __s_b = self.gpu.stream();
9415        let mut b = __s_b.launch_builder(&f);
9416        b.arg(x).arg(&mut y).arg(&n_i);
9417        unsafe { b.launch(cfg)?; }
9418        Ok(y)
9419    }
9420
9421    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
9422    pub fn bf16_to_f16(&self, xb: &CudaSlice<u8>, n: usize)
9423                       -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
9424        let mut y = self.alloc_uninit::<u8>(n * 2)?;
9425        self.bf16_to_f16_into(xb, n, &mut y)?;
9426        Ok(y)
9427    }
9428
9429    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
9430    pub fn bf16_to_f16_into(&self, xb: &CudaSlice<u8>, n: usize, y: &mut CudaSlice<u8>)
9431                            -> Result<(), Box<dyn std::error::Error>> {
9432        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
9433        assert!(y.len() >= n * 2);
9434        let f = self.func("bf16_to_f16_flat");
9435        let n2 = (n / 2) as i64;
9436        let cfg = LaunchConfig {
9437            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
9438            block_dim: (256, 1, 1), shared_mem_bytes: 0,
9439        };
9440        let __s_b = self.gpu.stream();
9441        let mut b = __s_b.launch_builder(&f);
9442        b.arg(xb).arg(y).arg(&n2);
9443        unsafe { b.launch(cfg)?; }
9444        Ok(())
9445    }
9446
9447    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
9448    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
9449    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
9450    /// head_dim in {256, 128}, bf16kv lane on.
9451    #[allow(clippy::too_many_arguments)]
9452    pub fn fa_prefill_vl8(&self, seqs: &[FaSeqVl], head_dim: usize, n_head: usize,
9453                          n_head_kv: usize, scale: f32)
9454                          -> Result<(), Box<dyn std::error::Error>> {
9455        const BK: usize = 32;
9456        let b = seqs.len();
9457        assert!(b >= 1 && b <= 8);
9458        let mut packed = [FaSeqVl::default(); 8];
9459        packed[..b].copy_from_slice(seqs);
9460        let v = FaVl8(packed);
9461        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
9462        let ept = (n_head_kv * head_dim) as i32;
9463        {
9464            let f = self.func("fa_mirror_vl");
9465            let max_n = (max_t as i64) * ept as i64;
9466            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
9467            for which in 0..2i32 {
9468                let cfg = LaunchConfig { grid_dim: (blocks, 1, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
9469                let __s_lb = self.gpu.stream();
9470                let mut lb = __s_lb.launch_builder(&f);
9471                lb.arg(&v).arg(&ept).arg(&which);
9472                unsafe { lb.launch(cfg)?; }
9473            }
9474        }
9475        let hd_sfx = fa_hd_suffix(head_dim)?;
9476        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
9477        let block_q = 64usize;
9478        let kv_stages = 2usize;
9479        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
9480                   + 4 * (block_q * BK + 2 * block_q)) as u32;
9481        use cudarc::driver::sys::CUfunction_attribute_enum as A;
9482        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9483        let cfg = LaunchConfig {
9484            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
9485            block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9486        };
9487        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
9488        let __s_lb = self.gpu.stream();
9489        let mut lb = __s_lb.launch_builder(&f);
9490        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
9491        unsafe { lb.launch(cfg)?; }
9492        Ok(())
9493    }
9494
9495    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
9496    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
9497    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
9498    #[allow(clippy::too_many_arguments)]
9499    pub fn attn_pre_vl8(&self, seqs: &[AttnPreVl], wq: &CudaSlice<f32>, wk: &CudaSlice<f32>,
9500                        head_dim: usize, rope_dims: usize, n_head: usize, n_head_kv: usize,
9501                        eps: f32, freq_base: f32, freq_scale: f32,
9502                        kv_dim_k: usize, kv_dim_v: usize,
9503                        k_tok_bytes: usize, v_tok_bytes: usize)
9504                        -> Result<(), Box<dyn std::error::Error>> {
9505        let b = seqs.len();
9506        assert!(b >= 1 && b <= 8);
9507        let mut packed = [AttnPreVl::default(); 8];
9508        packed[..b].copy_from_slice(seqs);
9509        let v = AttnPreVl8(packed);
9510        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
9511        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
9512        {
9513            let f = self.func("q_gate_split_vl");
9514            let n = max_t * (n_head * head_dim) as u32;
9515            let cfg = LaunchConfig { grid_dim: (n.div_ceil(256), 1, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
9516            let __s_lb = self.gpu.stream();
9517            let mut lb = __s_lb.launch_builder(&f);
9518            lb.arg(&v).arg(&hd).arg(&nh);
9519            unsafe { lb.launch(cfg)?; }
9520        }
9521        {
9522            let f = self.func("attn_rms_vl");
9523            let cfg = LaunchConfig { grid_dim: (max_t * n_head as u32, 2, b as u32), block_dim: (rms_block(), 1, 1), shared_mem_bytes: 0 };
9524            let __s_lb = self.gpu.stream();
9525            let mut lb = __s_lb.launch_builder(&f);
9526            lb.arg(&v).arg(wq).arg(wk).arg(&hd).arg(&nh).arg(&nhkv).arg(&eps);
9527            unsafe { lb.launch(cfg)?; }
9528        }
9529        {
9530            let f = self.func("attn_rope_vl");
9531            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
9532            let nd = rope_dims as i32;
9533            let cfg = LaunchConfig { grid_dim: (max_t * n_head as u32, 2, b as u32), block_dim: ((head_dim / 2) as u32, 1, 1), shared_mem_bytes: 0 };
9534            let __s_lb = self.gpu.stream();
9535            let mut lb = __s_lb.launch_builder(&f);
9536            lb.arg(&v).arg(&hd).arg(&nd).arg(&nh).arg(&nhkv).arg(&theta_scale).arg(&freq_scale);
9537            unsafe { lb.launch(cfg)?; }
9538        }
9539        {
9540            let f = self.func("append_kv_vl");
9541            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
9542            let cfg = LaunchConfig { grid_dim: (nblk, max_t, b as u32), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
9543            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
9544            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9545            let __s_lb = self.gpu.stream();
9546            let mut lb = __s_lb.launch_builder(&f);
9547            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
9548            unsafe { lb.launch(cfg)?; }
9549        }
9550        Ok(())
9551    }
9552
9553    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
9554    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
9555    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
9556    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
9557    pub fn fa_prefill_view(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
9558                           v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
9559                           head_dim: usize, n_head: usize, n_head_kv: usize,
9560                           t: usize, t_kv: usize, scale: f32, causal: bool,
9561                           k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
9562                           -> Result<(), Box<dyn std::error::Error>> {
9563        if portable_mma_gated() {
9564            return self.sdpa_naive_quantized_view(q, k, v, o, head_dim, n_head, n_head_kv,
9565                                                  t, t_kv, scale, causal,
9566                                                  k_tok_bytes, v_tok_bytes);
9567        }
9568        const BLOCK_Q: usize = 64; const BK: usize = 32;
9569        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
9570        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
9571        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
9572        let f = if g { self.func_g(&name) } else { self.func(&name) };
9573        let shmem = (2 * (2 * BK * head_dim + BLOCK_Q * BK)
9574                   + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
9575        use cudarc::driver::sys::CUfunction_attribute_enum as A;
9576        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9577        let cfg = LaunchConfig {
9578            grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
9579            block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9580        };
9581        let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
9582        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9583        let __s_b = self.gpu.stream();
9584        let mut b = __s_b.launch_builder(&f);
9585        b.arg(q).arg(k).arg(v).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi).arg(&scale).arg(&cz)
9586         .arg(&ktb).arg(&vtb);
9587        unsafe { b.launch(cfg)?; }
9588        Ok(())
9589    }
9590
9591    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
9592    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
9593    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
9594    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
9595    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
9596    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
9597    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
9598    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
9599    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
9600    #[allow(clippy::too_many_arguments)]
9601    pub fn fa_prefill_view_ws(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
9602                              v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
9603                              head_dim: usize, n_head: usize, n_head_kv: usize,
9604                              t: usize, t_kv: usize, scale: f32, causal: bool,
9605                              k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
9606                              -> Result<(), Box<dyn std::error::Error>> {
9607        if portable_mma_gated() {
9608            return self.sdpa_naive_quantized_view(q, k, v, o, head_dim, n_head, n_head_kv,
9609                                                  t, t_kv, scale, causal,
9610                                                  k_tok_bytes, v_tok_bytes);
9611        }
9612        const BLOCK_Q: usize = 64; const BK: usize = 32;
9613        let kv_dim_k = n_head_kv * head_dim;
9614        let kv_dim_v = n_head_kv * head_dim;
9615        let k_ws_bytes = t_kv * kv_dim_k * 2;   // bf16
9616        let v_ws_bytes = t_kv * kv_dim_v * 2;
9617        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
9618        let mut guard = self.prime_deqw_ws.lock().unwrap();
9619        let need_grow = match guard.as_ref() {
9620            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
9621            None => true,
9622        };
9623        if need_grow {
9624            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
9625            let (ck, cv) = guard.as_ref().map(|(a, b)| (a.len(), b.len())).unwrap_or((0, 0));
9626            *guard = Some((self.alloc_u8(grow(ck, k_ws_bytes))?, self.alloc_u8(grow(cv, v_ws_bytes))?));
9627        }
9628        let (kw, vw) = guard.as_mut().unwrap();
9629        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
9630        {
9631            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
9632            let f = if g { self.func_g("fa_dequant_kv_ws_bf16") } else { self.func("fa_dequant_kv_ws_bf16") };
9633            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
9634            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
9635            let cfg = LaunchConfig { grid_dim: (nblk.max(1), 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
9636            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
9637            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9638            let __s_b = self.gpu.stream();
9639            let mut b = __s_b.launch_builder(&f);
9640            b.arg(k).arg(v).arg(&mut *kw).arg(&mut *vw).arg(&kdk).arg(&kdv).arg(&tkvi).arg(&ktb).arg(&vtb);
9641            unsafe { b.launch(cfg)?; }
9642        }
9643        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
9644        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
9645        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
9646        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
9647        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
9648        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
9649        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
9650        let db = std::env::var("MEMRA_PRIME_DEQW_DB").map(|v| v != "0").unwrap_or(true);
9651        {
9652            let hd_sfx = fa_hd_suffix(head_dim)?;
9653            let f = self.func(&format!("fa_prefill_qw{}{hd_sfx}", if db { "_db" } else { "" }));
9654            let shmem = if db {
9655                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
9656                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
9657            } else {
9658                (2 * (2 * BK * head_dim + BLOCK_Q * BK)
9659                       + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
9660            };
9661            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9662            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9663            let cfg = LaunchConfig {
9664                grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
9665                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9666            };
9667            let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
9668            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
9669            let __s_b = self.gpu.stream();
9670            let mut b = __s_b.launch_builder(&f);
9671            b.arg(q).arg(&*kw).arg(&*vw).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi).arg(&scale).arg(&cz)
9672             .arg(&kdk).arg(&kdv);
9673            unsafe { b.launch(cfg)?; }
9674        }
9675        Ok(())
9676    }
9677
9678    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
9679    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
9680    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
9681    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
9682    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
9683    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
9684    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
9685    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
9686    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
9687    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
9688    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
9689    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
9690    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
9691    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
9692    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
9693    #[allow(clippy::too_many_arguments)]
9694    pub fn fa_prefill_view_ws_w_hd128(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
9695                                      v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
9696                                      head_dim: usize, n_head: usize, n_head_kv: usize,
9697                                      t: usize, t_kv: usize, scale: f32, causal: bool,
9698                                      window: usize, k_tok_bytes: usize, v_tok_bytes: usize)
9699                                      -> Result<(), Box<dyn std::error::Error>> {
9700        assert_eq!(head_dim, 128, "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped");
9701        if portable_mma_gated() {
9702            return self.sdpa_naive_w_quantized_view(q, k, v, o, head_dim, n_head, n_head_kv,
9703                                                    t, t_kv, scale, causal, window,
9704                                                    k_tok_bytes, v_tok_bytes);
9705        }
9706        const BLOCK_Q: usize = 64; const BK: usize = 32;
9707        let kv_dim_k = n_head_kv * head_dim;
9708        let kv_dim_v = n_head_kv * head_dim;
9709        let k_ws_bytes = t_kv * kv_dim_k * 2;   // bf16
9710        let v_ws_bytes = t_kv * kv_dim_v * 2;
9711        let mut guard = self.prime_deqw_ws.lock().unwrap();
9712        let need_grow = match guard.as_ref() {
9713            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
9714            None => true,
9715        };
9716        if need_grow {
9717            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
9718            let (ck, cv) = guard.as_ref().map(|(a, b)| (a.len(), b.len())).unwrap_or((0, 0));
9719            *guard = Some((self.alloc_u8(grow(ck, k_ws_bytes))?, self.alloc_u8(grow(cv, v_ws_bytes))?));
9720        }
9721        let (kw, vw) = guard.as_mut().unwrap();
9722        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
9723        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
9724        {
9725            let f = self.func("fa_dequant_kv_ws_bf16");
9726            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
9727            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
9728            let cfg = LaunchConfig { grid_dim: (nblk.max(1), 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
9729            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
9730            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9731            let __s_b = self.gpu.stream();
9732            let mut b = __s_b.launch_builder(&f);
9733            b.arg(k).arg(v).arg(&mut *kw).arg(&mut *vw).arg(&kdk).arg(&kdv).arg(&tkvi).arg(&ktb).arg(&vtb);
9734            unsafe { b.launch(cfg)?; }
9735        }
9736        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
9737        let db = std::env::var("MEMRA_PRIME_DEQW_DB").map(|v| v != "0").unwrap_or(true);
9738        {
9739            let f = self.func(if db { "fa_prefill_qw_db_w_hd128" } else { "fa_prefill_qw_w_hd128" });
9740            let shmem = if db {
9741                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
9742            } else {
9743                (2 * (2 * BK * head_dim + BLOCK_Q * BK)
9744                       + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
9745            };
9746            use cudarc::driver::sys::CUfunction_attribute_enum as A;
9747            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9748            let cfg = LaunchConfig {
9749                grid_dim: ((t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32, n_head as u32, 1),
9750                block_dim: (32, 4, 1), shared_mem_bytes: shmem,
9751            };
9752            let (hd, nh, nhkv, ti, tkvi, cz) = (head_dim as i32, n_head as i32, n_head_kv as i32, t as i32, t_kv as i32, causal as i32);
9753            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
9754            let __s_b = self.gpu.stream();
9755            let mut b = __s_b.launch_builder(&f);
9756            b.arg(q).arg(&*kw).arg(&*vw).arg(o).arg(&hd).arg(&nh).arg(&nhkv).arg(&ti).arg(&tkvi).arg(&scale).arg(&cz)
9757             .arg(&kdk).arg(&kdv).arg(&wnd);
9758            unsafe { b.launch(cfg)?; }
9759        }
9760        Ok(())
9761    }
9762
9763    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
9764    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
9765    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
9766    pub fn fa_decode(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
9767                     v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
9768                     head_dim: usize, n_head: usize, n_head_kv: usize, t_kv: usize, scale: f32,
9769                     k_tok_bytes: usize, v_tok_bytes: usize)
9770                     -> Result<(), Box<dyn std::error::Error>> {
9771        self.fa_decode_kvmod(q, k, v, o, head_dim, n_head, n_head_kv, t_kv, scale,
9772                             k_tok_bytes, v_tok_bytes, false)
9773    }
9774
9775    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
9776    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
9777    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
9778    #[allow(clippy::too_many_arguments)]
9779    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
9780    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
9781    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
9782    #[allow(clippy::too_many_arguments)]
9783    #[allow(clippy::too_many_arguments)]
9784    fn fa_decode_scalar_unified(&self, q: &cudarc::driver::CudaView<f32>,
9785                                k: &cudarc::driver::CudaView<u8>,
9786                                v: &cudarc::driver::CudaView<u8>,
9787                                o: &mut cudarc::driver::CudaViewMut<f32>,
9788                                head_dim: usize, n_head: usize, n_head_kv: usize,
9789                                t_kv_host: usize, t_kv_dev: Option<&CudaSlice<i32>>,
9790                                scale: f32, n_splits: usize, split_keys: usize,
9791                                k_tok_bytes: usize, v_tok_bytes: usize, g: bool,
9792                                part_o: &mut CudaSlice<f32>, part_m: &mut CudaSlice<f32>,
9793                                part_l: &mut CudaSlice<f32>,
9794                                q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>)
9795                                -> Result<(), Box<dyn std::error::Error>> {
9796        let f = if g { self.func_g("fa_decode_f32") } else { self.fa_func("fa_decode_f32", head_dim) };
9797        let cfg = LaunchConfig { grid_dim: (n_head as u32, n_splits as u32, 1),
9798            block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: (4 * (head_dim + 32)) as u32 };
9799        let (hd, nh, nhkv, nsp) = (head_dim as i32, n_head as i32, n_head_kv as i32, n_splits as i32);
9800        let (ktb, vtb, tkvi, ski) = (k_tok_bytes as i64, v_tok_bytes as i64, t_kv_host as i32,
9801                                     split_keys as i32);
9802        let __s_b = self.gpu.stream();
9803        let mut b = __s_b.launch_builder(&f);
9804        match t_kv_dev {
9805            Some(d) => { b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
9806                          .arg(&hd).arg(&nh).arg(&nhkv).arg(&tkvi).arg(d).arg(&scale).arg(&nsp)
9807                          .arg(&ski).arg(&ktb).arg(&vtb);
9808                         unsafe { b.launch(cfg)?; } }
9809            None => { let null: u64 = 0;
9810                      b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
9811                       .arg(&hd).arg(&nh).arg(&nhkv).arg(&tkvi).arg(&null).arg(&scale).arg(&nsp)
9812                       .arg(&ski).arg(&ktb).arg(&vtb);
9813                      unsafe { b.launch(cfg)?; } }
9814        }
9815        let cfg2 = LaunchConfig { grid_dim: (n_head as u32, 1, 1),
9816            block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
9817        if let Some((oq, od)) = q8_out {
9818            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
9819            let fc = if g { self.func_g("fa_decode_combine_q8_1") }
9820                     else { self.fa_func("fa_decode_combine_q8_1", head_dim) };
9821            let __s_b2 = self.gpu.stream();
9822            let mut b2 = __s_b2.launch_builder(&fc);
9823            b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(oq).arg(od).arg(&hd).arg(&nh).arg(&nsp);
9824            unsafe { b2.launch(cfg2)?; }
9825            return Ok(());
9826        }
9827        let fc = if g { self.func_g("fa_decode_combine_f32") } else { self.fa_func("fa_decode_combine_f32", head_dim) };
9828        let __s_b2 = self.gpu.stream();
9829        let mut b2 = __s_b2.launch_builder(&fc);
9830        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh).arg(&nsp);
9831        unsafe { b2.launch(cfg2)?; }
9832        Ok(())
9833    }
9834
9835    pub fn fa_decode_kvmod(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
9836                     v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
9837                     head_dim: usize, n_head: usize, n_head_kv: usize, t_kv: usize, scale: f32,
9838                     k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
9839                     -> Result<(), Box<dyn std::error::Error>> {
9840        let q_view = q.as_view();
9841        let mut o_view = o.as_view_mut();
9842        self.fa_decode_kvmod_view(&q_view, k, v, &mut o_view, head_dim, n_head, n_head_kv,
9843                                  t_kv, scale, k_tok_bytes, v_tok_bytes, g)
9844    }
9845
9846    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
9847    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
9848    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
9849    /// per-session KV view and FA launch.
9850    #[allow(clippy::too_many_arguments)]
9851    pub fn fa_decode_kvmod_view(&self, q: &cudarc::driver::CudaView<f32>,
9852                     k: &cudarc::driver::CudaView<u8>, v: &cudarc::driver::CudaView<u8>,
9853                     o: &mut cudarc::driver::CudaViewMut<f32>,
9854                     head_dim: usize, n_head: usize, n_head_kv: usize, t_kv: usize, scale: f32,
9855                     k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
9856                     -> Result<(), Box<dyn std::error::Error>> {
9857        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
9858        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
9859        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
9860        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
9861        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
9862        //
9863        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
9864        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
9865        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
9866        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
9867        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
9868        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
9869        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
9870        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
9871        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
9872        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
9873        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
9874        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
9875        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
9876        // fall to the exact scalar there instead of the broken register arm.
9877        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
9878        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
9879        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
9880        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
9881        if g && head_dim == 256 && !fa_v4_at(t_kv) { fa_vec = false; }
9882        let sp = fa_split_keys(t_kv, n_head_kv);
9883        let n_splits = if fa_vec { ((t_kv + sp - 1) / sp).max(1) } else { ((t_kv + 255) / 256).max(1) };
9884        let o_len = n_head * n_splits * head_dim;
9885        let ml_len = n_head * n_splits;
9886        let mut part_guard = self.fa_part_pool.lock().unwrap();
9887        if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
9888            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
9889            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
9890            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
9891            // later live allocations land at those addresses, and the next graph REPLAY writes
9892            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
9893            // output corruption began the burst after the trunk's t_kv growth first realloc'd
9894            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
9895            // the baked addresses alive (single-stream: eager writes the new buffers, replays
9896            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
9897            // (total retired < final size).
9898            let old = part_guard.take();
9899            let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
9900            if let Some(old) = old {
9901                self.fa_part_retired.lock().unwrap().push(old);
9902            }
9903            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
9904                eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
9905            }
9906            *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
9907                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
9908                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
9909        }
9910        let pg = part_guard.as_mut().unwrap();
9911        self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
9912        self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
9913        self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
9914        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
9915        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
9916        let (hd, nh, nhkv, tkvi, nsp) = (head_dim as i32, n_head as i32, n_head_kv as i32, t_kv as i32, n_splits as i32);
9917        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9918        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
9919        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
9920        // silently truncating the accumulator.
9921        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
9922        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
9923        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
9924        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
9925        // 178.4 -> 173.7 when 512 rode vec unconditionally).
9926        let fa512_min = fa512_min_tkv();
9927        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
9928        // g-module keeps the v4 pick (its class is not the depth-decay class).
9929        let deep = fa_vec && head_dim == 256 && fa_v4_at(t_kv) && !g
9930            && fa_deep_at(t_kv) && !matches!(fa_v4_mode(), "noB3" | "stage");
9931        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
9932            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
9933            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
9934            let gqa = (n_head / n_head_kv).max(1) as u32;
9935            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
9936            (fv, LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
9937                 block_dim: (32, gqa, 1), shared_mem_bytes: 0 })
9938        } else if fa_vec && head_dim <= 256 {
9939            let gqa = (n_head / n_head_kv).max(1) as u32;
9940            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
9941            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
9942            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
9943            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
9944            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
9945            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
9946            // dequant each tile ONCE per block.
9947            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
9948            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
9949            // there by 12x — latency, not bandwidth, rules small KV).
9950            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9951            let smem_tkv = *SMEM_TKV.get_or_init(|| {
9952                std::env::var("MEMRA_FA_SMEM_TKV").ok().and_then(|v| v.parse().ok())
9953                    .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
9954            });
9955            if fa_v4_at(t_kv) && head_dim == 256 {
9956                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
9957                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
9958                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
9959                let v4name = match fa_v4_mode() {
9960                    "noB3" => "fa_decode_vec_q_v4_noB3",     // phase probe (WRONG OUTPUT)
9961                    "stage" => "fa_decode_vec_q_v4_stage",   // phase probe (WRONG OUTPUT)
9962                    _ if deep => "fa_decode_vec_q_v4_deep",
9963                    _ => "fa_decode_vec_q_v4",
9964                };
9965                let fv = if g { self.func_g(v4name) } else { self.func(v4name) };
9966                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
9967                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
9968                let shmem = (if deep { 12160 } else { 11520 }
9969                             + 32 * head_dim * if g { 1 } else { 2 }) as u32;
9970                use cudarc::driver::sys::CUfunction_attribute_enum as A;
9971                fv.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
9972                (fv,
9973                 LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
9974                     block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
9975            } else if fa_v3_active(head_dim) {
9976                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
9977                // smem = sV only (half of v2's).
9978                let fv = if g { self.func_g("fa_decode_vec_q_v3") } else { self.func("fa_decode_vec_q_v3") };
9979                let shmem = (32 * head_dim * 2) as u32;      // sV bf16 [FA_DEC_TILE=32][hd]
9980                (fv,
9981                 LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
9982                     block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
9983            } else if fa_v2_on() {
9984                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
9985                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
9986                // partials; same 32KB sK+sV tile as the smem twin.
9987                let fv = if g { self.func_g("fa_decode_vec_q_v2") } else { self.func("fa_decode_vec_q_v2") };
9988                let shmem = (2 * 32 * head_dim * 2) as u32;   // sK+sV bf16 [FA_DEC_TILE=32][hd]
9989                (fv,
9990                 LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
9991                     block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
9992            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g
9993                && !(head_dim == 512 && Self::gkv_on()) {
9994                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
9995                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
9996                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
9997                let fv = if g { self.func_g("fa_decode_vec_q_smem") } else { self.func("fa_decode_vec_q_smem") };
9998                let shmem = (2 * 32 * head_dim * 2) as u32;   // sK+sV bf16 [FA_DEC_TILE=32][hd]
9999                use cudarc::driver::sys::CUfunction_attribute_enum as A;
10000                fv.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
10001                (fv,
10002                 LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10003                     block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
10004            } else {
10005                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
10006                // dequant, zero dynamic shared memory.
10007                let fv = if g { self.func_g("fa_decode_vec_q") } else { self.func("fa_decode_vec_q") };
10008                (fv,
10009                 LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10010                     block_dim: (32, gqa, 1), shared_mem_bytes: 0 })
10011            }
10012        } else {
10013            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
10014            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
10015            return self.fa_decode_scalar_unified(q, k, v, o, head_dim, n_head, n_head_kv,
10016                                                 t_kv, None, scale, n_splits,
10017                                                 if fa_vec { sp } else { 256 },
10018                                                 k_tok_bytes, v_tok_bytes, g,
10019                                                 part_o, part_m, part_l, None);
10020        };
10021        let __s_b = self.gpu.stream();
10022        let mut b = __s_b.launch_builder(&f);
10023        b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10024         .arg(&hd).arg(&nh).arg(&nhkv).arg(&tkvi).arg(&scale).arg(&nsp).arg(&ktb).arg(&vtb);
10025        unsafe { b.launch(cfg)?; }
10026        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
10027        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
10028        let (fc, cfg2) = (if g { self.func_g("fa_decode_combine_f32") } else { self.fa_func("fa_decode_combine_f32", head_dim) },
10029            LaunchConfig { grid_dim: (n_head as u32, 1, 1), block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 });
10030        let __s_b2 = self.gpu.stream();
10031        let mut b2 = __s_b2.launch_builder(&fc);
10032        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh).arg(&nsp);
10033        unsafe { b2.launch(cfg2)?; }
10034        Ok(())
10035    }
10036
10037    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
10038    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
10039    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
10040    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
10041    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
10042    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
10043    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
10044    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
10045    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
10046    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
10047    #[allow(clippy::too_many_arguments)]
10048    pub fn fa_decode_batch_seqs_v4(&self, q: &CudaSlice<f32>,
10049                                   kv_ptrs: &cudarc::driver::CudaView<u64>,
10050                                   pos_seq: &CudaSlice<i32>, o: &mut CudaSlice<f32>,
10051                                   head_dim: usize, n_head: usize, n_head_kv: usize,
10052                                   b_n: usize, t_kv_max: usize, scale: f32,
10053                                   split_keys: usize, k_tok_bytes: usize, v_tok_bytes: usize)
10054                                   -> Result<(), Box<dyn std::error::Error>> {
10055        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
10056        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
10057        let o_len = b_n * n_head * n_splits_max * head_dim;
10058        let ml_len = b_n * n_head * n_splits_max;
10059        let mut part_guard = self.fa_part_pool.lock().unwrap();
10060        if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
10061            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
10062            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
10063            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
10064            // later live allocations land at those addresses, and the next graph REPLAY writes
10065            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
10066            // output corruption began the burst after the trunk's t_kv growth first realloc'd
10067            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
10068            // the baked addresses alive (single-stream: eager writes the new buffers, replays
10069            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
10070            // (total retired < final size).
10071            let old = part_guard.take();
10072            let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
10073            if let Some(old) = old {
10074                self.fa_part_retired.lock().unwrap().push(old);
10075            }
10076            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
10077                eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
10078            }
10079            *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
10080                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
10081                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
10082        }
10083        let pg = part_guard.as_mut().unwrap();
10084        self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
10085        self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
10086        self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
10087        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
10088        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
10089        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
10090        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10091        let gqa = (n_head / n_head_kv).max(1) as u32;
10092        let f = self.func("fa_decode_vec_q_seqs_v4");
10093        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
10094        let shmem = (11520 + 32 * head_dim * 2) as u32;
10095        use cudarc::driver::sys::CUfunction_attribute_enum as A;
10096        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
10097        let cfg = LaunchConfig { grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
10098            block_dim: (32, gqa, 1), shared_mem_bytes: shmem };
10099        {
10100            let __s_b = self.gpu.stream();
10101            let mut b = __s_b.launch_builder(&f);
10102            b.arg(q).arg(kv_ptrs).arg(pos_seq).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10103             .arg(&hd).arg(&nh).arg(&nhkv).arg(&scale).arg(&nspm).arg(&spk).arg(&ktb).arg(&vtb);
10104            unsafe { b.launch(cfg)?; }
10105        }
10106        let fc = self.func("fa_decode_combine_seqs");
10107        let cfg2 = LaunchConfig { grid_dim: (n_head as u32, b_n as u32, 1),
10108            block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
10109        let __s_b2 = self.gpu.stream();
10110        let mut b2 = __s_b2.launch_builder(&fc);
10111        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh)
10112          .arg(pos_seq).arg(&nspm).arg(&spk);
10113        unsafe { b2.launch(cfg2)?; }
10114        Ok(())
10115    }
10116
10117    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
10118    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
10119    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
10120    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
10121    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
10122    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
10123    #[allow(clippy::too_many_arguments)]
10124    pub fn append_kv_quantized_seqs(&self, k_rows: &CudaSlice<f32>, v_rows: &CudaSlice<f32>,
10125                                    kv_ptrs: &cudarc::driver::CudaView<u64>,
10126                                    pos_seq: &CudaSlice<i32>, b_n: usize,
10127                                    kv_dim_k: usize, kv_dim_v: usize,
10128                                    k_tok_bytes: usize, v_tok_bytes: usize)
10129                                    -> Result<(), Box<dyn std::error::Error>> {
10130        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
10131        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
10132        let cfg = LaunchConfig { grid_dim: (nblk, b_n as u32, 1),
10133            block_dim: (32, 1, 1), shared_mem_bytes: 0 };
10134        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
10135        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10136        let __s_b = self.gpu.stream();
10137        let mut b = __s_b.launch_builder(&f);
10138        b.arg(k_rows).arg(v_rows).arg(kv_ptrs).arg(pos_seq)
10139         .arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
10140        unsafe { b.launch(cfg)?; }
10141        Ok(())
10142    }
10143
10144    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
10145    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
10146    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
10147    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
10148    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
10149    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
10150        std::env::var("MEMRA_NO_FA_VEC").is_err()
10151            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
10152            && base_len + 1 >= fa_vec_min_tkv()
10153            && head_dim <= 256 && head_dim % 32 == 0
10154    }
10155
10156    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
10157    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
10158    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
10159    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
10160    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
10161    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
10162    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
10163    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
10164    #[allow(clippy::too_many_arguments)]
10165    pub fn fa_decode_rows(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
10166                          v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
10167                          head_dim: usize, n_head: usize, n_head_kv: usize,
10168                          base_len: usize, t: usize, scale: f32,
10169                          k_tok_bytes: usize, v_tok_bytes: usize,
10170                          // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
10171                          // kernel; host base_len keeps sizing the splits/partials. hd256 twins
10172                          // keep the host arg. None is a bug for hd512 (asserted below).
10173                          base_dev: Option<(&CudaSlice<i32>, i32)>,
10174                          // K and V planes hold the same values (gemma globals, wv:=wk): pick
10175                          // the _kv twin — V plane never read, value rides the q8_0 key dq.
10176                          kv_shared: bool,
10177                          // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
10178                          // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
10179                          // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
10180                          g: bool,
10181                          // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
10182                          // (hd512 path) — the standalone quantize launch folds away.
10183                          mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>)
10184                          -> Result<(), Box<dyn std::error::Error>> {
10185        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
10186        let t_kv_max = base_len + t;                       // LAST row's key bound
10187        let mut sp = fa_split_keys(t_kv_max, n_head_kv);   // env/default — same value every row
10188        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
10189        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
10190        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
10191        // (parity law), so the partition is freely tunable — verify and decode move together.
10192        if head_dim == 512 {
10193            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10194            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
10195            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
10196            let v = *SP512.get_or_init(|| std::env::var("MEMRA_FA_SP512").ok()
10197                .and_then(|x| x.parse().ok()).unwrap_or(0));
10198            sp = if v >= 8 { v } else { FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed) };
10199        }
10200        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
10201        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10202        let gqa = (n_head / n_head_kv).max(1) as u32;
10203        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
10204        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
10205        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
10206        // the different partition changes the combine's FP order (greedy tie flips at depth;
10207        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
10208        // consecutive rows by their OWN ladder value and launch once per group — each row then
10209        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
10210        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
10211        // sp override is t_kv-independent by construction).
10212        let mut groups: Vec<(usize, usize, usize)> = Vec::new();   // (row0, t_g, sp_g)
10213        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
10214            groups.push((0, t, sp));
10215        } else {
10216            let mut r0 = 0usize;
10217            while r0 < t {
10218                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
10219                let mut r1 = r0 + 1;
10220                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g { r1 += 1; }
10221                groups.push((r0, r1 - r0, sp_g));
10222                r0 = r1;
10223            }
10224        }
10225        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
10226        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
10227        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
10228        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10229        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
10230            std::env::var("MEMRA_FA_SMEM_TKV").ok().and_then(|v| v.parse().ok())
10231                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
10232        });
10233        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
10234        let v3 = fa_v3_active(head_dim);
10235        let smem_rows = head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
10236        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
10237        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
10238        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
10239        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
10240        let _ = kv_shared;
10241        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
10242        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
10243        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
10244        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
10245        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
10246        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
10247        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
10248        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
10249        // (kv_head, split) stages its tile once and loops the rows over it — kills the
10250        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
10251        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
10252        // shared by every hd512 caller through this wrapper (decode+verify flip together;
10253        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
10254        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
10255        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
10256        // not unpack-bound; jsonl 2026-07-14.
10257        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10258        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
10259        let tb512 = head_dim == 512 && sp <= 32 && n_head / n_head_kv.max(1) <= 16
10260            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
10261        let fname = if tb512 { "fa_decode_vec_q_rows_v4_512_tb" }
10262                    else if i2 { "fa_decode_vec_q_rows_dpl16_i2" }
10263                    else if head_dim == 512 { "fa_decode_vec_q_rows_dpl16" }   // gemma globals (parity law)
10264                    else if v4 { "fa_decode_vec_q_rows_v4" }
10265                    else if v3 { "fa_decode_vec_q_rows_v3" }
10266                    else if fa_v2_on() { "fa_decode_vec_q_rows_v2" }
10267                    else if smem_rows { "fa_decode_vec_q_rows_smem" }
10268                    else { "fa_decode_vec_q_rows" };
10269        let f = if head_dim == 512 { self.fa_func(fname, head_dim) }
10270                else if g {
10271                    // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
10272                    // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
10273                    // g-module rows against decode's g-module v4 — different programs, short-VG
10274                    // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
10275                    // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
10276                    // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
10277                    // dq macros are format-aware.
10278                    self.func_g(if smem_rows { "fa_decode_vec_q_rows" } else { fname })
10279                }
10280                else { self.func(fname) };
10281        let shmem = if tb512 {
10282            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
10283            let gk = Self::gkv_on();
10284            let sh = (8192 + 1024 + 32 * 512 + 32 * 64
10285                      + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
10286            use cudarc::driver::sys::CUfunction_attribute_enum as A;
10287            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
10288            sh
10289        } else if v4 || v3 || smem_rows || fa_v2_on() {
10290            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
10291            let sh = (if v4 { 11520 + 32 * head_dim * if g { 1 } else { 2 } }
10292                      else if v3 { 32 * head_dim * 2 } else { 2 * 32 * head_dim * 2 }) as u32;
10293            use cudarc::driver::sys::CUfunction_attribute_enum as A;
10294            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
10295            sh
10296        } else { 0 };
10297        // Per-GROUP launches (single group in the common case — identical to the pre-fix
10298        // single launch there): each group gets its own partials (the rows kernel indexes
10299        // partials by its LOCAL grid.z row) and q/o row-offset views.
10300        for &(r0, t_g, sp_g) in &groups {
10301            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
10302            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
10303            let base_i = (base_len + r0) as i32;
10304            let o_len = t_g * n_head * n_splits_g * head_dim;
10305            let ml_len = t_g * n_head * n_splits_g;
10306            let mut part_guard = self.fa_part_pool.lock().unwrap();
10307        if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
10308            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
10309            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
10310            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
10311            // later live allocations land at those addresses, and the next graph REPLAY writes
10312            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
10313            // output corruption began the burst after the trunk's t_kv growth first realloc'd
10314            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
10315            // the baked addresses alive (single-stream: eager writes the new buffers, replays
10316            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
10317            // (total retired < final size).
10318            let old = part_guard.take();
10319            let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
10320            if let Some(old) = old {
10321                self.fa_part_retired.lock().unwrap().push(old);
10322            }
10323            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
10324                eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
10325            }
10326            *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
10327                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
10328                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
10329        }
10330        let pg = part_guard.as_mut().unwrap();
10331        self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
10332        self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
10333        self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
10334        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
10335            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
10336            let qv = self.view(q, t * n_head * head_dim);
10337            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
10338            let cfg = LaunchConfig { grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
10339                block_dim: (32, gqa, 1), shared_mem_bytes: shmem };
10340            {
10341                let __s_b = self.gpu.stream();
10342                let mut b = __s_b.launch_builder(&f);
10343                if tb512 {
10344                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
10345                    let (bd, plus) = base_dev.expect("hd512 rows twin requires a device base counter");
10346                    let plus_g = plus + r0 as i32;
10347                    let nr = t_g as i32;
10348                    if Self::pdl_on() && Self::pdl_wb_on() {
10349                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
10350                        use cudarc::driver::{DevicePtr, DevicePtrMut};
10351                        let s = &self.gpu.stream();
10352                        let (pq, _b0) = q_g.device_ptr(s); let (pk, _b1) = k.device_ptr(s);
10353                        let (pv, _b2) = v.device_ptr(s);
10354                        let (po, _b3) = part_o.device_ptr_mut(s);
10355                        let (pm, _b4) = part_m.device_ptr_mut(s);
10356                        let (pl, _b5) = part_l.device_ptr_mut(s);
10357                        let (pb, _b6) = bd.device_ptr(s);
10358                        let mut ps = [
10359                            &pq as *const _ as *mut std::ffi::c_void, &pk as *const _ as *mut _,
10360                            &pv as *const _ as *mut _, &po as *const _ as *mut _,
10361                            &pm as *const _ as *mut _, &pl as *const _ as *mut _,
10362                            &hd as *const _ as *mut _, &nh as *const _ as *mut _,
10363                            &nhkv as *const _ as *mut _, &pb as *const _ as *mut _,
10364                            &plus_g as *const _ as *mut _, &scale as *const _ as *mut _,
10365                            &nspm as *const _ as *mut _, &spk as *const _ as *mut _,
10366                            &ktb as *const _ as *mut _, &vtb as *const _ as *mut _,
10367                            &nr as *const _ as *mut _,
10368                        ];
10369                        unsafe { self.launch_pdl_flash(Self::gkv_on(),
10370                            "fa_decode_vec_q_rows_v4_512_tb",
10371                            (n_head_kv as u32, n_splits_g as u32, 1), (32, gqa, 1),
10372                            shmem, &mut ps)?; }
10373                    } else {
10374                    let cfg_tb = LaunchConfig {
10375                        grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
10376                        block_dim: (32, gqa, 1), shared_mem_bytes: shmem };
10377                    b.arg(&q_g).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10378                     .arg(&hd).arg(&nh).arg(&nhkv).arg(bd).arg(&plus_g).arg(&scale).arg(&nspm).arg(&spk)
10379                     .arg(&ktb).arg(&vtb).arg(&nr);
10380                    unsafe { b.launch(cfg_tb)?; }
10381                    }
10382                } else if head_dim == 512 {
10383                    let (bd, plus) = base_dev.expect("hd512 rows twin requires a device base counter");
10384                    let plus_g = plus + r0 as i32;
10385                    b.arg(&q_g).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10386                     .arg(&hd).arg(&nh).arg(&nhkv).arg(bd).arg(&plus_g).arg(&scale).arg(&nspm).arg(&spk)
10387                     .arg(&ktb).arg(&vtb);
10388                    unsafe { b.launch(cfg)?; }
10389                } else {
10390                    b.arg(&q_g).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10391                     .arg(&hd).arg(&nh).arg(&nhkv).arg(&base_i).arg(&scale).arg(&nspm).arg(&spk)
10392                     .arg(&ktb).arg(&vtb);
10393                    unsafe { b.launch(cfg)?; }
10394                }
10395            }
10396            let cfg2 = LaunchConfig { grid_dim: (n_head as u32, t_g as u32, 1),
10397                    block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
10398            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
10399            if head_dim == 512 {
10400                // device-len combine (shared by verify/eager/graph — parity by symbol): the
10401                // per-row n_splits derives from the SAME counter the rows kernel read.
10402                let (bd, plus) = base_dev.unwrap();
10403                let plus_g = plus + r0 as i32;
10404                if let Some((oq, od)) = q8_out.as_mut() {
10405                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
10406                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
10407                    if Self::pdl_on() && Self::pdl_wb_on() {
10408                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
10409                        use cudarc::driver::{DevicePtr, DevicePtrMut};
10410                        let s = &self.gpu.stream();
10411                        let (po, _g0) = part_o.device_ptr(s); let (pm, _g1) = part_m.device_ptr(s);
10412                        let (pl, _g2) = part_l.device_ptr(s);
10413                        let (pq, _g3) = oq.device_ptr_mut(s); let (pd, _g4) = od.device_ptr_mut(s);
10414                        let (pb, _g5) = bd.device_ptr(s);
10415                        let mut ps = [
10416                            &po as *const _ as *mut std::ffi::c_void, &pm as *const _ as *mut _,
10417                            &pl as *const _ as *mut _, &pq as *const _ as *mut _,
10418                            &pd as *const _ as *mut _, &hd as *const _ as *mut _,
10419                            &nh as *const _ as *mut _, &pb as *const _ as *mut _,
10420                            &plus_g as *const _ as *mut _, &nspm as *const _ as *mut _,
10421                            &spk as *const _ as *mut _,
10422                        ];
10423                        unsafe { self.launch_pdl_flash(Self::gkv_on(),
10424                            "fa_decode_combine_rows_dc_q8_1",
10425                            cfg2.grid_dim, cfg2.block_dim, 0, &mut ps)?; }
10426                        continue;
10427                    }
10428                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
10429                    let __s_b2 = self.gpu.stream();
10430                    let mut b2 = __s_b2.launch_builder(&fc);
10431                    b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(&mut **oq).arg(&mut **od)
10432                      .arg(&hd).arg(&nh).arg(bd).arg(&plus_g).arg(&nspm).arg(&spk);
10433                    unsafe { b2.launch(cfg2)?; }
10434                    continue;
10435                }
10436                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
10437                let __s_b2 = self.gpu.stream();
10438                let mut b2 = __s_b2.launch_builder(&fc);
10439                b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(&mut o_g).arg(&hd).arg(&nh)
10440                  .arg(bd).arg(&plus_g).arg(&nspm).arg(&spk);
10441                unsafe { b2.launch(cfg2)?; }
10442            } else {
10443                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
10444                // leave the caller's pair unwritten (consumer would read garbage).
10445                assert!(q8_out.is_none(), "rows q8 emit requires the hd512 dc combine");
10446                let fc = self.func("fa_decode_combine_rows");
10447                let __s_b2 = self.gpu.stream();
10448                let mut b2 = __s_b2.launch_builder(&fc);
10449                b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(&mut o_g).arg(&hd).arg(&nh)
10450                  .arg(&base_i).arg(&nspm).arg(&spk);
10451                unsafe { b2.launch(cfg2)?; }
10452            }
10453        }
10454        Ok(())
10455    }
10456
10457    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
10458    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
10459    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
10460    #[allow(clippy::too_many_arguments)]
10461    pub fn fa_decode_rows_w(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
10462                            v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
10463                            head_dim: usize, n_head: usize, n_head_kv: usize,
10464                            base_dev: &CudaSlice<i32>, base_plus: i32, t: usize, scale: f32,
10465                            window: usize, k_tok_bytes: usize, v_tok_bytes: usize,
10466                            q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>)
10467                            -> Result<(), Box<dyn std::error::Error>> {
10468        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
10469        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
10470        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
10471        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
10472        debug_assert!(head_dim == 256);
10473        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
10474        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
10475        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
10476        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
10477        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
10478        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
10479        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
10480        let sp = {
10481            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10482            let v = *SPW.get_or_init(|| std::env::var("MEMRA_FA_SPW").ok()
10483                .and_then(|x| x.parse().ok()).unwrap_or(0));
10484            if v >= 8 { v } else { FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed) }
10485        };
10486        let n_splits_max = (window + sp - 1) / sp;
10487        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
10488        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
10489        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10490        let gqa = (n_head / n_head_kv).max(1) as u32;
10491        let o_len = t * n_head * n_splits_max * head_dim;
10492        let ml_len = t * n_head * n_splits_max;
10493        let mut part_guard = self.fa_part_pool.lock().unwrap();
10494        if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
10495            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
10496            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
10497            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
10498            // later live allocations land at those addresses, and the next graph REPLAY writes
10499            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
10500            // output corruption began the burst after the trunk's t_kv growth first realloc'd
10501            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
10502            // the baked addresses alive (single-stream: eager writes the new buffers, replays
10503            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
10504            // (total retired < final size).
10505            let old = part_guard.take();
10506            let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
10507            if let Some(old) = old {
10508                self.fa_part_retired.lock().unwrap().push(old);
10509            }
10510            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
10511                eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
10512            }
10513            *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
10514                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
10515                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
10516        }
10517        let pg = part_guard.as_mut().unwrap();
10518        self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
10519        self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
10520        self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
10521        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
10522        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
10523        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
10524        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
10525        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
10526        // floor (deep-ctx broadcast win); register twin between.
10527        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10528        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
10529            std::env::var("MEMRA_FA_SMEM_TKV").ok().and_then(|v| v.parse().ok())
10530                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
10531        });
10532        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
10533        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
10534        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
10535        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
10536        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
10537        use cudarc::driver::sys::CUfunction_attribute_enum as A;
10538        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
10539        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
10540        // per (lane, format-module) keeps parity structural; the old register-i2 detour
10541        // (-33%) is retired.
10542        let wg = Self::wkv_on();
10543        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
10544        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
10545        let sp2 = gqa <= 4 && fa_v4_at(window)
10546            && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
10547        if sp2 {
10548            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
10549            if Self::pdl_on() && Self::pdl_wb_on() {
10550                // wave-B2b: flavor mirrors wg.
10551                use cudarc::driver::{DevicePtr, DevicePtrMut};
10552                let s = &self.gpu.stream();
10553                let (pq, _b0) = q.device_ptr(s); let (pk, _b1) = k.device_ptr(s);
10554                let (pv, _b2) = v.device_ptr(s);
10555                let (po, _b3) = part_o.device_ptr_mut(s);
10556                let (pm, _b4) = part_m.device_ptr_mut(s);
10557                let (pl, _b5) = part_l.device_ptr_mut(s);
10558                let (pb, _b6) = base_dev.device_ptr(s);
10559                let mut ps = [
10560                    &pq as *const _ as *mut std::ffi::c_void, &pk as *const _ as *mut _,
10561                    &pv as *const _ as *mut _, &po as *const _ as *mut _,
10562                    &pm as *const _ as *mut _, &pl as *const _ as *mut _,
10563                    &hd as *const _ as *mut _, &nh as *const _ as *mut _,
10564                    &nhkv as *const _ as *mut _, &pb as *const _ as *mut _,
10565                    &base_plus as *const _ as *mut _, &scale as *const _ as *mut _,
10566                    &nspm as *const _ as *mut _, &spk as *const _ as *mut _,
10567                    &ktb as *const _ as *mut _, &vtb as *const _ as *mut _,
10568                    &wini as *const _ as *mut _,
10569                ];
10570                unsafe { self.launch_pdl_flash(wg, "fa_decode_vec_q_rows_v4_w_sp",
10571                    (n_head_kv as u32, n_splits_max as u32, t as u32), (32, gqa + 1, 1),
10572                    sh, &mut ps)?; }
10573            } else {
10574            let f = if wg { self.func_g("fa_decode_vec_q_rows_v4_w_sp") }
10575                    else { self.func("fa_decode_vec_q_rows_v4_w_sp") };
10576            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
10577            let cfg = LaunchConfig { grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
10578                block_dim: (32, gqa + 1, 1), shared_mem_bytes: sh };
10579            let __s_b = self.gpu.stream();
10580            let mut b = __s_b.launch_builder(&f);
10581            b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10582             .arg(&hd).arg(&nh).arg(&nhkv).arg(base_dev).arg(&base_plus).arg(&scale).arg(&nspm).arg(&spk)
10583             .arg(&ktb).arg(&vtb).arg(&wini);
10584            unsafe { b.launch(cfg)?; }
10585            }
10586        } else {
10587        if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
10588            // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
10589            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
10590            use cudarc::driver::{DevicePtr, DevicePtrMut};
10591            let s = &self.gpu.stream();
10592            let (pq, _b0) = q.device_ptr(s); let (pk, _b1) = k.device_ptr(s);
10593            let (pv, _b2) = v.device_ptr(s);
10594            let (po, _b3) = part_o.device_ptr_mut(s);
10595            let (pm, _b4) = part_m.device_ptr_mut(s);
10596            let (pl, _b5) = part_l.device_ptr_mut(s);
10597            let (pb, _b6) = base_dev.device_ptr(s);
10598            let mut ps = [
10599                &pq as *const _ as *mut std::ffi::c_void, &pk as *const _ as *mut _,
10600                &pv as *const _ as *mut _, &po as *const _ as *mut _,
10601                &pm as *const _ as *mut _, &pl as *const _ as *mut _,
10602                &hd as *const _ as *mut _, &nh as *const _ as *mut _,
10603                &nhkv as *const _ as *mut _, &pb as *const _ as *mut _,
10604                &base_plus as *const _ as *mut _, &scale as *const _ as *mut _,
10605                &nspm as *const _ as *mut _, &spk as *const _ as *mut _,
10606                &ktb as *const _ as *mut _, &vtb as *const _ as *mut _,
10607                &wini as *const _ as *mut _,
10608            ];
10609            unsafe { self.launch_pdl_flash(wg, "fa_decode_vec_q_rows_v4_w",
10610                (n_head_kv as u32, n_splits_max as u32, t as u32), (32, gqa, 1),
10611                sh, &mut ps)?; }
10612        } else {
10613        let pick = |name: &str| if wg { self.func_g(name) } else { self.func(name) };
10614        let (f, sh) = if fa_v4_at(window) {
10615            let f = pick("fa_decode_vec_q_rows_v4_w");
10616            (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
10617        } else if smem_tkv > 0 && window >= smem_tkv {
10618            // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
10619            // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
10620            (pick("fa_decode_vec_q_rows_smem_w"), (2 * 32 * head_dim * 2) as u32)
10621        } else {
10622            (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
10623        };
10624        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
10625        let cfg = LaunchConfig { grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
10626            block_dim: (32, gqa, 1), shared_mem_bytes: sh };
10627        let __s_b = self.gpu.stream();
10628        let mut b = __s_b.launch_builder(&f);
10629        b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10630         .arg(&hd).arg(&nh).arg(&nhkv).arg(base_dev).arg(&base_plus).arg(&scale).arg(&nspm).arg(&spk)
10631         .arg(&ktb).arg(&vtb).arg(&wini);
10632        unsafe { b.launch(cfg)?; }
10633        }
10634        }
10635        let cfg2 = LaunchConfig { grid_dim: (n_head as u32, t as u32, 1),
10636                block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
10637        if let Some((oq, od)) = q8_out {
10638            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
10639            // consumes the pair directly; the standalone quantize launch folds away.
10640            if Self::pdl_on() && Self::pdl_wb_on() {
10641                // wave-B2: flavor mirrors the builder's wg choice.
10642                use cudarc::driver::{DevicePtr, DevicePtrMut};
10643                let s = &self.gpu.stream();
10644                let (po, _g0) = part_o.device_ptr(s); let (pm, _g1) = part_m.device_ptr(s);
10645                let (pl, _g2) = part_l.device_ptr(s);
10646                let (pq, _g3) = oq.device_ptr_mut(s); let (pd, _g4) = od.device_ptr_mut(s);
10647                let mut ps = [
10648                    &po as *const _ as *mut std::ffi::c_void, &pm as *const _ as *mut _,
10649                    &pl as *const _ as *mut _, &pq as *const _ as *mut _,
10650                    &pd as *const _ as *mut _, &hd as *const _ as *mut _,
10651                    &nh as *const _ as *mut _, &nspm as *const _ as *mut _,
10652                    &spk as *const _ as *mut _, &wini as *const _ as *mut _,
10653                ];
10654                unsafe { self.launch_pdl_flash(wg, "fa_decode_combine_rows_w_q8_1",
10655                                               cfg2.grid_dim, cfg2.block_dim, 0, &mut ps)?; }
10656                return Ok(());
10657            }
10658            let fc = if wg { self.func_g("fa_decode_combine_rows_w_q8_1") }
10659                     else { self.func("fa_decode_combine_rows_w_q8_1") };
10660            let __s_b2 = self.gpu.stream();
10661            let mut b2 = __s_b2.launch_builder(&fc);
10662            b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(oq).arg(od).arg(&hd).arg(&nh)
10663              .arg(&nspm).arg(&spk).arg(&wini);
10664            unsafe { b2.launch(cfg2)?; }
10665            return Ok(());
10666        }
10667        let fc = if wg { self.func_g("fa_decode_combine_rows_w") }
10668                 else { self.func("fa_decode_combine_rows_w") };
10669        let __s_b2 = self.gpu.stream();
10670        let mut b2 = __s_b2.launch_builder(&fc);
10671        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh)
10672          .arg(&nspm).arg(&spk).arg(&wini);
10673        unsafe { b2.launch(cfg2)?; }
10674        Ok(())
10675    }
10676
10677    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
10678    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
10679    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
10680    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
10681    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
10682    #[allow(clippy::too_many_arguments)]
10683    pub fn fa_decode_rows_dc(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
10684                             v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
10685                             head_dim: usize, n_head: usize, n_head_kv: usize,
10686                             base_dev: &CudaSlice<i32>, t_kv_upper: usize, t: usize, scale: f32,
10687                             k_tok_bytes: usize, v_tok_bytes: usize, base_plus: i32, g: bool)
10688                             -> Result<(), Box<dyn std::error::Error>> {
10689        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
10690        assert!(v4 || fa_v3_active(head_dim), "stream fa rows requires the v3 or v4 lane");
10691        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
10692        if v4 {
10693            let sp = fa_split_keys(t_kv_upper, n_head_kv);
10694            let n_splits_max = (t_kv_upper + sp - 1) / sp;
10695            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
10696            let (nspm, spk) = (n_splits_max as i32, sp as i32);
10697            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10698            let gqa = (n_head / n_head_kv).max(1) as u32;
10699            let o_len = t * n_head * n_splits_max * head_dim;
10700            let ml_len = t * n_head * n_splits_max;
10701            let mut part_guard = self.fa_part_pool.lock().unwrap();
10702            if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
10703                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
10704            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
10705            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
10706            // later live allocations land at those addresses, and the next graph REPLAY writes
10707            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
10708            // output corruption began the burst after the trunk's t_kv growth first realloc'd
10709            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
10710            // the baked addresses alive (single-stream: eager writes the new buffers, replays
10711            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
10712            // (total retired < final size).
10713                let old = part_guard.take();
10714                let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
10715                if let Some(old) = old {
10716                    self.fa_part_retired.lock().unwrap().push(old);
10717                }
10718                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
10719                    eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
10720                }
10721                *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
10722                                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
10723                                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
10724            }
10725            let pg = part_guard.as_mut().unwrap();
10726            self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
10727            self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
10728            self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
10729            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
10730            let f = if g { self.func_g("fa_decode_vec_q_rows_v4_dc") }
10731                    else { self.func("fa_decode_vec_q_rows_v4_dc") };
10732            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
10733            use cudarc::driver::sys::CUfunction_attribute_enum as A;
10734            f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
10735            let cfg = LaunchConfig { grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
10736                block_dim: (32, gqa, 1), shared_mem_bytes: sh };
10737            let __s_b = self.gpu.stream();
10738            let mut b = __s_b.launch_builder(&f);
10739            b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10740             .arg(&hd).arg(&nh).arg(&nhkv).arg(base_dev).arg(&base_plus).arg(&scale)
10741             .arg(&nspm).arg(&spk).arg(&ktb).arg(&vtb);
10742            unsafe { b.launch(cfg)?; }
10743            let fc = self.func("fa_decode_combine_rows_dc");
10744            let cfg2 = LaunchConfig { grid_dim: (n_head as u32, t as u32, 1),
10745                block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
10746            let __s_b2 = self.gpu.stream();
10747            let mut b2 = __s_b2.launch_builder(&fc);
10748            b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh)
10749              .arg(base_dev).arg(&base_plus).arg(&nspm).arg(&spk);
10750            unsafe { b2.launch(cfg2)?; }
10751            return Ok(());
10752        }
10753        let sp = fa_split_keys(t_kv_upper, n_head_kv);
10754        let n_splits_max = (t_kv_upper + sp - 1) / sp;
10755        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
10756        let (nspm, spk) = (n_splits_max as i32, sp as i32);
10757        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10758        let gqa = (n_head / n_head_kv).max(1) as u32;
10759        let o_len = t * n_head * n_splits_max * head_dim;
10760        let ml_len = t * n_head * n_splits_max;
10761        let mut part_guard = self.fa_part_pool.lock().unwrap();
10762        if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
10763            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
10764            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
10765            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
10766            // later live allocations land at those addresses, and the next graph REPLAY writes
10767            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
10768            // output corruption began the burst after the trunk's t_kv growth first realloc'd
10769            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
10770            // the baked addresses alive (single-stream: eager writes the new buffers, replays
10771            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
10772            // (total retired < final size).
10773            let old = part_guard.take();
10774            let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
10775            if let Some(old) = old {
10776                self.fa_part_retired.lock().unwrap().push(old);
10777            }
10778            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
10779                eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
10780            }
10781            *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
10782                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
10783                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
10784        }
10785        let pg = part_guard.as_mut().unwrap();
10786        self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
10787        self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
10788        self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
10789        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
10790        let f = self.func("fa_decode_vec_q_rows_v3_dc");
10791        let sh = (32 * head_dim * 2) as u32;
10792        use cudarc::driver::sys::CUfunction_attribute_enum as A;
10793        f.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, sh as i32)?;
10794        let cfg = LaunchConfig { grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
10795            block_dim: (32, gqa, 1), shared_mem_bytes: sh };
10796        let __s_b = self.gpu.stream();
10797        let mut b = __s_b.launch_builder(&f);
10798        b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10799         .arg(&hd).arg(&nh).arg(&nhkv).arg(base_dev).arg(&scale).arg(&nspm).arg(&spk)
10800         .arg(&ktb).arg(&vtb);
10801        unsafe { b.launch(cfg)?; }
10802        let fc = self.func("fa_decode_combine_rows_dc");
10803        let cfg2 = LaunchConfig { grid_dim: (n_head as u32, t as u32, 1),
10804            block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
10805        let plus0 = 0i32;
10806        let __s_b2 = self.gpu.stream();
10807        let mut b2 = __s_b2.launch_builder(&fc);
10808        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh)
10809          .arg(base_dev).arg(&plus0).arg(&nspm).arg(&spk);
10810        unsafe { b2.launch(cfg2)?; }
10811        Ok(())
10812    }
10813
10814    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
10815    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
10816    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
10817    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
10818    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
10819    ///
10820    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
10821    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
10822    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
10823    /// grouping (different but mathematically-equal log-sum-exp merge).
10824    pub fn fa_decode_dc(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
10825                        v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
10826                        head_dim: usize, n_head: usize, n_head_kv: usize,
10827                        t_kv_dev: &CudaSlice<i32>, bucket_max: usize, scale: f32,
10828                        k_tok_bytes: usize, v_tok_bytes: usize, g: bool)
10829                        -> Result<(), Box<dyn std::error::Error>> {
10830        self.fa_decode_dc_q8(q, k, v, o, head_dim, n_head, n_head_kv, t_kv_dev, bucket_max,
10831                             scale, k_tok_bytes, v_tok_bytes, g, None)
10832    }
10833
10834    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
10835    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
10836    #[allow(clippy::too_many_arguments)]
10837    pub fn fa_decode_dc_q8(&self, q: &CudaSlice<f32>, k: &cudarc::driver::CudaView<u8>,
10838                        v: &cudarc::driver::CudaView<u8>, o: &mut CudaSlice<f32>,
10839                        head_dim: usize, n_head: usize, n_head_kv: usize,
10840                        t_kv_dev: &CudaSlice<i32>, bucket_max: usize, scale: f32,
10841                        k_tok_bytes: usize, v_tok_bytes: usize, g: bool,
10842                        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>)
10843                        -> Result<(), Box<dyn std::error::Error>> {
10844        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
10845        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
10846        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
10847        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
10848        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
10849        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
10850        // 2026-07-12).
10851        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
10852        if g && head_dim == 256 && !fa_v4_at(bucket_max) { fa_vec = false; }   // mirror kvmod/geom
10853        let sp = fa_split_keys(bucket_max, n_head_kv);
10854        let n_splits = if fa_vec { ((bucket_max + sp - 1) / sp).max(1) } else { ((bucket_max + 255) / 256).max(1) };
10855        let o_len = n_head * n_splits * head_dim;
10856        let ml_len = n_head * n_splits;
10857        let mut part_guard = self.fa_part_pool.lock().unwrap();
10858        if part_guard.as_ref().map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len).unwrap_or(true) {
10859            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
10860            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
10861            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
10862            // later live allocations land at those addresses, and the next graph REPLAY writes
10863            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
10864            // output corruption began the burst after the trunk's t_kv growth first realloc'd
10865            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
10866            // the baked addresses alive (single-stream: eager writes the new buffers, replays
10867            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
10868            // (total retired < final size).
10869            let old = part_guard.take();
10870            let (co, cm) = old.as_ref().map(|pp| (pp.0.len(), pp.1.len())).unwrap_or((0, 0));
10871            if let Some(old) = old {
10872                self.fa_part_retired.lock().unwrap().push(old);
10873            }
10874            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
10875                eprintln!("[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)", co, o_len, cm, ml_len);
10876            }
10877            *part_guard = Some((self.alloc_uninit::<f32>(o_len.max(2 * co))?,
10878                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
10879                                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?));
10880        }
10881        let pg = part_guard.as_mut().unwrap();
10882        self.gpu.stream().memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
10883        self.gpu.stream().memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
10884        self.gpu.stream().memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
10885        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
10886        let (hd, nh, nhkv, nsp) = (head_dim as i32, n_head as i32, n_head_kv as i32, n_splits as i32);
10887        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10888        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
10889        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
10890        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
10891        let deep = fa_vec && head_dim == 256 && fa_v4_at(bucket_max) && !g
10892            && fa_deep_at(bucket_max) && !matches!(fa_v4_mode(), "noB3" | "stage");
10893        let (f, cfg) = if fa_vec && head_dim == 512 && bucket_max >= {
10894            static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
10895            *FA512_MIN_DC.get_or_init(|| std::env::var("MEMRA_FA512_MIN").ok()
10896                .and_then(|v| v.parse().ok()).unwrap_or(512))
10897        } {
10898            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
10899            let gqa = (n_head / n_head_kv).max(1) as u32;
10900            (self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
10901             LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10902                 block_dim: (32, gqa, 1), shared_mem_bytes: 0 })
10903        } else if fa_vec && head_dim == 512 {
10904            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
10905            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
10906            let q_view = q.as_view();
10907            let mut o_view = o.as_view_mut();
10908            return self.fa_decode_scalar_unified(&q_view, k, v, &mut o_view,
10909                                                 head_dim, n_head, n_head_kv,
10910                                                 0, Some(t_kv_dev), scale, n_splits, sp,
10911                                                 k_tok_bytes, v_tok_bytes, g,
10912                                                 &mut *part_o, &mut *part_m, &mut *part_l, q8_out);
10913        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
10914            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
10915            // incl the g-module route + raw-e4m3 sV sizing.
10916            let gqa = (n_head / n_head_kv).max(1) as u32;
10917            let fv = if g { self.func_g("fa_decode_vec_q_v4_dc") }
10918                     else if deep { self.func("fa_decode_vec_q_v4_deep_dc") }
10919                     else { self.func("fa_decode_vec_q_v4_dc") };
10920            let shmem = (if deep { 12160 } else { 11520 }
10921                         + 32 * head_dim * if g { 1 } else { 2 }) as u32;
10922            use cudarc::driver::sys::CUfunction_attribute_enum as A;
10923            fv.set_attribute(A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, shmem as i32)?;
10924            (fv, LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10925                 block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
10926        } else if fa_vec && fa_v3_active(head_dim) {
10927            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
10928            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
10929            let gqa = (n_head / n_head_kv).max(1) as u32;
10930            let fv = if g { self.func_g("fa_decode_vec_q_v3_dc") } else { self.func("fa_decode_vec_q_v3_dc") };
10931            let shmem = (32 * head_dim * 2) as u32;       // sV bf16 [FA_DEC_TILE=32][hd]
10932            (fv,
10933             LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10934                 block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
10935        } else if fa_vec && fa_v2_on() {
10936            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
10937            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
10938            // a numeric config; eager, rows-verify and graph all switch together).
10939            let gqa = (n_head / n_head_kv).max(1) as u32;
10940            let fv = if g { self.func_g("fa_decode_vec_q_v2_dc") } else { self.func("fa_decode_vec_q_v2_dc") };
10941            let shmem = (2 * 32 * head_dim * 2) as u32;   // sK+sV bf16 [FA_DEC_TILE=32][hd]
10942            (fv,
10943             LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10944                 block_dim: (32, gqa, 1), shared_mem_bytes: shmem })
10945        } else if fa_vec {
10946            let gqa = (n_head / n_head_kv).max(1) as u32;
10947            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
10948            let fv = if g { self.func_g("fa_decode_vec_q_dc") } else { self.func("fa_decode_vec_q_dc") };
10949            (fv,
10950             LaunchConfig { grid_dim: (n_head_kv as u32, n_splits as u32, 1),
10951                 block_dim: (32, gqa, 1), shared_mem_bytes: 0 })
10952        } else {
10953            let q_view = q.as_view();
10954            let mut o_view = o.as_view_mut();
10955            return self.fa_decode_scalar_unified(&q_view, k, v, &mut o_view,
10956                                                 head_dim, n_head, n_head_kv,
10957                                                 0, Some(t_kv_dev), scale, n_splits,
10958                                                 if fa_vec { sp } else { 256 },
10959                                                 k_tok_bytes, v_tok_bytes, g,
10960                                                 &mut *part_o, &mut *part_m, &mut *part_l, q8_out);
10961        };
10962        let ski = sp as i32;   // one-partition law: the twins derive ns_eff from (T_kv, ski)
10963        let __s_b = self.gpu.stream();
10964        let mut b = __s_b.launch_builder(&f);
10965        b.arg(q).arg(k).arg(v).arg(&mut *part_o).arg(&mut *part_m).arg(&mut *part_l)
10966         .arg(&hd).arg(&nh).arg(&nhkv).arg(t_kv_dev).arg(&scale).arg(&nsp).arg(&ski)
10967         .arg(&ktb).arg(&vtb);
10968        unsafe { b.launch(cfg)?; }
10969        let cfg2 = LaunchConfig { grid_dim: (n_head as u32, 1, 1), block_dim: (head_dim as u32, 1, 1), shared_mem_bytes: 0 };
10970        if let Some((oq, od)) = q8_out {
10971            let fc = if g { self.func_g("fa_decode_combine_q8_1") }
10972                     else { self.fa_func("fa_decode_combine_q8_1", head_dim) };
10973            let __s_b2 = self.gpu.stream();
10974            let mut b2 = __s_b2.launch_builder(&fc);
10975            b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(oq).arg(od).arg(&hd).arg(&nh).arg(&nsp);
10976            unsafe { b2.launch(cfg2)?; }
10977            return Ok(());
10978        }
10979        let fc = if g { self.func_g("fa_decode_combine_f32") } else { self.fa_func("fa_decode_combine_f32", head_dim) };
10980        let __s_b2 = self.gpu.stream();
10981        let mut b2 = __s_b2.launch_builder(&fc);
10982        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l).arg(o).arg(&hd).arg(&nh).arg(&nsp);
10983        unsafe { b2.launch(cfg2)?; }
10984        Ok(())
10985    }
10986
10987    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
10988    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
10989    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
10990    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
10991    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
10992    pub fn fa_geom_eager(&self, t_kv: usize, head_dim: usize, n_head_kv: usize, g: bool) -> (bool, usize) {
10993        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
10994        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
10995        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
10996        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
10997        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
10998        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
10999        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
11000        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
11001        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
11002        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
11003        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
11004        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
11005        // family; everything else falls to the g-module scalar.
11006        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
11007        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
11008        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
11009        if g && head_dim == 256 && !fa_v4_at(t_kv) { fa_vec = false; }
11010        let sp = fa_split_keys(t_kv, n_head_kv);
11011        let n_splits = if fa_vec { ((t_kv + sp - 1) / sp).max(1) } else { ((t_kv + 255) / 256).max(1) };
11012        (fa_vec, n_splits)
11013    }
11014
11015    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
11016    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
11017    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
11018    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
11019    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
11020    pub fn fa_bucket_key(&self, t_kv: usize, head_dim: usize, n_head_kv: usize, g: bool) -> (bool, usize) {
11021        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
11022    }
11023
11024    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
11025    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
11026    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
11027    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
11028    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
11029    /// device data) — every per-step varying scalar must come from a device counter. Returns the
11030    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
11031    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
11032    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
11033    /// replays (transients returning to the pool get reused by unrelated work and corrupt
11034    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
11035    pub fn capture_graph_retained<F>(&self, step: F)
11036        -> Result<(cudarc::driver::CudaGraph, Vec<Box<dyn std::any::Any + Send>>), Box<dyn std::error::Error>>
11037        where F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>
11038    {
11039        use cudarc::driver::sys::CUgraphInstantiate_flags;
11040        self.capture_graph_retained_flags(
11041            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH, step)
11042    }
11043
11044    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
11045    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
11046    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
11047    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
11048    pub fn capture_graph_retained_flags<F>(&self,
11049        flags: cudarc::driver::sys::CUgraphInstantiate_flags, mut step: F)
11050        -> Result<(cudarc::driver::CudaGraph, Vec<Box<dyn std::any::Any + Send>>), Box<dyn std::error::Error>>
11051        where F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>
11052    {
11053        use cudarc::driver::sys::CUstreamCaptureMode;
11054        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
11055        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
11056        // while the capture region is open become dead copy NODES replayed every launch
11057        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
11058        // warmup runs allocate the same transient sequence at the same pool addresses, so
11059        // retaining the warmup clones preserves the draft-graph fix without polluting the
11060        // captured graph.
11061        self.capture_keep.lock().unwrap().clear();
11062        let was_tracking = self.gpu.ctx.is_event_tracking();
11063        if was_tracking { unsafe { self.gpu.ctx.disable_event_tracking(); } }
11064        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
11065            self.capture_keep_on.store(true, std::sync::atomic::Ordering::Relaxed);
11066            let w = (|| { step(self)?; step(self) })();
11067            self.capture_keep_on.store(false, std::sync::atomic::Ordering::Relaxed);
11068            w?;
11069            self.gpu.stream().synchronize()?;
11070            self.gpu.stream().begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
11071            let r = step(self);
11072            let g = self.gpu.stream().end_capture(flags);
11073            r?;
11074            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
11075            graph.upload()?;
11076            Ok(graph)
11077        };
11078        let result = run();
11079        self.capture_keep_on.store(false, std::sync::atomic::Ordering::Relaxed);
11080        if was_tracking { unsafe { self.gpu.ctx.enable_event_tracking(); } }
11081        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
11082        Ok((result?, keeper))
11083    }
11084
11085    pub fn capture_graph<F>(&self, mut step: F) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
11086        where F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>
11087    {
11088        use cudarc::driver::sys::{CUstreamCaptureMode, CUgraphInstantiate_flags};
11089        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
11090        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
11091        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
11092        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
11093        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
11094        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
11095        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
11096        let was_tracking = self.gpu.ctx.is_event_tracking();
11097        if was_tracking { unsafe { self.gpu.ctx.disable_event_tracking(); } }
11098        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
11099        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
11100        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
11101        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
11102        // measure that scan's real cost on the generic path. Diagnostic door only; the
11103        // default stays AUTO_FREE until a measured A/B justifies moving it.
11104        let iflag = {
11105            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
11106            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
11107                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
11108                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
11109                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
11110                Ok("priority") =>
11111                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY,
11112                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
11113            })
11114        };
11115        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
11116        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
11117        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
11118        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
11119        // eager step executions and are node-count-invariant. Printing the split bounds the
11120        // refactor's ceiling instead of assuming it.
11121        let ct = {
11122            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11123            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
11124        };
11125        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
11126        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
11127        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
11128        // chased, and node-count-invariant, so no capture-body refactor could touch it.
11129        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
11130        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
11131        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
11132        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
11133        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
11134        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
11135        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
11136        // grow and never frees, resident counters/scratch, cache set in place), and the
11137        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
11138        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
11139        // settling and pool mapping. Arbitrated adversarially, not by taste:
11140        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
11141        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
11142        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
11143        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
11144        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
11145        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
11146        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
11147        let warmups = {
11148            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
11149            *W.get_or_init(|| std::env::var("MEMRA_GRAPH_WARMUPS").ok()
11150                .and_then(|v| v.parse().ok()).filter(|n| *n >= 1).unwrap_or(1))
11151        };
11152        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
11153            let t_w = std::time::Instant::now();
11154            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
11155            for _ in 0..warmups { step(self)?; }
11156            self.gpu.stream().synchronize()?;
11157            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
11158            // capture the third run.
11159            let t_c = std::time::Instant::now();
11160            self.gpu.stream().begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
11161            // If the body errors mid-capture, end the capture before propagating so the stream isn't
11162            // left in a capturing state.
11163            let r = step(self);
11164            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
11165            let t_i = std::time::Instant::now();
11166            let g = self.gpu.stream().end_capture(iflag);
11167            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
11168            r?;
11169            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
11170            let t_u = std::time::Instant::now();
11171            graph.upload()?;
11172            if ct {
11173                println!("[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
11174                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
11175                         t_u.elapsed().as_secs_f64() * 1e3);
11176            }
11177            Ok(graph)
11178        };
11179        let result = run();
11180        if was_tracking { unsafe { self.gpu.ctx.enable_event_tracking(); } }
11181        result
11182    }
11183
11184    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
11185    pub fn gdn_scan_s128_view(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
11186                              g: &CudaSlice<f32>, beta: &CudaSlice<f32>,
11187                              state_in: &cudarc::driver::CudaView<f32>,
11188                              state_out: &mut cudarc::driver::CudaViewMut<f32>,
11189                              o: &mut CudaSlice<f32>, n_head: usize, t: usize, scale: f32)
11190                              -> Result<(), Box<dyn std::error::Error>> {
11191        let f = self.func("gdn_scan_s128");
11192        const S_V: u32 = 128; const WARP: u32 = 32; const COLS: u32 = 4;
11193        let cfg = LaunchConfig { grid_dim: (n_head as u32, 1, S_V / COLS), block_dim: (WARP, COLS, 1), shared_mem_bytes: 0 };
11194        let (h, ti) = (n_head as i32, t as i32);
11195        let __s_b = self.gpu.stream();
11196        let mut b = __s_b.launch_builder(&f);
11197        b.arg(q).arg(k).arg(v).arg(g).arg(beta).arg(state_in).arg(state_out).arg(o).arg(&h).arg(&ti).arg(&scale);
11198        unsafe { b.launch(cfg)?; }
11199        Ok(())
11200    }
11201
11202    /// conv1d where the input is a CudaView (resident conv state assembled in place).
11203    pub fn ssm_conv1d_view(&self, x: &cudarc::driver::CudaView<f32>, w: &CudaSlice<f32>, y: &mut CudaSlice<f32>,
11204                           conv_dim: usize, t: usize, d_conv: usize, silu: bool)
11205                           -> Result<(), Box<dyn std::error::Error>> {
11206        let f = self.func("ssm_conv1d_silu_f32");
11207        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
11208        let cfg = LaunchConfig { grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
11209                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11210        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
11211        let __s_b = self.gpu.stream();
11212        let mut b = __s_b.launch_builder(&f);
11213        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
11214        unsafe { b.launch(cfg)?; }
11215        Ok(())
11216    }
11217
11218    /// Depthwise causal conv1d + optional SiLU.
11219    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
11220    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
11221    /// FUSED prefill conv (token-major input, zero left-state): replaces
11222    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
11223    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
11224    pub fn ssm_conv1d_tm(&self, qkv_tm: &CudaSlice<f32>, w: &CudaSlice<f32>, y: &mut CudaSlice<f32>,
11225                         conv_dim: usize, t: usize, d_conv: usize)
11226                         -> Result<(), Box<dyn std::error::Error>> {
11227        let f = self.func("ssm_conv1d_tm_f32");
11228        let cfg = LaunchConfig {
11229            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
11230            block_dim: (256, 1, 1), shared_mem_bytes: 0,
11231        };
11232        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11233        let __s_b = self.gpu.stream();
11234        let mut b = __s_b.launch_builder(&f);
11235        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
11236        unsafe { b.launch(cfg)?; }
11237        Ok(())
11238    }
11239
11240    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
11241    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
11242    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
11243    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
11244    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
11245    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
11246    /// columns; the final ring == what T sequential decode ring rolls leave).
11247    pub fn ssm_conv1d_tm_state(&self, qkv_tm: &CudaSlice<f32>, conv_state: &mut CudaSlice<f32>,
11248                               w: &CudaSlice<f32>, y: &mut CudaSlice<f32>,
11249                               conv_dim: usize, t: usize, d_conv: usize)
11250                               -> Result<(), Box<dyn std::error::Error>> {
11251        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
11252    }
11253
11254    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
11255    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
11256    #[allow(clippy::too_many_arguments)]
11257    pub fn ssm_conv1d_tm_state_pad(&self, qkv_tm: &CudaSlice<f32>, conv_state: &mut CudaSlice<f32>,
11258                               w: &CudaSlice<f32>, y: &mut CudaSlice<f32>,
11259                               conv_dim: usize, t: usize, d_conv: usize,
11260                               pad_len: Option<&CudaSlice<i32>>)
11261                               -> Result<(), Box<dyn std::error::Error>> {
11262        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
11263        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
11264        // the window kernel both read the pre-roll ring; the roll launches after both) — but
11265        // cloning first keeps the ordering trivially correct under any future stream split.
11266        let ring_old = if t < d_conv - 1 { Some(self.clone_dtod(conv_state)?) } else { None };
11267        {
11268            let f = self.func("ssm_conv1d_tm_state_f32");
11269            let cfg = LaunchConfig {
11270                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
11271                block_dim: (256, 1, 1), shared_mem_bytes: 0,
11272            };
11273            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11274            let __s_b = self.gpu.stream();
11275            let mut b = __s_b.launch_builder(&f);
11276            b.arg(qkv_tm).arg(&*conv_state).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
11277            unsafe { b.launch(cfg)?; }
11278        }
11279        match (ring_old, pad_len) {
11280            (None, Some(len_d)) => {
11281                let f = self.func("ssm_conv_ring_update_dev_f32");
11282                let n = conv_dim * (d_conv - 1);
11283                let cfg = LaunchConfig::for_num_elems(n as u32);
11284                let (cd, dc) = (conv_dim as i32, d_conv as i32);
11285                let __s_b = self.gpu.stream();
11286                let mut b = __s_b.launch_builder(&f);
11287                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
11288                unsafe { b.launch(cfg)?; }
11289            }
11290            (None, None) => {
11291                let f = self.func("ssm_conv_ring_update_f32");
11292                let n = conv_dim * (d_conv - 1);
11293                let cfg = LaunchConfig::for_num_elems(n as u32);
11294                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11295                let __s_b = self.gpu.stream();
11296                let mut b = __s_b.launch_builder(&f);
11297                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
11298                unsafe { b.launch(cfg)?; }
11299            }
11300            (Some(old), _) => self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?,
11301        }
11302        Ok(())
11303    }
11304
11305    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
11306    pub fn ssm_conv1d_tm_state_pad_v(&self, qkv_tm: &cudarc::driver::CudaView<f32>, conv_state: &mut CudaSlice<f32>,
11307                               w: &CudaSlice<f32>, y: &mut CudaSlice<f32>,
11308                               conv_dim: usize, t: usize, d_conv: usize,
11309                               pad_len: Option<&CudaSlice<i32>>)
11310                               -> Result<(), Box<dyn std::error::Error>> {
11311        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
11312        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
11313        // the window kernel both read the pre-roll ring; the roll launches after both) — but
11314        // cloning first keeps the ordering trivially correct under any future stream split.
11315        let ring_old = if t < d_conv - 1 { Some(self.clone_dtod(conv_state)?) } else { None };
11316        {
11317            let f = self.func("ssm_conv1d_tm_state_f32");
11318            let cfg = LaunchConfig {
11319                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
11320                block_dim: (256, 1, 1), shared_mem_bytes: 0,
11321            };
11322            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11323            let __s_b = self.gpu.stream();
11324            let mut b = __s_b.launch_builder(&f);
11325            b.arg(qkv_tm).arg(&*conv_state).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
11326            unsafe { b.launch(cfg)?; }
11327        }
11328        match (ring_old, pad_len) {
11329            (None, Some(len_d)) => {
11330                let f = self.func("ssm_conv_ring_update_dev_f32");
11331                let n = conv_dim * (d_conv - 1);
11332                let cfg = LaunchConfig::for_num_elems(n as u32);
11333                let (cd, dc) = (conv_dim as i32, d_conv as i32);
11334                let __s_b = self.gpu.stream();
11335                let mut b = __s_b.launch_builder(&f);
11336                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
11337                unsafe { b.launch(cfg)?; }
11338            }
11339            (None, None) => {
11340                let f = self.func("ssm_conv_ring_update_f32");
11341                let n = conv_dim * (d_conv - 1);
11342                let cfg = LaunchConfig::for_num_elems(n as u32);
11343                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11344                let __s_b = self.gpu.stream();
11345                let mut b = __s_b.launch_builder(&f);
11346                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
11347                unsafe { b.launch(cfg)?; }
11348            }
11349            (Some(_), _) => unreachable!(
11350                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"),
11351        }
11352        Ok(())
11353    }
11354
11355    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
11356    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
11357    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
11358    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
11359    pub fn ssm_conv_ring_rebuild(&self, qkv_tm: &CudaSlice<f32>, ring_old: &CudaSlice<f32>,
11360                                 conv_state: &mut CudaSlice<f32>,
11361                                 conv_dim: usize, tc: usize, d_conv: usize)
11362                                 -> Result<(), Box<dyn std::error::Error>> {
11363        let f = self.func("ssm_conv_ring_rebuild_f32");
11364        let n = conv_dim * (d_conv - 1);
11365        let cfg = LaunchConfig::for_num_elems(n as u32);
11366        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
11367        let __s_b = self.gpu.stream();
11368        let mut b = __s_b.launch_builder(&f);
11369        b.arg(qkv_tm).arg(ring_old).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
11370        unsafe { b.launch(cfg)?; }
11371        Ok(())
11372    }
11373
11374    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
11375    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
11376    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
11377    /// the argmax + run-spec gates are the authority.
11378    #[allow(clippy::too_many_arguments)]
11379    pub fn gdn_prep_decode(&self, conv_out: &CudaSlice<f32>, beta_raw: &CudaSlice<f32>,
11380                           alpha: &CudaSlice<f32>, dt_bias: &CudaSlice<f32>, a: &CudaSlice<f32>,
11381                           q_l2: &mut CudaSlice<f32>, k_l2: &mut CudaSlice<f32>, v_g: &mut CudaSlice<f32>,
11382                           beta: &mut CudaSlice<f32>, g_log: &mut CudaSlice<f32>,
11383                           d_state: usize, num_v: usize, num_k: usize, key_dim: usize, eps: f32)
11384                           -> Result<(), Box<dyn std::error::Error>> {
11385        let f = self.func("gdn_prep_decode_f32");
11386        let cfg = LaunchConfig { grid_dim: (num_v as u32, 1, 1), block_dim: (32, 4, 1), shared_mem_bytes: 0 };
11387        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
11388        let __s_b = self.gpu.stream();
11389        let mut b = __s_b.launch_builder(&f);
11390        b.arg(conv_out).arg(beta_raw).arg(alpha).arg(dt_bias).arg(a)
11391         .arg(q_l2).arg(k_l2).arg(v_g).arg(beta).arg(g_log)
11392         .arg(&ds).arg(&nv).arg(&nk).arg(&kd).arg(&eps);
11393        unsafe { b.launch(cfg)?; }
11394        Ok(())
11395    }
11396
11397    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
11398    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
11399    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
11400    #[allow(clippy::too_many_arguments)]
11401    pub fn ssm_conv1d_gdn(&self, qkv_tm: &CudaSlice<f32>, w: &CudaSlice<f32>,
11402                          q_g: &mut CudaSlice<f32>, k_g: &mut CudaSlice<f32>, v_g: &mut CudaSlice<f32>,
11403                          conv_dim: usize, t: usize, d_conv: usize,
11404                          d_state: usize, num_v: usize, num_k: usize, key_dim: usize)
11405                          -> Result<(), Box<dyn std::error::Error>> {
11406        let f = self.func("ssm_conv1d_gdn_f32");
11407        let cfg = LaunchConfig {
11408            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
11409            block_dim: (256, 1, 1), shared_mem_bytes: 0,
11410        };
11411        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11412        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
11413        let __s_b = self.gpu.stream();
11414        let mut b = __s_b.launch_builder(&f);
11415        b.arg(qkv_tm).arg(w).arg(q_g).arg(k_g).arg(v_g)
11416         .arg(&cd).arg(&ti).arg(&dc).arg(&ds).arg(&nv).arg(&nk).arg(&kd);
11417        unsafe { b.launch(cfg)?; }
11418        Ok(())
11419    }
11420
11421    pub fn ssm_conv1d(&self, x: &CudaSlice<f32>, w: &CudaSlice<f32>, y: &mut CudaSlice<f32>,
11422                      conv_dim: usize, t: usize, d_conv: usize, silu: bool)
11423                      -> Result<(), Box<dyn std::error::Error>> {
11424        let f = self.func("ssm_conv1d_silu_f32");
11425        let cfg = LaunchConfig { grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
11426                                 block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11427        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
11428        let __s_b = self.gpu.stream();
11429        let mut b = __s_b.launch_builder(&f);
11430        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
11431        unsafe { b.launch(cfg)?; }
11432        Ok(())
11433    }
11434
11435    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
11436    /// o:[128,H,T]. Single sequence.
11437    pub fn gdn_scan_s128(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
11438                         g: &CudaSlice<f32>, beta: &CudaSlice<f32>, state_in: &CudaSlice<f32>,
11439                         state_out: &mut CudaSlice<f32>, o: &mut CudaSlice<f32>,
11440                         n_head: usize, t: usize, scale: f32)
11441                         -> Result<(), Box<dyn std::error::Error>> {
11442        let f = self.func("gdn_scan_s128");
11443        const S_V: u32 = 128; const WARP: u32 = 32; const COLS_PER_BLOCK: u32 = 4;
11444        let cfg = LaunchConfig {
11445            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
11446            block_dim: (WARP, COLS_PER_BLOCK, 1),
11447            shared_mem_bytes: 0,
11448        };
11449        let (h, ti) = (n_head as i32, t as i32);
11450        let __s_b = self.gpu.stream();
11451        let mut b = __s_b.launch_builder(&f);
11452        b.arg(q).arg(k).arg(v).arg(g).arg(beta).arg(state_in).arg(state_out).arg(o).arg(&h).arg(&ti).arg(&scale);
11453        unsafe { b.launch(cfg)?; }
11454        Ok(())
11455    }
11456
11457    // ==== B2' batched decode state ops (decode_batch.rs) ====
11458    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
11459    // Bodies are the single-seq kernels per sequence — bit-identical per row.
11460
11461    #[allow(clippy::too_many_arguments)]
11462    pub fn ssm_conv1d_fused_decode_b(
11463        &self, qkv_cols: &CudaSlice<f32>, conv_state_ptrs: &cudarc::driver::CudaView<u64>,
11464        w: &CudaSlice<f32>, conv_outs: &mut CudaSlice<f32>, conv_dim: usize, d_conv: usize,
11465        b_n: usize) -> Result<(), Box<dyn std::error::Error>> {
11466        let f = self.func("ssm_conv1d_fused_decode_b_f32");
11467        let cfg = LaunchConfig {
11468            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
11469            block_dim: (256, 1, 1), shared_mem_bytes: 0,
11470        };
11471        let (cd, dc) = (conv_dim as i32, d_conv as i32);
11472        let __s_b = self.gpu.stream();
11473        let mut b = __s_b.launch_builder(&f);
11474        b.arg(qkv_cols).arg(conv_state_ptrs).arg(w).arg(conv_outs).arg(&cd).arg(&dc);
11475        unsafe { b.launch(cfg)?; }
11476        Ok(())
11477    }
11478
11479    #[allow(clippy::too_many_arguments)]
11480    pub fn gdn_prep_decode_b(
11481        &self, conv_outs: &CudaSlice<f32>, beta_raws: &CudaSlice<f32>, alphas: &CudaSlice<f32>,
11482        dt_bias: &CudaSlice<f32>, a: &CudaSlice<f32>,
11483        q_l2: &mut CudaSlice<f32>, k_l2: &mut CudaSlice<f32>, v_g: &mut CudaSlice<f32>,
11484        beta: &mut CudaSlice<f32>, g_log: &mut CudaSlice<f32>,
11485        d_state: usize, num_v: usize, num_k: usize, key_dim: usize, eps: f32,
11486        conv_dim: usize, b_n: usize) -> Result<(), Box<dyn std::error::Error>> {
11487        let f = self.func("gdn_prep_decode_b_f32");
11488        let cfg = LaunchConfig {
11489            grid_dim: (num_v as u32, 1, b_n as u32),
11490            block_dim: (32, 4, 1), shared_mem_bytes: 0,
11491        };
11492        let (ds, nv, nk, kd, cd) =
11493            (d_state as i32, num_v as i32, num_k as i32, key_dim as i32, conv_dim as i32);
11494        let __s_b = self.gpu.stream();
11495        let mut b = __s_b.launch_builder(&f);
11496        b.arg(conv_outs).arg(beta_raws).arg(alphas).arg(dt_bias).arg(a)
11497         .arg(q_l2).arg(k_l2).arg(v_g).arg(beta).arg(g_log)
11498         .arg(&ds).arg(&nv).arg(&nk).arg(&kd).arg(&eps).arg(&cd);
11499        unsafe { b.launch(cfg)?; }
11500        Ok(())
11501    }
11502
11503    #[allow(clippy::too_many_arguments)]
11504    pub fn gdn_scan_s128_batched(
11505        &self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
11506        g: &CudaSlice<f32>, beta: &CudaSlice<f32>,
11507        state_in_ptrs: &cudarc::driver::CudaView<u64>,
11508        state_out_ptrs: &cudarc::driver::CudaView<u64>,
11509        o: &mut CudaSlice<f32>, n_head: usize, b_n: usize, scale: f32)
11510        -> Result<(), Box<dyn std::error::Error>> {
11511        let f = self.func("gdn_scan_s128_b");
11512        const S_V: u32 = 128; const WARP: u32 = 32; const COLS_PER_BLOCK: u32 = 4;
11513        let cfg = LaunchConfig {
11514            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
11515            block_dim: (WARP, COLS_PER_BLOCK, 1), shared_mem_bytes: 0,
11516        };
11517        let h = n_head as i32;
11518        let __s_b = self.gpu.stream();
11519        let mut b = __s_b.launch_builder(&f);
11520        b.arg(q).arg(k).arg(v).arg(g).arg(beta).arg(state_in_ptrs).arg(state_out_ptrs)
11521         .arg(o).arg(&h).arg(&scale);
11522        unsafe { b.launch(cfg)?; }
11523        Ok(())
11524    }
11525
11526    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
11527    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
11528    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
11529    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
11530    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
11531    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
11532    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
11533    /// identity law); prime_cache/forward/forward_last are the only callers.
11534    pub fn gdn_chunked_enabled() -> bool {
11535        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11536        *E.get_or_init(|| std::env::var("MEMRA_GDN_CHUNKED").map(|v| v != "0").unwrap_or(true))
11537    }
11538
11539    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
11540    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
11541    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
11542    /// of 32 in [32, 128] (kernel row mappings require it).
11543    pub fn gdn_chunk_size() -> usize {
11544        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
11545        *C.get_or_init(|| {
11546            let c: usize = std::env::var("MEMRA_GDN_CHUNK").ok()
11547                .and_then(|v| v.parse().ok()).unwrap_or(32);
11548            c.clamp(32, 128) / 32 * 32
11549        })
11550    }
11551
11552    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
11553    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
11554    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
11555    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
11556    #[allow(clippy::too_many_arguments)]
11557    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
11558    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
11559    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
11560    #[allow(clippy::too_many_arguments)]
11561    pub fn gdn_chunk_k123(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
11562                          g: &CudaSlice<f32>, beta: &CudaSlice<f32>, wb16: Option<&mut CudaSlice<u8>>,
11563                          n_head: usize, t: usize, c: usize, hk: usize,
11564                          k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>)
11565                          -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11566        const D: usize = 128;
11567        let h = n_head;
11568        let nc = (t + c - 1) / c;
11569        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
11570        let mut gcum = self.uninit(t * h)?;
11571        let mut a = self.uninit(nc * h * c * c)?;
11572        let mut p = self.uninit(nc * h * c * c)?;
11573        let mut u = self.uninit(nc * h * c * D)?;
11574        let mut w = self.uninit(nc * h * c * D)?;
11575        {   // K1
11576            let f = self.func("gdn_chunk_cumgate_f32");
11577            let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, 1), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
11578            let __s_b = self.gpu.stream();
11579            let mut b = __s_b.launch_builder(&f);
11580            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
11581            unsafe { b.launch(cfg)?; }
11582        }
11583        if let Some((qb, kb, pb)) = k2w {
11584            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
11585            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
11586            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
11587            let f = self.func("gdn_k2_wgmma");
11588            let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
11589            let hki = hk as i32;
11590            let __s_b = self.gpu.stream();
11591            let mut b = __s_b.launch_builder(&f);
11592            b.arg(qb).arg(kb).arg(&gcum).arg(beta).arg(&mut a).arg(&mut *pb).arg(&hi).arg(&ti).arg(&ci).arg(&hki);
11593            unsafe { b.launch(cfg)?; }
11594        } else if c <= 64 && !portable_mma_gated() {   // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
11595            let f = self.func("gdn_chunk_attn_f32");
11596            let jt = ((c + 31) / 32) as u32;
11597            let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, jt), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11598            let hki = hk as i32;
11599            let __s_b = self.gpu.stream();
11600            let mut b = __s_b.launch_builder(&f);
11601            b.arg(q).arg(k).arg(&gcum).arg(beta).arg(&mut a).arg(&mut p).arg(&hi).arg(&ti).arg(&ci).arg(&hki);
11602            unsafe { b.launch(cfg)?; }
11603        } else {       // K2 generic (C = 128, or the portable target's low-smem fallback)
11604            assert!(hk == h, "generic K2 is broadcast-only (de-broadcast rides C==32)");
11605            let f = self.func("gdn_chunk_attn_g_f32");
11606            let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, 1), block_dim: (32, 8, 1), shared_mem_bytes: 0 };
11607            let __s_b = self.gpu.stream();
11608            let mut b = __s_b.launch_builder(&f);
11609            b.arg(q).arg(k).arg(&gcum).arg(beta).arg(&mut a).arg(&mut p).arg(&hi).arg(&ti).arg(&ci);
11610            unsafe { b.launch(cfg)?; }
11611        }
11612        {   // K3 (register-history templates for C=32/64; local-memory generic otherwise)
11613            let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11614            match c {
11615                32 | 64 => {
11616                    let f = self.func(if c == 32 { "gdn_chunk_solve32_f32" } else { "gdn_chunk_solve64_f32" });
11617                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
11618                    let wb: u64 = match wb16 { Some(d) => self.addr_u8(d), None => 0 };
11619                    let hki = hk as i32;
11620                    let __s_b = self.gpu.stream();
11621                    let mut b = __s_b.launch_builder(&f);
11622                    b.arg(v).arg(k).arg(&a).arg(&gcum).arg(&mut u).arg(&mut w).arg(&wb).arg(&hi).arg(&ti).arg(&hki);
11623                    unsafe { b.launch(cfg)?; }
11624                }
11625                _ => {
11626                    assert!(hk == h, "generic K3 is broadcast-only");
11627                    let f = self.func("gdn_chunk_solve_f32");
11628                    let __s_b = self.gpu.stream();
11629                    let mut b = __s_b.launch_builder(&f);
11630                    b.arg(v).arg(k).arg(&a).arg(&gcum).arg(&mut u).arg(&mut w).arg(&hi).arg(&ti).arg(&ci);
11631                    unsafe { b.launch(cfg)?; }
11632                }
11633            }
11634        }
11635        Ok((gcum, p, u, w))
11636    }
11637
11638    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
11639    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
11640    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
11641    pub fn gdn_db_on() -> bool {
11642        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
11643    }
11644
11645    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
11646    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
11647    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
11648        !portable_mma_gated() && c == 32
11649            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
11650                Ok("1") => true,
11651                Ok("0") => false,
11652                _ => cfg!(memra_hopper_mma),
11653            }
11654    }
11655
11656    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
11657    /// mma config; same per-call env read discipline).
11658    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
11659        self.gdn_mma_enabled(c)
11660            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
11661                Ok("0") => false,
11662                Ok("1") => true,
11663                _ => cfg!(memra_hopper_mma),
11664            }
11665    }
11666
11667    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
11668    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
11669    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
11670    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
11671    #[allow(clippy::too_many_arguments)]
11672    pub fn ssm_conv1d_gdn_state_pad(&self, qkv_tm: &cudarc::driver::CudaView<f32>,
11673                               conv_state: &mut CudaSlice<f32>, w: &CudaSlice<f32>,
11674                               q_g: &mut CudaSlice<f32>, k_g: &mut CudaSlice<f32>,
11675                               v_g: &mut CudaSlice<f32>,
11676                               conv_dim: usize, t: usize, d_conv: usize,
11677                               d_state: usize, num_v: usize, num_k: usize, key_dim: usize,
11678                               hk: usize,
11679                               pad_len: Option<&CudaSlice<i32>>)
11680                               -> Result<(), Box<dyn std::error::Error>> {
11681        assert!(t >= d_conv - 1, "fused state conv requires T >= pad (PRIME_MIN_T gates)");
11682        {
11683            let f = self.func("ssm_conv1d_gdn_state_f32");
11684            let cfg = LaunchConfig {
11685                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
11686                block_dim: (256, 1, 1), shared_mem_bytes: 0,
11687            };
11688            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11689            let (ds, nv, nk, kd, hki) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32, hk as i32);
11690            let __s_b = self.gpu.stream();
11691            let mut b = __s_b.launch_builder(&f);
11692            b.arg(qkv_tm).arg(&*conv_state).arg(w).arg(q_g).arg(k_g).arg(v_g)
11693             .arg(&cd).arg(&ti).arg(&dc).arg(&ds).arg(&nv).arg(&nk).arg(&kd).arg(&hki);
11694            unsafe { b.launch(cfg)?; }
11695        }
11696        match pad_len {
11697            Some(len_d) => {
11698                let f = self.func("ssm_conv_ring_update_dev_f32");
11699                let n = conv_dim * (d_conv - 1);
11700                let cfg = LaunchConfig::for_num_elems(n as u32);
11701                let (cd, dc) = (conv_dim as i32, d_conv as i32);
11702                let __s_b = self.gpu.stream();
11703                let mut b = __s_b.launch_builder(&f);
11704                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
11705                unsafe { b.launch(cfg)?; }
11706            }
11707            None => {
11708                let f = self.func("ssm_conv_ring_update_f32");
11709                let n = conv_dim * (d_conv - 1);
11710                let cfg = LaunchConfig::for_num_elems(n as u32);
11711                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
11712                let __s_b = self.gpu.stream();
11713                let mut b = __s_b.launch_builder(&f);
11714                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
11715                unsafe { b.launch(cfg)?; }
11716            }
11717        }
11718        Ok(())
11719    }
11720
11721    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
11722    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
11723    /// K2/K3 can write them.
11724    pub fn gdn_chunk_alloc(&self, n_head: usize, t: usize, c: usize, hk: usize)
11725                           -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
11726        const D: usize = 128;
11727        assert!(c == 32, "gdn_chunk_alloc: varlen chain is the C==32 mma pair");
11728        let h = n_head;
11729        let nc = (t + c - 1) / c;
11730        Ok(GdnChunkBufs {
11731            gcum: self.uninit(t * h)?,
11732            a: self.uninit(nc * h * c * c)?,
11733            p: self.uninit(nc * h * c * c)?,
11734            u: self.uninit(nc * h * c * D)?,
11735            w: self.uninit(nc * h * c * D)?,
11736            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
11737            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
11738            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
11739            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
11740            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
11741            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
11742            o: self.uninit(D * h * t)?,
11743            t, nc,
11744        })
11745    }
11746
11747    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
11748    pub fn f32_to_bf16_v(&self, x: &cudarc::driver::CudaView<f32>, dst: &mut CudaSlice<u8>, n: usize)
11749                         -> Result<(), Box<dyn std::error::Error>> {
11750        let f = self.func("f32_to_bf16_bulk");
11751        let ni = n as i64;
11752        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
11753        let __s_b = self.gpu.stream();
11754        let mut b = __s_b.launch_builder(&f);
11755        b.arg(x).arg(dst).arg(&ni);
11756        unsafe { b.launch(cfg)?; }
11757        Ok(())
11758    }
11759
11760    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
11761    pub fn f32_to_bf16_into(&self, x: &CudaSlice<f32>, dst: &mut CudaSlice<u8>, n: usize)
11762                       -> Result<(), Box<dyn std::error::Error>> {
11763        let f = self.func("f32_to_bf16_bulk");
11764        let ni = n as i64;
11765        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
11766        let __s_b = self.gpu.stream();
11767        let mut b = __s_b.launch_builder(&f);
11768        b.arg(x).arg(dst).arg(&ni);
11769        unsafe { b.launch(cfg)?; }
11770        Ok(())
11771    }
11772
11773    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
11774    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
11775    pub fn gdn_chunk_k123_vl8(&self, seqs: &[GdnSeqVl], n_head: usize, hk: usize,
11776                              wq: Option<&GdnWVl8>)
11777                              -> Result<(), Box<dyn std::error::Error>> {
11778        let b = seqs.len();
11779        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
11780        let mut packed = [GdnSeqVl::default(); 8];
11781        packed[..b].copy_from_slice(seqs);
11782        let v = GdnVl8(packed);
11783        let (hi, ci) = (n_head as i32, 32i32);
11784        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
11785        {
11786            let f = self.func("gdn_chunk_cumgate_vl");
11787            let cfg = LaunchConfig { grid_dim: (max_nc, n_head as u32, b as u32), block_dim: (32, 1, 1), shared_mem_bytes: 0 };
11788            let __s_lb = self.gpu.stream();
11789            let mut lb = __s_lb.launch_builder(&f);
11790            lb.arg(&v).arg(&hi).arg(&ci);
11791            unsafe { lb.launch(cfg)?; }
11792        }
11793        let hki = hk as i32;
11794        if let Some(w) = wq {   // K2-wgmma vl twin (writes A + pre-masked Pb16)
11795            let f = self.func("gdn_k2_wgmma_vl");
11796            let cfg = LaunchConfig { grid_dim: (max_nc, n_head as u32, b as u32), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
11797            let __s_lb = self.gpu.stream();
11798            let mut lb = __s_lb.launch_builder(&f);
11799            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
11800            unsafe { lb.launch(cfg)?; }
11801        } else {
11802            let f = self.func("gdn_chunk_attn_vl");
11803            let cfg = LaunchConfig { grid_dim: (max_nc, n_head as u32, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11804            let __s_lb = self.gpu.stream();
11805            let mut lb = __s_lb.launch_builder(&f);
11806            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
11807            unsafe { lb.launch(cfg)?; }
11808        }
11809        {
11810            let f = self.func("gdn_chunk_solve32_vl");
11811            let cfg = LaunchConfig { grid_dim: (max_nc, n_head as u32, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11812            let __s_lb = self.gpu.stream();
11813            let mut lb = __s_lb.launch_builder(&f);
11814            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
11815            unsafe { lb.launch(cfg)?; }
11816        }
11817        Ok(())
11818    }
11819
11820    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
11821    /// fused gate-prep, 5 launches for every sequence (per-element math identical
11822    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
11823    #[allow(clippy::too_many_arguments)]
11824    pub fn gdn_prep_vl8(&self, seqs: &[GdnPrepVl], conv_w: &CudaSlice<f32>,
11825                        dt_bias: &CudaSlice<f32>, a: &CudaSlice<f32>,
11826                        conv_dim: usize, d_conv: usize, d_state: usize,
11827                        num_v: usize, num_k: usize, key_dim: usize, hk: usize, eps: f32)
11828                        -> Result<(), Box<dyn std::error::Error>> {
11829        let b = seqs.len();
11830        assert!(b >= 1 && b <= 8);
11831        let mut packed = [GdnPrepVl::default(); 8];
11832        packed[..b].copy_from_slice(seqs);
11833        let v = GdnPrepVl8(packed);
11834        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
11835        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
11836        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
11837        assert!(conv_fuse || hk == num_v, "de-broadcast requires the fused conv");
11838        if conv_fuse {
11839            let f = self.func("ssm_conv1d_gdn_state_vl");
11840            let cfg = LaunchConfig { grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11841            let (dsi, nvi, nki, kdi, hki) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32, hk as i32);
11842            let __s_lb = self.gpu.stream();
11843            let mut lb = __s_lb.launch_builder(&f);
11844            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi).arg(&hki);
11845            unsafe { lb.launch(cfg)?; }
11846        } else {
11847            let f = self.func("ssm_conv1d_tm_state_vl");
11848            let cfg = LaunchConfig { grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11849            let __s_lb = self.gpu.stream();
11850            let mut lb = __s_lb.launch_builder(&f);
11851            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
11852            unsafe { lb.launch(cfg)?; }
11853        }
11854        {
11855            let f = self.func("ssm_conv_ring_update_vl");
11856            let n = (conv_dim * (d_conv - 1)) as u32;
11857            let cfg = LaunchConfig { grid_dim: (n.div_ceil(256), 1, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11858            let __s_lb = self.gpu.stream();
11859            let mut lb = __s_lb.launch_builder(&f);
11860            lb.arg(&v).arg(&cdi).arg(&dci);
11861            unsafe { lb.launch(cfg)?; }
11862        }
11863        if !conv_fuse {
11864            let f = self.func("qkv_to_gdn_repack_vl");
11865            let n = max_t * (num_v * d_state) as u32;
11866            let cfg = LaunchConfig { grid_dim: (n.div_ceil(256), 1, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11867            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
11868            let __s_lb = self.gpu.stream();
11869            let mut lb = __s_lb.launch_builder(&f);
11870            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
11871            unsafe { lb.launch(cfg)?; }
11872        }
11873        if Self::l2_v2_on(d_state) {
11874            let f = self.func("gdn_l2_v2_vl");
11875            let cfg = LaunchConfig { grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11876            let (dsi, nvi) = (d_state as i32, hk as i32);
11877            let __s_lb = self.gpu.stream();
11878            let mut lb = __s_lb.launch_builder(&f);
11879            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
11880            unsafe { lb.launch(cfg)?; }
11881        } else {
11882            let f = self.func("gdn_l2_vl");
11883            let cfg = LaunchConfig { grid_dim: (max_t * hk as u32, 2, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11884            let (dsi, nvi) = (d_state as i32, hk as i32);
11885            let __s_lb = self.gpu.stream();
11886            let mut lb = __s_lb.launch_builder(&f);
11887            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
11888            unsafe { lb.launch(cfg)?; }
11889        }
11890        {
11891            let f = self.func("gdn_gate_prep_vl");
11892            let n = max_t * num_v as u32;
11893            let cfg = LaunchConfig { grid_dim: (n.div_ceil(256), 1, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11894            let nvi = num_v as i32;
11895            let __s_lb = self.gpu.stream();
11896            let mut lb = __s_lb.launch_builder(&f);
11897            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
11898            unsafe { lb.launch(cfg)?; }
11899        }
11900        Ok(())
11901    }
11902
11903    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
11904    pub fn gdn_mirror_vl8(&self, seqs: &[GdnSeqVl], n_head: usize, which: i32, hk: usize)
11905                          -> Result<(), Box<dyn std::error::Error>> {
11906        let b = seqs.len();
11907        assert!(b >= 1 && b <= 8);
11908        let mut packed = [GdnSeqVl::default(); 8];
11909        packed[..b].copy_from_slice(seqs);
11910        let v = GdnVl8(packed);
11911        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
11912        let max_n = seqs.iter().map(|s| if which == 0 { s.t as i64 * ept as i64 }
11913                                        else { s.nc as i64 * ept as i64 * 32 }).max().unwrap();
11914        let f = self.func("gdn_mirror_vl");
11915        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
11916        let cfg = LaunchConfig { grid_dim: (blocks, 1, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11917        let __s_lb = self.gpu.stream();
11918        let mut lb = __s_lb.launch_builder(&f);
11919        lb.arg(&v).arg(&ept).arg(&which);
11920        unsafe { lb.launch(cfg)?; }
11921        Ok(())
11922    }
11923
11924    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
11925    pub fn gdn_tail_vl8(&self, seqs: &[GdnPrepVl], norm_w: &CudaSlice<f32>,
11926                        d_state: usize, num_v: usize, eps: f32)
11927                        -> Result<(), Box<dyn std::error::Error>> {
11928        let b = seqs.len();
11929        assert!(b >= 1 && b <= 8);
11930        let mut packed = [GdnPrepVl::default(); 8];
11931        packed[..b].copy_from_slice(seqs);
11932        let v = GdnPrepVl8(packed);
11933        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
11934        let f = self.func("gated_rmsnorm_f16out_vl");
11935        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
11936        let cfg = LaunchConfig { grid_dim: (max_t * num_v as u32, 1, b as u32), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
11937        let (dsi, nvi) = (d_state as i32, num_v as i32);
11938        let __s_lb = self.gpu.stream();
11939        let mut lb = __s_lb.launch_builder(&f);
11940        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
11941        unsafe { lb.launch(cfg)?; }
11942        Ok(())
11943    }
11944
11945    /// Raw device address helpers for the varlen by-value arg struct (single-stream
11946    /// launches; every buffer outlives the call — the f16 FFI discipline).
11947    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
11948        use cudarc::driver::DevicePtr;
11949        let s = self.gpu.stream();
11950        let (p, _g) = x.device_ptr(&s);
11951        p as u64
11952    }
11953    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
11954        use cudarc::driver::DevicePtrMut;
11955        let s = self.gpu.stream();
11956        let (p, _g) = x.device_ptr_mut(&s);
11957        p as u64
11958    }
11959    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
11960        use cudarc::driver::DevicePtr;
11961        let s = self.gpu.stream();
11962        let (p, _g) = x.device_ptr(&s);
11963        p as u64
11964    }
11965    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
11966        use cudarc::driver::DevicePtr;
11967        let s = self.gpu.stream();
11968        let (p, _g) = x.device_ptr(&s);
11969        p as u64
11970    }
11971
11972    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
11973    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
11974    /// launches, so this is strictly bit-gateable against them).
11975    pub fn gdn_chunk_vl8(&self, seqs: &[GdnSeqVl], n_head: usize, scale: f32, hk: usize,
11976                         wq: Option<&GdnWVl8>)
11977                         -> Result<(), Box<dyn std::error::Error>> {
11978        const NSPLIT: u32 = 4;
11979        let b = seqs.len();
11980        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
11981        let mut packed = [GdnSeqVl::default(); 8];
11982        packed[..b].copy_from_slice(seqs);
11983        let v = GdnVl8(packed);
11984        let (hi, ci) = (n_head as i32, 32i32);
11985        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
11986        let hki = hk as i32;
11987        if let Some(w) = wq {
11988            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
11989            let f = self.func("gdn_k45_wgmma_vl");
11990            let cfg = LaunchConfig { grid_dim: (n_head as u32, NSPLIT, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
11991            let __s_lb = self.gpu.stream();
11992            let mut lb = __s_lb.launch_builder(&f);
11993            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
11994            unsafe { lb.launch(cfg)?; }
11995            let _ = max_nc;
11996            return Ok(());
11997        }
11998        {
11999            let f = self.func("gdn_chunk_state_mma_vl");
12000            let cfg = LaunchConfig { grid_dim: (n_head as u32, NSPLIT, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12001            let __s_lb = self.gpu.stream();
12002            let mut lb = __s_lb.launch_builder(&f);
12003            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
12004            unsafe { lb.launch(cfg)?; }
12005        }
12006        {
12007            let f = self.func("gdn_chunk_output_mma_vl");
12008            let cfg = LaunchConfig { grid_dim: (max_nc, n_head as u32, b as u32), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12009            let __s_lb = self.gpu.stream();
12010            let mut lb = __s_lb.launch_builder(&f);
12011            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
12012            unsafe { lb.launch(cfg)?; }
12013        }
12014        Ok(())
12015    }
12016    pub fn gdn_scan_chunked(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
12017                            g: &CudaSlice<f32>, beta: &CudaSlice<f32>, kb16_pre: Option<&CudaSlice<u8>>,
12018                            qb16_pre: Option<&CudaSlice<u8>>,
12019                            state_in: &CudaSlice<f32>,
12020                            state_out: &mut CudaSlice<f32>, o: &mut CudaSlice<f32>,
12021                            n_head: usize, t: usize, scale: f32, c: usize, hk: usize)
12022                            -> Result<(), Box<dyn std::error::Error>> {
12023        const D: usize = 128;
12024        const NSPLIT: u32 = 4;
12025        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
12026        let h = n_head;
12027        let nc = (t + c - 1) / c;
12028        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
12029        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
12030        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
12031        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
12032        let gdn_mma_pre = !portable_mma_gated() && c == 32
12033            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
12034                Ok("1") => true,
12035                Ok("0") => false,
12036                _ => cfg!(memra_hopper_mma),
12037            };
12038        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
12039            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
12040        } else { None };
12041        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
12042        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
12043        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
12044        let gdn_wgmma_pre = gdn_mma_pre
12045            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
12046                Ok("0") => false,
12047                Ok("1") => true,
12048                _ => cfg!(memra_hopper_mma),
12049            };
12050        let nk = t * hk * D;
12051        let mut kb16_local: Option<CudaSlice<u8>> = None;
12052        if gdn_mma_pre && kb16_pre.is_none() {
12053            let mut kb = self.alloc_u8_uninit(nk * 2)?;
12054            let f = self.func("f32_to_bf16_bulk");
12055            let n2 = nk as i64;
12056            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
12057            let __s_b = self.gpu.stream();
12058            let mut b = __s_b.launch_builder(&f);
12059            b.arg(k).arg(&mut kb).arg(&n2);
12060            unsafe { b.launch(cfg2)?; }
12061            kb16_local = Some(kb);
12062        }
12063        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
12064        if let Some(kb) = kb16_pre { assert!(kb.len() >= nk * 2, "kb16_pre too small"); }
12065        let mut qb16: Option<CudaSlice<u8>> = None;
12066        let mut pb16: Option<CudaSlice<u8>> = None;
12067        if gdn_wgmma_pre {
12068            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
12069            // the standalone bulk cvt only serves callers without the prep mirror.
12070            if qb16_pre.is_none() {
12071                let mut qb = self.alloc_u8_uninit(nk * 2)?;
12072                let f = self.func("f32_to_bf16_bulk");
12073                let n2 = nk as i64;
12074                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
12075                let __s_b = self.gpu.stream();
12076                let mut b = __s_b.launch_builder(&f);
12077                b.arg(q).arg(&mut qb).arg(&n2);
12078                unsafe { b.launch(cfg2)?; }
12079                qb16 = Some(qb);
12080            } else if let Some(qb) = qb16_pre {
12081                assert!(qb.len() >= nk * 2, "qb16_pre too small");
12082            }
12083            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
12084        }
12085        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
12086        let k2w = if gdn_wgmma_pre {
12087            Some((*qb16_ref0.as_ref().unwrap(),
12088                  *kb16_ref0.as_ref().unwrap(),
12089                  pb16.as_mut().unwrap()))
12090        } else { None };
12091        let (gcum, p, u, w) = self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
12092        let _ = &w;
12093        let mut y = self.uninit(nc * h * c * D)?;
12094        let mut ssnap = self.uninit(nc * h * D * D)?;   // chunk-start state snapshots (K5 phase 1)
12095        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
12096        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
12097        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
12098        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
12099        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
12100        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
12101        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
12102        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
12103        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
12104        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
12105        let gdn_mma = !portable_mma_gated() && c == 32
12106            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
12107                Ok("1") => true,
12108                Ok("0") => false,
12109                _ => cfg!(memra_hopper_mma),
12110            };
12111        if gdn_mma {
12112            let wb16 = wb16_pre.take().expect("mma path pre-allocates wb16 (K3 store fold)");
12113            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
12114            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
12115            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
12116            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
12117            // pass runs inside the persistent-M kernel; Y and Ssnap are never
12118            // materialized. New numeric class (gk folds into k^T instead of ys) —
12119            // explicit opt-in until the state-carry battery promotes it. Env read per
12120            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
12121            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
12122            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
12123            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
12124            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
12125            if gdn_wgmma_pre {
12126                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
12127                let qb16 = qb16_ref0.unwrap();
12128                let pb16 = pb16.as_ref().unwrap();
12129                {
12130                    let f = self.func("gdn_k45_wgmma");
12131                    let cfg = LaunchConfig { grid_dim: (h as u32, 4, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12132                    let hki = hk as i32;
12133                    let __s_b = self.gpu.stream();
12134                    let mut b = __s_b.launch_builder(&f);
12135                    b.arg(kb16_ref).arg(&gcum).arg(beta).arg(&u).arg(&wb16).arg(qb16).arg(pb16)
12136                     .arg(o).arg(&scale).arg(state_in).arg(&mut *state_out).arg(&hi).arg(&ti).arg(&ci).arg(&hki);
12137                    unsafe { b.launch(cfg)?; }
12138                }
12139                return Ok(());
12140            }
12141            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
12142            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
12143            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
12144            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
12145            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
12146            {
12147                let f = self.func("gdn_chunk_state_mma");
12148                let cfg = LaunchConfig { grid_dim: (h as u32, NSPLIT, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12149                let hki = hk as i32;
12150                let __s_b = self.gpu.stream();
12151                let mut b = __s_b.launch_builder(&f);
12152                b.arg(kb16_ref).arg(&gcum).arg(beta).arg(&u).arg(&wb16).arg(&mut y16).arg(&mut ssnap16)
12153                 .arg(state_in).arg(&mut *state_out).arg(&hi).arg(&ti).arg(&ci).arg(&hki);
12154                unsafe { b.launch(cfg)?; }
12155            }
12156            {   // K5-mma (bf16 St/Y consumers)
12157                let f = self.func("gdn_chunk_output_mma");
12158                let jt = ((c + 31) / 32) as u32;
12159                let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, jt), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12160                let hki = hk as i32;
12161                let __s_b = self.gpu.stream();
12162                let mut b = __s_b.launch_builder(&f);
12163                b.arg(q).arg(&gcum).arg(&p).arg(&y16).arg(&ssnap16).arg(o).arg(&hi).arg(&ti).arg(&ci).arg(&scale).arg(&hki);
12164                unsafe { b.launch(cfg)?; }
12165            }
12166            return Ok(());
12167        }
12168        {   // K4 (sequential over chunks inside; blocks col-partition the state)
12169            let f = self.func("gdn_chunk_state_f32");
12170            let cfg = LaunchConfig { grid_dim: (h as u32, NSPLIT, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12171            let __s_b = self.gpu.stream();
12172            let mut b = __s_b.launch_builder(&f);
12173            b.arg(k).arg(&gcum).arg(beta).arg(&u).arg(&w).arg(&mut y).arg(&mut ssnap)
12174             .arg(state_in).arg(&mut *state_out).arg(&hi).arg(&ti).arg(&ci);
12175            unsafe { b.launch(cfg)?; }
12176        }
12177        {   // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
12178            let f = self.func("gdn_chunk_output_f32");
12179            let jt = ((c + 31) / 32) as u32;
12180            let cfg = LaunchConfig { grid_dim: (nc as u32, h as u32, jt), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
12181            let __s_b = self.gpu.stream();
12182            let mut b = __s_b.launch_builder(&f);
12183            b.arg(q).arg(&gcum).arg(&p).arg(&y).arg(&ssnap).arg(o).arg(&hi).arg(&ti).arg(&ci).arg(&scale);
12184            unsafe { b.launch(cfg)?; }
12185        }
12186        Ok(())
12187    }
12188
12189    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
12190    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
12191    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
12192    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
12193    ///
12194    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
12195    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
12196    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
12197    #[allow(clippy::too_many_arguments)]
12198    #[allow(clippy::too_many_arguments)]
12199    pub fn gdn_scan_prefill(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
12200                            g: &CudaSlice<f32>, beta: &CudaSlice<f32>, kb16_pre: Option<&CudaSlice<u8>>,
12201                            qb16_pre: Option<&CudaSlice<u8>>,
12202                            state_in: &CudaSlice<f32>,
12203                            state_out: &mut CudaSlice<f32>, o: &mut CudaSlice<f32>,
12204                            n_head: usize, t: usize, scale: f32, hk: usize)
12205                            -> Result<(), Box<dyn std::error::Error>> {
12206        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
12207            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
12208            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
12209        }
12210        if Self::gdn_chunked_enabled() && t >= 16 {
12211            self.gdn_scan_chunked(q, k, v, g, beta, kb16_pre, qb16_pre, state_in, state_out, o, n_head, t, scale,
12212                                  Self::gdn_chunk_size(), hk)
12213        } else {
12214            assert!(hk == n_head, "s128 scan is broadcast-only (prep guarantees by predicate)");
12215            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
12216        }
12217    }
12218
12219    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
12220    #[allow(clippy::too_many_arguments)]
12221    fn gdn_scan_diff(&self, q: &CudaSlice<f32>, k: &CudaSlice<f32>, v: &CudaSlice<f32>,
12222                     g: &CudaSlice<f32>, beta: &CudaSlice<f32>, state_in: &CudaSlice<f32>,
12223                     state_out: &mut CudaSlice<f32>, o: &mut CudaSlice<f32>,
12224                     n_head: usize, t: usize, scale: f32)
12225                     -> Result<(), Box<dyn std::error::Error>> {
12226        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
12227        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
12228        let mut o_c = self.uninit(o.len())?;
12229        let mut st_c = self.uninit(state_out.len())?;
12230        self.gdn_scan_chunked(q, k, v, g, beta, None, None, state_in, &mut st_c, &mut o_c,
12231                              n_head, t, scale, Self::gdn_chunk_size(), n_head)?;
12232        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
12233        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
12234        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
12235        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
12236            let mut max_abs = 0f32; let mut max_rel = 0f32; let mut sum_rel = 0f64;
12237            for (x, y) in a.iter().zip(b) {
12238                let ad = (x - y).abs();
12239                let rel = ad / x.abs().max(y.abs()).max(1e-3);
12240                if ad > max_abs { max_abs = ad; }
12241                if rel > max_rel { max_rel = rel; }
12242                sum_rel += rel as f64;
12243            }
12244            (max_abs, max_rel, sum_rel / a.len() as f64)
12245        };
12246        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
12247        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
12248        println!("[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
12249                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
12250                 Self::gdn_chunk_size());
12251        Ok(())
12252    }
12253
12254    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
12255    pub fn gdn_glog(&self, alpha: &CudaSlice<f32>, dt_bias: &CudaSlice<f32>, a: &CudaSlice<f32>,
12256                    g_log: &mut CudaSlice<f32>, n_head: usize, t: usize)
12257                    -> Result<(), Box<dyn std::error::Error>> {
12258        let f = self.func("gdn_glog_f32");
12259        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
12260        let (h, ti) = (n_head as i32, t as i32);
12261        let __s_b = self.gpu.stream();
12262        let mut b = __s_b.launch_builder(&f);
12263        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
12264        unsafe { b.launch(cfg)?; }
12265        Ok(())
12266    }
12267
12268    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
12269    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
12270    pub fn sigmoid_v(&self, x: &cudarc::driver::CudaView<f32>, y: &mut CudaSlice<f32>, n: usize)
12271                     -> Result<(), Box<dyn std::error::Error>> {
12272        let f = self.func("sigmoid_f32");
12273        let cfg = LaunchConfig::for_num_elems(n as u32);
12274        let ni = n as i32;
12275        let __s_b = self.gpu.stream();
12276        let mut b = __s_b.launch_builder(&f);
12277        b.arg(x).arg(y).arg(&ni);
12278        unsafe { b.launch(cfg)?; }
12279        Ok(())
12280    }
12281
12282    pub fn gdn_glog_v(&self, alpha: &cudarc::driver::CudaView<f32>, dt_bias: &CudaSlice<f32>,
12283                      a: &CudaSlice<f32>, g_log: &mut CudaSlice<f32>, n_head: usize, t: usize)
12284                      -> Result<(), Box<dyn std::error::Error>> {
12285        let f = self.func("gdn_glog_f32");
12286        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
12287        let (h, ti) = (n_head as i32, t as i32);
12288        let __s_b = self.gpu.stream();
12289        let mut b = __s_b.launch_builder(&f);
12290        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
12291        unsafe { b.launch(cfg)?; }
12292        Ok(())
12293    }
12294
12295    pub fn sigmoid(&self, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, n: usize)
12296                   -> Result<(), Box<dyn std::error::Error>> {
12297        let f = self.func("sigmoid_f32");
12298        let cfg = LaunchConfig::for_num_elems(n as u32);
12299        let ni = n as i32;
12300        let __s_b = self.gpu.stream();
12301        let mut b = __s_b.launch_builder(&f);
12302        b.arg(x).arg(y).arg(&ni);
12303        unsafe { b.launch(cfg)?; }
12304        Ok(())
12305    }
12306
12307    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
12308    /// (replaces sigmoid + mul + convert). Bit-identical class.
12309    pub fn sig_mul_f16out(&self, a: &CudaSlice<f32>, g: &CudaSlice<f32>,
12310                          dst: &mut CudaSlice<f32>, dst16: &mut CudaSlice<u8>, n: usize)
12311                          -> Result<(), Box<dyn std::error::Error>> {
12312        let f = self.func("sig_mul_f16out_f32");
12313        let cfg = LaunchConfig::for_num_elems(n as u32);
12314        let ni = n as i32;
12315        let __s_b = self.gpu.stream();
12316        let mut b = __s_b.launch_builder(&f);
12317        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
12318        unsafe { b.launch(cfg)?; }
12319        Ok(())
12320    }
12321
12322    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
12323    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
12324    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
12325    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
12326    ///
12327    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
12328    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
12329    /// applies the wrong number of distinct gate values.
12330    #[allow(clippy::too_many_arguments)]
12331    pub fn attn_head_gate(&self, a: &CudaSlice<f32>, g: &CudaSlice<f32>,
12332                          dst: &mut CudaSlice<f32>, dst16: Option<&mut CudaSlice<u8>>,
12333                          head_dim: usize, n_head: usize, t: usize)
12334                          -> Result<(), Box<dyn std::error::Error>> {
12335        let f = self.func("attn_head_gate_f32");
12336        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
12337        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
12338        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
12339        let d16: u64 = match dst16 { Some(d) => self.addr_u8(d), None => 0 };
12340        let __s_b = self.gpu.stream();
12341        let mut b = __s_b.launch_builder(&f);
12342        b.arg(a).arg(g).arg(dst).arg(&d16).arg(&hd).arg(&nh).arg(&ti);
12343        unsafe { b.launch(cfg)?; }
12344        Ok(())
12345    }
12346
12347    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
12348    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
12349    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
12350    ///
12351    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
12352    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
12353    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
12354    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
12355    #[allow(clippy::too_many_arguments)]
12356    pub fn swiglu_clamped_mul_scaled(&self, gate: &CudaSlice<f32>, up: &CudaSlice<f32>,
12357                                     gs: f32, us: f32, limit: f32,
12358                                     dst: &mut CudaSlice<f32>, n: usize)
12359                                     -> Result<(), Box<dyn std::error::Error>> {
12360        debug_assert!(limit > 1e-6, "swiglu_clamped needs a live limit; use silu_mul_scaled");
12361        let f = self.func("swiglu_clamped_mul_scaled_f32");
12362        let cfg = LaunchConfig::for_num_elems(n as u32);
12363        let ni = n as i32;
12364        let __s_b = self.gpu.stream();
12365        let mut b = __s_b.launch_builder(&f);
12366        b.arg(gate).arg(up).arg(&gs).arg(&us).arg(&limit).arg(dst).arg(&ni);
12367        unsafe { b.launch(cfg)?; }
12368        Ok(())
12369    }
12370
12371    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
12372    pub fn gated_rmsnorm(&self, o: &CudaSlice<f32>, w: &CudaSlice<f32>, z: &CudaSlice<f32>,
12373                         dst: &mut CudaSlice<f32>, ncols: usize, nrows: usize, eps: f32)
12374                         -> Result<(), Box<dyn std::error::Error>> {
12375        let f = self.func("gated_rmsnorm_f32");
12376        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
12377        let (nc, e) = (ncols as i32, eps);
12378        let __s_b = self.gpu.stream();
12379        let mut b = __s_b.launch_builder(&f);
12380        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
12381        unsafe { b.launch(cfg)?; }
12382        Ok(())
12383    }
12384
12385    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
12386    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
12387    pub fn gated_rmsnorm_f16out(&self, o: &CudaSlice<f32>, w: &CudaSlice<f32>, z: &CudaSlice<f32>,
12388                                dst: &mut CudaSlice<f32>, dst16: &mut CudaSlice<u8>,
12389                                ncols: usize, nrows: usize, eps: f32)
12390                                -> Result<(), Box<dyn std::error::Error>> {
12391        let f = self.func("gated_rmsnorm_f16out_f32");
12392        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
12393        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
12394        let (nc, e) = (ncols as i32, eps);
12395        let __s_b = self.gpu.stream();
12396        let mut b = __s_b.launch_builder(&f);
12397        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
12398        unsafe { b.launch(cfg)?; }
12399        Ok(())
12400    }
12401
12402    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
12403    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
12404    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
12405    #[allow(clippy::too_many_arguments)]
12406    pub fn add_rms_norm_zq8(&self, a: &CudaSlice<f32>, b_in: &CudaSlice<f32>, w: &CudaSlice<f32>,
12407                            res: &mut CudaSlice<f32>, z: &mut CudaSlice<f32>,
12408                            ncols: usize, nrows: usize, eps: f32)
12409                            -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12410        assert!(ncols % 32 == 0);
12411        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
12412        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
12413        let f = self.func("add_rms_norm_zq8");
12414        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (1024, 1, 1), shared_mem_bytes: 0 };
12415        let (nc, ep) = (ncols as i32, eps);
12416        let __s_b = self.gpu.stream();
12417        let mut b = __s_b.launch_builder(&f);
12418        b.arg(a).arg(b_in).arg(w).arg(res).arg(z).arg(&mut q).arg(&mut d).arg(&nc).arg(&ep);
12419        unsafe { b.launch(cfg)?; }
12420        Ok((q, d))
12421    }
12422
12423    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
12424    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
12425    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
12426    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
12427    pub fn gated_rmsnorm_zv(&self, o: &CudaSlice<f32>, w: &CudaSlice<f32>,
12428                            z: &cudarc::driver::CudaView<f32>,
12429                            dst: &mut CudaSlice<f32>, ncols: usize, nrows: usize, eps: f32)
12430                            -> Result<(), Box<dyn std::error::Error>> {
12431        let f = self.func("gated_rmsnorm_f32");
12432        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
12433        let (nc, e) = (ncols as i32, eps);
12434        let __s_b = self.gpu.stream();
12435        let mut b = __s_b.launch_builder(&f);
12436        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
12437        unsafe { b.launch(cfg)?; }
12438        Ok(())
12439    }
12440
12441    pub fn gated_rmsnorm_f16out_zv(&self, o: &CudaSlice<f32>, w: &CudaSlice<f32>,
12442                                   z: &cudarc::driver::CudaView<f32>,
12443                                   dst: &mut CudaSlice<f32>, dst16: &mut CudaSlice<u8>,
12444                                   ncols: usize, nrows: usize, eps: f32)
12445                                   -> Result<(), Box<dyn std::error::Error>> {
12446        let f = self.func("gated_rmsnorm_f16out_f32");
12447        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
12448        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
12449        let (nc, e) = (ncols as i32, eps);
12450        let __s_b = self.gpu.stream();
12451        let mut b = __s_b.launch_builder(&f);
12452        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
12453        unsafe { b.launch(cfg)?; }
12454        Ok(())
12455    }
12456
12457    pub fn gated_rmsnorm_q8_1(&self, o: &CudaSlice<f32>, w: &CudaSlice<f32>, z: &CudaSlice<f32>,
12458                              ncols: usize, nrows: usize, eps: f32)
12459                              -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12460        assert!(ncols % 32 == 0);
12461        let f = self.func("gated_rmsnorm_q8_1");
12462        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
12463        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
12464        let cfg = LaunchConfig { grid_dim: (nrows as u32, 1, 1), block_dim: (128, 1, 1), shared_mem_bytes: 0 };
12465        let (nc, ep) = (ncols as i32, eps);
12466        let __s_b = self.gpu.stream();
12467        let mut b = __s_b.launch_builder(&f);
12468        b.arg(o).arg(w).arg(z).arg(&mut out_q).arg(&mut out_d).arg(&nc).arg(&ep);
12469        unsafe { b.launch(cfg)?; }
12470        Ok((out_q, out_d))
12471    }
12472
12473    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
12474    pub fn transpose(&self, inp: &CudaSlice<f32>, rows: usize, cols: usize)
12475                     -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12476        let f = self.func("transpose_f32");
12477        let mut out = self.zeros(rows * cols)?;
12478        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
12479        let (r, c) = (rows as i32, cols as i32);
12480        let __s_b = self.gpu.stream();
12481        let mut b = __s_b.launch_builder(&f);
12482        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
12483        unsafe { b.launch(cfg)?; }
12484        Ok(out)
12485    }
12486
12487    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
12488    pub fn repeat_heads(&self, inp: &CudaSlice<f32>, out: &mut CudaSlice<f32>,
12489                        head_dim: usize, n_in: usize, n_out: usize, t: usize)
12490                        -> Result<(), Box<dyn std::error::Error>> {
12491        let f = self.func("repeat_heads_f32");
12492        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
12493        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
12494        let __s_b = self.gpu.stream();
12495        let mut b = __s_b.launch_builder(&f);
12496        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
12497        unsafe { b.launch(cfg)?; }
12498        Ok(())
12499    }
12500
12501    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
12502    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
12503    pub fn q_gate_split(&self, qf: &CudaSlice<f32>, q_out: &mut CudaSlice<f32>,
12504                        gate_out: &mut CudaSlice<f32>, head_dim: usize, n_head: usize, t: usize)
12505                        -> Result<(), Box<dyn std::error::Error>> {
12506        let f = self.func("q_gate_split_f32");
12507        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
12508        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
12509        let __s_b = self.gpu.stream();
12510        let mut b = __s_b.launch_builder(&f);
12511        b.arg(qf).arg(q_out).arg(gate_out).arg(&hd).arg(&nh).arg(&ti);
12512        unsafe { b.launch(cfg)?; }
12513        Ok(())
12514    }
12515
12516    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
12517    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
12518    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
12519    pub fn qkv_to_gdn_repack(&self, conv_out: &CudaSlice<f32>, q_g: &mut CudaSlice<f32>,
12520                             k_g: &mut CudaSlice<f32>, v_g: &mut CudaSlice<f32>,
12521                             d_state: usize, num_v: usize, num_k: usize, key_dim: usize, t: usize)
12522                             -> Result<(), Box<dyn std::error::Error>> {
12523        let f = self.func("qkv_to_gdn_repack_f32");
12524        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
12525        let (ds, nv, nk, kd, ti) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32, t as i32);
12526        let __s_b = self.gpu.stream();
12527        let mut b = __s_b.launch_builder(&f);
12528        b.arg(conv_out).arg(q_g).arg(k_g).arg(v_g).arg(&ds).arg(&nv).arg(&nk).arg(&kd).arg(&ti);
12529        unsafe { b.launch(cfg)?; }
12530        Ok(())
12531    }
12532
12533    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
12534    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
12535    pub fn conv_left_pad(&self, src: &CudaSlice<f32>, dst: &mut CudaSlice<f32>,
12536                         conv_dim: usize, t: usize, pad: usize)
12537                         -> Result<(), Box<dyn std::error::Error>> {
12538        let f = self.func("conv_left_pad_f32");
12539        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
12540        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
12541        let __s_b = self.gpu.stream();
12542        let mut b = __s_b.launch_builder(&f);
12543        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
12544        unsafe { b.launch(cfg)?; }
12545        Ok(())
12546    }
12547
12548    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
12549    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
12550    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
12551    pub fn conv_assemble_and_roll(&self, qkv_col: &CudaSlice<f32>, conv_state: &mut CudaSlice<f32>,
12552                                  conv_in: &mut CudaSlice<f32>, conv_dim: usize, pad: usize)
12553                                  -> Result<(), Box<dyn std::error::Error>> {
12554        let f = self.func("conv_assemble_and_roll_f32");
12555        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
12556        let (cd, p) = (conv_dim as i32, pad as i32);
12557        let __s_b = self.gpu.stream();
12558        let mut b = __s_b.launch_builder(&f);
12559        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
12560        unsafe { b.launch(cfg)?; }
12561        Ok(())
12562    }
12563
12564    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
12565    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
12566    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
12567    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
12568    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
12569    pub fn ssm_conv1d_fused_decode(&self, qkv_col: &CudaSlice<f32>, conv_state: &mut CudaSlice<f32>,
12570                                   w: &CudaSlice<f32>, conv_out: &mut CudaSlice<f32>,
12571                                   conv_dim: usize, d_conv: usize)
12572                                   -> Result<(), Box<dyn std::error::Error>> {
12573        let f = self.func("ssm_conv1d_fused_decode_f32");
12574        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
12575        let (cd, dc) = (conv_dim as i32, d_conv as i32);
12576        let __s_b = self.gpu.stream();
12577        let mut b = __s_b.launch_builder(&f);
12578        b.arg(qkv_col).arg(conv_state).arg(w).arg(conv_out).arg(&cd).arg(&dc);
12579        unsafe { b.launch(cfg)?; }
12580        Ok(())
12581    }
12582
12583    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
12584    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
12585    pub fn slice_range(&self, src: &CudaSlice<f32>, start: usize, len: usize)
12586                       -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12587        let host = self.gpu.stream().clone_dtoh(src)?;
12588        self.gpu.stream().synchronize()?;
12589        Ok(self.htod(&host[start..start + len])?)
12590    }
12591}
12592
12593#[cfg(test)]
12594mod target_dispatch_tests {
12595    use super::legacy_quant_gemm_allowed;
12596
12597    #[test]
12598    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
12599        // sm_120a native lane
12600        assert!(legacy_quant_gemm_allowed(false, false, false));
12601        assert!(!legacy_quant_gemm_allowed(false, false, true));
12602        // pure portable lane (sm_89): gated
12603        assert!(!legacy_quant_gemm_allowed(true, false, false));
12604        assert!(!legacy_quant_gemm_allowed(true, false, true));
12605        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
12606        assert!(legacy_quant_gemm_allowed(true, true, false));
12607        assert!(!legacy_quant_gemm_allowed(true, true, true));
12608    }
12609
12610    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
12611    #[test]
12612    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
12613        assert!(!legacy_quant_gemm_allowed(cfg!(memra_portable_cuda), cfg!(memra_hopper_mma), false));
12614    }
12615
12616    #[cfg(memra_hopper_mma)]
12617    #[test]
12618    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
12619        assert!(legacy_quant_gemm_allowed(cfg!(memra_portable_cuda), cfg!(memra_hopper_mma), false));
12620        assert!(super::portable_mma_gated() == false);
12621    }
12622}
12623
12624/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
12625/// inherent methods (inherent methods win name resolution, so no recursion).
12626impl memra_kv::KvDev for Engine {
12627    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12628        Engine::zeros(self, n)
12629    }
12630    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12631        Engine::uninit(self, n)
12632    }
12633    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
12634        Engine::alloc_u8(self, n)
12635    }
12636    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
12637        Engine::htod_i32(self, v)
12638    }
12639    fn clone_dtod(&self, src: &CudaSlice<f32>) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12640        Engine::clone_dtod(self, src)
12641    }
12642    fn copy_into(&self, dst: &mut CudaSlice<f32>, off: usize, src: &CudaSlice<f32>, len: usize)
12643                 -> Result<(), Box<dyn std::error::Error>> {
12644        Engine::copy_into(self, dst, off, src, len)
12645    }
12646    fn set_i32_one(&self, d: &mut CudaSlice<i32>, v: i32) -> Result<(), Box<dyn std::error::Error>> {
12647        Engine::set_i32_one(self, d, v)
12648    }
12649}