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);
53pub static KDA_FUSED6_E4M3_DISPATCHES: AtomicU64 = AtomicU64::new(0);
54
55/// Same door, BF16 operand arm (`qmatvec_kda6_bf16f32`, lane/glm5-decode-diet lever 3).
56/// Counted separately so a box A/B on the serving recipe (MEMRA_BF16_MMV=1, where the q8 arm
57/// refuses by design) can attribute engagement to the arm that actually ran.
58pub static KDA_FUSED6_BF16_DISPATCHES: AtomicU64 = AtomicU64::new(0);
59
60/// Same door, W8-MIRROR arm (`qmatvec_kda6_q8f32_rp_v2`, lane/b200-gemv-hbm-20260902 round 3).
61/// Counted separately for the same reason the bf16 arm is: a box A/B on the serving recipe must
62/// be able to attribute engagement to the arm that actually ran.
63pub static KDA_FUSED6_Q8RP_DISPATCHES: AtomicU64 = AtomicU64::new(0);
64
65/// The only head width `memra_kda_scan_s128` is instantiated for, and the only one glm5_next
66/// ships (`linear_attn_config.head_dim = 128`).
67pub const KDA_HEAD_DIM: usize = 128;
68/// The conv kernels hold their window in a fixed register array; wider kernels would silently
69/// read past it, so the loader refuses them.
70const KDA_MAX_CONV_KERNEL: usize = 8;
71/// FLA l2norm epsilon. Fixed at 1e-6 and INSIDE the sqrt — independent of the layer's rms eps,
72/// which is a different constant used by the output norm below.
73const KDA_L2_EPS: f32 = 1e-6;
74
75/// One loaded KDA mixer. Field names follow the reference's tensor roles, not the HF spellings.
76pub struct KdaAttnLayer {
77    pub plan: KimiDeltaNetPlan,
78    /// q/k/v projections, `[qkv, hidden]` each.
79    pub wq: GpuTensor,
80    pub wk: GpuTensor,
81    pub wv: GpuTensor,
82    /// Forget gate low-rank pair: `f_a [head_dim, hidden]`, `f_b [qkv, head_dim]`.
83    pub f_a: GpuTensor,
84    pub f_b: GpuTensor,
85    /// Output gate low-rank pair, same shapes as the forget pair.
86    pub g_a: GpuTensor,
87    pub g_b: GpuTensor,
88    /// Per-head beta projection, `[heads, hidden]`.
89    pub b_proj: GpuTensor,
90    /// Output projection, `[hidden, qkv]`.
91    pub wo: GpuTensor,
92    /// The three per-plane conv weights concatenated into `[3*qkv, kernel]` (see module header).
93    pub conv: CudaSlice<f32>,
94    /// `A_log [heads]`, `dt_bias [qkv]` (per CHANNEL, unlike GDN's per-head bias),
95    /// `o_norm [head_dim]`.
96    pub a_log: GpuTensor,
97    pub dt_bias: GpuTensor,
98    pub o_norm: GpuTensor,
99    /// glm5 TP-2 sidecar (`MEMRA_GLM5_TP`, lane/glm5-tp2). `Some` means THIS layer struct is
100    /// the ROOT-RANK HEAD SHARD (heads/2) and the sidecar carries the peer shard + runtime.
101    /// Every plain entry point REFUSES a sharded layer by name — only the TP walk
102    /// (`glm5_tp::kda_tp_*`) may execute it. `None` everywhere else (zero cost, zero change).
103    pub tp: Option<Box<crate::glm5_tp::Glm5TpKda>>,
104}
105
106impl KdaAttnLayer {
107    pub fn heads(&self) -> usize {
108        self.plan.num_heads as usize
109    }
110    pub fn head_dim(&self) -> usize {
111        self.plan.head_dim as usize
112    }
113    pub fn qkv(&self) -> usize {
114        self.heads() * self.head_dim()
115    }
116    pub fn conv_kernel(&self) -> usize {
117        self.plan.conv_kernel as usize
118    }
119    /// Fused conv ring width, matching `StatePlan::Recurrent { conv_width }` for this layer.
120    pub fn conv_width(&self) -> usize {
121        3 * self.qkv()
122    }
123    /// Recurrent state elements, matching `StatePlan::Recurrent { state_width }`.
124    pub fn state_width(&self) -> usize {
125        self.heads() * self.head_dim() * self.head_dim()
126    }
127
128    /// Load block `il`'s KDA tensors. Names are the ggml-dialect contract names from
129    /// `memra_gguf::tensor_contract::add_kda`; the safetensors source translates them.
130    pub fn load(
131        e: &Engine,
132        src: &dyn TensorSource,
133        il: u32,
134        plan: &KimiDeltaNetPlan,
135    ) -> Result<Self, Box<dyn std::error::Error>> {
136        let heads = plan.num_heads as usize;
137        let head_dim = plan.head_dim as usize;
138        let kernel = plan.conv_kernel as usize;
139        if head_dim != KDA_HEAD_DIM {
140            return Err(format!(
141                "blk.{il}: KDA head_dim {head_dim} is not the {KDA_HEAD_DIM} the scan kernel is \
142                 instantiated for; a new memra_kda_scan_s<N> instantiation is required before \
143                 this geometry can serve"
144            )
145            .into());
146        }
147        if heads == 0 {
148            return Err(format!("blk.{il}: KDA num_heads must be positive").into());
149        }
150        if !(2..=KDA_MAX_CONV_KERNEL).contains(&kernel) {
151            return Err(format!(
152                "blk.{il}: KDA conv_kernel {kernel} outside the 2..={KDA_MAX_CONV_KERNEL} window \
153                 the conv kernels hold in registers"
154            )
155            .into());
156        }
157        let p = |s: &str| format!("blk.{il}.{s}");
158        let load = |name: String| GpuTensor::load_from_source(e, src, &name);
159
160        let qkv = heads * head_dim;
161        // Fuse the three per-plane conv weights into one [3*qkv, kernel] buffer (module header).
162        // Each source tensor is [qkv, kernel] channel-major, so the planes concatenate as whole
163        // row blocks and plane p lands at row p*qkv — the ring's own plane offset.
164        let mut conv = e.zeros(3 * qkv * kernel)?;
165        for (plane, name) in [
166            "kda_q_conv1d.weight",
167            "kda_k_conv1d.weight",
168            "kda_v_conv1d.weight",
169        ]
170        .into_iter()
171        .enumerate()
172        {
173            let w = load(p(name))?;
174            let src_data = w.float_data();
175            if src_data.len() != qkv * kernel {
176                return Err(format!(
177                    "blk.{il}.{name}: {} elements, contract requires {}",
178                    src_data.len(),
179                    qkv * kernel
180                )
181                .into());
182            }
183            e.copy_into(&mut conv, plane * qkv * kernel, src_data, qkv * kernel)?;
184        }
185
186        Ok(Self {
187            plan: *plan,
188            wq: load(p("kda_q.weight"))?,
189            wk: load(p("kda_k.weight"))?,
190            wv: load(p("kda_v.weight"))?,
191            f_a: load(p("kda_f_a.weight"))?,
192            f_b: load(p("kda_f_b.weight"))?,
193            g_a: load(p("kda_g_a.weight"))?,
194            g_b: load(p("kda_g_b.weight"))?,
195            b_proj: load(p("kda_b.weight"))?,
196            wo: load(p("kda_out.weight"))?,
197            conv,
198            a_log: load(p("kda_a_log"))?,
199            dt_bias: load(p("kda_dt.bias"))?,
200            o_norm: load(p("kda_o_norm.weight"))?,
201            tp: None,
202        })
203    }
204}
205
206/// Which conv arm a call takes. `Prefill` reads the ring as a left pad and rolls it afterwards;
207/// `Decode` fuses assemble+conv+roll for the single new row. The two produce bit-identical
208/// values at T=1 (same ascending tap order over the same window) — the split exists so decode
209/// and the spec verify keep one dispatch class, per the cu/hybrid.cu decode==verify law.
210#[derive(Clone, Copy, PartialEq, Eq)]
211pub(crate) enum ConvArm {
212    Prefill,
213    Decode,
214}
215
216/// The scan-input buffers of one KDA step, STOLEN from the step instead of dropped
217/// (lane/glm5-loop-port, port 3 — the module doc's named GdnStash/ReplaySSM diet): the
218/// glm5 verify walk's rollback checkpoint keeps these ~160 KB of already-allocated
219/// buffers per row per layer and retires the per-row 4 MiB recurrent-state clones
220/// (~0.95 GiB transient at K=7). Replaying `kda_scan` over them from a pre-round state
221/// snapshot rebuilds the post-row state EXACTLY: each replay is the ORIGINAL t=1 launch
222/// re-issued — same kernel, same inputs, same shape — so the rebuilt state is
223/// byte-identical to the clone it replaces by construction, not by a numeric argument.
224pub struct KdaScanInputs {
225    pub q: CudaSlice<f32>,
226    pub k: CudaSlice<f32>,
227    pub v: CudaSlice<f32>,
228    pub g: CudaSlice<f32>,
229    pub beta: CudaSlice<f32>,
230}
231
232/// The rollback stash of one BATCHED verify-rows KDA call (lane/glm5-verify-batch): the
233/// per-layer t=K+1 twin of the per-row [`KdaScanInputs`] steal. Everything here is either
234/// stolen from buffers the call allocated anyway (`raws`, `scan` — zero copies) or one
235/// small clone per layer per round (`ring_snap`, `3*qkv*(kernel-1)` floats ~ 96 KiB).
236///
237/// Rollback to `keep` rows rebuilds both state planes EXACTLY:
238///   * conv ring: restore `ring_snap`, then re-issue `kda_conv_ring_roll` per plane over
239///     `raws` at T=keep — the roll is pure placement (no arithmetic), so the rebuilt ring
240///     is the sequential chain's ring after row keep-1 byte-for-byte.
241///   * ssm state: ONE `kda_scan` replay at T=keep from the caller's pre-round snapshot
242///     over the batched `scan` inputs (the kernel walks rows 0..keep of the [t, ..]
243///     buffers) — the in-kernel T-loop IS the chained t=1 program (register-resident
244///     state, identical per-step order), held by the scan-chain bit-gate.
245pub struct KdaRowsStash {
246    /// The fused conv ring BEFORE this call's rolls (one clone per layer per round).
247    pub ring_snap: CudaSlice<f32>,
248    /// RAW (pre-conv) q/k/v projection rows `[t, qkv]`, stolen post-roll (plane order).
249    pub raws: [CudaSlice<f32>; 3],
250    /// Batched scan inputs `[t, ..]`, stolen post-scan.
251    pub scan: KdaScanInputs,
252    /// Row count of the call that filled this stash; rollback validates `keep` against it.
253    pub rows: usize,
254}
255
256/// What a `kda_core` call is asked to leave behind for rollback — and, for `Rows`, which
257/// matmul class the call rides (the decode-exact rows classes, `matmul_rows_exact`).
258pub(crate) enum KdaStash<'a> {
259    /// No rollback stash (prefill / plain decode).
260    None,
261    /// Per-row t=1 steal (loop-port 3, the per-row verify walk).
262    Decode(&'a mut Option<KdaScanInputs>),
263    /// BATCHED verify-rows steal (lane/glm5-verify-batch): scan inputs + raw conv rows +
264    /// a pre-call ring snapshot; every matmul rides `matmul_rows_exact` so each row is
265    /// bit-identical to the t=1 decode program per the decode-exact class contracts.
266    Rows(&'a mut Option<KdaRowsStash>),
267}
268
269/// `MEMRA_KDA_STEP_TRACE=1` (gate-harness instrument, default OFF, never a serving flag): after
270/// each sub-step of the KDA core, print the non-finite element count of every live buffer on one
271/// line. memra#131 cell 9 placed the graph door's poison inside `kda_decode_cached` at layer 4
272/// (finite input, finite state, all-NaN mixer output after a capture); this names the kernel.
273fn kda_trace_on() -> bool {
274    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
275    *V.get_or_init(|| std::env::var("MEMRA_KDA_STEP_TRACE").as_deref() == Ok("1"))
276}
277fn kda_trace(e: &Engine, stage: &str, t: usize, bufs: &[(&str, &CudaSlice<f32>)]) {
278    if !kda_trace_on() {
279        return;
280    }
281    // A device-to-host copy is illegal inside an open CUDA graph capture (it invalidates the
282    // capture); box cell 11 (memra#131) hit exactly that on the session's first decode step,
283    // which the door captures. Print the stage with a note instead of counting.
284    if crate::glm5_graph_capture_open() {
285        eprintln!("[kda-step-trace] t={t} {stage}: (inside an open graph capture; not counted)");
286        return;
287    }
288    let mut line = format!("[kda-step-trace] t={t} {stage}:");
289    for (name, b) in bufs {
290        let n = match e.dtoh(b) {
291            Ok(v) => v.iter().filter(|x| !x.is_finite()).count(),
292            Err(_) => usize::MAX,
293        };
294        line.push_str(&format!(" {name}={n}/{}", b.len()));
295    }
296    eprintln!("{line}");
297}
298
299/// The whole mixer, stage for stage against `memra_reference::kimi_delta_net`.
300///
301/// `ring` is the fused `[3*qkv, kernel-1]` conv state (zeroed = fresh prefill's zero left pad)
302/// and is updated in place. `state_in`/`state_out` are the `[heads, 128, 128]` recurrent state
303/// in the kernel's transposed `M[col][i]` layout; they MUST be distinct buffers.
304#[allow(clippy::too_many_arguments)]
305// allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
306fn kda_core(
307    e: &Engine,
308    la: &KdaAttnLayer,
309    x: &CudaSlice<f32>,
310    t: usize,
311    eps: f32,
312    ring: &mut CudaSlice<f32>,
313    state_in: &CudaSlice<f32>,
314    state_out: &mut CudaSlice<f32>,
315    arm: ConvArm,
316    stash: KdaStash<'_>,
317    scan_clock: Option<&mut u64>,
318    pre_q8: KdaPreQ8<'_>,
319) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
320    // glm5 TP fail-closed choke point: every plain KDA entry (stateless, prime, decode,
321    // stash — INCLUDING the batched verify-rows walk, `kda_verify_rows_cached`) funnels
322    // through here. A TP-sharded layer holds heads/2 — running it on the plain path would
323    // compute a silently-halved mixer, so it refuses by name instead.
324    if la.tp.is_some() {
325        return Err(format!(
326            "KDA layer is glm5-TP-sharded (MEMRA_GLM5_TP): the plain mixer path is unwired \
327             for a head shard — only the TP decode/prime walk may execute it (t={t}, arm \
328             {})",
329            if arm == ConvArm::Decode {
330                "decode"
331            } else {
332                "prefill"
333            }
334        )
335        .into());
336    }
337    // Verify-batch wo seam (lane/glm5-verify-batch): the rows arm routes the output
338    // projection through the decode-exact classes, exactly like every projection inside
339    // the core — the wo dispatch moved into this wrapper with the TP split, its routing
340    // did not change.
341    let rows_exact = matches!(stash, KdaStash::Rows(_));
342    // MEMRA_KDA_ONORM_ZQ8: ask the core for the o_norm output's q8_1 pair when `wo` can take it.
343    let want_pair = !rows_exact && kda_onorm_zq8_on() && e.mmvq_fast_eligible(&la.wo, t);
344    let mut onorm_q8: KdaOnormQ8 = None;
345    let gated = kda_core_gated(
346        e,
347        la,
348        x,
349        t,
350        eps,
351        ring,
352        state_in,
353        state_out,
354        arm,
355        stash,
356        scan_clock,
357        pre_q8,
358        if want_pair { Some(&mut onorm_q8) } else { None },
359    )?;
360    if rows_exact {
361        let y = e.matmul_rows_exact(&la.wo, &gated, t);
362        // Door W: gated's last reader was the wo matmul above.
363        e.vws_recycle(gated);
364        y
365    } else {
366        if let Some((aq, ad)) = onorm_q8.as_ref()
367            && let Some(y) = e.matmul_q8_fast(&la.wo, aq, ad, t)?
368        {
369            if KDA_ONORM_ZQ8_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == 0 {
370                eprintln!(
371                    "[kda-onorm-zq8] engaged: the KDA o_norm launch emits wo's q8_1 pair; the \
372                     standalone quantize is gone (MEMRA_KDA_ONORM_ZQ8=1)"
373                );
374            }
375            return Ok(y);
376        }
377        e.matmul(&la.wo, &gated, t)
378    }
379}
380
381/// [`kda_core`] up to (and excluding) the output projection: returns the gated `[t, qkv]`
382/// mixer output. Split out for the glm5 TP-2 seam, whose column-parallel `wo` runs over the
383/// cross-rank GATHERED gated tensor rather than this shard's slice — the plain path is
384/// `kda_core` above, byte-for-byte the pre-split body (the wo matmul and its rows-exact
385/// routing moved, nothing else). This body is the CURRENT doored/batched core: it carries
386/// the `MEMRA_KDA_FUSED_PROJ` door and the verify-batch rows arm; the TP decode/prime walk
387/// calls it with `KdaStash::None`, the spec x TP verify walk (lane/glm5-composition) with
388/// `KdaStash::Rows` per rank, and the TP load preflight refuses the fused-proj door by
389/// name (unproven composition on head shards — see the FLAGS.md composition matrix).
390#[allow(clippy::too_many_arguments)] // mirrors kda_core's own contract-shaped list
391pub(crate) fn kda_core_gated(
392    e: &Engine,
393    la: &KdaAttnLayer,
394    x: &CudaSlice<f32>,
395    t: usize,
396    eps: f32,
397    ring: &mut CudaSlice<f32>,
398    state_in: &CudaSlice<f32>,
399    state_out: &mut CudaSlice<f32>,
400    arm: ConvArm,
401    stash: KdaStash<'_>,
402    mut scan_clock: Option<&mut u64>,
403    pre_q8: KdaPreQ8<'_>,
404    onorm_q8: Option<&mut KdaOnormQ8>,
405) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
406    let heads = la.heads();
407    let head_dim = la.head_dim();
408    let qkv = la.qkv();
409    let kernel = la.conv_kernel();
410    // The BATCHED verify-rows arm (lane/glm5-verify-batch): prefill conv dispatch (per-row
411    // bit-identical to the decode arm — same ascending taps over the same window values,
412    // held by the conv-arm bit-gate) + decode-exact matmul classes + the rows stash.
413    let rows_exact = matches!(stash, KdaStash::Rows(_));
414    if rows_exact && arm != ConvArm::Prefill {
415        return Err("KDA rows stash requires the prefill conv arm".into());
416    }
417    if arm == ConvArm::Decode && t != 1 {
418        return Err(format!("KDA decode arm requires t == 1, got {t}").into());
419    }
420    if ring.len() < la.conv_width() * (kernel - 1) {
421        return Err(format!(
422            "KDA conv ring holds {} floats, layer needs {}",
423            ring.len(),
424            la.conv_width() * (kernel - 1)
425        )
426        .into());
427    }
428    if state_in.len() < la.state_width() || state_out.len() < la.state_width() {
429        return Err(format!(
430            "KDA recurrent state holds {}/{} floats, layer needs {}",
431            state_in.len(),
432            state_out.len(),
433            la.state_width()
434        )
435        .into());
436    }
437
438    // Stage 1 — the six projections that read x directly. f_b/g_b are chained off their own
439    // down-projections below, exactly as the reference nests them.
440    //
441    // MEMRA_KDA_FUSED_PROJ=1 (default OFF): the six matvec calls collapse to one quantize +
442    // one `qmatvec_kda6_q8f32_mmvq` launch — the program shape both vLLM and SGLang ship for
443    // this trunk (ENGINE-SURVEY.md C1) and the step37 QKV_FUSED transfer (TRANSFER-MAP lever 1).
444    // `kda_proj_fused6` refuses (returns None) on any operand/env shape where its bit-identity
445    // claim would not hold, so the fall-through arm is always the unchanged program.
446    let mut g6 = match e.kda_proj_fused6_pre(la, x, t, pre_q8)? {
447        Some(outs) => outs,
448        None if rows_exact => {
449            // Verify-rows matmul class: per-weight decode-exact dispatch (the tcols /
450            // batched-MMVQ / per-token-linear classes — each row bit-identical to the
451            // t=1 program by the matmul_rows_exact contract).
452            [&la.wq, &la.wk, &la.wv, &la.f_a, &la.g_a, &la.b_proj]
453                .into_iter()
454                .map(|w| e.matmul_rows_exact(w, x, t))
455                .collect::<Result<Vec<_>, _>>()?
456        }
457        None => e.matmul_group(
458            &[&la.wq, &la.wk, &la.wv, &la.f_a, &la.g_a, &la.b_proj],
459            x,
460            t,
461        )?,
462    };
463    let beta_raw = g6.pop().unwrap(); // [T, heads]
464    let gate_down = g6.pop().unwrap(); // [T, head_dim]
465    let forget_down = g6.pop().unwrap(); // [T, head_dim]
466    if kda_trace_on() {
467        let mut v: Vec<(&str, &CudaSlice<f32>)> = vec![("x", x)];
468        let names = ["g6_0", "g6_1", "g6_2", "g6_3", "g6_4", "g6_5"];
469        for (k, b) in g6.iter().enumerate() {
470            v.push((names[k.min(5)], b));
471        }
472        v.push(("forget_down", &forget_down));
473        v.push(("gate_down", &gate_down));
474        v.push(("beta_raw", &beta_raw));
475        kda_trace(e, "proj", t, &v);
476    }
477    let v_raw = g6.pop().unwrap(); // [T, qkv]
478    let k_raw = g6.pop().unwrap();
479    let q_raw = g6.pop().unwrap();
480
481    // Rows stash: snapshot the ring BEFORE the rolls mutate it (one ~96 KiB clone per
482    // layer per round — the rollback's re-roll base). Door W: on the rows arm the snapshot
483    // (and every scratch below) is a pooled draw — vws_uninit == alloc_uninit with the
484    // door off, and the non-rows arms keep the plain allocs untouched.
485    let ring_snap = match &stash {
486        KdaStash::Rows(_) => {
487            let mut snap = e.vws_uninit(ring.len())?;
488            e.dtod_copy_into(ring, &mut snap, 0)?;
489            Some(snap)
490        }
491        _ => None,
492    };
493
494    // Stage 2 — per-plane causal short conv + SiLU. Planes are ordered q, k, v in both the fused
495    // weight buffer and the fused ring, which is the order the reference stores conv_state in.
496    let mut q_conv = if rows_exact {
497        e.vws_uninit(t * qkv)?
498    } else {
499        e.uninit(t * qkv)?
500    };
501    let mut k_conv = if rows_exact {
502        e.vws_uninit(t * qkv)?
503    } else {
504        e.uninit(t * qkv)?
505    };
506    let mut v_conv = if rows_exact {
507        e.vws_uninit(t * qkv)?
508    } else {
509        e.uninit(t * qkv)?
510    };
511    // MEMRA_KDA_CONV3 (lane/glm5-kda-conv3-20260904, default OFF): the decode arm's three
512    // per-plane launches as ONE (plane = blockIdx.y), bit-identical per channel; the prefill arm
513    // and the door-OFF decode keep the per-plane loop verbatim.
514    if arm == ConvArm::Decode && kda_conv3_on() && kernel <= 9 {
515        e.kda_conv_silu_decode3(
516            [&q_raw, &k_raw, &v_raw],
517            ring,
518            &la.conv,
519            [&mut q_conv, &mut k_conv, &mut v_conv],
520            qkv,
521            kernel,
522        )?;
523    } else {
524        for (plane, (raw, out)) in [
525            (&q_raw, &mut q_conv),
526            (&k_raw, &mut k_conv),
527            (&v_raw, &mut v_conv),
528        ]
529        .into_iter()
530        .enumerate()
531        {
532            match arm {
533                ConvArm::Prefill => {
534                    e.kda_conv_silu(raw, &la.conv, ring, out, qkv, t, kernel, plane)?
535                }
536                ConvArm::Decode => {
537                    e.kda_conv_silu_decode(raw, ring, &la.conv, out, qkv, kernel, plane)?
538                }
539            }
540        }
541    }
542    // The prefill arm reads the OLD ring for every token, so the roll runs only after all three
543    // planes have been convolved. The decode arm already rolled inside its fused kernel.
544    if arm == ConvArm::Prefill {
545        for (plane, raw) in [&q_raw, &k_raw, &v_raw].into_iter().enumerate() {
546            e.kda_conv_ring_roll(raw, ring, qkv, t, kernel, plane)?;
547        }
548    }
549
550    // Stage 3 — q/k L2 norm over head_dim (eps INSIDE the sqrt, fixed 1e-6). Rows of the
551    // token-major layout are contiguous head_dim runs, so no repack is needed.
552    let mut q_l2 = if rows_exact {
553        e.vws_uninit(t * qkv)?
554    } else {
555        e.uninit(t * qkv)?
556    };
557    let mut k_l2 = if rows_exact {
558        e.vws_uninit(t * qkv)?
559    } else {
560        e.uninit(t * qkv)?
561    };
562    e.l2_norm(&q_conv, &mut q_l2, head_dim, t * heads, KDA_L2_EPS)?;
563    e.l2_norm(&k_conv, &mut k_l2, head_dim, t * heads, KDA_L2_EPS)?;
564    kda_trace(
565        e,
566        "conv+l2",
567        t,
568        &[
569            ("q_conv", &q_conv),
570            ("k_conv", &k_conv),
571            ("v_conv", &v_conv),
572            ("q_l2", &q_l2),
573            ("k_l2", &k_l2),
574        ],
575    );
576    // Door W: the convs' last readers were the l2 norms (the ring rolls read the raws).
577    if rows_exact {
578        e.vws_recycle(q_conv);
579        e.vws_recycle(k_conv);
580    }
581
582    // Stage 4 — gates. forget: g = lower_bound * sigmoid(exp(A_log[h]) * (f_b(f_a(x)) + dt_bias)),
583    // emitted RAW (the scan applies expf). beta: per-head sigmoid of its own projection.
584    let forget = if rows_exact {
585        e.matmul_rows_exact(&la.f_b, &forget_down, t)?
586    } else {
587        matmul_lowrank(e, &la.f_b, &forget_down, t, "f_b")?
588    };
589    let mut g_log = if rows_exact {
590        e.vws_uninit(t * qkv)?
591    } else {
592        e.uninit(t * qkv)?
593    };
594    e.kda_gate(
595        &forget,
596        la.dt_bias.float_data(),
597        la.a_log.float_data(),
598        &mut g_log,
599        qkv,
600        t,
601        head_dim,
602        la.plan.gate_lower_bound,
603    )?;
604    let mut beta = if rows_exact {
605        e.vws_uninit(t * heads)?
606    } else {
607        e.uninit(t * heads)?
608    };
609    e.sigmoid(&beta_raw, &mut beta, t * heads)?;
610    kda_trace(e, "gates", t, &[("forget", &forget), ("beta", &beta)]);
611    // Door W: forget_down's last reader was the f_b matmul, forget's the gate kernel,
612    // beta_raw's the sigmoid.
613    if rows_exact {
614        e.vws_recycle(forget_down);
615        e.vws_recycle(forget);
616        e.vws_recycle(beta_raw);
617    }
618
619    // Stage 5 — the delta-rule recurrence. `scale` carries the reference's head_dim^-0.5 query
620    // scale: q feeds only the readout, never the state, so scaling the readout is exact.
621    // At t > 1 the kernel walks the T steps IN-KERNEL over register-resident state — the
622    // sequential chain preserved inside ONE launch (chained-t=1 identity by construction,
623    // held by the scan-chain bit-gate). `scan_clock` is the trace-level-2 instrument: it
624    // drains the stream around the launch so the sequential-class share lands in its own
625    // bucket (shares, never walls).
626    let scale = 1.0 / (head_dim as f32).sqrt();
627    let mut core = if rows_exact {
628        e.vws_uninit(t * qkv)?
629    } else {
630        e.uninit(t * qkv)?
631    };
632    let scan_t0 = scan_clock.as_ref().map(|_| {
633        let _ = e.stream().synchronize();
634        std::time::Instant::now()
635    });
636    e.kda_scan(
637        &q_l2, &k_l2, &v_conv, &g_log, &beta, state_in, state_out, &mut core, heads, t, scale,
638    )?;
639    kda_trace(
640        e,
641        "scan",
642        t,
643        &[
644            ("state_in", state_in),
645            ("core", &core),
646            ("state_out", state_out),
647        ],
648    );
649    if let (Some(ns), Some(t0)) = (scan_clock.take(), scan_t0) {
650        let _ = e.stream().synchronize();
651        *ns += t0.elapsed().as_nanos() as u64;
652    }
653
654    // Stage 6 — sigmoid-gated RMSNorm over head_dim (layer rms eps here, NOT the l2 eps), then
655    // the output projection.
656    let gate = if rows_exact {
657        e.matmul_rows_exact(&la.g_b, &gate_down, t)?
658    } else {
659        matmul_lowrank(e, &la.g_b, &gate_down, t, "g_b")?
660    };
661    let mut gated = if rows_exact {
662        e.vws_uninit(t * qkv)?
663    } else {
664        e.uninit(t * qkv)?
665    };
666    match onorm_q8 {
667        // MEMRA_KDA_ONORM_ZQ8: the fused norm+quantize twin hands `wo` its q8_1 pair.
668        Some(slot) if !rows_exact && head_dim.is_multiple_of(32) => {
669            let pair = e.kda_gated_rmsnorm_zq8(
670                &core,
671                la.o_norm.float_data(),
672                &gate,
673                &mut gated,
674                head_dim,
675                t * heads,
676                eps,
677            )?;
678            *slot = Some(pair);
679        }
680        _ => e.kda_gated_rmsnorm(
681            &core,
682            la.o_norm.float_data(),
683            &gate,
684            &mut gated,
685            head_dim,
686            t * heads,
687            eps,
688        )?,
689    }
690    kda_trace(e, "gated_norm", t, &[("gate", &gate), ("gated", &gated)]);
691    // Door W: gate_down's last reader was the g_b matmul; core's and gate's the
692    // gated-rmsnorm above.
693    if rows_exact {
694        e.vws_recycle(gate_down);
695        e.vws_recycle(gate);
696        e.vws_recycle(core);
697    }
698    // Steal the scan/conv inputs for the caller's rollback stash: stage 5 has consumed
699    // the scan inputs and the rolls were the raws' last readers — moving them out is
700    // free (no copy, no launch; the buffers were allocated this call either way).
701    match stash {
702        KdaStash::None => {}
703        KdaStash::Decode(s) => {
704            *s = Some(KdaScanInputs {
705                q: q_l2,
706                k: k_l2,
707                v: v_conv,
708                g: g_log,
709                beta,
710            });
711        }
712        KdaStash::Rows(s) => {
713            // Door W: the PREVIOUS round's stash dies here — its nine buffers restock
714            // the pool instead of falling to nine async frees (per layer per round).
715            if let Some(old) = s.take() {
716                e.vws_recycle(old.ring_snap);
717                for r in old.raws {
718                    e.vws_recycle(r);
719                }
720                e.vws_recycle(old.scan.q);
721                e.vws_recycle(old.scan.k);
722                e.vws_recycle(old.scan.v);
723                e.vws_recycle(old.scan.g);
724                e.vws_recycle(old.scan.beta);
725            }
726            *s = Some(KdaRowsStash {
727                ring_snap: ring_snap.expect("rows arm snapshotted the ring above"),
728                raws: [q_raw, k_raw, v_raw],
729                scan: KdaScanInputs {
730                    q: q_l2,
731                    k: k_l2,
732                    v: v_conv,
733                    g: g_log,
734                    beta,
735                },
736                rows: t,
737            });
738        }
739    }
740    Ok(gated)
741}
742
743/// STATELESS prefill from a zero conv ring and a zero recurrent state — the arm the logits-only
744/// forward paths take. Allocates and discards both state buffers.
745pub fn kda_attn(
746    e: &Engine,
747    la: &KdaAttnLayer,
748    x: &CudaSlice<f32>,
749    t: usize,
750    eps: f32,
751) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
752    let mut ring = e.zeros(la.conv_width() * (la.conv_kernel() - 1))?;
753    let state_in = e.zeros(la.state_width())?;
754    let mut state_out = e.zeros(la.state_width())?;
755    kda_core(
756        e,
757        la,
758        x,
759        t,
760        eps,
761        &mut ring,
762        &state_in,
763        &mut state_out,
764        ConvArm::Prefill,
765        KdaStash::None,
766        None,
767        None,
768    )
769}
770
771/// STATEFUL prefill: carries the ring forward and advances the recurrent state from `state_in`
772/// into `state_out`. Callers own the ping-pong; the two state buffers must be distinct.
773#[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
774pub fn kda_attn_prime(
775    e: &Engine,
776    la: &KdaAttnLayer,
777    x: &CudaSlice<f32>,
778    t: usize,
779    eps: f32,
780    ring: &mut CudaSlice<f32>,
781    state_in: &CudaSlice<f32>,
782    state_out: &mut CudaSlice<f32>,
783) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
784    kda_core(
785        e,
786        la,
787        x,
788        t,
789        eps,
790        ring,
791        state_in,
792        state_out,
793        ConvArm::Prefill,
794        KdaStash::None,
795        None,
796        None,
797    )
798}
799
800/// T=1 decode step. Same math as a one-token prime; separate conv arm so the fused
801/// assemble+conv+roll kernel keeps decode and the spec verify on one dispatch class.
802pub fn kda_attn_decode(
803    e: &Engine,
804    la: &KdaAttnLayer,
805    x: &CudaSlice<f32>,
806    eps: f32,
807    ring: &mut CudaSlice<f32>,
808    state_in: &CudaSlice<f32>,
809    state_out: &mut CudaSlice<f32>,
810) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
811    kda_core(
812        e,
813        la,
814        x,
815        1,
816        eps,
817        ring,
818        state_in,
819        state_out,
820        ConvArm::Decode,
821        KdaStash::None,
822        None,
823        None,
824    )
825}
826
827/// Stateful KDA against the shared recurrent-state carrier, in the eager GDN discipline: the
828/// scan reads `ssm_state` and writes the spare `ssm_state_alt`, then the two OWNED resident
829/// buffers swap in place. Stable pointers, no per-step alloc/free — the per-step scratch this
830/// replaced churned the stream-ordered pool and made decode run-to-run nondeterministic
831/// (crates/memra-kv `RecurLayer::ssm_state_alt`). NOT capture-safe: a captured graph bakes
832/// capture-time pointers and never re-runs the host swap, which is why the capture loops refuse.
833#[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
834fn kda_cached(
835    e: &Engine,
836    la: &KdaAttnLayer,
837    x: &CudaSlice<f32>,
838    t: usize,
839    eps: f32,
840    cache: &mut Cache,
841    il: usize,
842    arm: ConvArm,
843    stash: KdaStash<'_>,
844    scan_clock: Option<&mut u64>,
845    pre_q8: KdaPreQ8<'_>,
846) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
847    let rl = cache.recur[il].as_mut().ok_or_else(|| {
848        format!(
849            "blk.{il}: KDA layer has no recurrent state — the cache allocator saw a \
850                 non-Recurrent StatePlan for a KDA layer"
851        )
852    })?;
853    let out = {
854        let RecurLayer {
855            conv_state,
856            ssm_state,
857            ssm_state_alt,
858        } = rl;
859        kda_core(
860            e,
861            la,
862            x,
863            t,
864            eps,
865            conv_state,
866            ssm_state,
867            ssm_state_alt,
868            arm,
869            stash,
870            scan_clock,
871            pre_q8,
872        )?
873    };
874    std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
875    Ok(out)
876}
877
878/// Stateful prefill of `t` tokens through the cache's KDA state for layer `il`.
879pub fn kda_prime_cached(
880    e: &Engine,
881    la: &KdaAttnLayer,
882    x: &CudaSlice<f32>,
883    t: usize,
884    eps: f32,
885    cache: &mut Cache,
886    il: usize,
887) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
888    kda_cached(
889        e,
890        la,
891        x,
892        t,
893        eps,
894        cache,
895        il,
896        ConvArm::Prefill,
897        KdaStash::None,
898        None,
899        None,
900    )
901}
902
903/// One decode step through the cache's KDA state for layer `il`.
904/// `MEMRA_KDA_CONV3` (lane/glm5-kda-conv3-20260904; default ON on sm_100a builds since 2026-09-04,
905/// OFF elsewhere, `=0`/`=1` override): the T=1 KDA conv+SiLU runs its three planes in one launch. Read PER CALL. Why and receipts: the kernel header in
906/// cu/kda.cu and docs/FLAGS.md.
907pub(crate) fn kda_conv3_on() -> bool {
908    kda_conv3_on_from(
909        std::env::var("MEMRA_KDA_CONV3").ok().as_deref(),
910        env!("MEMRA_BUILT_CUDA_ARCH"),
911    )
912}
913
914/// The pure parse behind [`kda_conv3_on`]: `1` arms, `0` disarms, unset follows the BUILD ARCH
915/// (ON for `100a`, OFF otherwise): the fused launch carries a 2x B200 receipt (+1.82% at c1,
916/// darklanes research/glm5-b200-20260902/LANE.md, convab) and no SM120 one.
917pub fn kda_conv3_on_from(v: Option<&str>, built_arch: &str) -> bool {
918    match v.map(str::trim) {
919        Some("1") => true,
920        Some("0") => false,
921        _ => built_arch == "100a",
922    }
923}
924
925/// Engagement counter for `MEMRA_KDA_CONV3`; gates take a delta.
926pub static KDA_CONV3_DISPATCHES: std::sync::atomic::AtomicU64 =
927    std::sync::atomic::AtomicU64::new(0);
928
929/// Snapshot of [`KDA_CONV3_DISPATCHES`].
930pub fn kda_conv3_dispatches() -> u64 {
931    KDA_CONV3_DISPATCHES.load(std::sync::atomic::Ordering::Relaxed)
932}
933
934/// A pre-quantized q8_1 view of the mixer input (`(aq, ad)` from `rms_norm_zq8_f32`), or
935/// `None` for the launcher to quantize itself. Threaded from the walk to the fused
936/// six-projection launcher (`MEMRA_GLM5_Q8_FUSE_ATTN`, lane/glm5-attn-norm-zq8-20260904).
937pub type KdaPreQ8<'a> = Option<(&'a CudaSlice<i8>, &'a CudaSlice<f32>)>;
938
939/// `MEMRA_KDA_ONORM_ZQ8=1` (lane/kda-onorm-zq8-20260905, default OFF pending its model-scale row):
940/// the decode (t=1, non-rows) KDA core emits the o_norm output's q8_1 pair from the gated-norm
941/// launch itself (`memra_kda_gated_rmsnorm_zq8_f32`) and hands it to the `wo` MMVQ through
942/// `matmul_q8_fast`, dropping the standalone `quantize_q8_1` launch (34 per token on
943/// GLM-5.3-Flash, in-graph). BIT-IDENTICAL: same norm bytes, same q8_1 arithmetic as
944/// `quantize_q8_1` (gate `tests/kda_onorm_zq8_gpu.rs` + the decode-graph fixture arm). When `wo`
945/// is not MMVQ-fast-eligible the pair is dropped and `matmul` runs unchanged. Read per call.
946pub fn kda_onorm_zq8_on() -> bool {
947    std::env::var("MEMRA_KDA_ONORM_ZQ8").as_deref() == Ok("1")
948}
949
950/// Launches of the fused o_norm+quantize kernel whose pair the `wo` MMVQ consumed.
951pub static KDA_ONORM_ZQ8_DISPATCHES: std::sync::atomic::AtomicU64 =
952    std::sync::atomic::AtomicU64::new(0);
953
954/// The o_norm q8_1 pair handed from [`kda_core_gated`] to the `wo` projection.
955pub type KdaOnormQ8 = Option<(CudaSlice<i8>, CudaSlice<f32>)>;
956
957/// `MEMRA_KDA_NARROW_Q8=1` (lane/kda-narrow-q8-20260905, default OFF pending its model-scale row):
958/// the decode KDA core's low-rank `f_b` / `g_b` projections (128-wide inputs) ride
959/// `qmatvec_q8_0_mmvq_f32in_narrow`, which quantizes the row inside the launch, instead of
960/// `quantize_q8_1` + `qmatvec_q8_0_mmvq` (two launches per projection, 68 per token, in-graph).
961/// BIT-IDENTICAL: quantize_q8_1's arithmetic then the mmvq body verbatim (gate
962/// `tests/q8_narrow_f32in_gpu.rs`). Shapes or tensors that do not fit keep `matmul`. Read per call.
963pub fn kda_narrow_q8_on() -> bool {
964    std::env::var("MEMRA_KDA_NARROW_Q8").as_deref() == Ok("1")
965}
966
967/// Launches of the narrow in-kernel-quantize MMVQ on the f_b / g_b sites.
968pub static KDA_NARROW_Q8_DISPATCHES: std::sync::atomic::AtomicU64 =
969    std::sync::atomic::AtomicU64::new(0);
970
971/// `matmul` for a decode-arm low-rank projection, through the narrow in-kernel-quantize twin
972/// when the door is on and the shape fits, else the unchanged `matmul`.
973fn matmul_lowrank(
974    e: &Engine,
975    w: &crate::model::GpuTensor,
976    x: &CudaSlice<f32>,
977    t: usize,
978    which: &str,
979) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
980    if kda_narrow_q8_on()
981        && let Some(y) = e.matmul_q8_narrow_f32in(w, x, t)?
982    {
983        if KDA_NARROW_Q8_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == 0 {
984            eprintln!(
985                "[kda-narrow-q8] engaged: the KDA {which} projection quantizes its {}-wide input \
986                 inside the MMVQ launch (MEMRA_KDA_NARROW_Q8=1)",
987                w.in_features()
988            );
989        }
990        return Ok(y);
991    }
992    e.matmul(w, x, t)
993}
994
995/// [`kda_decode_cached`] with the mixer input's q8_1 view already emitted by the caller's norm
996/// (`MEMRA_GLM5_Q8_FUSE_ATTN`): identical launches minus the fused launcher's own quantize.
997pub fn kda_decode_cached_q8(
998    e: &Engine,
999    la: &KdaAttnLayer,
1000    x: &CudaSlice<f32>,
1001    pre_q8: KdaPreQ8<'_>,
1002    eps: f32,
1003    cache: &mut Cache,
1004    il: usize,
1005) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1006    kda_cached(
1007        e,
1008        la,
1009        x,
1010        1,
1011        eps,
1012        cache,
1013        il,
1014        ConvArm::Decode,
1015        KdaStash::None,
1016        None,
1017        pre_q8,
1018    )
1019}
1020
1021pub fn kda_decode_cached(
1022    e: &Engine,
1023    la: &KdaAttnLayer,
1024    x: &CudaSlice<f32>,
1025    eps: f32,
1026    cache: &mut Cache,
1027    il: usize,
1028) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1029    kda_cached(
1030        e,
1031        la,
1032        x,
1033        1,
1034        eps,
1035        cache,
1036        il,
1037        ConvArm::Decode,
1038        KdaStash::None,
1039        None,
1040        None,
1041    )
1042}
1043
1044/// [`kda_decode_cached`] with the step's scan inputs STOLEN for a rollback stash
1045/// (loop-port 3; doc on [`KdaScanInputs`]). Identical launches — the steal is a move of
1046/// buffers the step allocated either way.
1047pub fn kda_decode_cached_stash(
1048    e: &Engine,
1049    la: &KdaAttnLayer,
1050    x: &CudaSlice<f32>,
1051    eps: f32,
1052    cache: &mut Cache,
1053    il: usize,
1054) -> Result<(CudaSlice<f32>, KdaScanInputs), Box<dyn std::error::Error>> {
1055    let mut stash: Option<KdaScanInputs> = None;
1056    let out = kda_cached(
1057        e,
1058        la,
1059        x,
1060        1,
1061        eps,
1062        cache,
1063        il,
1064        ConvArm::Decode,
1065        KdaStash::Decode(&mut stash),
1066        None,
1067        None,
1068    )?;
1069    let stash = stash.ok_or("kda_core returned without filling the requested scan stash")?;
1070    Ok((out, stash))
1071}
1072
1073/// THE BATCHED VERIFY-ROWS KDA CALL (lane/glm5-verify-batch): one t=K+1 `kda_core` pass
1074/// per layer per round, replacing t per-row [`kda_decode_cached_stash`] calls. Projections,
1075/// gates and norms batch m=t through the decode-exact matmul classes (`matmul_rows_exact`);
1076/// the conv takes the prefill dispatch (per-token bit-identical to the decode arm's taps);
1077/// the recurrence stays SEQUENTIAL inside one `memra_kda_scan_s128` launch (the in-kernel
1078/// T-loop over register-resident state == the chained t=1 program). Per-row bit-identity
1079/// vs the t=1 chain is held by the walk gates (`glm5_tparallel_verify_gpu`) and the
1080/// kernel bit-gates (`glm5_verify_batch_gpu`).
1081///
1082/// The caller owns the pre-round ssm snapshot (`Glm5VerifyCkpt::kda_ssm_snap`, cloned
1083/// BEFORE this call); the returned [`KdaRowsStash`] carries everything else rollback
1084/// needs. `scan_clock`: the trace-level-2 sequential-class bucket (ns accumulated around
1085/// the scan launch with stream drains — an instrument, never a serving mode).
1086#[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kda_cached call contract plus the trace clock
1087pub fn kda_verify_rows_cached(
1088    e: &Engine,
1089    la: &KdaAttnLayer,
1090    x: &CudaSlice<f32>,
1091    t: usize,
1092    eps: f32,
1093    cache: &mut Cache,
1094    il: usize,
1095    scan_clock: Option<&mut u64>,
1096) -> Result<(CudaSlice<f32>, KdaRowsStash), Box<dyn std::error::Error>> {
1097    let mut stash: Option<KdaRowsStash> = None;
1098    let out = kda_cached(
1099        e,
1100        la,
1101        x,
1102        t,
1103        eps,
1104        cache,
1105        il,
1106        ConvArm::Prefill,
1107        KdaStash::Rows(&mut stash),
1108        scan_clock,
1109        None,
1110    )?;
1111    let stash = stash.ok_or("kda_core returned without filling the requested rows stash")?;
1112    Ok((out, stash))
1113}
1114
1115/// Roll layer `il` back to "after row `keep-1`" from a BATCHED verify-rows round
1116/// (lane/glm5-verify-batch; the [`KdaRowsStash`] doc states the two-plane contract):
1117/// restore the pre-round conv ring and re-roll `keep` raw rows (pure placement), then
1118/// replay the scan ONCE at T=keep from the pre-round ssm snapshot over the batched
1119/// inputs. Full accept (`keep == rows`) never calls this — the resident state IS the
1120/// state after the last kept row.
1121pub fn kda_verify_rollback_rows(
1122    e: &Engine,
1123    la: &KdaAttnLayer,
1124    snap: &CudaSlice<f32>,
1125    stash: &KdaRowsStash,
1126    keep: usize,
1127    cache: &mut Cache,
1128    il: usize,
1129) -> Result<(), Box<dyn std::error::Error>> {
1130    let rl = cache.recur[il]
1131        .as_mut()
1132        .ok_or_else(|| format!("blk.{il}: KDA rows rollback on a layer with no recurrent state"))?;
1133    kda_verify_rollback_rows_on(e, la, snap, stash, keep, rl, il)
1134}
1135
1136/// [`kda_verify_rollback_rows`] over a CALLER-OWNED state plane — the glm5 spec x TP seam
1137/// (lane/glm5-composition): under `MEMRA_GLM5_TP` each rank's shard-geometry conv ring +
1138/// ssm ping-pong lives in `cache.glm5_tp_recur[il][rank]` on that rank's engine, so the
1139/// rollback restores per rank through this entry with the rank's own `(engine, shard,
1140/// snapshot, stash)` tuple. The cache wrapper above delegates here — one body, byte-for-byte
1141/// the pre-refactor walk on the plain path.
1142pub fn kda_verify_rollback_rows_on(
1143    e: &Engine,
1144    la: &KdaAttnLayer,
1145    snap: &CudaSlice<f32>,
1146    stash: &KdaRowsStash,
1147    keep: usize,
1148    rl: &mut RecurLayer,
1149    il: usize,
1150) -> Result<(), Box<dyn std::error::Error>> {
1151    if keep == 0 || keep >= stash.rows {
1152        return Err(format!(
1153            "blk.{il}: KDA rows rollback keep={keep} outside 1..{} (full accept keeps the \
1154             resident state and never replays)",
1155            stash.rows
1156        )
1157        .into());
1158    }
1159    let qkv = la.qkv();
1160    let kernel = la.conv_kernel();
1161    let heads = la.heads();
1162    let scale = 1.0 / (la.head_dim() as f32).sqrt();
1163    // Conv ring: pre-round snapshot back, then re-roll the kept raw rows per plane. The
1164    // roll kernel reads every old slot into registers before any store, so T=keep < pad
1165    // mixes snapshot slots and kept rows exactly as the sequential chain's rolls did.
1166    e.copy_into(
1167        &mut rl.conv_state,
1168        0,
1169        &stash.ring_snap,
1170        stash.ring_snap.len(),
1171    )?;
1172    for (plane, raw) in stash.raws.iter().enumerate() {
1173        e.kda_conv_ring_roll(raw, &mut rl.conv_state, qkv, keep, kernel, plane)?;
1174    }
1175    // Recurrent state: ONE T=keep replay from the snapshot over the batched scan inputs
1176    // (the kernel walks rows 0..keep of the [t, ..] buffers); readout discarded. The
1177    // ping-pong ends with the rebuilt state under the `ssm_state` name, matching
1178    // `kda_cached`'s swap discipline.
1179    let mut o = e.uninit(keep * qkv)?;
1180    {
1181        let RecurLayer {
1182            ssm_state: _,
1183            ssm_state_alt,
1184            ..
1185        } = rl;
1186        e.kda_scan(
1187            &stash.scan.q,
1188            &stash.scan.k,
1189            &stash.scan.v,
1190            &stash.scan.g,
1191            &stash.scan.beta,
1192            snap,
1193            ssm_state_alt,
1194            &mut o,
1195            heads,
1196            keep,
1197            scale,
1198        )?;
1199    }
1200    std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
1201    Ok(())
1202}
1203
1204/// Rebuild layer `il`'s recurrent state to "after row `inputs.len()-1`" by REPLAYING the
1205/// stashed scan inputs from the pre-round snapshot `snap` (loop-port 3, the module-doc
1206/// diet made concrete): each replay is the original t=1 `memra_kda_scan_s128` launch
1207/// re-issued over the very buffers that step consumed, so the rebuilt state is
1208/// byte-identical to the per-row clone it replaces BY CONSTRUCTION. The readout is
1209/// discarded; the conv ring is not touched (the walk still clones it per row — 288 KiB
1210/// against the 4 MiB ssm plane this retires). The ping-pong rides the resident pair and
1211/// ends with the rebuilt state under the `ssm_state` name, matching `kda_cached`'s own
1212/// swap discipline.
1213pub fn kda_scan_replay(
1214    e: &Engine,
1215    la: &KdaAttnLayer,
1216    snap: &CudaSlice<f32>,
1217    inputs: &[KdaScanInputs],
1218    cache: &mut Cache,
1219    il: usize,
1220) -> Result<(), Box<dyn std::error::Error>> {
1221    if inputs.is_empty() {
1222        return Err(format!(
1223            "blk.{il}: KDA replay needs at least one stashed row (rollback keep >= 1; a \
1224             restore TO the snapshot itself is a different contract)"
1225        )
1226        .into());
1227    }
1228    if la.tp.is_some() {
1229        return Err(format!(
1230            "blk.{il}: KDA scan replay (the PER-ROW rollback seam) is unwired for a \
1231             glm5-TP-sharded layer — the spec x TP composition requires the BATCHED \
1232             verify walk, whose rollback rides kda_verify_rollback_rows_on per rank"
1233        )
1234        .into());
1235    }
1236    let heads = la.heads();
1237    let scale = 1.0 / (la.head_dim() as f32).sqrt();
1238    let qkv = la.qkv();
1239    let rl = cache.recur[il]
1240        .as_mut()
1241        .ok_or_else(|| format!("blk.{il}: KDA replay on a layer with no recurrent state"))?;
1242    let mut o = e.uninit(qkv)?; // discarded readout scratch, reused across rows
1243    for (r, inp) in inputs.iter().enumerate() {
1244        {
1245            let RecurLayer {
1246                ssm_state,
1247                ssm_state_alt,
1248                ..
1249            } = rl;
1250            let state_in: &CudaSlice<f32> = if r == 0 { snap } else { ssm_state };
1251            e.kda_scan(
1252                &inp.q,
1253                &inp.k,
1254                &inp.v,
1255                &inp.g,
1256                &inp.beta,
1257                state_in,
1258                ssm_state_alt,
1259                &mut o,
1260                heads,
1261                1,
1262                scale,
1263            )?;
1264        }
1265        std::mem::swap(&mut rl.ssm_state, &mut rl.ssm_state_alt);
1266    }
1267    Ok(())
1268}
1269
1270impl Engine {
1271    /// Per-plane causal short conv + SiLU over a T-token chunk (cu/kda.cu).
1272    #[allow(clippy::too_many_arguments)]
1273    pub fn kda_conv_silu(
1274        &self,
1275        x_tm: &CudaSlice<f32>,
1276        w: &CudaSlice<f32>,
1277        ring: &CudaSlice<f32>,
1278        y_tm: &mut CudaSlice<f32>,
1279        qkv: usize,
1280        t: usize,
1281        kernel: usize,
1282        plane: usize,
1283    ) -> Result<(), Box<dyn std::error::Error>> {
1284        let f = self.func("memra_kda_conv_silu_f32");
1285        let cfg = LaunchConfig {
1286            grid_dim: (qkv.div_ceil(256) as u32, t as u32, 1),
1287            block_dim: (256, 1, 1),
1288            shared_mem_bytes: 0,
1289        };
1290        let (n, tt, k, p) = (qkv as i32, t as i32, kernel as i32, plane as i32);
1291        let stream = self.gpu.stream();
1292        let mut b = stream.launch_builder(&f);
1293        b.arg(x_tm)
1294            .arg(w)
1295            .arg(ring)
1296            .arg(&mut *y_tm)
1297            .arg(&n)
1298            .arg(&tt)
1299            .arg(&k)
1300            .arg(&p);
1301        unsafe { b.launch(cfg)? };
1302        Ok(())
1303    }
1304
1305    /// Roll one plane of the fused conv ring forward over a T-token chunk (cu/kda.cu).
1306    pub fn kda_conv_ring_roll(
1307        &self,
1308        x_tm: &CudaSlice<f32>,
1309        ring: &mut CudaSlice<f32>,
1310        qkv: usize,
1311        t: usize,
1312        kernel: usize,
1313        plane: usize,
1314    ) -> Result<(), Box<dyn std::error::Error>> {
1315        let f = self.func("memra_kda_conv_ring_roll_f32");
1316        let cfg = LaunchConfig {
1317            grid_dim: (qkv.div_ceil(256) as u32, 1, 1),
1318            block_dim: (256, 1, 1),
1319            shared_mem_bytes: 0,
1320        };
1321        let (n, tt, k, p) = (qkv as i32, t as i32, kernel as i32, plane as i32);
1322        let stream = self.gpu.stream();
1323        let mut b = stream.launch_builder(&f);
1324        b.arg(x_tm).arg(&mut *ring).arg(&n).arg(&tt).arg(&k).arg(&p);
1325        unsafe { b.launch(cfg)? };
1326        Ok(())
1327    }
1328
1329    /// T=1 fused assemble + conv + SiLU + ring roll for one plane (cu/kda.cu).
1330    #[allow(clippy::too_many_arguments)]
1331    /// The three-plane form of [`Engine::kda_conv_silu_decode`] (door `MEMRA_KDA_CONV3`,
1332    /// lane/glm5-kda-conv3-20260904): one launch with `plane = blockIdx.y` in place of the three
1333    /// per-plane launches; per channel the same body, so outputs and the ring are bit-identical
1334    /// (gate `tests/kda_conv3_gpu.rs`). `kernel` (K) must be at most 9 (the `win[8]` window).
1335    #[allow(clippy::too_many_arguments)]
1336    pub fn kda_conv_silu_decode3(
1337        &self,
1338        x: [&CudaSlice<f32>; 3],
1339        ring: &mut CudaSlice<f32>,
1340        w: &CudaSlice<f32>,
1341        y: [&mut CudaSlice<f32>; 3],
1342        qkv: usize,
1343        kernel: usize,
1344    ) -> Result<(), Box<dyn std::error::Error>> {
1345        if kernel == 0 || kernel > 9 {
1346            return Err("kda_conv_silu_decode3: kernel width outside the 8-wide window".into());
1347        }
1348        if KDA_CONV3_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == 0 {
1349            eprintln!(
1350                "[kda-conv3] engaged: the three KDA conv+SiLU decode planes run as one launch \
1351                 (MEMRA_KDA_CONV3=1)"
1352            );
1353        }
1354        let f = self.func("memra_kda_conv_silu_decode3_f32");
1355        let cfg = LaunchConfig {
1356            grid_dim: (qkv.div_ceil(256) as u32, 3, 1),
1357            block_dim: (256, 1, 1),
1358            shared_mem_bytes: 0,
1359        };
1360        let (n, k) = (qkv as i32, kernel as i32);
1361        let [x0, x1, x2] = x;
1362        let [y0, y1, y2] = y;
1363        let stream = self.gpu.stream();
1364        let mut b = stream.launch_builder(&f);
1365        b.arg(x0)
1366            .arg(x1)
1367            .arg(x2)
1368            .arg(&mut *ring)
1369            .arg(w)
1370            .arg(&mut *y0)
1371            .arg(&mut *y1)
1372            .arg(&mut *y2)
1373            .arg(&n)
1374            .arg(&k);
1375        unsafe { b.launch(cfg)? };
1376        Ok(())
1377    }
1378
1379    #[allow(clippy::too_many_arguments)]
1380    pub fn kda_conv_silu_decode(
1381        &self,
1382        x_new: &CudaSlice<f32>,
1383        ring: &mut CudaSlice<f32>,
1384        w: &CudaSlice<f32>,
1385        y: &mut CudaSlice<f32>,
1386        qkv: usize,
1387        kernel: usize,
1388        plane: usize,
1389    ) -> Result<(), Box<dyn std::error::Error>> {
1390        let f = self.func("memra_kda_conv_silu_decode_f32");
1391        let cfg = LaunchConfig {
1392            grid_dim: (qkv.div_ceil(256) as u32, 1, 1),
1393            block_dim: (256, 1, 1),
1394            shared_mem_bytes: 0,
1395        };
1396        let (n, k, p) = (qkv as i32, kernel as i32, plane as i32);
1397        let stream = self.gpu.stream();
1398        let mut b = stream.launch_builder(&f);
1399        b.arg(x_new)
1400            .arg(&mut *ring)
1401            .arg(w)
1402            .arg(&mut *y)
1403            .arg(&n)
1404            .arg(&k)
1405            .arg(&p);
1406        unsafe { b.launch(cfg)? };
1407        Ok(())
1408    }
1409
1410    /// Per-channel forget gate, emitted as the RAW log-gate (cu/kda.cu).
1411    #[allow(clippy::too_many_arguments)]
1412    pub fn kda_gate(
1413        &self,
1414        forget: &CudaSlice<f32>,
1415        dt_bias: &CudaSlice<f32>,
1416        a_log: &CudaSlice<f32>,
1417        g: &mut CudaSlice<f32>,
1418        qkv: usize,
1419        t: usize,
1420        head_dim: usize,
1421        lower_bound: f32,
1422    ) -> Result<(), Box<dyn std::error::Error>> {
1423        let f = self.func("memra_kda_gate_f32");
1424        let cfg = LaunchConfig {
1425            grid_dim: (qkv.div_ceil(256) as u32, t as u32, 1),
1426            block_dim: (256, 1, 1),
1427            shared_mem_bytes: 0,
1428        };
1429        let (n, tt, hd, lb) = (qkv as i32, t as i32, head_dim as i32, lower_bound);
1430        let stream = self.gpu.stream();
1431        let mut b = stream.launch_builder(&f);
1432        b.arg(forget)
1433            .arg(dt_bias)
1434            .arg(a_log)
1435            .arg(&mut *g)
1436            .arg(&n)
1437            .arg(&tt)
1438            .arg(&hd)
1439            .arg(&lb);
1440        unsafe { b.launch(cfg)? };
1441        Ok(())
1442    }
1443
1444    /// The per-channel-decay delta-rule scan (cu/kda.cu). One warp per output column.
1445    #[allow(clippy::too_many_arguments)]
1446    pub fn kda_scan(
1447        &self,
1448        q: &CudaSlice<f32>,
1449        k: &CudaSlice<f32>,
1450        v: &CudaSlice<f32>,
1451        g: &CudaSlice<f32>,
1452        beta: &CudaSlice<f32>,
1453        state_in: &CudaSlice<f32>,
1454        state_out: &mut CudaSlice<f32>,
1455        o: &mut CudaSlice<f32>,
1456        heads: usize,
1457        t: usize,
1458        scale: f32,
1459    ) -> Result<(), Box<dyn std::error::Error>> {
1460        // Four columns per block keeps one warp per column at 128 threads, the same shape
1461        // gdn_scan_s128 launches with.
1462        const COLS_PER_BLOCK: u32 = 4;
1463        let f = self.func("memra_kda_scan_s128");
1464        let cfg = LaunchConfig {
1465            grid_dim: (
1466                heads as u32,
1467                1,
1468                (KDA_HEAD_DIM as u32).div_ceil(COLS_PER_BLOCK),
1469            ),
1470            block_dim: (32, COLS_PER_BLOCK, 1),
1471            shared_mem_bytes: 0,
1472        };
1473        let (h, tt, s) = (heads as i32, t as i32, scale);
1474        let stream = self.gpu.stream();
1475        let mut b = stream.launch_builder(&f);
1476        b.arg(q)
1477            .arg(k)
1478            .arg(v)
1479            .arg(g)
1480            .arg(beta)
1481            .arg(state_in)
1482            .arg(&mut *state_out)
1483            .arg(&mut *o)
1484            .arg(&h)
1485            .arg(&tt)
1486            .arg(&s);
1487        unsafe { b.launch(cfg)? };
1488        Ok(())
1489    }
1490
1491    /// Sigmoid-gated fp32 RMSNorm over head_dim (cu/kda.cu). GDN's `gated_rmsnorm` gates with
1492    /// SiLU; KDA's Glm5NextTextRMSNormGated hardcodes sigmoid.
1493    #[allow(clippy::too_many_arguments)]
1494    pub fn kda_gated_rmsnorm(
1495        &self,
1496        core: &CudaSlice<f32>,
1497        w: &CudaSlice<f32>,
1498        gate: &CudaSlice<f32>,
1499        dst: &mut CudaSlice<f32>,
1500        ncols: usize,
1501        nrows: usize,
1502        eps: f32,
1503    ) -> Result<(), Box<dyn std::error::Error>> {
1504        let f = self.func("memra_kda_gated_rmsnorm_f32");
1505        let cfg = LaunchConfig {
1506            grid_dim: (nrows as u32, 1, 1),
1507            block_dim: (256, 1, 1),
1508            shared_mem_bytes: 0,
1509        };
1510        let (nc, ep) = (ncols as i32, eps);
1511        let stream = self.gpu.stream();
1512        let mut b = stream.launch_builder(&f);
1513        b.arg(core)
1514            .arg(w)
1515            .arg(gate)
1516            .arg(&mut *dst)
1517            .arg(&nc)
1518            .arg(&ep);
1519        unsafe { b.launch(cfg)? };
1520        Ok(())
1521    }
1522
1523    /// [`Engine::kda_gated_rmsnorm`] emitting the q8_1 pair of `dst` beside it
1524    /// (`memra_kda_gated_rmsnorm_zq8_f32`): `dst` byte-identical to the plain kernel, the pair
1525    /// byte-identical to `quantize_q8_1(dst, t, heads*ncols)` (the `wo` MMVQ input). Returns
1526    /// `(q [nrows*ncols], d [nrows*ncols/32])`, which viewed per token is exactly the
1527    /// `[t, heads*ncols]` activation's q8_1 pair. Requires `ncols % 32 == 0`.
1528    #[allow(clippy::too_many_arguments)]
1529    pub fn kda_gated_rmsnorm_zq8(
1530        &self,
1531        core: &CudaSlice<f32>,
1532        w: &CudaSlice<f32>,
1533        gate: &CudaSlice<f32>,
1534        dst: &mut CudaSlice<f32>,
1535        ncols: usize,
1536        nrows: usize,
1537        eps: f32,
1538    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1539        if !ncols.is_multiple_of(32) {
1540            return Err(format!("kda_gated_rmsnorm_zq8 needs ncols % 32 == 0, got {ncols}").into());
1541        }
1542        let mut q = self.alloc_i8_uninit(nrows * ncols)?;
1543        let mut d = self.uninit(nrows * ncols / 32)?;
1544        let f = self.func("memra_kda_gated_rmsnorm_zq8_f32");
1545        let cfg = LaunchConfig {
1546            grid_dim: (nrows as u32, 1, 1),
1547            block_dim: (256, 1, 1),
1548            shared_mem_bytes: 0,
1549        };
1550        let (nc, ep) = (ncols as i32, eps);
1551        let stream = self.gpu.stream();
1552        let mut b = stream.launch_builder(&f);
1553        b.arg(core)
1554            .arg(w)
1555            .arg(gate)
1556            .arg(&mut *dst)
1557            .arg(&mut q)
1558            .arg(&mut d)
1559            .arg(&nc)
1560            .arg(&ep);
1561        unsafe { b.launch(cfg)? };
1562        Ok((q, d))
1563    }
1564
1565    /// The `MEMRA_KDA_FUSED_PROJ` door: run the KDA stage-1 six-projection group as ONE
1566    /// `quantize_q8_1` + ONE `qmatvec_kda6_q8f32_mmvq` launch, or return `None` and let the
1567    /// caller take the unchanged `matmul_group` arm.
1568    ///
1569    /// ENGAGEMENT IS DELIBERATELY NARROW — every condition below exists so the door's numeric
1570    /// claim stays exactly what the gate proves (`tests/kda_fused_proj_gpu.rs`):
1571    ///  * wq/wk/wv must be plain-layout Q8_0 (`rp: false`, no `rp4` mirror, `scale == 1.0`) —
1572    ///    the fused kernel's per-(token,row) body is `qmatvec_q8_0_mmvq` VERBATIM, so those
1573    ///    rows are BIT-IDENTICAL to the unfused MMVQ/batched arm; a repacked layout would ride
1574    ///    the `_rp` twins instead and the claim would be against the wrong kernel.
1575    ///  * f_a/g_a/b_proj must be f32 `Float` — their fused rows replace cuBLASLt with a
1576    ///    deterministic warp tree: a reduction-order class change (the step37 QKV_FUSED class),
1577    ///    measured and pinned in the gate.
1578    ///  * t in 1..=15 (the batch cap), and the env classes under which the UNFUSED arm rides
1579    ///    the MMVQ-class per-row program: `MEMRA_FAST!=0`, `mmvq_supports(Q8_0)`,
1580    ///    `MEMRA_NO_BATCHED` unset for t>=2, `MEMRA_B8!=0` for t>=5. Outside those envs the
1581    ///    unfused arm is a different kernel class (dp4a / Stage-A), so the door refuses rather
1582    ///    than weakening its identity claim.
1583    ///
1584    /// The flag is read PER CALL (the `MEMRA_MOE_FUSED_EPI` rollback-seam precedent), so both
1585    /// arms alternate inside one process. Output order matches `matmul_group`'s:
1586    /// `[q, k, v, forget_down, gate_down, beta_raw]`.
1587    pub fn kda_proj_fused6(
1588        &self,
1589        la: &KdaAttnLayer,
1590        x: &CudaSlice<f32>,
1591        t: usize,
1592    ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
1593        self.kda_proj_fused6_pre(la, x, t, None)
1594    }
1595
1596    /// [`Engine::kda_proj_fused6`] with an optional pre-quantized activation for the W8 arm
1597    /// (`MEMRA_GLM5_Q8_FUSE_ATTN`); every other arm ignores it (they quantize per projection or
1598    /// run bf16) and stays the program it was.
1599    pub fn kda_proj_fused6_pre(
1600        &self,
1601        la: &KdaAttnLayer,
1602        x: &CudaSlice<f32>,
1603        t: usize,
1604        pre_q8: KdaPreQ8<'_>,
1605    ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
1606        if std::env::var("MEMRA_KDA_FUSED_PROJ").as_deref() != Ok("1") {
1607            return Ok(None);
1608        }
1609        // glm5 TP composition guard (#82 review): the load preflight refuses this door at
1610        // ARM time, but the flag is read PER CALL — a post-load `set` would otherwise
1611        // engage the fused six-projection group on head shards inside the TP walk, an
1612        // unproven composition (the door's gate ran on full-width projections). A shard
1613        // declines here and takes the caller's unchanged arm, announced once.
1614        if la.tp.is_some() {
1615            static TP_F6_DECLINE: std::sync::Once = std::sync::Once::new();
1616            TP_F6_DECLINE.call_once(|| {
1617                eprintln!(
1618                    "[kda-fused-proj] DECLINED on a glm5-TP head shard: the door is gated \
1619                     on full-width projections (the load preflight refuses the pair; this \
1620                     is the per-call twin for a post-load flag set)"
1621                );
1622            });
1623            return Ok(None);
1624        }
1625        if !(1..=15).contains(&t) {
1626            return Ok(None);
1627        }
1628        // E4M3 SIX-GROUP ARM (lane/glm5-b200-mint-consume, 2026-09-04) — the operand class the
1629        // GLM-5.3-Flash B200 hybrid mint actually ships. That mint quantizes ALL SIX KDA
1630        // projections to per-tensor e4m3, so under MEMRA_ST_E4M3 (default ON) every one of them
1631        // is QT_F8_E4M3-resident at 1.0 B/weight: cheaper than the bf16 serving recipe's 2.0 and
1632        // cheaper than the Q8_0 re-encode's 1.0625, with no lossy re-quant hop. The two arms
1633        // below cannot serve that shape — they require a FloatBf16 trio plus an f32 trio — so
1634        // without this arm the mint's cheapest operand would fall to SIX separate launches on
1635        // each of the 34 KDA layers, with six redundant broadcasts of the same activation.
1636        //
1637        // This is an operand arm of an EXISTING door, not a new one: it rides
1638        // MEMRA_KDA_FUSED_PROJ=1 exactly as the bf16 and q8rp arms do, and it additionally
1639        // declines wherever the unfused e4m3 program it claims bit-identity against would not
1640        // be the shipped one (MEMRA_FAST=0, no MMVQ support for the qtype, or the
1641        // MEMRA_E4M3_DUAL=0 rollback that restores per-tensor launches).
1642        //
1643        // m=1 ONLY. `qmatvec_e4m3_mmvq_fused6` pins the token index at 0, matching the
1644        // `e4m3_mmvq_row1` body it shares with the pair and triple. t>1 keeps the caller's arm.
1645        let e4m3 = |w: &GpuTensor| -> Option<(usize, usize, f32)> {
1646            match w {
1647                GpuTensor::Quant {
1648                    qtype: crate::QT_F8_E4M3,
1649                    row_bytes,
1650                    scale,
1651                    rp: false,
1652                    rp4: None,
1653                    blk: None,
1654                    ..
1655                } => Some((w.in_features(), *row_bytes, *scale)),
1656                _ => None,
1657            }
1658        };
1659        if let (Some(e_q), Some(e_k), Some(e_v), Some(e_fa), Some(e_ga), Some(e_b)) = (
1660            e4m3(&la.wq),
1661            e4m3(&la.wk),
1662            e4m3(&la.wv),
1663            e4m3(&la.f_a),
1664            e4m3(&la.g_a),
1665            e4m3(&la.b_proj),
1666        ) {
1667            let six = [e_q, e_k, e_v, e_fa, e_ga, e_b];
1668            let in_f = e_q.0;
1669            if t != 1
1670                || std::env::var("MEMRA_FAST").as_deref() == Ok("0")
1671                || !self.mmvq_supports(crate::QT_F8_E4M3)
1672                || !self.e4m3_dual_on()
1673                // Every range must share in_f and the q8_1 activation block, and an e4m3 row is
1674                // exactly in_f bytes — a row_bytes that disagrees means a padded or foreign
1675                // layout this kernel's single `row_bytes` cannot address.
1676                || six.iter().any(|&(i, rb, _)| i != in_f || rb != in_f)
1677                || !in_f.is_multiple_of(32)
1678                || x.len() < in_f
1679            {
1680                return Ok(None);
1681            }
1682            let dims = [
1683                la.wq.out_features(),
1684                la.wk.out_features(),
1685                la.wv.out_features(),
1686                la.f_a.out_features(),
1687                la.g_a.out_features(),
1688                la.b_proj.out_features(),
1689            ];
1690            let ws: [f32; 6] = std::array::from_fn(|i| six[i].2);
1691            fn e4m3_bytes(w: &GpuTensor) -> &CudaSlice<u8> {
1692                match w {
1693                    GpuTensor::Quant { bytes, .. } => bytes,
1694                    _ => unreachable!("e4m3() above only admits Quant"),
1695                }
1696            }
1697            let bytes = e4m3_bytes;
1698            let w = [
1699                bytes(&la.wq),
1700                bytes(&la.wk),
1701                bytes(&la.wv),
1702                bytes(&la.f_a),
1703                bytes(&la.g_a),
1704                bytes(&la.b_proj),
1705            ];
1706            // ONE activation quantize for all six ranges — the six-launch path pays this per
1707            // projection. `pre_q8` is the W8 posture's pre-quantized pair and does not apply to
1708            // this operand class, so the arm always quantizes here.
1709            let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
1710            let mut outs = [
1711                self.uninit(dims[0])?,
1712                self.uninit(dims[1])?,
1713                self.uninit(dims[2])?,
1714                self.uninit(dims[3])?,
1715                self.uninit(dims[4])?,
1716                self.uninit(dims[5])?,
1717            ];
1718            self.e4m3_fused6_into(w, &aq, &ad, in_f, dims, in_f, ws, &mut outs)?;
1719            if KDA_FUSED6_E4M3_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1720                eprintln!(
1721                    "[kda-fused6] engaged arm=e4m3 in_f={in_f} out={dims:?} t={t} (one launch \
1722                     replaces the six per-tensor e4m3 projections and their six redundant \
1723                     activation quantizes; MEMRA_KDA_FUSED_PROJ=1 MEMRA_ST_E4M3=1)"
1724                );
1725            }
1726            return Ok(Some(outs.into_iter().collect()));
1727        }
1728        // The f32 trio is common to both operand arms. Any mismatch = refuse; the caller's
1729        // arm is the shipped program.
1730        let f32w = |w: &GpuTensor| -> Option<usize> {
1731            match w {
1732                GpuTensor::Float { .. } => Some(w.in_features()),
1733                _ => None,
1734            }
1735        };
1736        let (Some(in_fa), Some(in_ga), Some(in_b)) =
1737            (f32w(&la.f_a), f32w(&la.g_a), f32w(&la.b_proj))
1738        else {
1739            return Ok(None);
1740        };
1741        // BF16 operand arm (lever 3 of the decode diet): the serving recipe (MEMRA_BF16_MMV=1)
1742        // admits wq/wk/wv to raw bf16 residency, where the Q8_0 arm below never binds. Its
1743        // bit-identity bar is against `matvec_bf16_f32acc_x4_rows` (matmul's FloatBf16
1744        // decode-tier arm), so it refuses wherever that arm would not be the unfused program:
1745        // MEMRA_BF16_MMV off (the chunked cuBLASLt GEMM class), the step37 W8 mirror doors on
1746        // (matvec_bf16_rows_into reroutes through the q8 mirror when BOTH are set), or
1747        // MEMRA_GLM5_W8 on (2026-09-02, lane/b200-glm5-w8: the SAME reroute, independent
1748        // door — this fused kernel's bit-identity claim is against the unmirrored bf16
1749        // program, so it must decline whichever door moved that program's target).
1750        let bf16 = |w: &GpuTensor| -> Option<usize> {
1751            match w {
1752                GpuTensor::FloatBf16 { .. } => Some(w.in_features()),
1753                _ => None,
1754            }
1755        };
1756        if let (Some(in_q), Some(in_k), Some(in_v)) = (bf16(&la.wq), bf16(&la.wk), bf16(&la.wv)) {
1757            // MEMRA_B200_BF16_GEMV_LT (lane/b200-gemv-hbm-20260902) reroutes the SAME
1758            // unfused target (`matvec_bf16_f32acc_x4_rows`) to a cuBLASLt reference GEMV, so
1759            // this fused arm declines for exactly the reason it declines for the W8 mirrors:
1760            // its bit-identity bar is against the unmirrored, unrerouted bf16 program. With
1761            // the door on, the three bf16 projections fall to the unfused group and each one
1762            // takes the library GEMV, which is what the reference door is there to measure.
1763            // W8 POSTURE FUSION (lane/b200-gemv-hbm-20260902 round 3). Under MEMRA_GLM5_W8 the
1764            // six projections each reroute through `matvec_bf16_via_q8_mirror`, so this group
1765            // runs as SIX separate launches plus six redundant quantizes of the same `x` — and
1766            // the bf16 fused arm below cannot serve it, because its bit-identity bar is against
1767            // the unmirrored bf16 program. `qmatvec_kda6_q8f32_rp_v2` is the fused twin for
1768            // that posture: three mirrored ranges on the rp v2 body (bit-identical to
1769            // `qmatvec_q8_0_mmvq_rp` per row) and three f32 ranges on the same deterministic
1770            // warp tree the q8 arm of this door already ships and has pinned. Gated on
1771            // MEMRA_B200_GEMV_V2 so it carries its own receipt; without that door W8 still
1772            // declines to the unfused path exactly as before.
1773            if crate::glm5_w8_on() && !(crate::step_tp_w8_on() && crate::w8_hybrid_on()) {
1774                if !Self::bf16_mmv_on() || !crate::b200_gemv_v2_on() {
1775                    return Ok(None);
1776                }
1777                let in_f = in_q;
1778                if [in_k, in_v, in_fa, in_ga, in_b].iter().any(|&i| i != in_f)
1779                    || !in_f.is_multiple_of(128)
1780                    || x.len() < t * in_f
1781                    || Engine::q8_v2_smem_bytes(in_f) > 48 * 1024
1782                {
1783                    return Ok(None);
1784                }
1785                let dims = [
1786                    la.wq.out_features(),
1787                    la.wk.out_features(),
1788                    la.wv.out_features(),
1789                    la.f_a.out_features(),
1790                    la.g_a.out_features(),
1791                    la.b_proj.out_features(),
1792                ];
1793                let (
1794                    GpuTensor::FloatBf16 { data: bq, .. },
1795                    GpuTensor::FloatBf16 { data: bk, .. },
1796                    GpuTensor::FloatBf16 { data: bv, .. },
1797                ) = (&la.wq, &la.wk, &la.wv)
1798                else {
1799                    unreachable!("bf16() above only admits FloatBf16");
1800                };
1801                let (
1802                    GpuTensor::Float { data: wfa, .. },
1803                    GpuTensor::Float { data: wga, .. },
1804                    GpuTensor::Float { data: wb, .. },
1805                ) = (&la.f_a, &la.g_a, &la.b_proj)
1806                else {
1807                    unreachable!("f32w() above only admits Float");
1808                };
1809                let mut outs = [
1810                    self.uninit(t * dims[0])?,
1811                    self.uninit(t * dims[1])?,
1812                    self.uninit(t * dims[2])?,
1813                    self.uninit(t * dims[3])?,
1814                    self.uninit(t * dims[4])?,
1815                    self.uninit(t * dims[5])?,
1816                ];
1817                self.kda_proj_fused6_q8rp_raw_pre(
1818                    bq,
1819                    bk,
1820                    bv,
1821                    wfa,
1822                    wga,
1823                    wb,
1824                    x,
1825                    &mut outs,
1826                    in_f,
1827                    dims,
1828                    t,
1829                    crate::q8_row_ilp_on(),
1830                    pre_q8,
1831                )?;
1832                if KDA_FUSED6_Q8RP_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1833                    eprintln!(
1834                        "[kda-fused6] engaged arm=q8rp_v2 in_f={in_f} out={dims:?} t={t} (one \
1835                         launch replaces the six W8-mirror projections and their six redundant \
1836                         activation quantizes; MEMRA_KDA_FUSED_PROJ=1 MEMRA_B200_GEMV_V2=1)"
1837                    );
1838                }
1839                return Ok(Some(outs.into_iter().collect()));
1840            }
1841            if !Self::bf16_mmv_on()
1842                || (crate::step_tp_w8_on() && crate::w8_hybrid_on())
1843                || crate::b200_bf16_gemv_lt_on()
1844            {
1845                return Ok(None);
1846            }
1847            let in_f = in_q;
1848            if [in_k, in_v, in_fa, in_ga, in_b].iter().any(|&i| i != in_f)
1849                || !in_f.is_multiple_of(128)
1850                || x.len() < t * in_f
1851            {
1852                return Ok(None);
1853            }
1854            let dims = [
1855                la.wq.out_features(),
1856                la.wk.out_features(),
1857                la.wv.out_features(),
1858                la.f_a.out_features(),
1859                la.g_a.out_features(),
1860                la.b_proj.out_features(),
1861            ];
1862            let (
1863                GpuTensor::FloatBf16 { data: bq, .. },
1864                GpuTensor::FloatBf16 { data: bk, .. },
1865                GpuTensor::FloatBf16 { data: bv, .. },
1866            ) = (&la.wq, &la.wk, &la.wv)
1867            else {
1868                unreachable!("bf16() above only admits FloatBf16");
1869            };
1870            let (
1871                GpuTensor::Float { data: wfa, .. },
1872                GpuTensor::Float { data: wga, .. },
1873                GpuTensor::Float { data: wb, .. },
1874            ) = (&la.f_a, &la.g_a, &la.b_proj)
1875            else {
1876                unreachable!("f32w() above only admits Float");
1877            };
1878            let mut outs = [
1879                self.uninit(t * dims[0])?,
1880                self.uninit(t * dims[1])?,
1881                self.uninit(t * dims[2])?,
1882                self.uninit(t * dims[3])?,
1883                self.uninit(t * dims[4])?,
1884                self.uninit(t * dims[5])?,
1885            ];
1886            self.kda_proj_fused6_bf16_raw(bq, bk, bv, wfa, wga, wb, x, &mut outs, in_f, dims, t)?;
1887            if KDA_FUSED6_BF16_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1888                eprintln!(
1889                    "[kda-fused6] engaged arm=bf16 in_f={in_f} out={dims:?} t={t} (one launch \
1890                     replaces the six-projection group on the bf16-resident serving recipe; \
1891                     MEMRA_KDA_FUSED_PROJ=1)"
1892                );
1893            }
1894            return Ok(Some(outs.into_iter().collect()));
1895        }
1896        // Dispatch-class envs: the bit-identity bar is against the MMVQ-class per-row program.
1897        if std::env::var("MEMRA_FAST").as_deref() == Ok("0")
1898            || !self.mmvq_supports(crate::QT_Q8_0)
1899            || (t >= 2 && std::env::var("MEMRA_NO_BATCHED").is_ok())
1900            || (t >= 5 && !Self::b8_enabled())
1901        {
1902            return Ok(None);
1903        }
1904        // Q8_0 operand classes (the non-BF16_MMV shapes).
1905        let q8 = |w: &GpuTensor| -> Option<(usize, usize)> {
1906            match w {
1907                GpuTensor::Quant {
1908                    qtype: crate::QT_Q8_0,
1909                    row_bytes,
1910                    scale,
1911                    rp: false,
1912                    rp4: None,
1913                    ..
1914                } if *scale == 1.0 => Some((w.in_features(), *row_bytes)),
1915                _ => None,
1916            }
1917        };
1918        let (Some((in_q, rb_q)), Some((in_k, rb_k)), Some((in_v, rb_v))) =
1919            (q8(&la.wq), q8(&la.wk), q8(&la.wv))
1920        else {
1921            return Ok(None);
1922        };
1923        let in_f = in_q;
1924        if [in_k, in_v, in_fa, in_ga, in_b].iter().any(|&i| i != in_f)
1925            || rb_k != rb_q
1926            || rb_v != rb_q
1927            || !in_f.is_multiple_of(128)
1928            || x.len() < t * in_f
1929        {
1930            return Ok(None);
1931        }
1932        let dims = [
1933            la.wq.out_features(),
1934            la.wk.out_features(),
1935            la.wv.out_features(),
1936            la.f_a.out_features(),
1937            la.g_a.out_features(),
1938            la.b_proj.out_features(),
1939        ];
1940        let (
1941            GpuTensor::Quant { bytes: bq, .. },
1942            GpuTensor::Quant { bytes: bk, .. },
1943            GpuTensor::Quant { bytes: bv, .. },
1944        ) = (&la.wq, &la.wk, &la.wv)
1945        else {
1946            unreachable!("q8() above only admits Quant");
1947        };
1948        let (
1949            GpuTensor::Float { data: wfa, .. },
1950            GpuTensor::Float { data: wga, .. },
1951            GpuTensor::Float { data: wb, .. },
1952        ) = (&la.f_a, &la.g_a, &la.b_proj)
1953        else {
1954            unreachable!("f32w() above only admits Float");
1955        };
1956
1957        let (aq, ad) = self.quantize_q8_1(x, t, in_f)?;
1958        let mut outs = [
1959            self.uninit(t * dims[0])?,
1960            self.uninit(t * dims[1])?,
1961            self.uninit(t * dims[2])?,
1962            self.uninit(t * dims[3])?,
1963            self.uninit(t * dims[4])?,
1964            self.uninit(t * dims[5])?,
1965        ];
1966        self.kda_proj_fused6_raw(
1967            bq, bk, bv, wfa, wga, wb, &aq, &ad, x, &mut outs, in_f, dims, t, rb_q,
1968        )?;
1969
1970        // Engagement receipt: counted at the arm's own call site, announced once per boot
1971        // (the [bf16-mmv] RESIDENT lesson: engagement lines are receipts, never inferred).
1972        if KDA_FUSED6_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
1973            eprintln!(
1974                "[kda-fused6] engaged in_f={in_f} out={dims:?} t={t} (one launch replaces the \
1975                 six-projection group; MEMRA_KDA_FUSED_PROJ=1)"
1976            );
1977        }
1978        Ok(Some(outs.into_iter().collect()))
1979    }
1980
1981    /// The raw fused-6 launch (`qmatvec_kda6_q8f32_mmvq`): three Q8_0 weights + three f32
1982    /// weights, one q8_1 activation pair + the raw f32 activation, six outputs, t token rows.
1983    /// Geometry-checked but POLICY-FREE: the gate's red arms drive mutations (transposed slice
1984    /// data, dropped ranges via `dims[i] = 0`) through this entry, so the mutation reaches the
1985    /// exact program the door serves.
1986    #[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
1987    pub fn kda_proj_fused6_raw(
1988        &self,
1989        wq: &CudaSlice<u8>,
1990        wk: &CudaSlice<u8>,
1991        wv: &CudaSlice<u8>,
1992        wfa: &CudaSlice<f32>,
1993        wga: &CudaSlice<f32>,
1994        wb: &CudaSlice<f32>,
1995        aq: &CudaSlice<i8>,
1996        ad: &CudaSlice<f32>,
1997        x: &CudaSlice<f32>,
1998        outs: &mut [CudaSlice<f32>; 6],
1999        in_f: usize,
2000        dims: [usize; 6],
2001        t: usize,
2002        row_bytes: usize,
2003    ) -> Result<(), Box<dyn std::error::Error>> {
2004        const ROWS_PER_BLOCK: usize = 4; // MEMRA_MMVQ_ROWS in qmatvec.cu
2005        if t == 0
2006            || !in_f.is_multiple_of(128)
2007            || x.len() < t * in_f
2008            || aq.len() < t * in_f
2009            || ad.len() < t * (in_f / 32)
2010        {
2011            return Err("kda_proj_fused6 geometry".into());
2012        }
2013        for (i, (w, want_rows)) in [(wq, dims[0]), (wk, dims[1]), (wv, dims[2])]
2014            .into_iter()
2015            .enumerate()
2016        {
2017            if w.len() < want_rows * row_bytes {
2018                return Err(format!(
2019                    "kda_proj_fused6: q8 weight {i} holds {} bytes, needs {}",
2020                    w.len(),
2021                    want_rows * row_bytes
2022                )
2023                .into());
2024            }
2025        }
2026        for (i, (w, want_rows)) in [(wfa, dims[3]), (wga, dims[4]), (wb, dims[5])]
2027            .into_iter()
2028            .enumerate()
2029        {
2030            if w.len() < want_rows * in_f {
2031                return Err(format!(
2032                    "kda_proj_fused6: f32 weight {} holds {} floats, needs {}",
2033                    i + 3,
2034                    w.len(),
2035                    want_rows * in_f
2036                )
2037                .into());
2038            }
2039        }
2040        for (i, (o, want)) in outs.iter().zip(dims).enumerate() {
2041            if o.len() < t * want {
2042                return Err(format!("kda_proj_fused6: output {i} too small").into());
2043            }
2044        }
2045        let blocks: usize = dims.iter().map(|d| d.div_ceil(ROWS_PER_BLOCK)).sum();
2046        let f = self.func("qmatvec_kda6_q8f32_mmvq");
2047        let cfg = LaunchConfig {
2048            grid_dim: (blocks as u32, t as u32, 1),
2049            block_dim: (32, ROWS_PER_BLOCK as u32, 1),
2050            shared_mem_bytes: 0,
2051        };
2052        let inf = in_f as i32;
2053        let d = dims.map(|v| v as i32);
2054        let (mi, rb) = (t as i32, row_bytes as i64);
2055        let [o0, o1, o2, o3, o4, o5] = outs;
2056        let stream = self.gpu.stream();
2057        let mut b = stream.launch_builder(&f);
2058        b.arg(wq)
2059            .arg(wk)
2060            .arg(wv)
2061            .arg(wfa)
2062            .arg(wga)
2063            .arg(wb)
2064            .arg(aq)
2065            .arg(ad)
2066            .arg(x)
2067            .arg(&mut *o0)
2068            .arg(&mut *o1)
2069            .arg(&mut *o2)
2070            .arg(&mut *o3)
2071            .arg(&mut *o4)
2072            .arg(&mut *o5)
2073            .arg(&inf)
2074            .arg(&d[0])
2075            .arg(&d[1])
2076            .arg(&d[2])
2077            .arg(&d[3])
2078            .arg(&d[4])
2079            .arg(&d[5])
2080            .arg(&mi)
2081            .arg(&rb);
2082        unsafe { b.launch(cfg)? };
2083        Ok(())
2084    }
2085
2086    /// The raw BF16-arm fused-6 launch (`qmatvec_kda6_bf16f32`): three bf16-resident weights
2087    /// (raw checkpoint u16 bytes, the `admit=bf16_mmv` residency) + three f32 weights, one raw
2088    /// f32 activation, six outputs, t token rows. Block = `mmv_block()` — the SAME blockDim
2089    /// `matvec_bf16_rows_into` pins, because the bf16 body's shared-tree reduction shape (and
2090    /// therefore its bits) is a function of blockDim. Geometry-checked but POLICY-FREE: the
2091    /// gate's red arms drive mutations through this entry, exactly like the q8 raw above.
2092    #[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
2093    pub fn kda_proj_fused6_bf16_raw(
2094        &self,
2095        wq: &CudaSlice<u8>,
2096        wk: &CudaSlice<u8>,
2097        wv: &CudaSlice<u8>,
2098        wfa: &CudaSlice<f32>,
2099        wga: &CudaSlice<f32>,
2100        wb: &CudaSlice<f32>,
2101        x: &CudaSlice<f32>,
2102        outs: &mut [CudaSlice<f32>; 6],
2103        in_f: usize,
2104        dims: [usize; 6],
2105        t: usize,
2106    ) -> Result<(), Box<dyn std::error::Error>> {
2107        self.kda_proj_fused6_bf16_arm_raw(
2108            wq,
2109            wk,
2110            wv,
2111            wfa,
2112            wga,
2113            wb,
2114            x,
2115            outs,
2116            in_f,
2117            dims,
2118            t,
2119            crate::b200_gemv_v2_level(),
2120        )
2121    }
2122
2123    /// The same launch with the arm chosen EXPLICITLY instead of from the memoized
2124    /// `MEMRA_B200_GEMV_V2` door, so a bench or gate can drive every arm inside one process
2125    /// (`b200_matvec_bench`, the `_arm_raw` precedent).
2126    ///
2127    /// `arm`: `0` = the shipped `qmatvec_kda6_bf16f32`; `1` = `_v2`, whose three BF16 ranges take
2128    /// the eight-rows-per-block walk (activation loaded once and reused across the rows, ten
2129    /// 16 B loads in flight before the first fma, one barrier chain per block) instead of
2130    /// `kda6_bf16_rows4`'s four sequential rows; `2` = `_v3`, the same walk with its weight
2131    /// tiles staged through shared memory by `cp.async` so the in-flight budget stops being
2132    /// register-bound. `2` falls back to `1` when v3's dynamic smem would exceed the 48 KB
2133    /// default cap. Per row the arithmetic is unchanged in every arm, so all three are
2134    /// BIT-IDENTICAL to each other and to `matvec_bf16_f32acc_x4_rows`.
2135    #[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
2136    pub fn kda_proj_fused6_bf16_arm_raw(
2137        &self,
2138        wq: &CudaSlice<u8>,
2139        wk: &CudaSlice<u8>,
2140        wv: &CudaSlice<u8>,
2141        wfa: &CudaSlice<f32>,
2142        wga: &CudaSlice<f32>,
2143        wb: &CudaSlice<f32>,
2144        x: &CudaSlice<f32>,
2145        outs: &mut [CudaSlice<f32>; 6],
2146        in_f: usize,
2147        dims: [usize; 6],
2148        t: usize,
2149        arm: u8,
2150    ) -> Result<(), Box<dyn std::error::Error>> {
2151        if t == 0 || !in_f.is_multiple_of(128) || x.len() < t * in_f {
2152            return Err("kda_proj_fused6_bf16 geometry".into());
2153        }
2154        for (i, (w, want_rows)) in [(wq, dims[0]), (wk, dims[1]), (wv, dims[2])]
2155            .into_iter()
2156            .enumerate()
2157        {
2158            if w.len() < want_rows * in_f * 2 {
2159                return Err(format!(
2160                    "kda_proj_fused6_bf16: bf16 weight {i} holds {} bytes, needs {}",
2161                    w.len(),
2162                    want_rows * in_f * 2
2163                )
2164                .into());
2165            }
2166        }
2167        for (i, (w, want_rows)) in [(wfa, dims[3]), (wga, dims[4]), (wb, dims[5])]
2168            .into_iter()
2169            .enumerate()
2170        {
2171            if w.len() < want_rows * in_f {
2172                return Err(format!(
2173                    "kda_proj_fused6_bf16: f32 weight {} holds {} floats, needs {}",
2174                    i + 3,
2175                    w.len(),
2176                    want_rows * in_f
2177                )
2178                .into());
2179            }
2180        }
2181        for (i, (o, want)) in outs.iter().zip(dims).enumerate() {
2182            if o.len() < t * want {
2183                return Err(format!("kda_proj_fused6_bf16: output {i} too small").into());
2184            }
2185        }
2186        // v3 declines to v2 when its staged tiles would not fit the 48 KB default dynamic
2187        // shared-memory cap (36 KB at the default mmv_block()=128, 72 KB at 256).
2188        let arm = if arm >= 2 && !crate::gemv_v3_fits() {
2189            1
2190        } else {
2191            arm
2192        };
2193        // Rows per block, and therefore the block partition of the six ranges: 4 for the
2194        // shipped kernel, `GEMV_V2_ROWS` for the v2/v3 twins. v2 takes the R-row reduction
2195        // window as DYNAMIC shared memory (R * blockDim.x floats); v3 takes that plus its
2196        // cp.async stage buffers.
2197        let nb = crate::mmv_block();
2198        let rpb = if arm >= 1 { crate::GEMV_V2_ROWS } else { 4 };
2199        let blocks: usize = dims.iter().map(|d| d.div_ceil(rpb)).sum();
2200        let f = self.func(match arm {
2201            0 => "qmatvec_kda6_bf16f32",
2202            1 => "qmatvec_kda6_bf16f32_v2",
2203            _ => "qmatvec_kda6_bf16f32_v3",
2204        });
2205        let cfg = LaunchConfig {
2206            grid_dim: (blocks as u32, t as u32, 1),
2207            block_dim: (nb, 1, 1),
2208            shared_mem_bytes: match arm {
2209                0 => 0,
2210                1 => (crate::GEMV_V2_ROWS as u32) * nb * 4,
2211                _ => crate::gemv_v3_smem_bytes(nb as usize) as u32,
2212            },
2213        };
2214        let inf = in_f as i32;
2215        let d = dims.map(|v| v as i32);
2216        let mi = t as i32;
2217        let [o0, o1, o2, o3, o4, o5] = outs;
2218        let stream = self.gpu.stream();
2219        let mut b = stream.launch_builder(&f);
2220        b.arg(wq)
2221            .arg(wk)
2222            .arg(wv)
2223            .arg(wfa)
2224            .arg(wga)
2225            .arg(wb)
2226            .arg(x)
2227            .arg(&mut *o0)
2228            .arg(&mut *o1)
2229            .arg(&mut *o2)
2230            .arg(&mut *o3)
2231            .arg(&mut *o4)
2232            .arg(&mut *o5)
2233            .arg(&inf)
2234            .arg(&d[0])
2235            .arg(&d[1])
2236            .arg(&d[2])
2237            .arg(&d[3])
2238            .arg(&d[4])
2239            .arg(&d[5])
2240            .arg(&mi);
2241        unsafe { b.launch(cfg)? };
2242        Ok(())
2243    }
2244}
2245
2246#[cfg(test)]
2247mod kda_conv3_default_tests {
2248    use super::kda_conv3_on_from;
2249
2250    #[test]
2251    fn arch_keyed_default_with_explicit_override() {
2252        assert!(kda_conv3_on_from(None, "100a"));
2253        assert!(!kda_conv3_on_from(None, "120a"));
2254        assert!(kda_conv3_on_from(Some("1"), "120a"));
2255        assert!(!kda_conv3_on_from(Some("0"), "100a"));
2256    }
2257}