Skip to main content

memra_engine/
mla_ffi.rs

1//! FFI declarations + safe Engine wrappers for the MLA CUDA forward (`cu/mla_attn.cu`).
2//!
3//! House pattern (mmq_ffi / dsv4_ffi kind): C-ABI host launchers in the `libmemra_mmq.a`
4//! static lib, returning 0 ok / 10000+cudaError / 40000+contract; the stream rides as
5//! `*mut c_void` (`stream.cu_stream()`).
6//!
7//! The numeric truth for the dense core is `crate::mla` (the CPU f32 oracle), gated in
8//! `tests/mla_gpu_forward.rs`. The truth for the DSA k-pool indexer wrappers at the bottom of
9//! this file is `memra_reference::kpool_allowed_tokens`, gated in
10//! `tests/glm5_kpool_indexer_gpu.rs`.
11
12use crate::Engine;
13use cudarc::driver::{CudaSlice, DevicePtr, DevicePtrMut};
14use std::os::raw::c_void;
15
16/// Engagement counter for the MLA decode-split door (`MEMRA_MLA_DECODE_SPLIT`): counted at
17/// the arm's own call site, announced once per boot — the receipt a box A/B arm must show.
18pub static MLA_DECODE_SPLIT_DISPATCHES: std::sync::atomic::AtomicU64 =
19    std::sync::atomic::AtomicU64::new(0);
20
21/// `MEMRA_MLA_DECODE_SPLIT=1` (default OFF, read per call — rollback seam): the absorb /
22/// decompress launchers split each (token, head) block's output range across several blocks.
23/// PURE LAUNCH GEOMETRY: every output element keeps the same one-thread serial dot, so the
24/// bytes are identical for every split value (asserted in `tests/mla_decode_split_gpu.rs`);
25/// only occupancy changes — 64 blocks at t=1 on the glm5 geometry is single-digit-percent
26/// occupancy on the serving card class, the census's ~211 us/layer absorb+decompress pair.
27fn mla_decode_split_on() -> bool {
28    std::env::var("MEMRA_MLA_DECODE_SPLIT").as_deref() == Ok("1")
29}
30
31/// The split policy: engage only in the block-starved regime (fewer than 1024 (token, head)
32/// blocks — decode and short verify widths; prefill widths already fill the card and the TC
33/// prefill chain owns them anyway), aiming for ~1024 blocks while keeping at least 32 outputs
34/// per block. The OUTPUT BYTES ARE SPLIT-INVARIANT by construction, so this arithmetic is a
35/// throughput policy, never a numerics decision.
36fn mla_decode_split_for(blocks: usize, out_dim: usize) -> Option<i32> {
37    if !mla_decode_split_on() || blocks == 0 || blocks >= 1024 {
38        return None;
39    }
40    let want = 1024usize.div_ceil(blocks);
41    let cap = (out_dim / 32).max(1);
42    let split = want.min(cap);
43    if split <= 1 { None } else { Some(split as i32) }
44}
45
46fn mla_split_announce(kind: &str, t_q: usize, n_head: usize, split: i32) {
47    use std::sync::atomic::Ordering;
48    if MLA_DECODE_SPLIT_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
49        eprintln!(
50            "[mla-decode-split] engaged {kind} t={t_q} heads={n_head} split={split} \
51             (output-range split of the (token, head) blocks; MEMRA_MLA_DECODE_SPLIT=1)"
52        );
53    }
54}
55
56/// Engagement counter for the B200 decode arm (`MEMRA_B200_MLA_DECODE_ARM`), announced once
57/// per boot: the receipt a B200 box A/B arm must show.
58pub static MLA_B200_DECODE_ARM_DISPATCHES: std::sync::atomic::AtomicU64 =
59    std::sync::atomic::AtomicU64::new(0);
60
61/// `MEMRA_B200_MLA_DECODE_ARM=1` (default OFF, read per call: the rollback seam), compile-time
62/// gated to sm_100a builds (`cfg!(memra_sm100_tcgen05)`, set by build.rs for
63/// `MEMRA_CUDA_ARCH=100a`): on a 120a/90a/89 build this is `false` unconditionally, so naked
64/// non-B200 commands and the flag census see no behavior change from a var they cannot even
65/// engage. The arch guard is a compile-time fact here, not a per-call detection cost.
66///
67/// Owner order 2026-09-02: "hardly improve the decode on these cards, before the full 1M."
68/// This is a genuinely separate door from `MEMRA_MLA_DECODE_SPLIT` (glm5-decode-diet lever 4,
69/// rig-generic, target ~1024 blocks, PRO6000-tuned) rather than a rename of it, per the
70/// per-hardware-arm-selection law in CLAUDE.md: B200 SXM carries more SMs per device than the
71/// PRO6000 pair that door was tuned on, and this arm ALSO covers `attn_gathered`, which the
72/// generic split door never touched (no independent-output split existed for it before this
73/// lane; see `memra_mla_attn_gathered_split_kernel` in cu/mla_attn.cu).
74fn mla_b200_decode_arm_on() -> bool {
75    cfg!(memra_sm100_tcgen05) && std::env::var("MEMRA_B200_MLA_DECODE_ARM").as_deref() == Ok("1")
76}
77
78/// The three kernels the B200 arm covers. The gate bin (`mla_decode_arm_gate.rs`) walks this
79/// same enum and the same table below, so its regression check and the serving policy cannot
80/// disagree about which split a t_q gets.
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum MlaB200Kernel {
83    AbsorbQ,
84    DecompressV,
85    AttnGathered,
86}
87
88impl MlaB200Kernel {
89    pub const ALL: [MlaB200Kernel; 3] = [
90        MlaB200Kernel::AbsorbQ,
91        MlaB200Kernel::DecompressV,
92        MlaB200Kernel::AttnGathered,
93    ];
94
95    pub fn name(self) -> &'static str {
96        match self {
97            MlaB200Kernel::AbsorbQ => "absorb_q",
98            MlaB200Kernel::DecompressV => "decompress_v",
99            MlaB200Kernel::AttnGathered => "attn_gathered",
100        }
101    }
102}
103
104/// Widest query width the B200 arm keys on. Wider widths fall through untouched: the generic
105/// `MEMRA_MLA_DECODE_SPLIT` door if set, else the shipped kernels (t >= 16 reaches
106/// `MEMRA_MLA_TC_PREFILL` before either).
107pub const MLA_B200_ARM_T_MAX: usize = 8;
108
109/// The B200 arm's split tables, keyed on t_q (index = t_q in 1..=MLA_B200_ARM_T_MAX; index 0
110/// is unused and always 1). A cell of 1 means THE SHIPPED KERNEL: the wrapper falls through to
111/// the unsplit launcher and the split twin is never launched with split=1, so "shipped" is the
112/// shipped binary path, not a re-implementation of it. Any other cell is the output-range
113/// split factor handed to the bit-identical split twin.
114///
115/// Why a table and not a block-count target: the first cut of this door aimed at ~2048 blocks
116/// at every t_q <= 8 and the real box refuted that shape. Measured 2026-09-02 on the 2x B200
117/// SXM pair (sm_100a), `mla-decode-arm-gate` device 0, geometry nh=64 kv_rank=512 d_nope=256
118/// d_v=256 d_rope=0 n_slots=2048 pool_rows=32768, N=5, every arm BIT-IDENTICAL to shipped:
119///
120/// | kernel        | t_q | shipped  | arm           | verdict                   |
121/// |---------------|-----|----------|---------------|---------------------------|
122/// | absorb_q      | 1   | 81.8 us  | split=4 49.1  | win                       |
123/// | decompress_v  | 1   | 82.2 us  | split=4 48.0  | win                       |
124/// | attn_gathered | 1   | 564.6 us | split=2 516.4 | win                       |
125/// | absorb_q      | 4   | 150.3 us | split=4 133.2 | win                       |
126/// | decompress_v  | 4   | 150.4 us | split=4 246.6 | REGRESSION, shipped wins  |
127/// | attn_gathered | 4   | 665.3 us | split=2 822.7 | REGRESSION, shipped wins  |
128///
129/// t_q=4..8 is the DFlash2 spec-verify shape the box serves, so a target-driven policy that
130/// splits there costs the spec route. The tables ship exactly what that run showed and nothing
131/// it did not: the measured winner at t_q=1 for all three kernels, absorb_q's measured split=4
132/// win at t_q=4, and the shipped kernel everywhere else (t_q=2,3,5..8 are unmeasured, and
133/// unmeasured behavior does not go on). The gate times every split in {1,2,4,8} at every t_q
134/// in {1,2,4,8} for all three kernels, prints the per-t winner table, and FAILS (`REGRESSION`,
135/// exit 1) when a cell of THESE tables is slower than shipped by more than
136/// `MLA_B200_ARM_REGRESSION_MARGIN`, so a box run either confirms the tables or names the cell
137/// to change. Cite the box run in this comment when editing a cell.
138pub const MLA_B200_ABSORB_Q_SPLIT: [i32; MLA_B200_ARM_T_MAX + 1] = [1, 4, 1, 1, 4, 1, 1, 1, 1];
139pub const MLA_B200_DECOMPRESS_V_SPLIT: [i32; MLA_B200_ARM_T_MAX + 1] = [1, 4, 1, 1, 1, 1, 1, 1, 1];
140pub const MLA_B200_ATTN_GATHERED_SPLIT: [i32; MLA_B200_ARM_T_MAX + 1] = [1, 2, 1, 1, 1, 1, 1, 1, 1];
141
142/// The gate's regression bar: at every measured t_q the table's arm may not be slower than
143/// shipped by more than 5% (arm/shipped above this ratio fails `mla-decode-arm-gate`).
144pub const MLA_B200_ARM_REGRESSION_MARGIN: f64 = 1.05;
145
146/// Table lookup, independent of the door: 1 (shipped) outside 1..=MLA_B200_ARM_T_MAX. Pure,
147/// so the gate bin can read the table on any build, including the 120a builds where the door
148/// itself cannot engage.
149pub fn mla_b200_arm_table_split(kernel: MlaB200Kernel, t_q: usize) -> i32 {
150    if t_q == 0 || t_q > MLA_B200_ARM_T_MAX {
151        return 1;
152    }
153    match kernel {
154        MlaB200Kernel::AbsorbQ => MLA_B200_ABSORB_Q_SPLIT[t_q],
155        MlaB200Kernel::DecompressV => MLA_B200_DECOMPRESS_V_SPLIT[t_q],
156        MlaB200Kernel::AttnGathered => MLA_B200_ATTN_GATHERED_SPLIT[t_q],
157    }
158}
159
160/// The serving policy: door on, table cell above 1, and the cell legal for this geometry (the
161/// split twins need `split <= out_dim`; this keeps at least 32 outputs per block, the same
162/// floor as the generic door). A cell the geometry cannot honour falls through to the shipped
163/// kernel rather than clamping to a split the box never measured. The tables were measured on
164/// the glm5 geometry (kv_rank=512, d_v=256); `None` here means "shipped path", and the caller
165/// falls through in order to the generic split door, then the unsplit launcher.
166fn mla_b200_split_for(kernel: MlaB200Kernel, t_q: usize, out_dim: usize) -> Option<i32> {
167    if !mla_b200_decode_arm_on() {
168        return None;
169    }
170    let split = mla_b200_arm_table_split(kernel, t_q);
171    let cap = (out_dim / 32).max(1) as i32;
172    if split <= 1 || split > cap {
173        None
174    } else {
175        Some(split)
176    }
177}
178
179fn mla_b200_split_announce(kind: &str, t_q: usize, n_head: usize, split: i32) {
180    use std::sync::atomic::Ordering;
181    if MLA_B200_DECODE_ARM_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
182        eprintln!(
183            "[mla-b200-decode-arm] engaged {kind} t={t_q} heads={n_head} split={split} \
184             (sm_100a output-range split; MEMRA_B200_MLA_DECODE_ARM=1)"
185        );
186    }
187}
188
189/// Engagement counter for the DSA decode door (`MEMRA_B200_DSA_DECODE`), announced once per
190/// boot per arm: the receipt a B200 box A/B has to show.
191pub static MLA_DSA_DECODE_DISPATCHES: std::sync::atomic::AtomicU64 =
192    std::sync::atomic::AtomicU64::new(0);
193
194/// `MEMRA_B200_DSA_DECODE` (default OFF = 0, read per call: the rollback seam), compile-time
195/// gated to sm_100a builds exactly like its sibling `MEMRA_B200_MLA_DECODE_ARM`, so a
196/// 120a/90a/89 build sees no behavior change from a var it cannot engage.
197///
198/// THE DOOR IS A LEVEL, not a boolean, and the level is the numeric-class boundary:
199///
200/// * `0` (default) - nothing engages. Every kernel is the shipped one.
201/// * `1` - the BIT-IDENTICAL arms only: `memra_mla_attn_gathered_dsa_kernel` (same fold, same
202///   lane stride, same shuffle tree; what changes is that each tile's KV rows are staged once
203///   into shared memory with float4 loads and serve BOTH the score dot and the PV accumulate,
204///   and that the 8 tile exponentials are hoisted into registers instead of being recomputed
205///   3x per thread per tile) and `memra_mla_kpool_score_dsa_kernel` (head-blocked decode
206///   scorer; c-ascending dot, h-ascending mix, all six rounding steps spelled with explicit
207///   intrinsics). Both are asserted bytewise by `dsa-decode-gate`, not merely argued.
208/// * `2` - additionally admits the WARP-ONLINE gathered arm
209///   (`memra_mla_dsa_attn_warp_kernel` + `memra_mla_dsa_attn_combine_kernel`), numeric class
210///   **`dsa-warp-online-f32`**: one warp owns one (token, head, slot-chunk) and holds the whole
211///   kv_rank-wide accumulator in registers, so every KV element is read from memory ONCE and
212///   consumed twice from registers, there is not a single `__syncthreads`, and the two `expf`
213///   per slot replace ~196k per warp per layer. It folds PER SLOT and merges `chunks` partials,
214///   where the shipped kernel folds in 8-slot tiles: same sum in real arithmetic, different
215///   rounding, so `dsa-decode-gate` holds it to an ARGMAX gate plus a maxdiff/max-relative bound
216///   on real-shaped inputs and never to bit identity. It exists because at t_q=1 the gathered
217///   attention has exactly 64 independent (token, head) outputs for 148 SMs and the slot axis is
218///   the only one that buys parallelism without duplicating the walk. ADMISSIBLE ONLY AT
219///   `t_q <= MLA_DSA_NAMED_CLASS_T_MAX` = 1 (plain decode): the 2026-09-03 B200 run saw this
220///   class move 1 of 256 latent-row argmaxes at kv=131072 / t_q=4, and t_q=4..8 is the DFlash2
221///   spec-verify shape where a moved argmax is a moved draft acceptance. Enforced by
222///   `mla_dsa_attn_arm_effective`, not by table convention.
223///
224/// Rollback seam: unset the var (or set 0). Both arms are read per call, so a rollback is the
225/// next request, not a restart.
226fn mla_dsa_decode_level() -> u32 {
227    if !cfg!(memra_sm100_tcgen05) {
228        return 0;
229    }
230    match std::env::var("MEMRA_B200_DSA_DECODE").as_deref() {
231        Ok("1") => 1,
232        Ok("2") => 2,
233        _ => 0,
234    }
235}
236
237/// Widest query width the DSA decode door keys on. Wider widths fall through untouched: the
238/// `MEMRA_B200_MLA_DECODE_ARM` split door if set, then the shipped kernels (t >= 16 reaches
239/// `MEMRA_MLA_TC_PREFILL` before either).
240pub const MLA_DSA_ARM_T_MAX: usize = 8;
241
242/// Which gathered-attention arm the door selects, keyed on t_q (index = t_q in
243/// 1..=MLA_DSA_ARM_T_MAX; index 0 unused):
244///
245/// * `0` - the SHIPPED kernel (`memra_mla_attn_gathered_f32`). The door does nothing here.
246/// * `1` - the single-pass BIT-IDENTICAL kernel (`memra_mla_attn_gathered_dsa_f32`).
247/// * `n >= 2` - the WARP-ONLINE arm with `n` slot chunks, numeric class
248///   `dsa-warp-online-f32`. Level 2 only.
249///
250/// B200-MEASURED, 2026-09-03, and the widths are not free: read the width rule below before
251/// editing a cell. `dsa-decode-gate` on the 2x B200 SXM pair (sm_100a), device 0, N=5
252/// interleaved, engine commit f3a0091cd; banked log
253/// `darklanes:research/glm5-b200-20260902/box/gates/gate-dsa-decode.txt`. Means in us, and the
254/// gathered stage is depth-flat so the three contexts agree to a few percent:
255///
256/// | t_q | context | shipped | single-pass (1) | c=4 | c=8 | c=16 | **c=32** |
257/// |---|---|---|---|---|---|---|---|
258/// | 1 | 128k | 556.3 | 573.1 | 272.9 | 136.9 | 80.5 | **57.1** |
259/// | 1 | 256k | 553.3 | 572.4 | 273.3 | 136.7 | 79.7 | **54.8** |
260/// | 1 | 1M | 552.1 | 571.5 | 273.1 | 139.0 | 79.9 | **54.3** |
261/// | 4 | 128k | 641.3 | **618.8** | 276.9 | 156.0 | 159.6 | 131.3 |
262/// | 4 | 256k | 663.1 | **616.6** | 277.1 | 154.7 | 158.9 | 131.8 |
263/// | 4 | 1M | 667.6 | **618.5** | 277.5 | 156.5 | 160.1 | 132.3 |
264///
265/// THE WIDTH RULE, and it is a correctness rule, not a tuning one. The named class
266/// (`dsa-warp-online-f32`, arm >= 2) is admissible ONLY at `t_q <= MLA_DSA_NAMED_CLASS_T_MAX`
267/// = 1, i.e. plain decode. The same box run that produced the table above ALSO recorded the
268/// class moving an argmax: at `kv=131072, t_q=4` every swept chunk count (4, 8, 16, 32) moved
269/// **1 of 256** latent rows, maxdiff ~1.7e-6. It was argmax-clean at t_q=1 in every measured
270/// cell (0 of 64, three contexts on the box plus five on the 5090) and clean at t_q=4 at 256k
271/// and 1M, but "clean in the cells we measured" is not a proof, and t_q=4..8 is the DFlash2
272/// SPEC-VERIFY shape: a moved argmax there is a moved draft acceptance. So the spec-verify
273/// batch never sees the named class. `mla_dsa_attn_arm_effective` enforces this in code, not by
274/// table convention: a cell >= 2 at any width above the rule is demoted to 0 (the shipped
275/// kernel, the always-safe path), never silently run and never quietly promoted to the
276/// single-pass arm at a width where nobody measured it.
277///
278/// The cells therefore ship exactly what the box measured, under that rule:
279///
280/// * `t_q=1` -> **32**. The fastest arm at every context (54.3-57.1 us, a 10.2x on the shipped
281///   552.1 us at 1M) and argmax-clean at every context. 32 beats 16 by ~1.45x here where it lost
282///   to 16 on the 5090 -- 148 SMs want `64 * 32` = 2048 warps, an 82-SM laptop part does not.
283///   That disagreement is the per-hardware-arm-selection law working as intended.
284/// * `t_q=4` -> **1**, the BIT-IDENTICAL single-pass kernel. On the B200 it is a 3.5-7.4% WIN
285///   (618.5 vs 667.6 us at 1M), the opposite sign from the 5090, where it lost by 30% and this
286///   table shipped 0. Same code, different machine: on 148 SMs the shared-memory staging pays
287///   for itself where on 82 it did not. Bit-identical, so this cell carries no numeric risk at
288///   the spec-verify width at all. Banked evidence covers 128k/256k/1M; the two shallow contexts
289///   were not in the log this cell was set from, and the kernel is depth-flat.
290/// * `t_q=2,3,5..8` -> **0** (shipped). Unmeasured, and unmeasured behavior does not go on.
291///
292/// The door is default OFF and arm >= 2 additionally needs level 2, so nothing here reaches a
293/// request without two deliberate acts. `dsa-decode-gate` FAILS with a `REGRESSION` line if a
294/// cell is slower than shipped by more than `MLA_DSA_REGRESSION_MARGIN` on a later run, so the
295/// next box run either confirms these cells or names the one to change. Cite the run here when
296/// a cell moves.
297pub const MLA_DSA_ATTN_ARM: [i32; MLA_DSA_ARM_T_MAX + 1] = [0, 32, 0, 0, 1, 0, 0, 0, 0];
298
299/// Widest query width at which the NAMED numeric class (`dsa-warp-online-f32`, arm >= 2) may be
300/// selected. 1: plain decode only. Above it the door takes a bit-identical arm or the shipped
301/// kernel, so the DFlash2 spec-verify batch (t_q=4..8) never runs a rounding program that could
302/// move a draft acceptance. Set by the 2026-09-03 B200 run, which observed the class move 1 of
303/// 256 latent-row argmaxes at kv=131072 / t_q=4 for every swept chunk count. Raising this needs
304/// its own argmax evidence at the widths it opens, not an inference from t_q=1.
305pub const MLA_DSA_NAMED_CLASS_T_MAX: usize = 1;
306
307/// Chunk counts `dsa-decode-gate` sweeps for the warp-online arm. The warp arm puts
308/// `t_q * n_head * chunks` WARPS on the die, so chunks is the whole occupancy knob at t_q=1
309/// (64 pairs alone is 8 CTAs of 8 warps); 64 is the kernel's ceiling (`MLA_DSA_MAX_CHUNKS`).
310pub const MLA_DSA_ATTN_CHUNK_SWEEP: [i32; 4] = [4, 8, 16, 32];
311
312/// The decode scorer engages only from this pool count up. Below it the block count
313/// (`n_pools / (128 * 2)`) cannot fill the die and the shipped dispatch's own measured
314/// crossover already sends small-pool decode to the reference kernel, which wins there
315/// (cu/mla_attn.cu, MLA_KPOOL_SMALL_TILE_MIN_POOLS note). 4096 pools = 16 blocks = 16k context
316/// at the shipped pool size 4.
317pub const MLA_DSA_SCORE_MIN_POOLS: usize = 4096;
318
319/// The gate's regression bar, shared with the sibling arm's: an arm may not be slower than the
320/// kernel it replaces by more than 5%.
321pub const MLA_DSA_REGRESSION_MARGIN: f64 = 1.05;
322
323/// The gathered-attention arm code at this width (see [`MLA_DSA_ATTN_ARM`]). Pure, so the gate
324/// can read the policy on any build including the 120a ones where the door is dead.
325pub fn mla_dsa_attn_arm(t_q: usize) -> i32 {
326    if t_q == 0 || t_q > MLA_DSA_ARM_T_MAX {
327        return 0;
328    }
329    MLA_DSA_ATTN_ARM[t_q]
330}
331
332/// The arm the door may actually run at this width: the table cell, with the named-class width
333/// rule enforced in CODE rather than by table convention. A cell >= 2 above
334/// `MLA_DSA_NAMED_CLASS_T_MAX` is demoted to 0 (the shipped kernel), not to the single-pass arm
335/// — a width nobody measured gets the path that cannot be wrong, not the path that happens to
336/// be bit-identical. The gate reads this same function, so an edit that violates the rule shows
337/// up as the gate timing a shipped cell, never as a silently-served numeric class.
338pub fn mla_dsa_attn_arm_effective(t_q: usize) -> i32 {
339    let arm = mla_dsa_attn_arm(t_q);
340    if arm >= 2 && t_q > MLA_DSA_NAMED_CLASS_T_MAX {
341        return 0;
342    }
343    arm
344}
345
346/// Geometry refusals from the DSA launchers: the door has nothing for this shape, so the
347/// caller falls through to the shipped kernel instead of failing the request. Every other
348/// non-zero rc (a real cudaError included) still goes through `ck` and surfaces.
349/// Engagement counter for the k-pool SELECT door (`MEMRA_B200_DSA_SELECT`), announced once per
350/// boot: the receipt a B200 A/B has to show.
351pub static MLA_DSA_SELECT_DISPATCHES: std::sync::atomic::AtomicU64 =
352    std::sync::atomic::AtomicU64::new(0);
353
354/// `MEMRA_B200_DSA_SELECT=1` (default OFF, read per call: the rollback seam), compile-time gated
355/// to sm_100a builds exactly like its two siblings, so a 120a/90a/89 build sees no behaviour
356/// change from a var it cannot engage.
357///
358/// WHAT IT REPLACES. `memra_mla_kpool_select_kernel` grids `t_q` blocks, so plain decode runs it
359/// on ONE CTA -- 0.68% of a 148-SM die -- sweeping `n_pools` up to ten times (8 MSB-first radix
360/// passes, an optional unique-resolution scan, then the membership count and the emit). It is
361/// depth-LINEAR in `n_pools = t_kv / pool` and it is what the `MEMRA_B200_DSA_DECODE` lane's
362/// scorer fix stopped hiding.
363///
364/// THE CLASS IS EXACT, not banded, and that is a construction rather than a hope. The emitted
365/// plane is a pure function of ONE 64-bit number: the `select_k`-th smallest order key
366/// `(desc32(score) << 32) | pool_index`. That key is a strictly decreasing injection composed
367/// with a unique index, so keys are DISTINCT and "the k-th smallest" is unambiguous; reproducing
368/// it bit-for-bit reproduces the selection bit-for-bit. The parallel pipeline computes the same
369/// key and runs the same `key(p) <= thr` test, so this is a launch-geometry change with an exact
370/// answer. `dsa-select-gate` asserts the `idx` plane byte-identical to the shipped kernel and
371/// carries a RED ARM that must fail first.
372fn mla_dsa_select_on() -> bool {
373    cfg!(memra_sm100_tcgen05) && std::env::var("MEMRA_B200_DSA_SELECT").as_deref() == Ok("1")
374}
375
376/// The select door engages only from this pool count up, and the value is MEASURED, not
377/// inherited. The first `dsa-select-gate` run (RTX 5090, N=3 interleaved -- an exactness rig, so
378/// direction only) put the crossover between 32768 and 65536 pools, i.e. between 128k and 256k
379/// of context:
380///
381/// | n_pools | context | shipped | parallel | ratio |
382/// |---|---|---|---|---|
383/// | 8192 | 32k | 30.3 us (t=4) | 140.9 us | **0.22x** |
384/// | 32768 | 128k | 82.9 us (t=1) | 92.7 us | 0.89x |
385/// | **65536** | **256k** | **150.9 us (t=1)** | **99.5 us** | **1.52x** |
386/// | 262144 | 1M | 554.5 us (t=1) | 174.7 us | **3.17x** |
387///
388/// The pipeline is SIX launches where the shipped kernel is one, so below the crossover that
389/// fixed cost is simply larger than the sweep it removes. That is the honest reason this is a
390/// DEPTH door and not a decode door, and it is why the constant is not a round number copied
391/// from a sibling: an initial guess of 4096 (inherited from `MLA_DSA_SCORE_MIN_POOLS`) would
392/// have shipped a measured 4.6x REGRESSION at 32k, and the gate's regression bar is what caught
393/// it. 65536 is the first swept cell that wins at BOTH measured widths (1.52x at t_q=1, 1.07x
394/// at t_q=4). A B200 run confirms it or names the cell to change.
395pub const MLA_DSA_SELECT_MIN_POOLS: usize = 65_536;
396
397/// Widest query width the select door keys on: decode and the spec-verify batch. Wider widths
398/// already have `t_q` CTAs of parallelism and fall through to the shipped kernel untouched.
399pub const MLA_DSA_SELECT_T_MAX: usize = 8;
400
401/// Whether the serving policy engages the parallel selector at this shape. Pure, so the gate
402/// reads the same predicate the wrapper does and the two cannot drift apart.
403pub fn mla_dsa_select_engages(t_q: usize, n_pools: usize) -> bool {
404    (1..=MLA_DSA_SELECT_T_MAX).contains(&t_q) && n_pools >= MLA_DSA_SELECT_MIN_POOLS
405}
406
407fn mla_dsa_select_announce(t_q: usize, n_pools: usize, n_ctas: i32) {
408    use std::sync::atomic::Ordering;
409    if MLA_DSA_SELECT_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
410        eprintln!(
411            "[mla-b200-dsa-select] engaged kpool_select t={t_q} pools={n_pools} ctas={n_ctas} \
412             class=exact (sm_100a; MEMRA_B200_DSA_SELECT=1)"
413        );
414    }
415}
416
417fn mla_dsa_geometry_refusal(rc: i32) -> bool {
418    matches!(rc, 40020 | 40021 | 40023)
419}
420
421fn mla_dsa_announce(kind: &str, t_q: usize, detail: &str) {
422    use std::sync::atomic::Ordering;
423    if MLA_DSA_DECODE_DISPATCHES.fetch_add(1, Ordering::Relaxed) == 0 {
424        eprintln!(
425            "[mla-b200-dsa-decode] engaged {kind} t={t_q} {detail} \
426             (sm_100a; MEMRA_B200_DSA_DECODE)"
427        );
428    }
429}
430
431unsafe extern "C" {
432    pub fn memra_mla_rope_interleaved_f32(
433        x: *mut f32,
434        n_pos: i32,
435        n_vec: i32,
436        d_rope: i32,
437        positions: *const i32,
438        base: f32,
439        stream: *mut c_void,
440    ) -> i32;
441    pub fn memra_mla_split_latent_f32(
442        kv: *const f32,
443        c_kv: *mut f32,
444        k_pe: *mut f32,
445        t: i32,
446        kv_rank: i32,
447        d_rope: i32,
448        stream: *mut c_void,
449    ) -> i32;
450    pub fn memra_mla_append_latent_f32(
451        cache: *mut f32,
452        c_kv: *const f32,
453        k_pe: *const f32,
454        slot: i32,
455        t: i32,
456        kv_rank: i32,
457        d_rope: i32,
458        stream: *mut c_void,
459    ) -> i32;
460    pub fn memra_mla_absorb_q_f32(
461        q_nope: *const f32,
462        wk_b: *const f32,
463        q_lat: *mut f32,
464        t_q: i32,
465        n_head: i32,
466        d_nope: i32,
467        kv_rank: i32,
468        stream: *mut c_void,
469    ) -> i32;
470    pub fn memra_mla_decompress_v_f32(
471        o_lat: *const f32,
472        wv_b: *const f32,
473        out: *mut f32,
474        t_q: i32,
475        n_head: i32,
476        d_v: i32,
477        kv_rank: i32,
478        stream: *mut c_void,
479    ) -> i32;
480    /// Decode-split twin of `memra_mla_absorb_q_f32` (MEMRA_MLA_DECODE_SPLIT): the same
481    /// per-output serial dot, its output range split across `split` blocks — bit-identical
482    /// by construction, gated in `tests/mla_decode_split_gpu.rs`.
483    #[allow(clippy::too_many_arguments)]
484    pub fn memra_mla_absorb_q_split_f32(
485        q_nope: *const f32,
486        wk_b: *const f32,
487        q_lat: *mut f32,
488        t_q: i32,
489        n_head: i32,
490        d_nope: i32,
491        kv_rank: i32,
492        split: i32,
493        stream: *mut c_void,
494    ) -> i32;
495    /// Decode-split twin of `memra_mla_decompress_v_f32` (see above).
496    #[allow(clippy::too_many_arguments)]
497    pub fn memra_mla_decompress_v_split_f32(
498        o_lat: *const f32,
499        wv_b: *const f32,
500        out: *mut f32,
501        t_q: i32,
502        n_head: i32,
503        d_v: i32,
504        kv_rank: i32,
505        split: i32,
506        stream: *mut c_void,
507    ) -> i32;
508    pub fn memra_mla_attn_absorbed_f32(
509        q_lat: *const f32,
510        q_pe: *const f32,
511        cache: *const f32,
512        o_lat: *mut f32,
513        n_head: i32,
514        kv_rank: i32,
515        d_rope: i32,
516        t_q: i32,
517        t_kv: i32,
518        scale: f32,
519        stream: *mut c_void,
520    ) -> i32;
521    pub fn memra_mla_index_append_ring_f32(
522        plane: *mut f32,
523        a: *const f32,
524        b: *const f32,
525        slot: i32,
526        t: i32,
527        wa: i32,
528        wb: i32,
529        rows: i32,
530        stream: *mut c_void,
531    ) -> i32;
532    pub fn memra_mla_kpool_pool_keys_f32(
533        state: *const f32,
534        ape: *const f32,
535        pool_keys: *mut f32,
536        pool_begin: i32,
537        n_pools: i32,
538        pool: i32,
539        d: i32,
540        state_rows: i32,
541        stream: *mut c_void,
542    ) -> i32;
543    pub fn memra_mla_kpool_score_f32(
544        q: *const f32,
545        pool_keys: *const f32,
546        hw: *const f32,
547        score: *mut f32,
548        t_q: i32,
549        heads: i32,
550        d: i32,
551        n_pools: i32,
552        pool: i32,
553        first_pos: i32,
554        qk_scale: f32,
555        head_scale: f32,
556        stream: *mut c_void,
557    ) -> i32;
558    pub fn memra_mla_kpool_score_ref_f32(
559        q: *const f32,
560        pool_keys: *const f32,
561        hw: *const f32,
562        score: *mut f32,
563        t_q: i32,
564        heads: i32,
565        d: i32,
566        n_pools: i32,
567        pool: i32,
568        first_pos: i32,
569        qk_scale: f32,
570        head_scale: f32,
571        stream: *mut c_void,
572    ) -> i32;
573    /// Ints of scratch one query needs for the parallel selector, given its CTA count.
574    pub fn memra_mla_kpool_select_ws_ints(n_ctas: i32) -> i64;
575    /// CTA count the parallel selector launches per query. The host sizes the workspace from
576    /// this same entry point, so a mismatch is impossible by construction.
577    pub fn memra_mla_kpool_select_ctas(n_pools: i32) -> i32;
578    /// Exact multi-CTA k-pool selection (`MEMRA_B200_DSA_SELECT`): same threshold key, same
579    /// membership test, same emit order, byte-identical `idx`.
580    #[allow(clippy::too_many_arguments)]
581    pub fn memra_mla_kpool_select_dsa_f32(
582        score: *const f32,
583        idx: *mut i32,
584        ws: *mut i32,
585        t_q: i32,
586        n_pools: i32,
587        pool: i32,
588        select_k: i32,
589        width: i32,
590        first_pos: i32,
591        always_tail: i32,
592        stream: *mut c_void,
593    ) -> i32;
594    /// RED ARM for `dsa-select-gate`, never a serving path: the exact pipeline with the resolved
595    /// threshold deliberately bumped, so the gate can prove its byte comparison actually fails
596    /// on a wrong selection before it is allowed to pass the real kernel.
597    #[allow(clippy::too_many_arguments)]
598    pub fn memra_mla_kpool_select_dsa_redarm_f32(
599        score: *const f32,
600        idx: *mut i32,
601        ws: *mut i32,
602        t_q: i32,
603        n_pools: i32,
604        pool: i32,
605        select_k: i32,
606        width: i32,
607        first_pos: i32,
608        always_tail: i32,
609        bump: i32,
610        stream: *mut c_void,
611    ) -> i32;
612    pub fn memra_mla_kpool_select_f32(
613        score: *const f32,
614        idx: *mut i32,
615        t_q: i32,
616        n_pools: i32,
617        pool: i32,
618        select_k: i32,
619        width: i32,
620        first_pos: i32,
621        always_tail: i32,
622        stream: *mut c_void,
623    ) -> i32;
624    pub fn memra_mla_kpool_select_ref_f32(
625        score: *const f32,
626        idx: *mut i32,
627        t_q: i32,
628        n_pools: i32,
629        pool: i32,
630        select_k: i32,
631        width: i32,
632        first_pos: i32,
633        always_tail: i32,
634        stream: *mut c_void,
635    ) -> i32;
636    pub fn memra_mla_attn_gathered_f32(
637        q_lat: *const f32,
638        q_pe: *const f32,
639        cache: *const f32,
640        idx: *const i32,
641        o_lat: *mut f32,
642        n_head: i32,
643        kv_rank: i32,
644        d_rope: i32,
645        t_q: i32,
646        n_slots: i32,
647        scale: f32,
648        stream: *mut c_void,
649    ) -> i32;
650    /// B200 decode-arm twin of `memra_mla_attn_gathered_f32` (MEMRA_B200_MLA_DECODE_ARM): same
651    /// per-l accumulate chain, its output range [0, kv_rank) split across `split` blocks; the
652    /// shared score/softmax tile walk (m, dsum) is recomputed IN FULL, unchanged, by every
653    /// split block — bit-identical by construction, gated in `mla_decode_arm_gate.rs`.
654    #[allow(clippy::too_many_arguments)]
655    /// Single-pass bit-identical rewrite of `memra_mla_attn_gathered_f32`
656    /// (`MEMRA_B200_DSA_DECODE>=1`): each tile's KV rows staged once into shared memory with
657    /// float4 loads and read back for BOTH the score dot and the PV accumulate, the 8 tile
658    /// exponentials hoisted into registers. Same grid, same fold, same bits. Returns 40020
659    /// (width not a multiple of 4) or 40021 (staging over the smem cap) for a geometry it
660    /// refuses, and the caller falls through to the shipped kernel.
661    pub fn memra_mla_attn_gathered_dsa_f32(
662        q_lat: *const f32,
663        q_pe: *const f32,
664        cache: *const f32,
665        idx: *const i32,
666        o_lat: *mut f32,
667        n_head: i32,
668        kv_rank: i32,
669        d_rope: i32,
670        t_q: i32,
671        n_slots: i32,
672        scale: f32,
673        stream: *mut c_void,
674    ) -> i32;
675    /// Slot-per-chunk span the partial kernel walks. The host MUST size the workspace and
676    /// launch from this, never from its own division, so the two cannot disagree.
677    pub fn memra_mla_dsa_attn_chunk_span(n_slots: i32, chunks: i32) -> i32;
678    /// Warp-online slot-split gathered attention, numeric class `dsa-warp-online-f32`
679    /// (`MEMRA_B200_DSA_DECODE=2`). `part_m` / `part_d` hold `t_q * n_head * chunks` floats
680    /// each; `part_acc` holds `t_q * n_head * chunks * kv_rank`. Returns 40023 for a
681    /// (kv_rank, d_rope) with no template instantiation, and the caller takes the shipped path.
682    pub fn memra_mla_dsa_attn_split_f32(
683        q_lat: *const f32,
684        q_pe: *const f32,
685        cache: *const f32,
686        idx: *const i32,
687        o_lat: *mut f32,
688        part_m: *mut f32,
689        part_d: *mut f32,
690        part_acc: *mut f32,
691        n_head: i32,
692        kv_rank: i32,
693        d_rope: i32,
694        t_q: i32,
695        n_slots: i32,
696        chunks: i32,
697        scale: f32,
698        stream: *mut c_void,
699    ) -> i32;
700    /// Head-blocked decode pool scorer (`MEMRA_B200_DSA_DECODE>=1`), bit-identical to
701    /// `memra_mla_kpool_score_ref_f32`. Returns 40023 when this (heads, d) has no
702    /// instantiation, and the caller falls through to the shipped dispatch.
703    pub fn memra_mla_kpool_score_dsa_f32(
704        q: *const f32,
705        pool_keys: *const f32,
706        hw: *const f32,
707        score: *mut f32,
708        t_q: i32,
709        heads: i32,
710        d: i32,
711        n_pools: i32,
712        pool: i32,
713        first_pos: i32,
714        qk_scale: f32,
715        head_scale: f32,
716        stream: *mut c_void,
717    ) -> i32;
718    pub fn memra_mla_attn_gathered_split_f32(
719        q_lat: *const f32,
720        q_pe: *const f32,
721        cache: *const f32,
722        idx: *const i32,
723        o_lat: *mut f32,
724        n_head: i32,
725        kv_rank: i32,
726        d_rope: i32,
727        t_q: i32,
728        n_slots: i32,
729        scale: f32,
730        split: i32,
731        stream: *mut c_void,
732    ) -> i32;
733    /// Strided-batched BF16 tensor-core GEMM (cu/f16_prefill.cu): per batch b,
734    /// `y_b[m, n] = x_b[m, k] @ w_b[n, k]^T`, f32 accumulate, y f32 or bf16 by flag.
735    /// The MEMRA_MLA_TC_PREFILL absorb/decompress engine (one launch replaces the
736    /// per-position absorb_q / decompress_v kernels at prefill widths).
737    fn memra_bf16_gemm_sb(
738        w_bf16: *const c_void,
739        x_bf16: *const c_void,
740        y: *mut c_void,
741        m: i32,
742        n: i32,
743        k: i32,
744        x_rs: i64,
745        x_bs: i64,
746        y_rs: i64,
747        y_bs: i64,
748        batch: i32,
749        y_is_bf16: i32,
750        ws: *mut c_void,
751        ws_bytes: usize,
752        stream: *mut c_void,
753    ) -> i32;
754}
755
756type Res<T> = Result<T, Box<dyn std::error::Error>>;
757
758/// Turn a launcher's status band into a named error. Every MLA launch goes through this —
759/// a silently-ignored non-zero status is how a contract violation becomes garbage activations.
760fn ck(what: &str, rc: i32) -> Res<()> {
761    if rc == 0 {
762        return Ok(());
763    }
764    let detail = match rc {
765        40001 => " (d_rope must be even — interleaved rope rotates (2j, 2j+1) pairs)",
766        40002 => " (kv_rank exceeds the kernel's MLA_MAX_RANK shared-memory ceiling)",
767        40003 => " (d_rope exceeds the kernel's MLA_MAX_ROPE ceiling)",
768        40004 => " (t_q > t_kv — queries must be a suffix of the latent cache)",
769        40010 => " (k-pool size out of range — 1..=MLA_MAX_POOL)",
770        40011 => " (indexer head count out of range — 1..=1024, one thread per head)",
771        40012 => " (t_q * n_pools exceeds the grid.x contract)",
772        40017 => " (indexer head dim must be positive)",
773        40013 => {
774            " (always_select_tail=false: queries before the first complete pool would have an \
775             empty candidate set, which the memra-reference oracle refuses outright)"
776        }
777        40014 => " (index-list width is narrower than select_k * pool + pool - 1)",
778        40015 => " (empty gathered candidate list — a zero softmax denominator)",
779        40020 => " (latent row width is not a multiple of 4 — the DSA float4 staging needs it)",
780        40021 => " (DSA tile staging exceeds MLA_DSA_KV_SMEM_MAX)",
781        40022 => " (DSA slot-chunk count out of range — 1..=64)",
782        40023 => " (no DSA scorer instantiation for this (heads, d))",
783        r if (10000..20000).contains(&r) => " (cudaError)",
784        _ => "",
785    };
786    Err(format!("mla kernel `{what}` failed: rc {rc}{detail}").into())
787}
788
789impl Engine {
790    /// Interleaved ("NORM") RoPE in place over `x` laid out [n_pos][n_vec][d_rope].
791    /// `d_rope == 0` (NoPE, glm5_next) is a no-op — the caller must still not pass an empty
792    /// slice through a path that dereferences it, which is why the rope plane is skipped
793    /// entirely in the forward arm rather than launched with a zero extent.
794    pub fn mla_rope_interleaved(
795        &self,
796        x: &mut CudaSlice<f32>,
797        pos_d: &CudaSlice<i32>,
798        n_pos: usize,
799        n_vec: usize,
800        d_rope: usize,
801        base: f32,
802    ) -> Res<()> {
803        if d_rope == 0 {
804            return Ok(());
805        }
806        let s = self.stream();
807        unsafe {
808            ck(
809                "rope_interleaved",
810                memra_mla_rope_interleaved_f32(
811                    x.device_ptr_mut(&s).0 as *mut f32,
812                    n_pos as i32,
813                    n_vec as i32,
814                    d_rope as i32,
815                    pos_d.device_ptr(&s).0 as *const i32,
816                    base,
817                    s.cu_stream() as *mut c_void,
818                ),
819            )
820        }
821    }
822
823    /// Split the `wkv_a` output rows [t][kv_rank + d_rope] into `c_kv` and `k_pe` planes.
824    pub fn mla_split_latent(
825        &self,
826        kv: &CudaSlice<f32>,
827        c_kv: &mut CudaSlice<f32>,
828        k_pe: &mut CudaSlice<f32>,
829        t: usize,
830        kv_rank: usize,
831        d_rope: usize,
832    ) -> Res<()> {
833        let s = self.stream();
834        unsafe {
835            ck(
836                "split_latent",
837                memra_mla_split_latent_f32(
838                    kv.device_ptr(&s).0 as *const f32,
839                    c_kv.device_ptr_mut(&s).0 as *mut f32,
840                    k_pe.device_ptr_mut(&s).0 as *mut f32,
841                    t as i32,
842                    kv_rank as i32,
843                    d_rope as i32,
844                    s.cu_stream() as *mut c_void,
845                ),
846            )
847        }
848    }
849
850    /// Append `t` latent rows `[c_kv | k_pe]` to the cache plane starting at row `slot`.
851    #[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
852    pub fn mla_append_latent(
853        &self,
854        cache: &mut CudaSlice<f32>,
855        c_kv: &CudaSlice<f32>,
856        k_pe: &CudaSlice<f32>,
857        slot: usize,
858        t: usize,
859        kv_rank: usize,
860        d_rope: usize,
861    ) -> Res<()> {
862        let s = self.stream();
863        unsafe {
864            ck(
865                "append_latent",
866                memra_mla_append_latent_f32(
867                    cache.device_ptr_mut(&s).0 as *mut f32,
868                    c_kv.device_ptr(&s).0 as *const f32,
869                    k_pe.device_ptr(&s).0 as *const f32,
870                    slot as i32,
871                    t as i32,
872                    kv_rank as i32,
873                    d_rope as i32,
874                    s.cu_stream() as *mut c_void,
875                ),
876            )
877        }
878    }
879
880    /// Absorb: `q_lat[i][h][:] = w_uk[h]ᵀ · q_nope[i][h][:]` (rank space).
881    #[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
882    pub fn mla_absorb_q(
883        &self,
884        q_nope: &CudaSlice<f32>,
885        wk_b: &CudaSlice<f32>,
886        q_lat: &mut CudaSlice<f32>,
887        t_q: usize,
888        n_head: usize,
889        d_nope: usize,
890        kv_rank: usize,
891    ) -> Res<()> {
892        let s = self.stream();
893        // MEMRA_B200_MLA_DECODE_ARM door (checked first; split from the t_q-keyed table
894        // MLA_B200_ABSORB_Q_SPLIT, a 1 cell falls through to the doors below; the split twin is
895        // the same kernel the generic door launches, so this is only a policy pick).
896        if let Some(split) = mla_b200_split_for(MlaB200Kernel::AbsorbQ, t_q, kv_rank) {
897            mla_b200_split_announce("absorb_q", t_q, n_head, split);
898            return unsafe {
899                ck(
900                    "absorb_q_split_b200",
901                    memra_mla_absorb_q_split_f32(
902                        q_nope.device_ptr(&s).0 as *const f32,
903                        wk_b.device_ptr(&s).0 as *const f32,
904                        q_lat.device_ptr_mut(&s).0 as *mut f32,
905                        t_q as i32,
906                        n_head as i32,
907                        d_nope as i32,
908                        kv_rank as i32,
909                        split,
910                        s.cu_stream() as *mut c_void,
911                    ),
912                )
913            };
914        }
915        // MEMRA_MLA_DECODE_SPLIT door: same bytes at any split (see mla_decode_split_for).
916        if let Some(split) = mla_decode_split_for(t_q * n_head, kv_rank) {
917            mla_split_announce("absorb_q", t_q, n_head, split);
918            return unsafe {
919                ck(
920                    "absorb_q_split",
921                    memra_mla_absorb_q_split_f32(
922                        q_nope.device_ptr(&s).0 as *const f32,
923                        wk_b.device_ptr(&s).0 as *const f32,
924                        q_lat.device_ptr_mut(&s).0 as *mut f32,
925                        t_q as i32,
926                        n_head as i32,
927                        d_nope as i32,
928                        kv_rank as i32,
929                        split,
930                        s.cu_stream() as *mut c_void,
931                    ),
932                )
933            };
934        }
935        unsafe {
936            ck(
937                "absorb_q",
938                memra_mla_absorb_q_f32(
939                    q_nope.device_ptr(&s).0 as *const f32,
940                    wk_b.device_ptr(&s).0 as *const f32,
941                    q_lat.device_ptr_mut(&s).0 as *mut f32,
942                    t_q as i32,
943                    n_head as i32,
944                    d_nope as i32,
945                    kv_rank as i32,
946                    s.cu_stream() as *mut c_void,
947                ),
948            )
949        }
950    }
951
952    /// Decompress: `out[i][h][:] = w_uv[h] · o_lat[i][h][:]`.
953    #[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
954    pub fn mla_decompress_v(
955        &self,
956        o_lat: &CudaSlice<f32>,
957        wv_b: &CudaSlice<f32>,
958        out: &mut CudaSlice<f32>,
959        t_q: usize,
960        n_head: usize,
961        d_v: usize,
962        kv_rank: usize,
963    ) -> Res<()> {
964        let s = self.stream();
965        // MEMRA_B200_MLA_DECODE_ARM door (checked first, table MLA_B200_DECOMPRESS_V_SPLIT; see
966        // mla_absorb_q above).
967        if let Some(split) = mla_b200_split_for(MlaB200Kernel::DecompressV, t_q, d_v) {
968            mla_b200_split_announce("decompress_v", t_q, n_head, split);
969            return unsafe {
970                ck(
971                    "decompress_v_split_b200",
972                    memra_mla_decompress_v_split_f32(
973                        o_lat.device_ptr(&s).0 as *const f32,
974                        wv_b.device_ptr(&s).0 as *const f32,
975                        out.device_ptr_mut(&s).0 as *mut f32,
976                        t_q as i32,
977                        n_head as i32,
978                        d_v as i32,
979                        kv_rank as i32,
980                        split,
981                        s.cu_stream() as *mut c_void,
982                    ),
983                )
984            };
985        }
986        // MEMRA_MLA_DECODE_SPLIT door: same bytes at any split (see mla_decode_split_for).
987        if let Some(split) = mla_decode_split_for(t_q * n_head, d_v) {
988            mla_split_announce("decompress_v", t_q, n_head, split);
989            return unsafe {
990                ck(
991                    "decompress_v_split",
992                    memra_mla_decompress_v_split_f32(
993                        o_lat.device_ptr(&s).0 as *const f32,
994                        wv_b.device_ptr(&s).0 as *const f32,
995                        out.device_ptr_mut(&s).0 as *mut f32,
996                        t_q as i32,
997                        n_head as i32,
998                        d_v as i32,
999                        kv_rank as i32,
1000                        split,
1001                        s.cu_stream() as *mut c_void,
1002                    ),
1003                )
1004            };
1005        }
1006        unsafe {
1007            ck(
1008                "decompress_v",
1009                memra_mla_decompress_v_f32(
1010                    o_lat.device_ptr(&s).0 as *const f32,
1011                    wv_b.device_ptr(&s).0 as *const f32,
1012                    out.device_ptr_mut(&s).0 as *mut f32,
1013                    t_q as i32,
1014                    n_head as i32,
1015                    d_v as i32,
1016                    kv_rank as i32,
1017                    s.cu_stream() as *mut c_void,
1018                ),
1019            )
1020        }
1021    }
1022
1023    /// Absorbed-form MQA attention over the latent cache. `q_pe` is ignored when
1024    /// `d_rope == 0`; callers on the NoPE path may pass any allocated slice.
1025    #[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
1026    pub fn mla_attn_absorbed(
1027        &self,
1028        q_lat: &CudaSlice<f32>,
1029        q_pe: &CudaSlice<f32>,
1030        cache: &CudaSlice<f32>,
1031        o_lat: &mut CudaSlice<f32>,
1032        n_head: usize,
1033        kv_rank: usize,
1034        d_rope: usize,
1035        t_q: usize,
1036        t_kv: usize,
1037        scale: f32,
1038    ) -> Res<()> {
1039        let s = self.stream();
1040        unsafe {
1041            ck(
1042                "attn_absorbed",
1043                memra_mla_attn_absorbed_f32(
1044                    q_lat.device_ptr(&s).0 as *const f32,
1045                    q_pe.device_ptr(&s).0 as *const f32,
1046                    cache.device_ptr(&s).0 as *const f32,
1047                    o_lat.device_ptr_mut(&s).0 as *mut f32,
1048                    n_head as i32,
1049                    kv_rank as i32,
1050                    d_rope as i32,
1051                    t_q as i32,
1052                    t_kv as i32,
1053                    scale,
1054                    s.cu_stream() as *mut c_void,
1055                ),
1056            )
1057        }
1058    }
1059}
1060
1061/// Safe wrappers for the DSA k-pool indexer (`cu/mla_attn.cu`, "DSA k-pool indexer" section).
1062/// Numeric truth is `memra_reference::kpool_allowed_tokens`; the gate is
1063/// `tests/glm5_kpool_indexer_gpu.rs`.
1064impl Engine {
1065    /// Collapse pools `[pool_begin, n_pools)` of `pool` cached indexer rows each into one key by a
1066    /// learned per-channel softmax over (gate score + positional embedding).
1067    /// `state` rows are `[k | gate]`, `2 * d` wide; `ape` is `[pool][d]` row-major.
1068    ///
1069    /// `pool_begin` is the RESIDENCY seam: a pool's key depends only on its own `pool` state rows
1070    /// (append-only, never rewritten) and the constant `ape`, so it is final the instant the
1071    /// pool's last row lands. Pools below `pool_begin` are already resident and are left alone —
1072    /// bit-identically to what rebuilding them would produce. Pass 0 for a full rebuild.
1073    ///
1074    /// `state_rows` is the indexer plane's TAIL-RING size in rows (0 = flat, absolute
1075    /// addressing). It is always a multiple of `pool`, so a pool's members stay contiguous
1076    /// across the wrap and the collapse reads the same values in the same order either way.
1077    #[allow(clippy::too_many_arguments)]
1078    pub fn mla_kpool_pool_keys(
1079        &self,
1080        state: &CudaSlice<f32>,
1081        ape: &CudaSlice<f32>,
1082        pool_keys: &mut CudaSlice<f32>,
1083        pool_begin: usize,
1084        n_pools: usize,
1085        pool: usize,
1086        d: usize,
1087        state_rows: usize,
1088    ) -> Res<()> {
1089        let s = self.stream();
1090        unsafe {
1091            ck(
1092                "kpool_pool_keys",
1093                memra_mla_kpool_pool_keys_f32(
1094                    state.device_ptr(&s).0 as *const f32,
1095                    ape.device_ptr(&s).0 as *const f32,
1096                    pool_keys.device_ptr_mut(&s).0 as *mut f32,
1097                    pool_begin as i32,
1098                    n_pools as i32,
1099                    pool as i32,
1100                    d as i32,
1101                    state_rows as i32,
1102                    s.cu_stream() as *mut c_void,
1103                ),
1104            )
1105        }
1106    }
1107
1108    /// Append `t` packed indexer rows `[k_norm | gate]` at absolute row `slot`, wrapping mod
1109    /// `rows` when the plane is a TAIL RING (`rows == 0` is the flat plane).
1110    ///
1111    /// SEPARATE from [`Engine::mla_append_latent`] on purpose: the latent plane is re-read by
1112    /// every later query through the gathered attention walk and is NOT a ring, so the two planes
1113    /// must not share a row-addressing contract even though they share a row shape.
1114    #[allow(clippy::too_many_arguments)]
1115    ///
1116    /// `src_row` is the first SOURCE row of `a`/`b` to append: the call's `k_norm`/`gate` are
1117    /// computed once for the whole call, and the tail-ring drain (`mla_kpool_indices`) walks them
1118    /// in sub-ranges. `src_row` 0 is the whole-call append.
1119    pub fn mla_index_append(
1120        &self,
1121        plane: &mut CudaSlice<f32>,
1122        a: &CudaSlice<f32>,
1123        b: &CudaSlice<f32>,
1124        src_row: usize,
1125        slot: usize,
1126        t: usize,
1127        wa: usize,
1128        wb: usize,
1129        rows: usize,
1130    ) -> Res<()> {
1131        let s = self.stream();
1132        unsafe {
1133            ck(
1134                "index_append_ring",
1135                memra_mla_index_append_ring_f32(
1136                    plane.device_ptr_mut(&s).0 as *mut f32,
1137                    (a.device_ptr(&s).0 as *const f32).add(src_row * wa),
1138                    (b.device_ptr(&s).0 as *const f32).add(src_row * wb),
1139                    slot as i32,
1140                    t as i32,
1141                    wa as i32,
1142                    wb as i32,
1143                    rows as i32,
1144                    s.cu_stream() as *mut c_void,
1145                ),
1146            )
1147        }
1148    }
1149
1150    /// Head-mixed pool scores, `-inf` on pools whose last token is not visible to the query.
1151    /// `first_pos` is the absolute cache row of query 0 (queries are the cache's last `t_q` rows).
1152    ///
1153    /// Register-tiled fused GEMM+head-reduce: the pool-key tile stays resident in shared memory
1154    /// across the head loop, so `pool_keys` is read once per query TILE instead of once per
1155    /// query, and the head mix lands in the accumulator instead of costing a second pass over a
1156    /// `[t_q * heads, n_pools]` plane (17 GB at the shipped 1M/512 shape). BIT-IDENTICAL to
1157    /// [`Engine::mla_kpool_score_ref`] by construction — same six-step rounding sequence, spelled
1158    /// with explicit intrinsics — and gated so
1159    /// (`gpu_kpool_scoring_is_byte_identical_to_the_reference_kernel`). See the scoring section
1160    /// of `cu/mla_attn.cu` for why that identity is the requirement and not a nicety.
1161    #[allow(clippy::too_many_arguments)]
1162    pub fn mla_kpool_score(
1163        &self,
1164        q: &CudaSlice<f32>,
1165        pool_keys: &CudaSlice<f32>,
1166        head_weights: &CudaSlice<f32>,
1167        score: &mut CudaSlice<f32>,
1168        t_q: usize,
1169        heads: usize,
1170        d: usize,
1171        n_pools: usize,
1172        pool: usize,
1173        first_pos: usize,
1174        qk_scale: f32,
1175        head_scale: f32,
1176    ) -> Res<()> {
1177        let s = self.stream();
1178        // MEMRA_B200_DSA_DECODE door (level >= 1): the head-blocked decode scorer. Engages only
1179        // at decode widths and only from MLA_DSA_SCORE_MIN_POOLS up, where the block count can
1180        // fill the die; below that the shipped dispatch's own measured crossover already sends
1181        // decode to the reference kernel, which wins there. Bit-identical, so this is a speed
1182        // choice and nothing else. See research/b200-dsa-decode-20260902/ROOFLINE.md §2.
1183        if mla_dsa_decode_level() >= 1
1184            && (1..=MLA_DSA_ARM_T_MAX).contains(&t_q)
1185            && n_pools >= MLA_DSA_SCORE_MIN_POOLS
1186        {
1187            let rc = unsafe {
1188                memra_mla_kpool_score_dsa_f32(
1189                    q.device_ptr(&s).0 as *const f32,
1190                    pool_keys.device_ptr(&s).0 as *const f32,
1191                    head_weights.device_ptr(&s).0 as *const f32,
1192                    score.device_ptr_mut(&s).0 as *mut f32,
1193                    t_q as i32,
1194                    heads as i32,
1195                    d as i32,
1196                    n_pools as i32,
1197                    pool as i32,
1198                    first_pos as i32,
1199                    qk_scale,
1200                    head_scale,
1201                    s.cu_stream() as *mut c_void,
1202                )
1203            };
1204            if !mla_dsa_geometry_refusal(rc) {
1205                mla_dsa_announce(
1206                    "kpool_score",
1207                    t_q,
1208                    &format!("arm=head-blocked heads={heads} pools={n_pools} class=bit-identical"),
1209                );
1210                return ck("kpool_score_dsa", rc);
1211            }
1212        }
1213        unsafe {
1214            ck(
1215                "kpool_score",
1216                memra_mla_kpool_score_f32(
1217                    q.device_ptr(&s).0 as *const f32,
1218                    pool_keys.device_ptr(&s).0 as *const f32,
1219                    head_weights.device_ptr(&s).0 as *const f32,
1220                    score.device_ptr_mut(&s).0 as *mut f32,
1221                    t_q as i32,
1222                    heads as i32,
1223                    d as i32,
1224                    n_pools as i32,
1225                    pool as i32,
1226                    first_pos as i32,
1227                    qk_scale,
1228                    head_scale,
1229                    s.cu_stream() as *mut c_void,
1230                ),
1231            )
1232        }
1233    }
1234
1235    /// The RETAINED reference scorer: block per (query, pool), one thread per head, head sum
1236    /// walked sequentially by thread 0. It defines the arithmetic [`Engine::mla_kpool_score`]
1237    /// reproduces, and it is the only consumer-visible reason this crate still builds the slow
1238    /// kernel. Not a serving path — `O(t_q * n_pools)` blocks of `heads` threads.
1239    #[allow(clippy::too_many_arguments)]
1240    pub fn mla_kpool_score_ref(
1241        &self,
1242        q: &CudaSlice<f32>,
1243        pool_keys: &CudaSlice<f32>,
1244        head_weights: &CudaSlice<f32>,
1245        score: &mut CudaSlice<f32>,
1246        t_q: usize,
1247        heads: usize,
1248        d: usize,
1249        n_pools: usize,
1250        pool: usize,
1251        first_pos: usize,
1252        qk_scale: f32,
1253        head_scale: f32,
1254    ) -> Res<()> {
1255        let s = self.stream();
1256        unsafe {
1257            ck(
1258                "kpool_score_ref",
1259                memra_mla_kpool_score_ref_f32(
1260                    q.device_ptr(&s).0 as *const f32,
1261                    pool_keys.device_ptr(&s).0 as *const f32,
1262                    head_weights.device_ptr(&s).0 as *const f32,
1263                    score.device_ptr_mut(&s).0 as *mut f32,
1264                    t_q as i32,
1265                    heads as i32,
1266                    d as i32,
1267                    n_pools as i32,
1268                    pool as i32,
1269                    first_pos as i32,
1270                    qk_scale,
1271                    head_scale,
1272                    s.cu_stream() as *mut c_void,
1273                ),
1274            )
1275        }
1276    }
1277
1278    /// Top-`select_k` pools per query expanded to ascending cache rows, tail appended, -1 padded.
1279    ///
1280    /// Radix select on the 64-bit order key `(desc32(score) << 32) | pool_index`, whose ascending
1281    /// order IS the oracle's "score descending, pool index ascending" — see the ORDER contract
1282    /// block in `cu/mla_attn.cu`. `O(8 * n_pools / threads)` per query, independent of `select_k`.
1283    #[allow(clippy::too_many_arguments)]
1284    pub fn mla_kpool_select(
1285        &self,
1286        score: &CudaSlice<f32>,
1287        idx: &mut CudaSlice<i32>,
1288        t_q: usize,
1289        n_pools: usize,
1290        pool: usize,
1291        select_k: usize,
1292        width: usize,
1293        first_pos: usize,
1294        always_tail: bool,
1295    ) -> Res<()> {
1296        let s = self.stream();
1297        // MEMRA_B200_DSA_SELECT door: the exact multi-CTA selector. Byte-identical output, so
1298        // this is a speed choice and nothing else; it engages only where the single-CTA kernel
1299        // has parallelism to gain (see MLA_DSA_SELECT_MIN_POOLS).
1300        if mla_dsa_select_on() && mla_dsa_select_engages(t_q, n_pools) {
1301            let n_ctas = unsafe { memra_mla_kpool_select_ctas(n_pools as i32) };
1302            let stride = unsafe { memra_mla_kpool_select_ws_ints(n_ctas) };
1303            let mut ws = self.uninit_i32(t_q * stride as usize)?;
1304            mla_dsa_select_announce(t_q, n_pools, n_ctas);
1305            return unsafe {
1306                ck(
1307                    "kpool_select_dsa",
1308                    memra_mla_kpool_select_dsa_f32(
1309                        score.device_ptr(&s).0 as *const f32,
1310                        idx.device_ptr_mut(&s).0 as *mut i32,
1311                        ws.device_ptr_mut(&s).0 as *mut i32,
1312                        t_q as i32,
1313                        n_pools as i32,
1314                        pool as i32,
1315                        select_k as i32,
1316                        width as i32,
1317                        first_pos as i32,
1318                        i32::from(always_tail),
1319                        s.cu_stream() as *mut c_void,
1320                    ),
1321                )
1322            };
1323        }
1324        unsafe {
1325            ck(
1326                "kpool_select",
1327                memra_mla_kpool_select_f32(
1328                    score.device_ptr(&s).0 as *const f32,
1329                    idx.device_ptr_mut(&s).0 as *mut i32,
1330                    t_q as i32,
1331                    n_pools as i32,
1332                    pool as i32,
1333                    select_k as i32,
1334                    width as i32,
1335                    first_pos as i32,
1336                    i32::from(always_tail),
1337                    s.cu_stream() as *mut c_void,
1338                ),
1339            )
1340        }
1341    }
1342
1343    /// The `select_k`-rounds reference selection — the DEFINITION of the order the radix kernel
1344    /// above must reproduce. NOT a serving path: it is `O(select_k * n_pools / threads)` and
1345    /// exists so `gpu_kpool_radix_selection_is_byte_identical_to_the_reference_kernel` can hold
1346    /// the fast kernel to it at shapes the micro fixture cannot reach.
1347    #[allow(clippy::too_many_arguments)]
1348    pub fn mla_kpool_select_ref(
1349        &self,
1350        score: &CudaSlice<f32>,
1351        idx: &mut CudaSlice<i32>,
1352        t_q: usize,
1353        n_pools: usize,
1354        pool: usize,
1355        select_k: usize,
1356        width: usize,
1357        first_pos: usize,
1358        always_tail: bool,
1359    ) -> Res<()> {
1360        let s = self.stream();
1361        unsafe {
1362            ck(
1363                "kpool_select_ref",
1364                memra_mla_kpool_select_ref_f32(
1365                    score.device_ptr(&s).0 as *const f32,
1366                    idx.device_ptr_mut(&s).0 as *mut i32,
1367                    t_q as i32,
1368                    n_pools as i32,
1369                    pool as i32,
1370                    select_k as i32,
1371                    width as i32,
1372                    first_pos as i32,
1373                    i32::from(always_tail),
1374                    s.cu_stream() as *mut c_void,
1375                ),
1376            )
1377        }
1378    }
1379
1380    /// Strided-batched BF16 tensor-core GEMM over per-head planes — the
1381    /// MEMRA_MLA_TC_PREFILL absorb/decompress engine. Per head `b` in `0..batch`:
1382    /// `y_b[m, n] = x_b[m, k] @ w_b[n, k]^T`, f32 accumulate.
1383    ///
1384    /// `w` is the bf16 conversion-split weight plane: per-head `[n, k]` row-major,
1385    /// batch stride `n * k` (baked into the C side). `x` is a bf16 VIEW of a
1386    /// `[m, batch, k]` activation plane: per-head row stride `x_rs`, per-head base
1387    /// offset `x_bs` — for the canonical `[t, n_head, d]` layout that is
1388    /// `x_rs = batch * k`, `x_bs = k`. `y` mirrors that with `y_rs`/`y_bs` over `n`.
1389    ///
1390    /// `y_bf16` selects the output dtype: `true` writes bf16 (feeds the TC attention
1391    /// kernel directly, one fewer convert), `false` writes f32 (re-enters the f32
1392    /// stream). The caller passes `y` as raw bytes either way; an f32 output slice
1393    /// is viewed through its byte layout by the caller (`mla_bf16_gemm_sb_f32out`).
1394    ///
1395    /// rc 2xxxx (no cuBLASLt heuristic for the shape) is a DECLINE class the caller
1396    /// may fall back on; everything else is a hard error.
1397    #[allow(clippy::too_many_arguments)]
1398    pub fn mla_bf16_gemm_sb_raw(
1399        &self,
1400        w_bf16: &CudaSlice<u8>,
1401        x_bf16: &CudaSlice<u8>,
1402        y_ptr: u64,
1403        m: usize,
1404        n: usize,
1405        k: usize,
1406        x_rs: usize,
1407        x_bs: usize,
1408        y_rs: usize,
1409        y_bs: usize,
1410        batch: usize,
1411        y_bf16: bool,
1412    ) -> Res<i32> {
1413        // Workspace from the shared f16/bf16 Lt scratch (bf16_tc_gemm pattern).
1414        let mut guard = self.f16_scratch.lock().unwrap();
1415        if guard.is_none() {
1416            *guard = Some(crate::f16_ffi::F16Scratch::with_capacity(self, 2)?);
1417        }
1418        let s_scr = guard.as_mut().unwrap();
1419        let s = self.stream();
1420        let rc = unsafe {
1421            memra_bf16_gemm_sb(
1422                w_bf16.device_ptr(&s).0 as *const c_void,
1423                x_bf16.device_ptr(&s).0 as *const c_void,
1424                y_ptr as *mut c_void,
1425                m as i32,
1426                n as i32,
1427                k as i32,
1428                x_rs as i64,
1429                x_bs as i64,
1430                y_rs as i64,
1431                y_bs as i64,
1432                batch as i32,
1433                i32::from(y_bf16),
1434                s_scr.ws.device_ptr_mut(&s).0 as *mut c_void,
1435                crate::f16_ffi::F16_WS_BYTES,
1436                s.cu_stream() as *mut c_void,
1437            )
1438        };
1439        Ok(rc)
1440    }
1441
1442    /// [`Engine::mla_bf16_gemm_sb_raw`] with a bf16 output plane (absorb: feeds the TC
1443    /// attention kernel). Non-decline errors are named; a 2xxxx decline is returned as
1444    /// `Ok(false)` so the door can fall back to the per-position kernels.
1445    #[allow(clippy::too_many_arguments)]
1446    pub fn mla_bf16_gemm_sb_bf16out(
1447        &self,
1448        w_bf16: &CudaSlice<u8>,
1449        x_bf16: &CudaSlice<u8>,
1450        y_bf16: &mut CudaSlice<u8>,
1451        m: usize,
1452        n: usize,
1453        k: usize,
1454        x_rs: usize,
1455        x_bs: usize,
1456        y_rs: usize,
1457        y_bs: usize,
1458        batch: usize,
1459    ) -> Res<bool> {
1460        let s = self.stream();
1461        let (y_ptr, _gy) = y_bf16.device_ptr_mut(&s);
1462        let rc = self.mla_bf16_gemm_sb_raw(
1463            w_bf16, x_bf16, y_ptr, m, n, k, x_rs, x_bs, y_rs, y_bs, batch, true,
1464        )?;
1465        match rc {
1466            0 => Ok(true),
1467            r if (20000..30000).contains(&r) => Ok(false),
1468            r => Err(format!(
1469                "mla bf16 strided-batched GEMM (bf16 out) failed: rc {r} \
1470                 (m={m} n={n} k={k} batch={batch})"
1471            )
1472            .into()),
1473        }
1474    }
1475
1476    /// [`Engine::mla_bf16_gemm_sb_raw`] with an f32 output plane (decompress: re-enters
1477    /// the f32 stream). Same decline contract as the bf16-out twin.
1478    #[allow(clippy::too_many_arguments)]
1479    pub fn mla_bf16_gemm_sb_f32out(
1480        &self,
1481        w_bf16: &CudaSlice<u8>,
1482        x_bf16: &CudaSlice<u8>,
1483        y_f32: &mut CudaSlice<f32>,
1484        m: usize,
1485        n: usize,
1486        k: usize,
1487        x_rs: usize,
1488        x_bs: usize,
1489        y_rs: usize,
1490        y_bs: usize,
1491        batch: usize,
1492    ) -> Res<bool> {
1493        let s = self.stream();
1494        let (y_ptr, _gy) = y_f32.device_ptr_mut(&s);
1495        let rc = self.mla_bf16_gemm_sb_raw(
1496            w_bf16, x_bf16, y_ptr, m, n, k, x_rs, x_bs, y_rs, y_bs, batch, false,
1497        )?;
1498        match rc {
1499            0 => Ok(true),
1500            r if (20000..30000).contains(&r) => Ok(false),
1501            r => Err(format!(
1502                "mla bf16 strided-batched GEMM (f32 out) failed: rc {r} \
1503                 (m={m} n={n} k={k} batch={batch})"
1504            )
1505            .into()),
1506        }
1507    }
1508
1509    /// Absorbed-form MQA attention over a GATHERED index list (one list per query, shared across
1510    /// heads). Same body as `mla_attn_absorbed`; only the cache walk differs.
1511    #[allow(clippy::too_many_arguments)]
1512    pub fn mla_attn_gathered(
1513        &self,
1514        q_lat: &CudaSlice<f32>,
1515        q_pe: &CudaSlice<f32>,
1516        cache: &CudaSlice<f32>,
1517        idx: &CudaSlice<i32>,
1518        o_lat: &mut CudaSlice<f32>,
1519        n_head: usize,
1520        kv_rank: usize,
1521        d_rope: usize,
1522        t_q: usize,
1523        n_slots: usize,
1524        scale: f32,
1525    ) -> Res<()> {
1526        let s = self.stream();
1527        // MEMRA_B200_DSA_DECODE door, checked FIRST: its arms fight the same 64-CTA t_q=1
1528        // geometry the output-range split below does, without repeating the slot walk.
1529        // THE TWO LEVELS DIFFER TODAY, and the difference is this PR's headline: the shipped
1530        // table is [0, 32, 0, 0, 1, ...], so at t_q=1 level 1 takes arm 0 (falls through to
1531        // the sibling split door) while level 2 takes warp-online chunks=32 -- +9.7% vs
1532        // +43.1% in the 256k serving A/B. The `a >= 2 && dsa_level < 2` guard below exists
1533        // precisely because they differ, and the level boundary IS the numeric-class
1534        // admission boundary. See research/b200-dsa-decode-20260902/ROOFLINE.md.
1535        let dsa_level = mla_dsa_decode_level();
1536        let dsa_arm = if dsa_level >= 1 && t_q <= MLA_DSA_ARM_T_MAX {
1537            // `_effective` already enforces the named-class width rule (plain decode only, so
1538            // the spec-verify batch never sees `dsa-warp-online-f32`); level 2 is the second,
1539            // independent admission for the same class.
1540            let a = mla_dsa_attn_arm_effective(t_q);
1541            if a >= 2 && dsa_level < 2 { 0 } else { a }
1542        } else {
1543            0
1544        };
1545        if dsa_arm >= 2 {
1546            let cells = t_q * n_head * dsa_arm as usize;
1547            let mut part_m = self.uninit(cells)?;
1548            let mut part_d = self.uninit(cells)?;
1549            let mut part_acc = self.uninit(cells * kv_rank)?;
1550            let rc = unsafe {
1551                memra_mla_dsa_attn_split_f32(
1552                    q_lat.device_ptr(&s).0 as *const f32,
1553                    q_pe.device_ptr(&s).0 as *const f32,
1554                    cache.device_ptr(&s).0 as *const f32,
1555                    idx.device_ptr(&s).0 as *const i32,
1556                    o_lat.device_ptr_mut(&s).0 as *mut f32,
1557                    part_m.device_ptr_mut(&s).0 as *mut f32,
1558                    part_d.device_ptr_mut(&s).0 as *mut f32,
1559                    part_acc.device_ptr_mut(&s).0 as *mut f32,
1560                    n_head as i32,
1561                    kv_rank as i32,
1562                    d_rope as i32,
1563                    t_q as i32,
1564                    n_slots as i32,
1565                    dsa_arm,
1566                    scale,
1567                    s.cu_stream() as *mut c_void,
1568                )
1569            };
1570            if !mla_dsa_geometry_refusal(rc) {
1571                mla_dsa_announce(
1572                    "attn_gathered",
1573                    t_q,
1574                    &format!("arm=warp-online chunks={dsa_arm} class=dsa-warp-online-f32"),
1575                );
1576                return ck("attn_gathered_dsa_warp", rc);
1577            }
1578        } else if dsa_arm == 1 {
1579            let rc = unsafe {
1580                memra_mla_attn_gathered_dsa_f32(
1581                    q_lat.device_ptr(&s).0 as *const f32,
1582                    q_pe.device_ptr(&s).0 as *const f32,
1583                    cache.device_ptr(&s).0 as *const f32,
1584                    idx.device_ptr(&s).0 as *const i32,
1585                    o_lat.device_ptr_mut(&s).0 as *mut f32,
1586                    n_head as i32,
1587                    kv_rank as i32,
1588                    d_rope as i32,
1589                    t_q as i32,
1590                    n_slots as i32,
1591                    scale,
1592                    s.cu_stream() as *mut c_void,
1593                )
1594            };
1595            if !mla_dsa_geometry_refusal(rc) {
1596                mla_dsa_announce("attn_gathered", t_q, "arm=single-pass class=bit-identical");
1597                return ck("attn_gathered_dsa", rc);
1598            }
1599        }
1600        // MEMRA_B200_MLA_DECODE_ARM door: output-range split from the t_q-keyed table
1601        // MLA_B200_ATTN_GATHERED_SPLIT. This twin repeats the score/softmax walk per split
1602        // block, unlike the absorb/decompress splits, which is why the B200 run found it a win
1603        // at t_q=1 only (see the table comment); every other cell is the shipped kernel.
1604        if let Some(split) = mla_b200_split_for(MlaB200Kernel::AttnGathered, t_q, kv_rank) {
1605            mla_b200_split_announce("attn_gathered", t_q, n_head, split);
1606            return unsafe {
1607                ck(
1608                    "attn_gathered_split_b200",
1609                    memra_mla_attn_gathered_split_f32(
1610                        q_lat.device_ptr(&s).0 as *const f32,
1611                        q_pe.device_ptr(&s).0 as *const f32,
1612                        cache.device_ptr(&s).0 as *const f32,
1613                        idx.device_ptr(&s).0 as *const i32,
1614                        o_lat.device_ptr_mut(&s).0 as *mut f32,
1615                        n_head as i32,
1616                        kv_rank as i32,
1617                        d_rope as i32,
1618                        t_q as i32,
1619                        n_slots as i32,
1620                        scale,
1621                        split,
1622                        s.cu_stream() as *mut c_void,
1623                    ),
1624                )
1625            };
1626        }
1627        unsafe {
1628            ck(
1629                "attn_gathered",
1630                memra_mla_attn_gathered_f32(
1631                    q_lat.device_ptr(&s).0 as *const f32,
1632                    q_pe.device_ptr(&s).0 as *const f32,
1633                    cache.device_ptr(&s).0 as *const f32,
1634                    idx.device_ptr(&s).0 as *const i32,
1635                    o_lat.device_ptr_mut(&s).0 as *mut f32,
1636                    n_head as i32,
1637                    kv_rank as i32,
1638                    d_rope as i32,
1639                    t_q as i32,
1640                    n_slots as i32,
1641                    scale,
1642                    s.cu_stream() as *mut c_void,
1643                ),
1644            )
1645        }
1646    }
1647}