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