Skip to main content

memra_engine/
kda.rs

1//! Kimi Delta Attention (KDA) — the glm5_next (GLM-5.3-Flash) linear-attention mixer.
2//!
3//! Arithmetic contract: `memra_reference::kimi_delta_net`, pinned by
4//! `kimi_delta_net_matches_hand_derived_three_token_recurrence`. Every step below cites the
5//! reference stage it reproduces; the GPU-vs-reference gate is
6//! `crates/memra-engine/tests/kda_fixture_gpu.rs`.
7//!
8//! Geometry (research/glm53-flash-bringup-20260827/CENSUS.md): 64 heads x 128, q/k/v all the
9//! same width, short conv kernel 4, forget-gate lower bound -5.0. Symmetric widths and no GQA
10//! repeat mean channel `c == h*head_dim + i` IS the (head, dim) pair, so every per-token tensor
11//! stays token-major end to end — there is no analogue of GDN's qkv_to_gdn_repack scatter here.
12//!
13//! PREFILL DISPATCH — SEQUENTIAL SCAN, not the chunked UT transform (deliberate).
14//! `memra_kda_scan_s128` runs prefill and decode alike, which is exactly the shipped
15//! GDN arrangement next door: `gdn_scan_s128` IS the default prefill path and the chunked WY
16//! kernels sit behind `MEMRA_GDN_CHUNKED`. One kernel for both also keeps the decode==verify
17//! dispatch identity that cu/hybrid.cu's headers require. A chunked twin exists but is
18//! SHELVED, ATTRIBUTED-NEGATIVE — it is not a pending tuning follow-up. It was built as L3
19//! of the prefill-gap plan (`MEMRA_KDA_CHUNKED`, unmerged branch lane/glm5-kda-chunk-scan),
20//! and the box prefill census then attributed the wall elsewhere: on a cold 4626-token prime
21//! the whole kda family is 221.6 GPU ms of 6598 (3.4%, "confirms L3's ATTRIBUTED-NEGATIVE:
22//! scan ~2.4%") while mla-prefill-attn owns 75.8% — receipts
23//! `research/glm53-flash-bringup-20260827/launch-diet-20260830/WINDOW-20260830.md` §4 and
24//! `box-receipts-20260830/census-analysis.txt`. No A/B is owed on the scan; a revival needs
25//! a new attribution first. The algebra stays banked for that day: it is NOT a transcription
26//! of the GDN K1-K5 chain — KDA's decay is per channel, so the chunk form needs a per-channel
27//! cumulative log gate `Gcum[t][i]` with `k` scaled by `exp(-Gcum)` and `q` by `exp(+Gcum)`
28//! (banked `chunk_kimi_delta_attention` in
29//! research/glm53-flash-bringup-20260827/modular_glm5_next-ref.py), where GDN gets away with
30//! one scalar `G` per (token, head).
31//!
32//! CONV FUSION — fused WEIGHTS and a fused RING, per-plane launches. The checkpoint ships three
33//! per-plane conv weights; they are concatenated once at load into one `[3*qkv, kernel]` f32
34//! buffer, because the plan already declares the state carrier fused (`StatePlan::Recurrent`
35//! `conv_width = 3*qkv`) and that makes a plane's weight offset and its ring offset the same
36//! `plane*qkv` arithmetic. The three PROJECTIONS stay separate: they are independently
37//! quantized tensors, and concatenating them would mean dequantizing to build one matmul.
38//! Applying each plane's taps to its own plane is the fused grouped conv exactly (the reference
39//! says so in-line), so nothing is approximated by the split.
40
41use crate::Engine;
42use crate::cache::{Cache, RecurLayer};
43use crate::model::GpuTensor;
44use cudarc::driver::{CudaSlice, LaunchConfig, PushKernelArg};
45use memra_gguf::model_plan::KimiDeltaNetPlan;
46use memra_gguf::source::TensorSource;
47use std::sync::atomic::{AtomicU64, Ordering};
48
49/// Engagement counter for the fused 6-way projection door (`MEMRA_KDA_FUSED_PROJ`), the
50/// grouped-prefill `moe_grouped_prefill_dispatches` precedent: gates and box A/B arms count
51/// dispatches at the arm's own call site instead of inferring engagement from a 200.
52pub static KDA_FUSED6_DISPATCHES: AtomicU64 = AtomicU64::new(0);
53
54/// Same door, BF16 operand arm (`qmatvec_kda6_bf16f32`, lane/glm5-decode-diet lever 3).
55/// Counted separately so a box A/B on the serving recipe (MEMRA_BF16_MMV=1, where the q8 arm
56/// refuses by design) can attribute engagement to the arm that actually ran.
57pub static KDA_FUSED6_BF16_DISPATCHES: AtomicU64 = AtomicU64::new(0);
58
59/// Same door, W8-MIRROR arm (`qmatvec_kda6_q8f32_rp_v2`, lane/b200-gemv-hbm-20260902 round 3).
60/// Counted separately for the same reason the bf16 arm is: a box A/B on the serving recipe must
61/// be able to attribute engagement to the arm that actually ran.
62pub static KDA_FUSED6_Q8RP_DISPATCHES: AtomicU64 = AtomicU64::new(0);
63
64/// The only head width `memra_kda_scan_s128` is instantiated for, and the only one glm5_next
65/// ships (`linear_attn_config.head_dim = 128`).
66pub const KDA_HEAD_DIM: usize = 128;
67/// The conv kernels hold their window in a fixed register array; wider kernels would silently
68/// read past it, so the loader refuses them.
69const KDA_MAX_CONV_KERNEL: usize = 8;
70/// FLA l2norm epsilon. Fixed at 1e-6 and INSIDE the sqrt — independent of the layer's rms eps,
71/// which is a different constant used by the output norm below.
72const KDA_L2_EPS: f32 = 1e-6;
73
74/// One loaded KDA mixer. Field names follow the reference's tensor roles, not the HF spellings.
75pub struct KdaAttnLayer {
76    pub plan: KimiDeltaNetPlan,
77    /// q/k/v projections, `[qkv, hidden]` each.
78    pub wq: GpuTensor,
79    pub wk: GpuTensor,
80    pub wv: GpuTensor,
81    /// Forget gate low-rank pair: `f_a [head_dim, hidden]`, `f_b [qkv, head_dim]`.
82    pub f_a: GpuTensor,
83    pub f_b: GpuTensor,
84    /// Output gate low-rank pair, same shapes as the forget pair.
85    pub g_a: GpuTensor,
86    pub g_b: GpuTensor,
87    /// Per-head beta projection, `[heads, hidden]`.
88    pub b_proj: GpuTensor,
89    /// Output projection, `[hidden, qkv]`.
90    pub wo: GpuTensor,
91    /// The three per-plane conv weights concatenated into `[3*qkv, kernel]` (see module header).
92    pub conv: CudaSlice<f32>,
93    /// `A_log [heads]`, `dt_bias [qkv]` (per CHANNEL, unlike GDN's per-head bias),
94    /// `o_norm [head_dim]`.
95    pub a_log: GpuTensor,
96    pub dt_bias: GpuTensor,
97    pub o_norm: GpuTensor,
98    /// glm5 TP-2 sidecar (`MEMRA_GLM5_TP`, lane/glm5-tp2). `Some` means THIS layer struct is
99    /// the ROOT-RANK HEAD SHARD (heads/2) and the sidecar carries the peer shard + runtime.
100    /// Every plain entry point REFUSES a sharded layer by name — only the TP walk
101    /// (`glm5_tp::kda_tp_*`) may execute it. `None` everywhere else (zero cost, zero change).
102    pub tp: Option<Box<crate::glm5_tp::Glm5TpKda>>,
103}
104
105impl KdaAttnLayer {
106    pub fn heads(&self) -> usize {
107        self.plan.num_heads as usize
108    }
109    pub fn head_dim(&self) -> usize {
110        self.plan.head_dim as usize
111    }
112    pub fn qkv(&self) -> usize {
113        self.heads() * self.head_dim()
114    }
115    pub fn conv_kernel(&self) -> usize {
116        self.plan.conv_kernel as usize
117    }
118    /// Fused conv ring width, matching `StatePlan::Recurrent { conv_width }` for this layer.
119    pub fn conv_width(&self) -> usize {
120        3 * self.qkv()
121    }
122    /// Recurrent state elements, matching `StatePlan::Recurrent { state_width }`.
123    pub fn state_width(&self) -> usize {
124        self.heads() * self.head_dim() * self.head_dim()
125    }
126
127    /// Load block `il`'s KDA tensors. Names are the ggml-dialect contract names from
128    /// `memra_gguf::tensor_contract::add_kda`; the safetensors source translates them.
129    pub fn load(
130        e: &Engine,
131        src: &dyn TensorSource,
132        il: u32,
133        plan: &KimiDeltaNetPlan,
134    ) -> Result<Self, Box<dyn std::error::Error>> {
135        let heads = plan.num_heads as usize;
136        let head_dim = plan.head_dim as usize;
137        let kernel = plan.conv_kernel as usize;
138        if head_dim != KDA_HEAD_DIM {
139            return Err(format!(
140                "blk.{il}: KDA head_dim {head_dim} is not the {KDA_HEAD_DIM} the scan kernel is \
141                 instantiated for; a new memra_kda_scan_s<N> instantiation is required before \
142                 this geometry can serve"
143            )
144            .into());
145        }
146        if heads == 0 {
147            return Err(format!("blk.{il}: KDA num_heads must be positive").into());
148        }
149        if !(2..=KDA_MAX_CONV_KERNEL).contains(&kernel) {
150            return Err(format!(
151                "blk.{il}: KDA conv_kernel {kernel} outside the 2..={KDA_MAX_CONV_KERNEL} window \
152                 the conv kernels hold in registers"
153            )
154            .into());
155        }
156        let p = |s: &str| format!("blk.{il}.{s}");
157        let load = |name: String| GpuTensor::load_from_source(e, src, &name);
158
159        let qkv = heads * head_dim;
160        // Fuse the three per-plane conv weights into one [3*qkv, kernel] buffer (module header).
161        // Each source tensor is [qkv, kernel] channel-major, so the planes concatenate as whole
162        // row blocks and plane p lands at row p*qkv — the ring's own plane offset.
163        let mut conv = e.zeros(3 * qkv * kernel)?;
164        for (plane, name) in [
165            "kda_q_conv1d.weight",
166            "kda_k_conv1d.weight",
167            "kda_v_conv1d.weight",
168        ]
169        .into_iter()
170        .enumerate()
171        {
172            let w = load(p(name))?;
173            let src_data = w.float_data();
174            if src_data.len() != qkv * kernel {
175                return Err(format!(
176                    "blk.{il}.{name}: {} elements, contract requires {}",
177                    src_data.len(),
178                    qkv * kernel
179                )
180                .into());
181            }
182            e.copy_into(&mut conv, plane * qkv * kernel, src_data, qkv * kernel)?;
183        }
184
185        Ok(Self {
186            plan: *plan,
187            wq: load(p("kda_q.weight"))?,
188            wk: load(p("kda_k.weight"))?,
189            wv: load(p("kda_v.weight"))?,
190            f_a: load(p("kda_f_a.weight"))?,
191            f_b: load(p("kda_f_b.weight"))?,
192            g_a: load(p("kda_g_a.weight"))?,
193            g_b: load(p("kda_g_b.weight"))?,
194            b_proj: load(p("kda_b.weight"))?,
195            wo: load(p("kda_out.weight"))?,
196            conv,
197            a_log: load(p("kda_a_log"))?,
198            dt_bias: load(p("kda_dt.bias"))?,
199            o_norm: load(p("kda_o_norm.weight"))?,
200            tp: None,
201        })
202    }
203}
204
205/// Which conv arm a call takes. `Prefill` reads the ring as a left pad and rolls it afterwards;
206/// `Decode` fuses assemble+conv+roll for the single new row. The two produce bit-identical
207/// values at T=1 (same ascending tap order over the same window) — the split exists so decode
208/// and the spec verify keep one dispatch class, per the cu/hybrid.cu decode==verify law.
209#[derive(Clone, Copy, PartialEq, Eq)]
210pub(crate) enum ConvArm {
211    Prefill,
212    Decode,
213}
214
215/// The scan-input buffers of one KDA step, STOLEN from the step instead of dropped
216/// (lane/glm5-loop-port, port 3 — the module doc's named GdnStash/ReplaySSM diet): the
217/// glm5 verify walk's rollback checkpoint keeps these ~160 KB of already-allocated
218/// buffers per row per layer and retires the per-row 4 MiB recurrent-state clones
219/// (~0.95 GiB transient at K=7). Replaying `kda_scan` over them from a pre-round state
220/// snapshot rebuilds the post-row state EXACTLY: each replay is the ORIGINAL t=1 launch
221/// re-issued — same kernel, same inputs, same shape — so the rebuilt state is
222/// byte-identical to the clone it replaces by construction, not by a numeric argument.
223pub struct KdaScanInputs {
224    pub q: CudaSlice<f32>,
225    pub k: CudaSlice<f32>,
226    pub v: CudaSlice<f32>,
227    pub g: CudaSlice<f32>,
228    pub beta: CudaSlice<f32>,
229}
230
231/// The rollback stash of one BATCHED verify-rows KDA call (lane/glm5-verify-batch): the
232/// per-layer t=K+1 twin of the per-row [`KdaScanInputs`] steal. Everything here is either
233/// stolen from buffers the call allocated anyway (`raws`, `scan` — zero copies) or one
234/// small clone per layer per round (`ring_snap`, `3*qkv*(kernel-1)` floats ~ 96 KiB).
235///
236/// Rollback to `keep` rows rebuilds both state planes EXACTLY:
237///   * conv ring: restore `ring_snap`, then re-issue `kda_conv_ring_roll` per plane over
238///     `raws` at T=keep — the roll is pure placement (no arithmetic), so the rebuilt ring
239///     is the sequential chain's ring after row keep-1 byte-for-byte.
240///   * ssm state: ONE `kda_scan` replay at T=keep from the caller's pre-round snapshot
241///     over the batched `scan` inputs (the kernel walks rows 0..keep of the [t, ..]
242///     buffers) — the in-kernel T-loop IS the chained t=1 program (register-resident
243///     state, identical per-step order), held by the scan-chain bit-gate.
244pub struct KdaRowsStash {
245    /// The fused conv ring BEFORE this call's rolls (one clone per layer per round).
246    pub ring_snap: CudaSlice<f32>,
247    /// RAW (pre-conv) q/k/v projection rows `[t, qkv]`, stolen post-roll (plane order).
248    pub raws: [CudaSlice<f32>; 3],
249    /// Batched scan inputs `[t, ..]`, stolen post-scan.
250    pub scan: KdaScanInputs,
251    /// Row count of the call that filled this stash; rollback validates `keep` against it.
252    pub rows: usize,
253}
254
255/// What a `kda_core` call is asked to leave behind for rollback — and, for `Rows`, which
256/// matmul class the call rides (the decode-exact rows classes, `matmul_rows_exact`).
257pub(crate) enum KdaStash<'a> {
258    /// No rollback stash (prefill / plain decode).
259    None,
260    /// Per-row t=1 steal (loop-port 3, the per-row verify walk).
261    Decode(&'a mut Option<KdaScanInputs>),
262    /// BATCHED verify-rows steal (lane/glm5-verify-batch): scan inputs + raw conv rows +
263    /// a pre-call ring snapshot; every matmul rides `matmul_rows_exact` so each row is
264    /// bit-identical to the t=1 decode program per the decode-exact class contracts.
265    Rows(&'a mut Option<KdaRowsStash>),
266}
267
268/// The whole mixer, stage for stage against `memra_reference::kimi_delta_net`.
269///
270/// `ring` is the fused `[3*qkv, kernel-1]` conv state (zeroed = fresh prefill's zero left pad)
271/// and is updated in place. `state_in`/`state_out` are the `[heads, 128, 128]` recurrent state
272/// in the kernel's transposed `M[col][i]` layout; they MUST be distinct buffers.
273#[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
274fn kda_core(
275    e: &Engine,
276    la: &KdaAttnLayer,
277    x: &CudaSlice<f32>,
278    t: usize,
279    eps: f32,
280    ring: &mut CudaSlice<f32>,
281    state_in: &CudaSlice<f32>,
282    state_out: &mut CudaSlice<f32>,
283    arm: ConvArm,
284    stash: KdaStash<'_>,
285    scan_clock: Option<&mut u64>,
286) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
287    // glm5 TP fail-closed choke point: every plain KDA entry (stateless, prime, decode,
288    // stash — INCLUDING the batched verify-rows walk, `kda_verify_rows_cached`) funnels
289    // through here. A TP-sharded layer holds heads/2 — running it on the plain path would
290    // compute a silently-halved mixer, so it refuses by name instead.
291    if la.tp.is_some() {
292        return Err(format!(
293            "KDA layer is glm5-TP-sharded (MEMRA_GLM5_TP): the plain mixer path is unwired \
294             for a head shard — only the TP decode/prime walk may execute it (t={t}, arm \
295             {})",
296            if arm == ConvArm::Decode {
297                "decode"
298            } else {
299                "prefill"
300            }
301        )
302        .into());
303    }
304    // Verify-batch wo seam (lane/glm5-verify-batch): the rows arm routes the output
305    // projection through the decode-exact classes, exactly like every projection inside
306    // the core — the wo dispatch moved into this wrapper with the TP split, its routing
307    // did not change.
308    let rows_exact = matches!(stash, KdaStash::Rows(_));
309    let gated = kda_core_gated(
310        e, la, x, t, eps, ring, state_in, state_out, arm, stash, scan_clock,
311    )?;
312    if rows_exact {
313        let y = e.matmul_rows_exact(&la.wo, &gated, t);
314        // Door W: gated's last reader was the wo matmul above.
315        e.vws_recycle(gated);
316        y
317    } else {
318        e.matmul(&la.wo, &gated, t)
319    }
320}
321
322/// [`kda_core`] up to (and excluding) the output projection: returns the gated `[t, qkv]`
323/// mixer output. Split out for the glm5 TP-2 seam, whose column-parallel `wo` runs over the
324/// cross-rank GATHERED gated tensor rather than this shard's slice — the plain path is
325/// `kda_core` above, byte-for-byte the pre-split body (the wo matmul and its rows-exact
326/// routing moved, nothing else). This body is the CURRENT doored/batched core: it carries
327/// the `MEMRA_KDA_FUSED_PROJ` door and the verify-batch rows arm; the TP decode/prime walk
328/// calls it with `KdaStash::None`, the spec x TP verify walk (lane/glm5-composition) with
329/// `KdaStash::Rows` per rank, and the TP load preflight refuses the fused-proj door by
330/// name (unproven composition on head shards — see the FLAGS.md composition matrix).
331#[allow(clippy::too_many_arguments)] // mirrors kda_core's own contract-shaped list
332pub(crate) fn kda_core_gated(
333    e: &Engine,
334    la: &KdaAttnLayer,
335    x: &CudaSlice<f32>,
336    t: usize,
337    eps: f32,
338    ring: &mut CudaSlice<f32>,
339    state_in: &CudaSlice<f32>,
340    state_out: &mut CudaSlice<f32>,
341    arm: ConvArm,
342    stash: KdaStash<'_>,
343    mut scan_clock: Option<&mut u64>,
344) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
345    let heads = la.heads();
346    let head_dim = la.head_dim();
347    let qkv = la.qkv();
348    let kernel = la.conv_kernel();
349    // The BATCHED verify-rows arm (lane/glm5-verify-batch): prefill conv dispatch (per-row
350    // bit-identical to the decode arm — same ascending taps over the same window values,
351    // held by the conv-arm bit-gate) + decode-exact matmul classes + the rows stash.
352    let rows_exact = matches!(stash, KdaStash::Rows(_));
353    if rows_exact && arm != ConvArm::Prefill {
354        return Err("KDA rows stash requires the prefill conv arm".into());
355    }
356    if arm == ConvArm::Decode && t != 1 {
357        return Err(format!("KDA decode arm requires t == 1, got {t}").into());
358    }
359    if ring.len() < la.conv_width() * (kernel - 1) {
360        return Err(format!(
361            "KDA conv ring holds {} floats, layer needs {}",
362            ring.len(),
363            la.conv_width() * (kernel - 1)
364        )
365        .into());
366    }
367    if state_in.len() < la.state_width() || state_out.len() < la.state_width() {
368        return Err(format!(
369            "KDA recurrent state holds {}/{} floats, layer needs {}",
370            state_in.len(),
371            state_out.len(),
372            la.state_width()
373        )
374        .into());
375    }
376
377    // Stage 1 — the six projections that read x directly. f_b/g_b are chained off their own
378    // down-projections below, exactly as the reference nests them.
379    //
380    // MEMRA_KDA_FUSED_PROJ=1 (default OFF): the six matvec calls collapse to one quantize +
381    // one `qmatvec_kda6_q8f32_mmvq` launch — the program shape both vLLM and SGLang ship for
382    // this trunk (ENGINE-SURVEY.md C1) and the step37 QKV_FUSED transfer (TRANSFER-MAP lever 1).
383    // `kda_proj_fused6` refuses (returns None) on any operand/env shape where its bit-identity
384    // claim would not hold, so the fall-through arm is always the unchanged program.
385    let mut g6 = match e.kda_proj_fused6(la, x, t)? {
386        Some(outs) => outs,
387        None if rows_exact => {
388            // Verify-rows matmul class: per-weight decode-exact dispatch (the tcols /
389            // batched-MMVQ / per-token-linear classes — each row bit-identical to the
390            // t=1 program by the matmul_rows_exact contract).
391            [&la.wq, &la.wk, &la.wv, &la.f_a, &la.g_a, &la.b_proj]
392                .into_iter()
393                .map(|w| e.matmul_rows_exact(w, x, t))
394                .collect::<Result<Vec<_>, _>>()?
395        }
396        None => e.matmul_group(
397            &[&la.wq, &la.wk, &la.wv, &la.f_a, &la.g_a, &la.b_proj],
398            x,
399            t,
400        )?,
401    };
402    let beta_raw = g6.pop().unwrap(); // [T, heads]
403    let gate_down = g6.pop().unwrap(); // [T, head_dim]
404    let forget_down = g6.pop().unwrap(); // [T, head_dim]
405    let v_raw = g6.pop().unwrap(); // [T, qkv]
406    let k_raw = g6.pop().unwrap();
407    let q_raw = g6.pop().unwrap();
408
409    // Rows stash: snapshot the ring BEFORE the rolls mutate it (one ~96 KiB clone per
410    // layer per round — the rollback's re-roll base). Door W: on the rows arm the snapshot
411    // (and every scratch below) is a pooled draw — vws_uninit == alloc_uninit with the
412    // door off, and the non-rows arms keep the plain allocs untouched.
413    let ring_snap = match &stash {
414        KdaStash::Rows(_) => {
415            let mut snap = e.vws_uninit(ring.len())?;
416            e.dtod_copy_into(ring, &mut snap, 0)?;
417            Some(snap)
418        }
419        _ => None,
420    };
421
422    // Stage 2 — per-plane causal short conv + SiLU. Planes are ordered q, k, v in both the fused
423    // weight buffer and the fused ring, which is the order the reference stores conv_state in.
424    let mut q_conv = if rows_exact {
425        e.vws_uninit(t * qkv)?
426    } else {
427        e.uninit(t * qkv)?
428    };
429    let mut k_conv = if rows_exact {
430        e.vws_uninit(t * qkv)?
431    } else {
432        e.uninit(t * qkv)?
433    };
434    let mut v_conv = if rows_exact {
435        e.vws_uninit(t * qkv)?
436    } else {
437        e.uninit(t * qkv)?
438    };
439    for (plane, (raw, out)) in [
440        (&q_raw, &mut q_conv),
441        (&k_raw, &mut k_conv),
442        (&v_raw, &mut v_conv),
443    ]
444    .into_iter()
445    .enumerate()
446    {
447        match arm {
448            ConvArm::Prefill => e.kda_conv_silu(raw, &la.conv, ring, out, qkv, t, kernel, plane)?,
449            ConvArm::Decode => {
450                e.kda_conv_silu_decode(raw, ring, &la.conv, out, qkv, kernel, plane)?
451            }
452        }
453    }
454    // The prefill arm reads the OLD ring for every token, so the roll runs only after all three
455    // planes have been convolved. The decode arm already rolled inside its fused kernel.
456    if arm == ConvArm::Prefill {
457        for (plane, raw) in [&q_raw, &k_raw, &v_raw].into_iter().enumerate() {
458            e.kda_conv_ring_roll(raw, ring, qkv, t, kernel, plane)?;
459        }
460    }
461
462    // Stage 3 — q/k L2 norm over head_dim (eps INSIDE the sqrt, fixed 1e-6). Rows of the
463    // token-major layout are contiguous head_dim runs, so no repack is needed.
464    let mut q_l2 = if rows_exact {
465        e.vws_uninit(t * qkv)?
466    } else {
467        e.uninit(t * qkv)?
468    };
469    let mut k_l2 = if rows_exact {
470        e.vws_uninit(t * qkv)?
471    } else {
472        e.uninit(t * qkv)?
473    };
474    e.l2_norm(&q_conv, &mut q_l2, head_dim, t * heads, KDA_L2_EPS)?;
475    e.l2_norm(&k_conv, &mut k_l2, head_dim, t * heads, KDA_L2_EPS)?;
476    // Door W: the convs' last readers were the l2 norms (the ring rolls read the raws).
477    if rows_exact {
478        e.vws_recycle(q_conv);
479        e.vws_recycle(k_conv);
480    }
481
482    // Stage 4 — gates. forget: g = lower_bound * sigmoid(exp(A_log[h]) * (f_b(f_a(x)) + dt_bias)),
483    // emitted RAW (the scan applies expf). beta: per-head sigmoid of its own projection.
484    let forget = if rows_exact {
485        e.matmul_rows_exact(&la.f_b, &forget_down, t)?
486    } else {
487        e.matmul(&la.f_b, &forget_down, t)?
488    };
489    let mut g_log = if rows_exact {
490        e.vws_uninit(t * qkv)?
491    } else {
492        e.uninit(t * qkv)?
493    };
494    e.kda_gate(
495        &forget,
496        la.dt_bias.float_data(),
497        la.a_log.float_data(),
498        &mut g_log,
499        qkv,
500        t,
501        head_dim,
502        la.plan.gate_lower_bound,
503    )?;
504    let mut beta = if rows_exact {
505        e.vws_uninit(t * heads)?
506    } else {
507        e.uninit(t * heads)?
508    };
509    e.sigmoid(&beta_raw, &mut beta, t * heads)?;
510    // Door W: forget_down's last reader was the f_b matmul, forget's the gate kernel,
511    // beta_raw's the sigmoid.
512    if rows_exact {
513        e.vws_recycle(forget_down);
514        e.vws_recycle(forget);
515        e.vws_recycle(beta_raw);
516    }
517
518    // Stage 5 — the delta-rule recurrence. `scale` carries the reference's head_dim^-0.5 query
519    // scale: q feeds only the readout, never the state, so scaling the readout is exact.
520    // At t > 1 the kernel walks the T steps IN-KERNEL over register-resident state — the
521    // sequential chain preserved inside ONE launch (chained-t=1 identity by construction,
522    // held by the scan-chain bit-gate). `scan_clock` is the trace-level-2 instrument: it
523    // drains the stream around the launch so the sequential-class share lands in its own
524    // bucket (shares, never walls).
525    let scale = 1.0 / (head_dim as f32).sqrt();
526    let mut core = if rows_exact {
527        e.vws_uninit(t * qkv)?
528    } else {
529        e.uninit(t * qkv)?
530    };
531    let scan_t0 = scan_clock.as_ref().map(|_| {
532        let _ = e.stream().synchronize();
533        std::time::Instant::now()
534    });
535    e.kda_scan(
536        &q_l2, &k_l2, &v_conv, &g_log, &beta, state_in, state_out, &mut core, heads, t, scale,
537    )?;
538    if let (Some(ns), Some(t0)) = (scan_clock.take(), scan_t0) {
539        let _ = e.stream().synchronize();
540        *ns += t0.elapsed().as_nanos() as u64;
541    }
542
543    // Stage 6 — sigmoid-gated RMSNorm over head_dim (layer rms eps here, NOT the l2 eps), then
544    // the output projection.
545    let gate = if rows_exact {
546        e.matmul_rows_exact(&la.g_b, &gate_down, t)?
547    } else {
548        e.matmul(&la.g_b, &gate_down, t)?
549    };
550    let mut gated = if rows_exact {
551        e.vws_uninit(t * qkv)?
552    } else {
553        e.uninit(t * qkv)?
554    };
555    e.kda_gated_rmsnorm(
556        &core,
557        la.o_norm.float_data(),
558        &gate,
559        &mut gated,
560        head_dim,
561        t * heads,
562        eps,
563    )?;
564    // Door W: gate_down's last reader was the g_b matmul; core's and gate's the
565    // gated-rmsnorm above.
566    if rows_exact {
567        e.vws_recycle(gate_down);
568        e.vws_recycle(gate);
569        e.vws_recycle(core);
570    }
571    // Steal the scan/conv inputs for the caller's rollback stash: stage 5 has consumed
572    // the scan inputs and the rolls were the raws' last readers — moving them out is
573    // free (no copy, no launch; the buffers were allocated this call either way).
574    match stash {
575        KdaStash::None => {}
576        KdaStash::Decode(s) => {
577            *s = Some(KdaScanInputs {
578                q: q_l2,
579                k: k_l2,
580                v: v_conv,
581                g: g_log,
582                beta,
583            });
584        }
585        KdaStash::Rows(s) => {
586            // Door W: the PREVIOUS round's stash dies here — its nine buffers restock
587            // the pool instead of falling to nine async frees (per layer per round).
588            if let Some(old) = s.take() {
589                e.vws_recycle(old.ring_snap);
590                for r in old.raws {
591                    e.vws_recycle(r);
592                }
593                e.vws_recycle(old.scan.q);
594                e.vws_recycle(old.scan.k);
595                e.vws_recycle(old.scan.v);
596                e.vws_recycle(old.scan.g);
597                e.vws_recycle(old.scan.beta);
598            }
599            *s = Some(KdaRowsStash {
600                ring_snap: ring_snap.expect("rows arm snapshotted the ring above"),
601                raws: [q_raw, k_raw, v_raw],
602                scan: KdaScanInputs {
603                    q: q_l2,
604                    k: k_l2,
605                    v: v_conv,
606                    g: g_log,
607                    beta,
608                },
609                rows: t,
610            });
611        }
612    }
613    Ok(gated)
614}
615
616/// STATELESS prefill from a zero conv ring and a zero recurrent state — the arm the logits-only
617/// forward paths take. Allocates and discards both state buffers.
618pub fn kda_attn(
619    e: &Engine,
620    la: &KdaAttnLayer,
621    x: &CudaSlice<f32>,
622    t: usize,
623    eps: f32,
624) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
625    let mut ring = e.zeros(la.conv_width() * (la.conv_kernel() - 1))?;
626    let state_in = e.zeros(la.state_width())?;
627    let mut state_out = e.zeros(la.state_width())?;
628    kda_core(
629        e,
630        la,
631        x,
632        t,
633        eps,
634        &mut ring,
635        &state_in,
636        &mut state_out,
637        ConvArm::Prefill,
638        KdaStash::None,
639        None,
640    )
641}
642
643/// STATEFUL prefill: carries the ring forward and advances the recurrent state from `state_in`
644/// into `state_out`. Callers own the ping-pong; the two state buffers must be distinct.
645#[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
646pub fn kda_attn_prime(
647    e: &Engine,
648    la: &KdaAttnLayer,
649    x: &CudaSlice<f32>,
650    t: usize,
651    eps: f32,
652    ring: &mut CudaSlice<f32>,
653    state_in: &CudaSlice<f32>,
654    state_out: &mut CudaSlice<f32>,
655) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
656    kda_core(
657        e,
658        la,
659        x,
660        t,
661        eps,
662        ring,
663        state_in,
664        state_out,
665        ConvArm::Prefill,
666        KdaStash::None,
667        None,
668    )
669}
670
671/// T=1 decode step. Same math as a one-token prime; separate conv arm so the fused
672/// assemble+conv+roll kernel keeps decode and the spec verify on one dispatch class.
673pub fn kda_attn_decode(
674    e: &Engine,
675    la: &KdaAttnLayer,
676    x: &CudaSlice<f32>,
677    eps: f32,
678    ring: &mut CudaSlice<f32>,
679    state_in: &CudaSlice<f32>,
680    state_out: &mut CudaSlice<f32>,
681) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
682    kda_core(
683        e,
684        la,
685        x,
686        1,
687        eps,
688        ring,
689        state_in,
690        state_out,
691        ConvArm::Decode,
692        KdaStash::None,
693        None,
694    )
695}
696
697/// Stateful KDA against the shared recurrent-state carrier, in the eager GDN discipline: the
698/// scan reads `ssm_state` and writes the spare `ssm_state_alt`, then the two OWNED resident
699/// buffers swap in place. Stable pointers, no per-step alloc/free — the per-step scratch this
700/// replaced churned the stream-ordered pool and made decode run-to-run nondeterministic
701/// (crates/memra-kv `RecurLayer::ssm_state_alt`). NOT capture-safe: a captured graph bakes
702/// capture-time pointers and never re-runs the host swap, which is why the capture loops refuse.
703#[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
704fn kda_cached(
705    e: &Engine,
706    la: &KdaAttnLayer,
707    x: &CudaSlice<f32>,
708    t: usize,
709    eps: f32,
710    cache: &mut Cache,
711    il: usize,
712    arm: ConvArm,
713    stash: KdaStash<'_>,
714    scan_clock: Option<&mut u64>,
715) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
716    let rl = cache.recur[il].as_mut().ok_or_else(|| {
717        format!(
718            "blk.{il}: KDA layer has no recurrent state — the cache allocator saw a \
719                 non-Recurrent StatePlan for a KDA layer"
720        )
721    })?;
722    let out = {
723        let RecurLayer {
724            conv_state,
725            ssm_state,
726            ssm_state_alt,
727        } = rl;
728        kda_core(
729            e,
730            la,
731            x,
732            t,
733            eps,
734            conv_state,
735            ssm_state,
736            ssm_state_alt,
737            arm,
738            stash,
739            scan_clock,
740        )?
741    };
742    std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
743    Ok(out)
744}
745
746/// Stateful prefill of `t` tokens through the cache's KDA state for layer `il`.
747pub fn kda_prime_cached(
748    e: &Engine,
749    la: &KdaAttnLayer,
750    x: &CudaSlice<f32>,
751    t: usize,
752    eps: f32,
753    cache: &mut Cache,
754    il: usize,
755) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
756    kda_cached(
757        e,
758        la,
759        x,
760        t,
761        eps,
762        cache,
763        il,
764        ConvArm::Prefill,
765        KdaStash::None,
766        None,
767    )
768}
769
770/// One decode step through the cache's KDA state for layer `il`.
771pub fn kda_decode_cached(
772    e: &Engine,
773    la: &KdaAttnLayer,
774    x: &CudaSlice<f32>,
775    eps: f32,
776    cache: &mut Cache,
777    il: usize,
778) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
779    kda_cached(
780        e,
781        la,
782        x,
783        1,
784        eps,
785        cache,
786        il,
787        ConvArm::Decode,
788        KdaStash::None,
789        None,
790    )
791}
792
793/// [`kda_decode_cached`] with the step's scan inputs STOLEN for a rollback stash
794/// (loop-port 3; doc on [`KdaScanInputs`]). Identical launches — the steal is a move of
795/// buffers the step allocated either way.
796pub fn kda_decode_cached_stash(
797    e: &Engine,
798    la: &KdaAttnLayer,
799    x: &CudaSlice<f32>,
800    eps: f32,
801    cache: &mut Cache,
802    il: usize,
803) -> Result<(CudaSlice<f32>, KdaScanInputs), Box<dyn std::error::Error>> {
804    let mut stash: Option<KdaScanInputs> = None;
805    let out = kda_cached(
806        e,
807        la,
808        x,
809        1,
810        eps,
811        cache,
812        il,
813        ConvArm::Decode,
814        KdaStash::Decode(&mut stash),
815        None,
816    )?;
817    let stash = stash.ok_or("kda_core returned without filling the requested scan stash")?;
818    Ok((out, stash))
819}
820
821/// THE BATCHED VERIFY-ROWS KDA CALL (lane/glm5-verify-batch): one t=K+1 `kda_core` pass
822/// per layer per round, replacing t per-row [`kda_decode_cached_stash`] calls. Projections,
823/// gates and norms batch m=t through the decode-exact matmul classes (`matmul_rows_exact`);
824/// the conv takes the prefill dispatch (per-token bit-identical to the decode arm's taps);
825/// the recurrence stays SEQUENTIAL inside one `memra_kda_scan_s128` launch (the in-kernel
826/// T-loop over register-resident state == the chained t=1 program). Per-row bit-identity
827/// vs the t=1 chain is held by the walk gates (`glm5_tparallel_verify_gpu`) and the
828/// kernel bit-gates (`glm5_verify_batch_gpu`).
829///
830/// The caller owns the pre-round ssm snapshot (`Glm5VerifyCkpt::kda_ssm_snap`, cloned
831/// BEFORE this call); the returned [`KdaRowsStash`] carries everything else rollback
832/// needs. `scan_clock`: the trace-level-2 sequential-class bucket (ns accumulated around
833/// the scan launch with stream drains — an instrument, never a serving mode).
834#[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kda_cached call contract plus the trace clock
835pub fn kda_verify_rows_cached(
836    e: &Engine,
837    la: &KdaAttnLayer,
838    x: &CudaSlice<f32>,
839    t: usize,
840    eps: f32,
841    cache: &mut Cache,
842    il: usize,
843    scan_clock: Option<&mut u64>,
844) -> Result<(CudaSlice<f32>, KdaRowsStash), Box<dyn std::error::Error>> {
845    let mut stash: Option<KdaRowsStash> = None;
846    let out = kda_cached(
847        e,
848        la,
849        x,
850        t,
851        eps,
852        cache,
853        il,
854        ConvArm::Prefill,
855        KdaStash::Rows(&mut stash),
856        scan_clock,
857    )?;
858    let stash = stash.ok_or("kda_core returned without filling the requested rows stash")?;
859    Ok((out, stash))
860}
861
862/// Roll layer `il` back to "after row `keep-1`" from a BATCHED verify-rows round
863/// (lane/glm5-verify-batch; the [`KdaRowsStash`] doc states the two-plane contract):
864/// restore the pre-round conv ring and re-roll `keep` raw rows (pure placement), then
865/// replay the scan ONCE at T=keep from the pre-round ssm snapshot over the batched
866/// inputs. Full accept (`keep == rows`) never calls this — the resident state IS the
867/// state after the last kept row.
868pub fn kda_verify_rollback_rows(
869    e: &Engine,
870    la: &KdaAttnLayer,
871    snap: &CudaSlice<f32>,
872    stash: &KdaRowsStash,
873    keep: usize,
874    cache: &mut Cache,
875    il: usize,
876) -> Result<(), Box<dyn std::error::Error>> {
877    let rl = cache.recur[il]
878        .as_mut()
879        .ok_or_else(|| format!("blk.{il}: KDA rows rollback on a layer with no recurrent state"))?;
880    kda_verify_rollback_rows_on(e, la, snap, stash, keep, rl, il)
881}
882
883/// [`kda_verify_rollback_rows`] over a CALLER-OWNED state plane — the glm5 spec x TP seam
884/// (lane/glm5-composition): under `MEMRA_GLM5_TP` each rank's shard-geometry conv ring +
885/// ssm ping-pong lives in `cache.glm5_tp_recur[il][rank]` on that rank's engine, so the
886/// rollback restores per rank through this entry with the rank's own `(engine, shard,
887/// snapshot, stash)` tuple. The cache wrapper above delegates here — one body, byte-for-byte
888/// the pre-refactor walk on the plain path.
889pub fn kda_verify_rollback_rows_on(
890    e: &Engine,
891    la: &KdaAttnLayer,
892    snap: &CudaSlice<f32>,
893    stash: &KdaRowsStash,
894    keep: usize,
895    rl: &mut RecurLayer,
896    il: usize,
897) -> Result<(), Box<dyn std::error::Error>> {
898    if keep == 0 || keep >= stash.rows {
899        return Err(format!(
900            "blk.{il}: KDA rows rollback keep={keep} outside 1..{} (full accept keeps the \
901             resident state and never replays)",
902            stash.rows
903        )
904        .into());
905    }
906    let qkv = la.qkv();
907    let kernel = la.conv_kernel();
908    let heads = la.heads();
909    let scale = 1.0 / (la.head_dim() as f32).sqrt();
910    // Conv ring: pre-round snapshot back, then re-roll the kept raw rows per plane. The
911    // roll kernel reads every old slot into registers before any store, so T=keep < pad
912    // mixes snapshot slots and kept rows exactly as the sequential chain's rolls did.
913    e.copy_into(
914        &mut rl.conv_state,
915        0,
916        &stash.ring_snap,
917        stash.ring_snap.len(),
918    )?;
919    for (plane, raw) in stash.raws.iter().enumerate() {
920        e.kda_conv_ring_roll(raw, &mut rl.conv_state, qkv, keep, kernel, plane)?;
921    }
922    // Recurrent state: ONE T=keep replay from the snapshot over the batched scan inputs
923    // (the kernel walks rows 0..keep of the [t, ..] buffers); readout discarded. The
924    // ping-pong ends with the rebuilt state under the `ssm_state` name, matching
925    // `kda_cached`'s swap discipline.
926    let mut o = e.uninit(keep * qkv)?;
927    {
928        let RecurLayer {
929            ssm_state: _,
930            ssm_state_alt,
931            ..
932        } = rl;
933        e.kda_scan(
934            &stash.scan.q,
935            &stash.scan.k,
936            &stash.scan.v,
937            &stash.scan.g,
938            &stash.scan.beta,
939            snap,
940            ssm_state_alt,
941            &mut o,
942            heads,
943            keep,
944            scale,
945        )?;
946    }
947    std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
948    Ok(())
949}
950
951/// Rebuild layer `il`'s recurrent state to "after row `inputs.len()-1`" by REPLAYING the
952/// stashed scan inputs from the pre-round snapshot `snap` (loop-port 3, the module-doc
953/// diet made concrete): each replay is the original t=1 `memra_kda_scan_s128` launch
954/// re-issued over the very buffers that step consumed, so the rebuilt state is
955/// byte-identical to the per-row clone it replaces BY CONSTRUCTION. The readout is
956/// discarded; the conv ring is not touched (the walk still clones it per row — 288 KiB
957/// against the 4 MiB ssm plane this retires). The ping-pong rides the resident pair and
958/// ends with the rebuilt state under the `ssm_state` name, matching `kda_cached`'s own
959/// swap discipline.
960pub fn kda_scan_replay(
961    e: &Engine,
962    la: &KdaAttnLayer,
963    snap: &CudaSlice<f32>,
964    inputs: &[KdaScanInputs],
965    cache: &mut Cache,
966    il: usize,
967) -> Result<(), Box<dyn std::error::Error>> {
968    if inputs.is_empty() {
969        return Err(format!(
970            "blk.{il}: KDA replay needs at least one stashed row (rollback keep >= 1; a \
971             restore TO the snapshot itself is a different contract)"
972        )
973        .into());
974    }
975    if la.tp.is_some() {
976        return Err(format!(
977            "blk.{il}: KDA scan replay (the PER-ROW rollback seam) is unwired for a \
978             glm5-TP-sharded layer — the spec x TP composition requires the BATCHED \
979             verify walk, whose rollback rides kda_verify_rollback_rows_on per rank"
980        )
981        .into());
982    }
983    let heads = la.heads();
984    let scale = 1.0 / (la.head_dim() as f32).sqrt();
985    let qkv = la.qkv();
986    let rl = cache.recur[il]
987        .as_mut()
988        .ok_or_else(|| format!("blk.{il}: KDA replay on a layer with no recurrent state"))?;
989    let mut o = e.uninit(qkv)?; // discarded readout scratch, reused across rows
990    for (r, inp) in inputs.iter().enumerate() {
991        {
992            let RecurLayer {
993                ssm_state,
994                ssm_state_alt,
995                ..
996            } = rl;
997            let state_in: &CudaSlice<f32> = if r == 0 { snap } else { ssm_state };
998            e.kda_scan(
999                &inp.q,
1000                &inp.k,
1001                &inp.v,
1002                &inp.g,
1003                &inp.beta,
1004                state_in,
1005                ssm_state_alt,
1006                &mut o,
1007                heads,
1008                1,
1009                scale,
1010            )?;
1011        }
1012        std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
1013    }
1014    Ok(())
1015}
1016
1017impl Engine {
1018    /// Per-plane causal short conv + SiLU over a T-token chunk (cu/kda.cu).
1019    #[allow(clippy::too_many_arguments)]
1020    pub fn kda_conv_silu(
1021        &self,
1022        x_tm: &CudaSlice<f32>,
1023        w: &CudaSlice<f32>,
1024        ring: &CudaSlice<f32>,
1025        y_tm: &mut CudaSlice<f32>,
1026        qkv: usize,
1027        t: usize,
1028        kernel: usize,
1029        plane: usize,
1030    ) -> Result<(), Box<dyn std::error::Error>> {
1031        let f = self.func("memra_kda_conv_silu_f32");
1032        let cfg = LaunchConfig {
1033            grid_dim: (qkv.div_ceil(256) as u32, t as u32, 1),
1034            block_dim: (256, 1, 1),
1035            shared_mem_bytes: 0,
1036        };
1037        let (n, tt, k, p) = (qkv as i32, t as i32, kernel as i32, plane as i32);
1038        let stream = self.gpu.stream();
1039        let mut b = stream.launch_builder(&f);
1040        b.arg(x_tm)
1041            .arg(w)
1042            .arg(ring)
1043            .arg(&mut *y_tm)
1044            .arg(&n)
1045            .arg(&tt)
1046            .arg(&k)
1047            .arg(&p);
1048        unsafe { b.launch(cfg)? };
1049        Ok(())
1050    }
1051
1052    /// Roll one plane of the fused conv ring forward over a T-token chunk (cu/kda.cu).
1053    pub fn kda_conv_ring_roll(
1054        &self,
1055        x_tm: &CudaSlice<f32>,
1056        ring: &mut CudaSlice<f32>,
1057        qkv: usize,
1058        t: usize,
1059        kernel: usize,
1060        plane: usize,
1061    ) -> Result<(), Box<dyn std::error::Error>> {
1062        let f = self.func("memra_kda_conv_ring_roll_f32");
1063        let cfg = LaunchConfig {
1064            grid_dim: (qkv.div_ceil(256) as u32, 1, 1),
1065            block_dim: (256, 1, 1),
1066            shared_mem_bytes: 0,
1067        };
1068        let (n, tt, k, p) = (qkv as i32, t as i32, kernel as i32, plane as i32);
1069        let stream = self.gpu.stream();
1070        let mut b = stream.launch_builder(&f);
1071        b.arg(x_tm).arg(&mut *ring).arg(&n).arg(&tt).arg(&k).arg(&p);
1072        unsafe { b.launch(cfg)? };
1073        Ok(())
1074    }
1075
1076    /// T=1 fused assemble + conv + SiLU + ring roll for one plane (cu/kda.cu).
1077    #[allow(clippy::too_many_arguments)]
1078    pub fn kda_conv_silu_decode(
1079        &self,
1080        x_new: &CudaSlice<f32>,
1081        ring: &mut CudaSlice<f32>,
1082        w: &CudaSlice<f32>,
1083        y: &mut CudaSlice<f32>,
1084        qkv: usize,
1085        kernel: usize,
1086        plane: usize,
1087    ) -> Result<(), Box<dyn std::error::Error>> {
1088        let f = self.func("memra_kda_conv_silu_decode_f32");
1089        let cfg = LaunchConfig {
1090            grid_dim: (qkv.div_ceil(256) as u32, 1, 1),
1091            block_dim: (256, 1, 1),
1092            shared_mem_bytes: 0,
1093        };
1094        let (n, k, p) = (qkv as i32, kernel as i32, plane as i32);
1095        let stream = self.gpu.stream();
1096        let mut b = stream.launch_builder(&f);
1097        b.arg(x_new)
1098            .arg(&mut *ring)
1099            .arg(w)
1100            .arg(&mut *y)
1101            .arg(&n)
1102            .arg(&k)
1103            .arg(&p);
1104        unsafe { b.launch(cfg)? };
1105        Ok(())
1106    }
1107
1108    /// Per-channel forget gate, emitted as the RAW log-gate (cu/kda.cu).
1109    #[allow(clippy::too_many_arguments)]
1110    pub fn kda_gate(
1111        &self,
1112        forget: &CudaSlice<f32>,
1113        dt_bias: &CudaSlice<f32>,
1114        a_log: &CudaSlice<f32>,
1115        g: &mut CudaSlice<f32>,
1116        qkv: usize,
1117        t: usize,
1118        head_dim: usize,
1119        lower_bound: f32,
1120    ) -> Result<(), Box<dyn std::error::Error>> {
1121        let f = self.func("memra_kda_gate_f32");
1122        let cfg = LaunchConfig {
1123            grid_dim: (qkv.div_ceil(256) as u32, t as u32, 1),
1124            block_dim: (256, 1, 1),
1125            shared_mem_bytes: 0,
1126        };
1127        let (n, tt, hd, lb) = (qkv as i32, t as i32, head_dim as i32, lower_bound);
1128        let stream = self.gpu.stream();
1129        let mut b = stream.launch_builder(&f);
1130        b.arg(forget)
1131            .arg(dt_bias)
1132            .arg(a_log)
1133            .arg(&mut *g)
1134            .arg(&n)
1135            .arg(&tt)
1136            .arg(&hd)
1137            .arg(&lb);
1138        unsafe { b.launch(cfg)? };
1139        Ok(())
1140    }
1141
1142    /// The per-channel-decay delta-rule scan (cu/kda.cu). One warp per output column.
1143    #[allow(clippy::too_many_arguments)]
1144    pub fn kda_scan(
1145        &self,
1146        q: &CudaSlice<f32>,
1147        k: &CudaSlice<f32>,
1148        v: &CudaSlice<f32>,
1149        g: &CudaSlice<f32>,
1150        beta: &CudaSlice<f32>,
1151        state_in: &CudaSlice<f32>,
1152        state_out: &mut CudaSlice<f32>,
1153        o: &mut CudaSlice<f32>,
1154        heads: usize,
1155        t: usize,
1156        scale: f32,
1157    ) -> Result<(), Box<dyn std::error::Error>> {
1158        // Four columns per block keeps one warp per column at 128 threads, the same shape
1159        // gdn_scan_s128 launches with.
1160        const COLS_PER_BLOCK: u32 = 4;
1161        let f = self.func("memra_kda_scan_s128");
1162        let cfg = LaunchConfig {
1163            grid_dim: (
1164                heads as u32,
1165                1,
1166                (KDA_HEAD_DIM as u32).div_ceil(COLS_PER_BLOCK),
1167            ),
1168            block_dim: (32, COLS_PER_BLOCK, 1),
1169            shared_mem_bytes: 0,
1170        };
1171        let (h, tt, s) = (heads as i32, t as i32, scale);
1172        let stream = self.gpu.stream();
1173        let mut b = stream.launch_builder(&f);
1174        b.arg(q)
1175            .arg(k)
1176            .arg(v)
1177            .arg(g)
1178            .arg(beta)
1179            .arg(state_in)
1180            .arg(&mut *state_out)
1181            .arg(&mut *o)
1182            .arg(&h)
1183            .arg(&tt)
1184            .arg(&s);
1185        unsafe { b.launch(cfg)? };
1186        Ok(())
1187    }
1188
1189    /// Sigmoid-gated fp32 RMSNorm over head_dim (cu/kda.cu). GDN's `gated_rmsnorm` gates with
1190    /// SiLU; KDA's Glm5NextTextRMSNormGated hardcodes sigmoid.
1191    #[allow(clippy::too_many_arguments)]
1192    pub fn kda_gated_rmsnorm(
1193        &self,
1194        core: &CudaSlice<f32>,
1195        w: &CudaSlice<f32>,
1196        gate: &CudaSlice<f32>,
1197        dst: &mut CudaSlice<f32>,
1198        ncols: usize,
1199        nrows: usize,
1200        eps: f32,
1201    ) -> Result<(), Box<dyn std::error::Error>> {
1202        let f = self.func("memra_kda_gated_rmsnorm_f32");
1203        let cfg = LaunchConfig {
1204            grid_dim: (nrows as u32, 1, 1),
1205            block_dim: (256, 1, 1),
1206            shared_mem_bytes: 0,
1207        };
1208        let (nc, ep) = (ncols as i32, eps);
1209        let stream = self.gpu.stream();
1210        let mut b = stream.launch_builder(&f);
1211        b.arg(core)
1212            .arg(w)
1213            .arg(gate)
1214            .arg(&mut *dst)
1215            .arg(&nc)
1216            .arg(&ep);
1217        unsafe { b.launch(cfg)? };
1218        Ok(())
1219    }
1220
1221    /// The `MEMRA_KDA_FUSED_PROJ` door: run the KDA stage-1 six-projection group as ONE
1222    /// `quantize_q8_1` + ONE `qmatvec_kda6_q8f32_mmvq` launch, or return `None` and let the
1223    /// caller take the unchanged `matmul_group` arm.
1224    ///
1225    /// ENGAGEMENT IS DELIBERATELY NARROW — every condition below exists so the door's numeric
1226    /// claim stays exactly what the gate proves (`tests/kda_fused_proj_gpu.rs`):
1227    ///  * wq/wk/wv must be plain-layout Q8_0 (`rp: false`, no `rp4` mirror, `scale == 1.0`) —
1228    ///    the fused kernel's per-(token,row) body is `qmatvec_q8_0_mmvq` VERBATIM, so those
1229    ///    rows are BIT-IDENTICAL to the unfused MMVQ/batched arm; a repacked layout would ride
1230    ///    the `_rp` twins instead and the claim would be against the wrong kernel.
1231    ///  * f_a/g_a/b_proj must be f32 `Float` — their fused rows replace cuBLASLt with a
1232    ///    deterministic warp tree: a reduction-order class change (the step37 QKV_FUSED class),
1233    ///    measured and pinned in the gate.
1234    ///  * t in 1..=15 (the batch cap), and the env classes under which the UNFUSED arm rides
1235    ///    the MMVQ-class per-row program: `MEMRA_FAST!=0`, `mmvq_supports(Q8_0)`,
1236    ///    `MEMRA_NO_BATCHED` unset for t>=2, `MEMRA_B8!=0` for t>=5. Outside those envs the
1237    ///    unfused arm is a different kernel class (dp4a / Stage-A), so the door refuses rather
1238    ///    than weakening its identity claim.
1239    ///
1240    /// The flag is read PER CALL (the `MEMRA_MOE_FUSED_EPI` rollback-seam precedent), so both
1241    /// arms alternate inside one process. Output order matches `matmul_group`'s:
1242    /// `[q, k, v, forget_down, gate_down, beta_raw]`.
1243    pub fn kda_proj_fused6(
1244        &self,
1245        la: &KdaAttnLayer,
1246        x: &CudaSlice<f32>,
1247        t: usize,
1248    ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
1249        if std::env::var("MEMRA_KDA_FUSED_PROJ").as_deref() != Ok("1") {
1250            return Ok(None);
1251        }
1252        // glm5 TP composition guard (#82 review): the load preflight refuses this door at
1253        // ARM time, but the flag is read PER CALL — a post-load `set` would otherwise
1254        // engage the fused six-projection group on head shards inside the TP walk, an
1255        // unproven composition (the door's gate ran on full-width projections). A shard
1256        // declines here and takes the caller's unchanged arm, announced once.
1257        if la.tp.is_some() {
1258            static TP_F6_DECLINE: std::sync::Once = std::sync::Once::new();
1259            TP_F6_DECLINE.call_once(|| {
1260                eprintln!(
1261                    "[kda-fused-proj] DECLINED on a glm5-TP head shard: the door is gated \
1262                     on full-width projections (the load preflight refuses the pair; this \
1263                     is the per-call twin for a post-load flag set)"
1264                );
1265            });
1266            return Ok(None);
1267        }
1268        if !(1..=15).contains(&t) {
1269            return Ok(None);
1270        }
1271        // The f32 trio is common to both operand arms. Any mismatch = refuse; the caller's
1272        // arm is the shipped program.
1273        let f32w = |w: &GpuTensor| -> Option<usize> {
1274            match w {
1275                GpuTensor::Float { .. } => Some(w.in_features()),
1276                _ => None,
1277            }
1278        };
1279        let (Some(in_fa), Some(in_ga), Some(in_b)) =
1280            (f32w(&la.f_a), f32w(&la.g_a), f32w(&la.b_proj))
1281        else {
1282            return Ok(None);
1283        };
1284        // BF16 operand arm (lever 3 of the decode diet): the serving recipe (MEMRA_BF16_MMV=1)
1285        // admits wq/wk/wv to raw bf16 residency, where the Q8_0 arm below never binds. Its
1286        // bit-identity bar is against `matvec_bf16_f32acc_x4_rows` (matmul's FloatBf16
1287        // decode-tier arm), so it refuses wherever that arm would not be the unfused program:
1288        // MEMRA_BF16_MMV off (the chunked cuBLASLt GEMM class), the step37 W8 mirror doors on
1289        // (matvec_bf16_rows_into reroutes through the q8 mirror when BOTH are set), or
1290        // MEMRA_GLM5_W8 on (2026-09-02, lane/b200-glm5-w8: the SAME reroute, independent
1291        // door — this fused kernel's bit-identity claim is against the unmirrored bf16
1292        // program, so it must decline whichever door moved that program's target).
1293        let bf16 = |w: &GpuTensor| -> Option<usize> {
1294            match w {
1295                GpuTensor::FloatBf16 { .. } => Some(w.in_features()),
1296                _ => None,
1297            }
1298        };
1299        if let (Some(in_q), Some(in_k), Some(in_v)) = (bf16(&la.wq), bf16(&la.wk), bf16(&la.wv)) {
1300            // MEMRA_B200_BF16_GEMV_LT (lane/b200-gemv-hbm-20260902) reroutes the SAME
1301            // unfused target (`matvec_bf16_f32acc_x4_rows`) to a cuBLASLt reference GEMV, so
1302            // this fused arm declines for exactly the reason it declines for the W8 mirrors:
1303            // its bit-identity bar is against the unmirrored, unrerouted bf16 program. With
1304            // the door on, the three bf16 projections fall to the unfused group and each one
1305            // takes the library GEMV, which is what the reference door is there to measure.
1306            // W8 POSTURE FUSION (lane/b200-gemv-hbm-20260902 round 3). Under MEMRA_GLM5_W8 the
1307            // six projections each reroute through `matvec_bf16_via_q8_mirror`, so this group
1308            // runs as SIX separate launches plus six redundant quantizes of the same `x` — and
1309            // the bf16 fused arm below cannot serve it, because its bit-identity bar is against
1310            // the unmirrored bf16 program. `qmatvec_kda6_q8f32_rp_v2` is the fused twin for
1311            // that posture: three mirrored ranges on the rp v2 body (bit-identical to
1312            // `qmatvec_q8_0_mmvq_rp` per row) and three f32 ranges on the same deterministic
1313            // warp tree the q8 arm of this door already ships and has pinned. Gated on
1314            // MEMRA_B200_GEMV_V2 so it carries its own receipt; without that door W8 still
1315            // declines to the unfused path exactly as before.
1316            if crate::glm5_w8_on() && !(crate::step_tp_w8_on() && crate::w8_hybrid_on()) {
1317                if !Self::bf16_mmv_on() || !crate::b200_gemv_v2_on() {
1318                    return Ok(None);
1319                }
1320                let in_f = in_q;
1321                if [in_k, in_v, in_fa, in_ga, in_b].iter().any(|&i| i != in_f)
1322                    || !in_f.is_multiple_of(128)
1323                    || x.len() < t * in_f
1324                    || Engine::q8_v2_smem_bytes(in_f) > 48 * 1024
1325                {
1326                    return Ok(None);
1327                }
1328                let dims = [
1329                    la.wq.out_features(),
1330                    la.wk.out_features(),
1331                    la.wv.out_features(),
1332                    la.f_a.out_features(),
1333                    la.g_a.out_features(),
1334                    la.b_proj.out_features(),
1335                ];
1336                let (
1337                    GpuTensor::FloatBf16 { data: bq, .. },
1338                    GpuTensor::FloatBf16 { data: bk, .. },
1339                    GpuTensor::FloatBf16 { data: bv, .. },
1340                ) = (&la.wq, &la.wk, &la.wv)
1341                else {
1342                    unreachable!("bf16() above only admits FloatBf16");
1343                };
1344                let (
1345                    GpuTensor::Float { data: wfa, .. },
1346                    GpuTensor::Float { data: wga, .. },
1347                    GpuTensor::Float { data: wb, .. },
1348                ) = (&la.f_a, &la.g_a, &la.b_proj)
1349                else {
1350                    unreachable!("f32w() above only admits Float");
1351                };
1352                let mut outs = [
1353                    self.uninit(t * dims[0])?,
1354                    self.uninit(t * dims[1])?,
1355                    self.uninit(t * dims[2])?,
1356                    self.uninit(t * dims[3])?,
1357                    self.uninit(t * dims[4])?,
1358                    self.uninit(t * dims[5])?,
1359                ];
1360                self.kda_proj_fused6_q8rp_raw(
1361                    bq, bk, bv, wfa, wga, wb, x, &mut outs, in_f, dims, t,
1362                )?;
1363                if KDA_FUSED6_Q8RP_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1364                    eprintln!(
1365                        "[kda-fused6] engaged arm=q8rp_v2 in_f={in_f} out={dims:?} t={t} (one \
1366                         launch replaces the six W8-mirror projections and their six redundant \
1367                         activation quantizes; MEMRA_KDA_FUSED_PROJ=1 MEMRA_B200_GEMV_V2=1)"
1368                    );
1369                }
1370                return Ok(Some(outs.into_iter().collect()));
1371            }
1372            if !Self::bf16_mmv_on()
1373                || (crate::step_tp_w8_on() && crate::w8_hybrid_on())
1374                || crate::b200_bf16_gemv_lt_on()
1375            {
1376                return Ok(None);
1377            }
1378            let in_f = in_q;
1379            if [in_k, in_v, in_fa, in_ga, in_b].iter().any(|&i| i != in_f)
1380                || !in_f.is_multiple_of(128)
1381                || x.len() < t * in_f
1382            {
1383                return Ok(None);
1384            }
1385            let dims = [
1386                la.wq.out_features(),
1387                la.wk.out_features(),
1388                la.wv.out_features(),
1389                la.f_a.out_features(),
1390                la.g_a.out_features(),
1391                la.b_proj.out_features(),
1392            ];
1393            let (
1394                GpuTensor::FloatBf16 { data: bq, .. },
1395                GpuTensor::FloatBf16 { data: bk, .. },
1396                GpuTensor::FloatBf16 { data: bv, .. },
1397            ) = (&la.wq, &la.wk, &la.wv)
1398            else {
1399                unreachable!("bf16() above only admits FloatBf16");
1400            };
1401            let (
1402                GpuTensor::Float { data: wfa, .. },
1403                GpuTensor::Float { data: wga, .. },
1404                GpuTensor::Float { data: wb, .. },
1405            ) = (&la.f_a, &la.g_a, &la.b_proj)
1406            else {
1407                unreachable!("f32w() above only admits Float");
1408            };
1409            let mut outs = [
1410                self.uninit(t * dims[0])?,
1411                self.uninit(t * dims[1])?,
1412                self.uninit(t * dims[2])?,
1413                self.uninit(t * dims[3])?,
1414                self.uninit(t * dims[4])?,
1415                self.uninit(t * dims[5])?,
1416            ];
1417            self.kda_proj_fused6_bf16_raw(bq, bk, bv, wfa, wga, wb, x, &mut outs, in_f, dims, t)?;
1418            if KDA_FUSED6_BF16_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1419                eprintln!(
1420                    "[kda-fused6] engaged arm=bf16 in_f={in_f} out={dims:?} t={t} (one launch \
1421                     replaces the six-projection group on the bf16-resident serving recipe; \
1422                     MEMRA_KDA_FUSED_PROJ=1)"
1423                );
1424            }
1425            return Ok(Some(outs.into_iter().collect()));
1426        }
1427        // Dispatch-class envs: the bit-identity bar is against the MMVQ-class per-row program.
1428        if std::env::var("MEMRA_FAST").as_deref() == Ok("0")
1429            || !self.mmvq_supports(crate::QT_Q8_0)
1430            || (t >= 2 && std::env::var("MEMRA_NO_BATCHED").is_ok())
1431            || (t >= 5 && !Self::b8_enabled())
1432        {
1433            return Ok(None);
1434        }
1435        // Q8_0 operand classes (the non-BF16_MMV shapes).
1436        let q8 = |w: &GpuTensor| -> Option<(usize, usize)> {
1437            match w {
1438                GpuTensor::Quant {
1439                    qtype: crate::QT_Q8_0,
1440                    row_bytes,
1441                    scale,
1442                    rp: false,
1443                    rp4: None,
1444                    ..
1445                } if *scale == 1.0 => Some((w.in_features(), *row_bytes)),
1446                _ => None,
1447            }
1448        };
1449        let (Some((in_q, rb_q)), Some((in_k, rb_k)), Some((in_v, rb_v))) =
1450            (q8(&la.wq), q8(&la.wk), q8(&la.wv))
1451        else {
1452            return Ok(None);
1453        };
1454        let in_f = in_q;
1455        if [in_k, in_v, in_fa, in_ga, in_b].iter().any(|&i| i != in_f)
1456            || rb_k != rb_q
1457            || rb_v != rb_q
1458            || !in_f.is_multiple_of(128)
1459            || x.len() < t * in_f
1460        {
1461            return Ok(None);
1462        }
1463        let dims = [
1464            la.wq.out_features(),
1465            la.wk.out_features(),
1466            la.wv.out_features(),
1467            la.f_a.out_features(),
1468            la.g_a.out_features(),
1469            la.b_proj.out_features(),
1470        ];
1471        let (
1472            GpuTensor::Quant { bytes: bq, .. },
1473            GpuTensor::Quant { bytes: bk, .. },
1474            GpuTensor::Quant { bytes: bv, .. },
1475        ) = (&la.wq, &la.wk, &la.wv)
1476        else {
1477            unreachable!("q8() above only admits Quant");
1478        };
1479        let (
1480            GpuTensor::Float { data: wfa, .. },
1481            GpuTensor::Float { data: wga, .. },
1482            GpuTensor::Float { data: wb, .. },
1483        ) = (&la.f_a, &la.g_a, &la.b_proj)
1484        else {
1485            unreachable!("f32w() above only admits Float");
1486        };
1487
1488        let (aq, ad) = self.quantize_q8_1(x, t, in_f)?;
1489        let mut outs = [
1490            self.uninit(t * dims[0])?,
1491            self.uninit(t * dims[1])?,
1492            self.uninit(t * dims[2])?,
1493            self.uninit(t * dims[3])?,
1494            self.uninit(t * dims[4])?,
1495            self.uninit(t * dims[5])?,
1496        ];
1497        self.kda_proj_fused6_raw(
1498            bq, bk, bv, wfa, wga, wb, &aq, &ad, x, &mut outs, in_f, dims, t, rb_q,
1499        )?;
1500
1501        // Engagement receipt: counted at the arm's own call site, announced once per boot
1502        // (the [bf16-mmv] RESIDENT lesson: engagement lines are receipts, never inferred).
1503        if KDA_FUSED6_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1504            eprintln!(
1505                "[kda-fused6] engaged in_f={in_f} out={dims:?} t={t} (one launch replaces the \
1506                 six-projection group; MEMRA_KDA_FUSED_PROJ=1)"
1507            );
1508        }
1509        Ok(Some(outs.into_iter().collect()))
1510    }
1511
1512    /// The raw fused-6 launch (`qmatvec_kda6_q8f32_mmvq`): three Q8_0 weights + three f32
1513    /// weights, one q8_1 activation pair + the raw f32 activation, six outputs, t token rows.
1514    /// Geometry-checked but POLICY-FREE: the gate's red arms drive mutations (transposed slice
1515    /// data, dropped ranges via `dims[i] = 0`) through this entry, so the mutation reaches the
1516    /// exact program the door serves.
1517    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1518    pub fn kda_proj_fused6_raw(
1519        &self,
1520        wq: &CudaSlice<u8>,
1521        wk: &CudaSlice<u8>,
1522        wv: &CudaSlice<u8>,
1523        wfa: &CudaSlice<f32>,
1524        wga: &CudaSlice<f32>,
1525        wb: &CudaSlice<f32>,
1526        aq: &CudaSlice<i8>,
1527        ad: &CudaSlice<f32>,
1528        x: &CudaSlice<f32>,
1529        outs: &mut [CudaSlice<f32>; 6],
1530        in_f: usize,
1531        dims: [usize; 6],
1532        t: usize,
1533        row_bytes: usize,
1534    ) -> Result<(), Box<dyn std::error::Error>> {
1535        const ROWS_PER_BLOCK: usize = 4; // MEMRA_MMVQ_ROWS in qmatvec.cu
1536        if t == 0
1537            || !in_f.is_multiple_of(128)
1538            || x.len() < t * in_f
1539            || aq.len() < t * in_f
1540            || ad.len() < t * (in_f / 32)
1541        {
1542            return Err("kda_proj_fused6 geometry".into());
1543        }
1544        for (i, (w, want_rows)) in [(wq, dims[0]), (wk, dims[1]), (wv, dims[2])]
1545            .into_iter()
1546            .enumerate()
1547        {
1548            if w.len() < want_rows * row_bytes {
1549                return Err(format!(
1550                    "kda_proj_fused6: q8 weight {i} holds {} bytes, needs {}",
1551                    w.len(),
1552                    want_rows * row_bytes
1553                )
1554                .into());
1555            }
1556        }
1557        for (i, (w, want_rows)) in [(wfa, dims[3]), (wga, dims[4]), (wb, dims[5])]
1558            .into_iter()
1559            .enumerate()
1560        {
1561            if w.len() < want_rows * in_f {
1562                return Err(format!(
1563                    "kda_proj_fused6: f32 weight {} holds {} floats, needs {}",
1564                    i + 3,
1565                    w.len(),
1566                    want_rows * in_f
1567                )
1568                .into());
1569            }
1570        }
1571        for (i, (o, want)) in outs.iter().zip(dims).enumerate() {
1572            if o.len() < t * want {
1573                return Err(format!("kda_proj_fused6: output {i} too small").into());
1574            }
1575        }
1576        let blocks: usize = dims.iter().map(|d| d.div_ceil(ROWS_PER_BLOCK)).sum();
1577        let f = self.func("qmatvec_kda6_q8f32_mmvq");
1578        let cfg = LaunchConfig {
1579            grid_dim: (blocks as u32, t as u32, 1),
1580            block_dim: (32, ROWS_PER_BLOCK as u32, 1),
1581            shared_mem_bytes: 0,
1582        };
1583        let inf = in_f as i32;
1584        let d = dims.map(|v| v as i32);
1585        let (mi, rb) = (t as i32, row_bytes as i64);
1586        let [o0, o1, o2, o3, o4, o5] = outs;
1587        let stream = self.gpu.stream();
1588        let mut b = stream.launch_builder(&f);
1589        b.arg(wq)
1590            .arg(wk)
1591            .arg(wv)
1592            .arg(wfa)
1593            .arg(wga)
1594            .arg(wb)
1595            .arg(aq)
1596            .arg(ad)
1597            .arg(x)
1598            .arg(&mut *o0)
1599            .arg(&mut *o1)
1600            .arg(&mut *o2)
1601            .arg(&mut *o3)
1602            .arg(&mut *o4)
1603            .arg(&mut *o5)
1604            .arg(&inf)
1605            .arg(&d[0])
1606            .arg(&d[1])
1607            .arg(&d[2])
1608            .arg(&d[3])
1609            .arg(&d[4])
1610            .arg(&d[5])
1611            .arg(&mi)
1612            .arg(&rb);
1613        unsafe { b.launch(cfg)? };
1614        Ok(())
1615    }
1616
1617    /// The raw BF16-arm fused-6 launch (`qmatvec_kda6_bf16f32`): three bf16-resident weights
1618    /// (raw checkpoint u16 bytes, the `admit=bf16_mmv` residency) + three f32 weights, one raw
1619    /// f32 activation, six outputs, t token rows. Block = `mmv_block()` — the SAME blockDim
1620    /// `matvec_bf16_rows_into` pins, because the bf16 body's shared-tree reduction shape (and
1621    /// therefore its bits) is a function of blockDim. Geometry-checked but POLICY-FREE: the
1622    /// gate's red arms drive mutations through this entry, exactly like the q8 raw above.
1623    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1624    pub fn kda_proj_fused6_bf16_raw(
1625        &self,
1626        wq: &CudaSlice<u8>,
1627        wk: &CudaSlice<u8>,
1628        wv: &CudaSlice<u8>,
1629        wfa: &CudaSlice<f32>,
1630        wga: &CudaSlice<f32>,
1631        wb: &CudaSlice<f32>,
1632        x: &CudaSlice<f32>,
1633        outs: &mut [CudaSlice<f32>; 6],
1634        in_f: usize,
1635        dims: [usize; 6],
1636        t: usize,
1637    ) -> Result<(), Box<dyn std::error::Error>> {
1638        self.kda_proj_fused6_bf16_arm_raw(
1639            wq,
1640            wk,
1641            wv,
1642            wfa,
1643            wga,
1644            wb,
1645            x,
1646            outs,
1647            in_f,
1648            dims,
1649            t,
1650            crate::b200_gemv_v2_level(),
1651        )
1652    }
1653
1654    /// The same launch with the arm chosen EXPLICITLY instead of from the memoized
1655    /// `MEMRA_B200_GEMV_V2` door, so a bench or gate can drive every arm inside one process
1656    /// (`b200_matvec_bench`, the `_arm_raw` precedent).
1657    ///
1658    /// `arm`: `0` = the shipped `qmatvec_kda6_bf16f32`; `1` = `_v2`, whose three BF16 ranges take
1659    /// the eight-rows-per-block walk (activation loaded once and reused across the rows, ten
1660    /// 16 B loads in flight before the first fma, one barrier chain per block) instead of
1661    /// `kda6_bf16_rows4`'s four sequential rows; `2` = `_v3`, the same walk with its weight
1662    /// tiles staged through shared memory by `cp.async` so the in-flight budget stops being
1663    /// register-bound. `2` falls back to `1` when v3's dynamic smem would exceed the 48 KB
1664    /// default cap. Per row the arithmetic is unchanged in every arm, so all three are
1665    /// BIT-IDENTICAL to each other and to `matvec_bf16_f32acc_x4_rows`.
1666    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1667    pub fn kda_proj_fused6_bf16_arm_raw(
1668        &self,
1669        wq: &CudaSlice<u8>,
1670        wk: &CudaSlice<u8>,
1671        wv: &CudaSlice<u8>,
1672        wfa: &CudaSlice<f32>,
1673        wga: &CudaSlice<f32>,
1674        wb: &CudaSlice<f32>,
1675        x: &CudaSlice<f32>,
1676        outs: &mut [CudaSlice<f32>; 6],
1677        in_f: usize,
1678        dims: [usize; 6],
1679        t: usize,
1680        arm: u8,
1681    ) -> Result<(), Box<dyn std::error::Error>> {
1682        if t == 0 || !in_f.is_multiple_of(128) || x.len() < t * in_f {
1683            return Err("kda_proj_fused6_bf16 geometry".into());
1684        }
1685        for (i, (w, want_rows)) in [(wq, dims[0]), (wk, dims[1]), (wv, dims[2])]
1686            .into_iter()
1687            .enumerate()
1688        {
1689            if w.len() < want_rows * in_f * 2 {
1690                return Err(format!(
1691                    "kda_proj_fused6_bf16: bf16 weight {i} holds {} bytes, needs {}",
1692                    w.len(),
1693                    want_rows * in_f * 2
1694                )
1695                .into());
1696            }
1697        }
1698        for (i, (w, want_rows)) in [(wfa, dims[3]), (wga, dims[4]), (wb, dims[5])]
1699            .into_iter()
1700            .enumerate()
1701        {
1702            if w.len() < want_rows * in_f {
1703                return Err(format!(
1704                    "kda_proj_fused6_bf16: f32 weight {} holds {} floats, needs {}",
1705                    i + 3,
1706                    w.len(),
1707                    want_rows * in_f
1708                )
1709                .into());
1710            }
1711        }
1712        for (i, (o, want)) in outs.iter().zip(dims).enumerate() {
1713            if o.len() < t * want {
1714                return Err(format!("kda_proj_fused6_bf16: output {i} too small").into());
1715            }
1716        }
1717        // v3 declines to v2 when its staged tiles would not fit the 48 KB default dynamic
1718        // shared-memory cap (36 KB at the default mmv_block()=128, 72 KB at 256).
1719        let arm = if arm >= 2 && !crate::gemv_v3_fits() {
1720            1
1721        } else {
1722            arm
1723        };
1724        // Rows per block, and therefore the block partition of the six ranges: 4 for the
1725        // shipped kernel, `GEMV_V2_ROWS` for the v2/v3 twins. v2 takes the R-row reduction
1726        // window as DYNAMIC shared memory (R * blockDim.x floats); v3 takes that plus its
1727        // cp.async stage buffers.
1728        let nb = crate::mmv_block();
1729        let rpb = if arm >= 1 { crate::GEMV_V2_ROWS } else { 4 };
1730        let blocks: usize = dims.iter().map(|d| d.div_ceil(rpb)).sum();
1731        let f = self.func(match arm {
1732            0 => "qmatvec_kda6_bf16f32",
1733            1 => "qmatvec_kda6_bf16f32_v2",
1734            _ => "qmatvec_kda6_bf16f32_v3",
1735        });
1736        let cfg = LaunchConfig {
1737            grid_dim: (blocks as u32, t as u32, 1),
1738            block_dim: (nb, 1, 1),
1739            shared_mem_bytes: match arm {
1740                0 => 0,
1741                1 => (crate::GEMV_V2_ROWS as u32) * nb * 4,
1742                _ => crate::gemv_v3_smem_bytes(nb as usize) as u32,
1743            },
1744        };
1745        let inf = in_f as i32;
1746        let d = dims.map(|v| v as i32);
1747        let mi = t as i32;
1748        let [o0, o1, o2, o3, o4, o5] = outs;
1749        let stream = self.gpu.stream();
1750        let mut b = stream.launch_builder(&f);
1751        b.arg(wq)
1752            .arg(wk)
1753            .arg(wv)
1754            .arg(wfa)
1755            .arg(wga)
1756            .arg(wb)
1757            .arg(x)
1758            .arg(&mut *o0)
1759            .arg(&mut *o1)
1760            .arg(&mut *o2)
1761            .arg(&mut *o3)
1762            .arg(&mut *o4)
1763            .arg(&mut *o5)
1764            .arg(&inf)
1765            .arg(&d[0])
1766            .arg(&d[1])
1767            .arg(&d[2])
1768            .arg(&d[3])
1769            .arg(&d[4])
1770            .arg(&d[5])
1771            .arg(&mi);
1772        unsafe { b.launch(cfg)? };
1773        Ok(())
1774    }
1775}