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_append_latent_live_f32(
540 cache: *mut f32,
541 c_kv: *const f32,
542 k_pe: *const f32,
543 pos_d: *const i32,
544 t: i32,
545 kv_rank: i32,
546 d_rope: i32,
547 stream: *mut c_void,
548 ) -> i32;
549 pub fn memra_mla_absorb_q_f32(
550 q_nope: *const f32,
551 wk_b: *const f32,
552 q_lat: *mut f32,
553 t_q: i32,
554 n_head: i32,
555 d_nope: i32,
556 kv_rank: i32,
557 stream: *mut c_void,
558 ) -> i32;
559 pub fn memra_mla_decompress_v_f32(
560 o_lat: *const f32,
561 wv_b: *const f32,
562 out: *mut f32,
563 t_q: i32,
564 n_head: i32,
565 d_v: i32,
566 kv_rank: i32,
567 stream: *mut c_void,
568 ) -> i32;
569 /// COALESCED warp-per-row twin of `memra_mla_absorb_q_f32` (`MEMRA_MLA_COALESCE`,
570 /// lane/mla-coalesce). The shipped kernel gives output row `l` to THREAD `l`, which then
571 /// walks its row serially, so at step `p` a warp's 32 lanes read addresses `d_nope` floats
572 /// (512 B) apart and pull 32 transactions where one would do. Here a WARP owns a row and
573 /// lane `k` reads `row[k], row[k+32], ...`, which is one 128-byte transaction per step,
574 /// finished by a shuffle reduction. Takes the same `split` output-range partition as the
575 /// decode-split twins, so the two doors COMPOSE: the split fixes the GRID, this fixes the
576 /// LOADS. NAMED NUMERIC CLASS `mla_warp_row_reduce`: the per-output sum becomes 32
577 /// lane-partial sums combined by a shuffle tree instead of one serial ascending dot, so
578 /// this is NOT bit-identical and NOT the split twins' contract.
579 #[allow(clippy::too_many_arguments)]
580 /// BF16-plane twins of the `_wp` decode kernels (lane/mla-absorb-bf16-20260905): the weight
581 /// plane is `u16` BF16 bits, widened per element inside the kernel; same order, same math.
582 pub fn memra_mla_absorb_q_wp_bf16(
583 q_nope: *const f32,
584 wk_b: *const u16,
585 q_lat: *mut f32,
586 t_q: i32,
587 n_head: i32,
588 d_nope: i32,
589 kv_rank: i32,
590 split: i32,
591 stream: *mut c_void,
592 ) -> i32;
593 pub fn memra_mla_decompress_v_wp_bf16(
594 o_lat: *const f32,
595 wv_b: *const u16,
596 out: *mut f32,
597 t_q: i32,
598 n_head: i32,
599 d_v: i32,
600 kv_rank: i32,
601 split: i32,
602 stream: *mut c_void,
603 ) -> i32;
604 pub fn memra_mla_absorb_q_wp_f32(
605 q_nope: *const f32,
606 wk_b: *const f32,
607 q_lat: *mut f32,
608 t_q: i32,
609 n_head: i32,
610 d_nope: i32,
611 kv_rank: i32,
612 split: i32,
613 stream: *mut c_void,
614 ) -> i32;
615 /// COALESCED warp-per-row twin of `memra_mla_decompress_v_f32` (`MEMRA_MLA_COALESCE`).
616 /// Same defect and same fix as `memra_mla_absorb_q_wp_f32`, and worse in the shipped form:
617 /// the lane stride there is `kv_rank` floats (2 KB), and with `d_v` typically 128 against
618 /// `MLA_THREADS` 256 half the block never enters the loop. Numeric class
619 /// `mla_warp_row_reduce`.
620 #[allow(clippy::too_many_arguments)]
621 pub fn memra_mla_decompress_v_wp_f32(
622 o_lat: *const f32,
623 wv_b: *const f32,
624 out: *mut f32,
625 t_q: i32,
626 n_head: i32,
627 d_v: i32,
628 kv_rank: i32,
629 split: i32,
630 stream: *mut c_void,
631 ) -> i32;
632 pub fn memra_mla_decompress_v_wp_zq8_f32(
633 o_lat: *const f32,
634 wv_b: *const f32,
635 out: *mut f32,
636 out_q: *mut i8,
637 out_d: *mut f32,
638 t_q: i32,
639 n_head: i32,
640 d_v: i32,
641 kv_rank: i32,
642 split: i32,
643 stream: *mut c_void,
644 ) -> i32;
645 pub fn memra_mla_decompress_v_wp_bf16_zq8(
646 o_lat: *const f32,
647 wv_b: *const u16,
648 out: *mut f32,
649 out_q: *mut i8,
650 out_d: *mut f32,
651 t_q: i32,
652 n_head: i32,
653 d_v: i32,
654 kv_rank: i32,
655 split: i32,
656 stream: *mut c_void,
657 ) -> i32;
658 /// Decode-split twin of `memra_mla_absorb_q_f32` (MEMRA_MLA_DECODE_SPLIT): the same
659 /// per-output serial dot, its output range split across `split` blocks — bit-identical
660 /// by construction, gated in `tests/mla_decode_split_gpu.rs`.
661 #[allow(clippy::too_many_arguments)]
662 pub fn memra_mla_absorb_q_split_f32(
663 q_nope: *const f32,
664 wk_b: *const f32,
665 q_lat: *mut f32,
666 t_q: i32,
667 n_head: i32,
668 d_nope: i32,
669 kv_rank: i32,
670 split: i32,
671 stream: *mut c_void,
672 ) -> i32;
673 /// Decode-split twin of `memra_mla_decompress_v_f32` (see above).
674 #[allow(clippy::too_many_arguments)]
675 pub fn memra_mla_decompress_v_split_f32(
676 o_lat: *const f32,
677 wv_b: *const f32,
678 out: *mut f32,
679 t_q: i32,
680 n_head: i32,
681 d_v: i32,
682 kv_rank: i32,
683 split: i32,
684 stream: *mut c_void,
685 ) -> i32;
686 pub fn memra_mla_attn_absorbed_f32(
687 q_lat: *const f32,
688 q_pe: *const f32,
689 cache: *const f32,
690 o_lat: *mut f32,
691 n_head: i32,
692 kv_rank: i32,
693 d_rope: i32,
694 t_q: i32,
695 t_kv: i32,
696 scale: f32,
697 stream: *mut c_void,
698 ) -> i32;
699 pub fn memra_mla_attn_absorbed_live_f32(
700 q_lat: *const f32,
701 q_pe: *const f32,
702 cache: *const f32,
703 o_lat: *mut f32,
704 n_head: i32,
705 kv_rank: i32,
706 d_rope: i32,
707 t_q: i32,
708 pos_d: *const i32,
709 scale: f32,
710 stream: *mut c_void,
711 ) -> i32;
712 pub fn memra_mla_index_append_ring_f32(
713 plane: *mut f32,
714 a: *const f32,
715 b: *const f32,
716 slot: i32,
717 t: i32,
718 wa: i32,
719 wb: i32,
720 rows: i32,
721 stream: *mut c_void,
722 ) -> i32;
723 pub fn memra_mla_kpool_pool_keys_f32(
724 state: *const f32,
725 ape: *const f32,
726 pool_keys: *mut f32,
727 pool_begin: i32,
728 n_pools: i32,
729 pool: i32,
730 d: i32,
731 state_rows: i32,
732 stream: *mut c_void,
733 ) -> i32;
734 pub fn memra_mla_kpool_score_f32(
735 q: *const f32,
736 pool_keys: *const f32,
737 hw: *const f32,
738 score: *mut f32,
739 t_q: i32,
740 heads: i32,
741 d: i32,
742 n_pools: i32,
743 pool: i32,
744 first_pos: i32,
745 qk_scale: f32,
746 head_scale: f32,
747 stream: *mut c_void,
748 ) -> i32;
749 pub fn memra_mla_kpool_score_ref_f32(
750 q: *const f32,
751 pool_keys: *const f32,
752 hw: *const f32,
753 score: *mut f32,
754 t_q: i32,
755 heads: i32,
756 d: i32,
757 n_pools: i32,
758 pool: i32,
759 first_pos: i32,
760 qk_scale: f32,
761 head_scale: f32,
762 stream: *mut c_void,
763 ) -> i32;
764 /// Ints of scratch one query needs for the parallel selector, given its CTA count.
765 pub fn memra_mla_kpool_select_ws_ints(n_ctas: i32) -> i64;
766 /// CTA count the parallel selector launches per query. The host sizes the workspace from
767 /// this same entry point, so a mismatch is impossible by construction.
768 pub fn memra_mla_kpool_select_ctas(n_pools: i32) -> i32;
769 /// Exact multi-CTA k-pool selection (`MEMRA_B200_DSA_SELECT`): same threshold key, same
770 /// membership test, same emit order, byte-identical `idx`.
771 #[allow(clippy::too_many_arguments)]
772 pub fn memra_mla_kpool_score_dsa_live_f32(
773 q: *const f32,
774 pool_keys: *const f32,
775 hw: *const f32,
776 score: *mut f32,
777 t_q: i32,
778 heads: i32,
779 d: i32,
780 n_pools_d: *const i32,
781 n_pools_cap: i32,
782 pool: i32,
783 first_pos: i32,
784 qk_scale: f32,
785 head_scale: f32,
786 stream: *mut c_void,
787 ) -> i32;
788 pub fn memra_mla_kpool_select_live_f32(
789 score: *const f32,
790 idx: *mut i32,
791 t_q: i32,
792 n_pools_d: *const i32,
793 pool: i32,
794 select_k: i32,
795 width: i32,
796 first_pos: i32,
797 always_tail: i32,
798 stream: *mut c_void,
799 ) -> i32;
800 pub fn memra_mla_kpool_select_dsa_f32(
801 score: *const f32,
802 idx: *mut i32,
803 ws: *mut i32,
804 t_q: i32,
805 n_pools: i32,
806 pool: i32,
807 select_k: i32,
808 width: i32,
809 first_pos: i32,
810 always_tail: i32,
811 stream: *mut c_void,
812 ) -> i32;
813 /// RED ARM for `dsa-select-gate`, never a serving path: the exact pipeline with the resolved
814 /// threshold deliberately bumped, so the gate can prove its byte comparison actually fails
815 /// on a wrong selection before it is allowed to pass the real kernel.
816 #[allow(clippy::too_many_arguments)]
817 pub fn memra_mla_kpool_select_dsa_redarm_f32(
818 score: *const f32,
819 idx: *mut i32,
820 ws: *mut i32,
821 t_q: i32,
822 n_pools: i32,
823 pool: i32,
824 select_k: i32,
825 width: i32,
826 first_pos: i32,
827 always_tail: i32,
828 bump: i32,
829 stream: *mut c_void,
830 ) -> i32;
831 pub fn memra_mla_kpool_select_f32(
832 score: *const f32,
833 idx: *mut i32,
834 t_q: i32,
835 n_pools: i32,
836 pool: i32,
837 select_k: i32,
838 width: i32,
839 first_pos: i32,
840 always_tail: i32,
841 stream: *mut c_void,
842 ) -> i32;
843 pub fn memra_mla_kpool_select_ref_f32(
844 score: *const f32,
845 idx: *mut i32,
846 t_q: i32,
847 n_pools: i32,
848 pool: i32,
849 select_k: i32,
850 width: i32,
851 first_pos: i32,
852 always_tail: i32,
853 stream: *mut c_void,
854 ) -> i32;
855 pub fn memra_mla_attn_gathered_f32(
856 q_lat: *const f32,
857 q_pe: *const f32,
858 cache: *const f32,
859 idx: *const i32,
860 o_lat: *mut f32,
861 n_head: i32,
862 kv_rank: i32,
863 d_rope: i32,
864 t_q: i32,
865 n_slots: i32,
866 scale: f32,
867 stream: *mut c_void,
868 ) -> i32;
869 /// B200 decode-arm twin of `memra_mla_attn_gathered_f32` (MEMRA_B200_MLA_DECODE_ARM): same
870 /// per-l accumulate chain, its output range [0, kv_rank) split across `split` blocks; the
871 /// shared score/softmax tile walk (m, dsum) is recomputed IN FULL, unchanged, by every
872 /// split block — bit-identical by construction, gated in `mla_decode_arm_gate.rs`.
873 #[allow(clippy::too_many_arguments)]
874 /// Single-pass bit-identical rewrite of `memra_mla_attn_gathered_f32`
875 /// (`MEMRA_B200_DSA_DECODE>=1`): each tile's KV rows staged once into shared memory with
876 /// float4 loads and read back for BOTH the score dot and the PV accumulate, the 8 tile
877 /// exponentials hoisted into registers. Same grid, same fold, same bits. Returns 40020
878 /// (width not a multiple of 4) or 40021 (staging over the smem cap) for a geometry it
879 /// refuses, and the caller falls through to the shipped kernel.
880 pub fn memra_mla_attn_gathered_dsa_f32(
881 q_lat: *const f32,
882 q_pe: *const f32,
883 cache: *const f32,
884 idx: *const i32,
885 o_lat: *mut f32,
886 n_head: i32,
887 kv_rank: i32,
888 d_rope: i32,
889 t_q: i32,
890 n_slots: i32,
891 scale: f32,
892 stream: *mut c_void,
893 ) -> i32;
894 /// Slot-per-chunk span the partial kernel walks. The host MUST size the workspace and
895 /// launch from this, never from its own division, so the two cannot disagree.
896 pub fn memra_mla_dsa_attn_chunk_span(n_slots: i32, chunks: i32) -> i32;
897 /// Warp-online slot-split gathered attention, numeric class `dsa-warp-online-f32`
898 /// (`MEMRA_B200_DSA_DECODE=2`). `part_m` / `part_d` hold `t_q * n_head * chunks` floats
899 /// each; `part_acc` holds `t_q * n_head * chunks * kv_rank`. Returns 40023 for a
900 /// (kv_rank, d_rope) with no template instantiation, and the caller takes the shipped path.
901 pub fn memra_mla_dsa_attn_split_f32(
902 q_lat: *const f32,
903 q_pe: *const f32,
904 cache: *const f32,
905 idx: *const i32,
906 o_lat: *mut f32,
907 part_m: *mut f32,
908 part_d: *mut f32,
909 part_acc: *mut f32,
910 n_head: i32,
911 kv_rank: i32,
912 d_rope: i32,
913 t_q: i32,
914 n_slots: i32,
915 chunks: i32,
916 scale: f32,
917 stream: *mut c_void,
918 ) -> i32;
919 /// Head-blocked decode pool scorer (`MEMRA_B200_DSA_DECODE>=1`), bit-identical to
920 /// `memra_mla_kpool_score_ref_f32`. Returns 40023 when this (heads, d) has no
921 /// instantiation, and the caller falls through to the shipped dispatch.
922 pub fn memra_mla_kpool_score_dsa_f32(
923 q: *const f32,
924 pool_keys: *const f32,
925 hw: *const f32,
926 score: *mut f32,
927 t_q: i32,
928 heads: i32,
929 d: i32,
930 n_pools: i32,
931 pool: i32,
932 first_pos: i32,
933 qk_scale: f32,
934 head_scale: f32,
935 stream: *mut c_void,
936 ) -> i32;
937 pub fn memra_mla_attn_gathered_split_f32(
938 q_lat: *const f32,
939 q_pe: *const f32,
940 cache: *const f32,
941 idx: *const i32,
942 o_lat: *mut f32,
943 n_head: i32,
944 kv_rank: i32,
945 d_rope: i32,
946 t_q: i32,
947 n_slots: i32,
948 scale: f32,
949 split: i32,
950 stream: *mut c_void,
951 ) -> i32;
952 /// Strided-batched BF16 tensor-core GEMM (cu/f16_prefill.cu): per batch b,
953 /// `y_b[m, n] = x_b[m, k] @ w_b[n, k]^T`, f32 accumulate, y f32 or bf16 by flag.
954 /// The MEMRA_MLA_TC_PREFILL absorb/decompress engine (one launch replaces the
955 /// per-position absorb_q / decompress_v kernels at prefill widths).
956 fn memra_bf16_gemm_sb(
957 w_bf16: *const c_void,
958 x_bf16: *const c_void,
959 y: *mut c_void,
960 m: i32,
961 n: i32,
962 k: i32,
963 x_rs: i64,
964 x_bs: i64,
965 y_rs: i64,
966 y_bs: i64,
967 batch: i32,
968 y_is_bf16: i32,
969 ws: *mut c_void,
970 ws_bytes: usize,
971 stream: *mut c_void,
972 ) -> i32;
973}
974
975type Res<T> = Result<T, Box<dyn std::error::Error>>;
976
977/// Turn a launcher's status band into a named error. Every MLA launch goes through this —
978/// a silently-ignored non-zero status is how a contract violation becomes garbage activations.
979fn ck(what: &str, rc: i32) -> Res<()> {
980 if rc == 0 {
981 return Ok(());
982 }
983 let detail = match rc {
984 40001 => " (d_rope must be even — interleaved rope rotates (2j, 2j+1) pairs)",
985 40002 => " (kv_rank exceeds the kernel's MLA_MAX_RANK shared-memory ceiling)",
986 40003 => " (d_rope exceeds the kernel's MLA_MAX_ROPE ceiling)",
987 40004 => " (t_q > t_kv — queries must be a suffix of the latent cache)",
988 40010 => " (k-pool size out of range — 1..=MLA_MAX_POOL)",
989 40011 => " (indexer head count out of range — 1..=1024, one thread per head)",
990 40012 => " (t_q * n_pools exceeds the grid.x contract)",
991 40017 => " (indexer head dim must be positive)",
992 40013 => {
993 " (always_select_tail=false: queries before the first complete pool would have an \
994 empty candidate set, which the memra-reference oracle refuses outright)"
995 }
996 40014 => " (index-list width is narrower than select_k * pool + pool - 1)",
997 40015 => " (empty gathered candidate list — a zero softmax denominator)",
998 40020 => " (latent row width is not a multiple of 4 — the DSA float4 staging needs it)",
999 40021 => " (DSA tile staging exceeds MLA_DSA_KV_SMEM_MAX)",
1000 40022 => " (DSA slot-chunk count out of range — 1..=64)",
1001 40023 => " (no DSA scorer instantiation for this (heads, d))",
1002 r if (10000..20000).contains(&r) => " (cudaError)",
1003 _ => "",
1004 };
1005 Err(format!("mla kernel `{what}` failed: rc {rc}{detail}").into())
1006}
1007
1008impl Engine {
1009 /// Interleaved ("NORM") RoPE in place over `x` laid out [n_pos][n_vec][d_rope].
1010 /// `d_rope == 0` (NoPE, glm5_next) is a no-op — the caller must still not pass an empty
1011 /// slice through a path that dereferences it, which is why the rope plane is skipped
1012 /// entirely in the forward arm rather than launched with a zero extent.
1013 pub fn mla_rope_interleaved(
1014 &self,
1015 x: &mut CudaSlice<f32>,
1016 pos_d: &CudaSlice<i32>,
1017 n_pos: usize,
1018 n_vec: usize,
1019 d_rope: usize,
1020 base: f32,
1021 ) -> Res<()> {
1022 if d_rope == 0 {
1023 return Ok(());
1024 }
1025 let s = self.stream();
1026 unsafe {
1027 ck(
1028 "rope_interleaved",
1029 memra_mla_rope_interleaved_f32(
1030 x.device_ptr_mut(&s).0 as *mut f32,
1031 n_pos as i32,
1032 n_vec as i32,
1033 d_rope as i32,
1034 pos_d.device_ptr(&s).0 as *const i32,
1035 base,
1036 s.cu_stream() as *mut c_void,
1037 ),
1038 )
1039 }
1040 }
1041
1042 /// Split the `wkv_a` output rows [t][kv_rank + d_rope] into `c_kv` and `k_pe` planes.
1043 pub fn mla_split_latent(
1044 &self,
1045 kv: &CudaSlice<f32>,
1046 c_kv: &mut CudaSlice<f32>,
1047 k_pe: &mut CudaSlice<f32>,
1048 t: usize,
1049 kv_rank: usize,
1050 d_rope: usize,
1051 ) -> Res<()> {
1052 let s = self.stream();
1053 unsafe {
1054 ck(
1055 "split_latent",
1056 memra_mla_split_latent_f32(
1057 kv.device_ptr(&s).0 as *const f32,
1058 c_kv.device_ptr_mut(&s).0 as *mut f32,
1059 k_pe.device_ptr_mut(&s).0 as *mut f32,
1060 t as i32,
1061 kv_rank as i32,
1062 d_rope as i32,
1063 s.cu_stream() as *mut c_void,
1064 ),
1065 )
1066 }
1067 }
1068
1069 /// Append `t` latent rows `[c_kv | k_pe]` to the cache plane starting at row `slot`.
1070 #[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
1071 pub fn mla_append_latent(
1072 &self,
1073 cache: &mut CudaSlice<f32>,
1074 c_kv: &CudaSlice<f32>,
1075 k_pe: &CudaSlice<f32>,
1076 slot: usize,
1077 t: usize,
1078 kv_rank: usize,
1079 d_rope: usize,
1080 ) -> Res<()> {
1081 let s = self.stream();
1082 unsafe {
1083 ck(
1084 "append_latent",
1085 memra_mla_append_latent_f32(
1086 cache.device_ptr_mut(&s).0 as *mut f32,
1087 c_kv.device_ptr(&s).0 as *const f32,
1088 k_pe.device_ptr(&s).0 as *const f32,
1089 slot as i32,
1090 t as i32,
1091 kv_rank as i32,
1092 d_rope as i32,
1093 s.cu_stream() as *mut c_void,
1094 ),
1095 )
1096 }
1097 }
1098
1099 /// Absorb: `q_lat[i][h][:] = w_uk[h]ᵀ · q_nope[i][h][:]` (rank space).
1100 #[allow(clippy::too_many_arguments)]
1101 // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
1102 /// BENCH-ONLY raw arm dispatch for `mla-coalesce-bench` (structural ncu target): `arm` 0 =
1103 /// shipped thread-per-row, 1 = decode-split twin at `split`, 2 = warp-per-row coalesced twin
1104 /// at `split` (1 = unsplit). Bypasses every door on purpose so one process can be profiled
1105 /// across all three kernels. Not a serving entry point.
1106 #[allow(clippy::too_many_arguments)]
1107 pub fn mla_absorb_q_raw_arm(
1108 &self,
1109 q_nope: &CudaSlice<f32>,
1110 wk_b: &CudaSlice<f32>,
1111 q_lat: &mut CudaSlice<f32>,
1112 t_q: usize,
1113 n_head: usize,
1114 d_nope: usize,
1115 kv_rank: usize,
1116 arm: u8,
1117 split: i32,
1118 ) -> Res<()> {
1119 let s = self.stream();
1120 unsafe {
1121 let (q, w, o) = (
1122 q_nope.device_ptr(&s).0 as *const f32,
1123 wk_b.device_ptr(&s).0 as *const f32,
1124 q_lat.device_ptr_mut(&s).0 as *mut f32,
1125 );
1126 let (t, h, dn, kr) = (t_q as i32, n_head as i32, d_nope as i32, kv_rank as i32);
1127 let st = s.cu_stream() as *mut c_void;
1128 match arm {
1129 0 => ck(
1130 "absorb_q_raw",
1131 memra_mla_absorb_q_f32(q, w, o, t, h, dn, kr, st),
1132 ),
1133 1 => ck(
1134 "absorb_q_split_raw",
1135 memra_mla_absorb_q_split_f32(q, w, o, t, h, dn, kr, split, st),
1136 ),
1137 _ => ck(
1138 "absorb_q_wp_raw",
1139 memra_mla_absorb_q_wp_f32(q, w, o, t, h, dn, kr, split, st),
1140 ),
1141 }
1142 }
1143 }
1144
1145 /// BENCH-ONLY raw arm dispatch, decompress twin of `mla_absorb_q_raw_arm`.
1146 #[allow(clippy::too_many_arguments)]
1147 pub fn mla_decompress_v_raw_arm(
1148 &self,
1149 o_lat: &CudaSlice<f32>,
1150 wv_b: &CudaSlice<f32>,
1151 out: &mut CudaSlice<f32>,
1152 t_q: usize,
1153 n_head: usize,
1154 d_v: usize,
1155 kv_rank: usize,
1156 arm: u8,
1157 split: i32,
1158 ) -> Res<()> {
1159 let s = self.stream();
1160 unsafe {
1161 let (a, w, o) = (
1162 o_lat.device_ptr(&s).0 as *const f32,
1163 wv_b.device_ptr(&s).0 as *const f32,
1164 out.device_ptr_mut(&s).0 as *mut f32,
1165 );
1166 let (t, h, dv, kr) = (t_q as i32, n_head as i32, d_v as i32, kv_rank as i32);
1167 let st = s.cu_stream() as *mut c_void;
1168 match arm {
1169 0 => ck(
1170 "decompress_v_raw",
1171 memra_mla_decompress_v_f32(a, w, o, t, h, dv, kr, st),
1172 ),
1173 1 => ck(
1174 "decompress_v_split_raw",
1175 memra_mla_decompress_v_split_f32(a, w, o, t, h, dv, kr, split, st),
1176 ),
1177 _ => ck(
1178 "decompress_v_wp_raw",
1179 memra_mla_decompress_v_wp_f32(a, w, o, t, h, dv, kr, split, st),
1180 ),
1181 }
1182 }
1183 }
1184
1185 /// BENCH-ONLY raw dispatch of the fused hc pre-chain for `mla-coalesce-bench` (structural
1186 /// ncu target): `arm` 0 = `memra_dsv4_hc_pre_fused_v2` (block 128, the shipped `=2` arm),
1187 /// 1 = `_v3` at `block` with the shared-memory Sinkhorn, 2 = `_v3` at `block` with the
1188 /// register Sinkhorn (`MEMRA_HC_PRE_SINK_REG`). Bypasses the door readers on purpose. The
1189 /// `hc-fused-gate` calls the v1/v2 launchers directly and predates v3, so this is the only
1190 /// way to put the register Sinkhorn under a profiler.
1191 #[allow(clippy::too_many_arguments)]
1192 pub fn hc_pre_raw_arm(
1193 &self,
1194 x: &CudaSlice<f32>,
1195 mixes: &CudaSlice<f32>,
1196 scale: &CudaSlice<f32>,
1197 base: &CudaSlice<f32>,
1198 pre: &mut CudaSlice<f32>,
1199 post: &mut CudaSlice<f32>,
1200 comb: &mut CudaSlice<f32>,
1201 y: &mut CudaSlice<f32>,
1202 s_rows: usize,
1203 hc: usize,
1204 d: usize,
1205 iters: usize,
1206 eps: f32,
1207 arm: u8,
1208 block: i32,
1209 niters: Option<&mut CudaSlice<i32>>,
1210 ) -> Res<()> {
1211 let st = self.stream();
1212 unsafe {
1213 let np: *mut i32 = match niters {
1214 Some(n) => n.device_ptr_mut(&st).0 as *mut i32,
1215 None => std::ptr::null_mut(),
1216 };
1217 let (xp, mp, sp, bp) = (
1218 x.device_ptr(&st).0 as *const f32,
1219 mixes.device_ptr(&st).0 as *const f32,
1220 scale.device_ptr(&st).0 as *const f32,
1221 base.device_ptr(&st).0 as *const f32,
1222 );
1223 let (pp, qp, cp, yp) = (
1224 pre.device_ptr_mut(&st).0 as *mut f32,
1225 post.device_ptr_mut(&st).0 as *mut f32,
1226 comb.device_ptr_mut(&st).0 as *mut f32,
1227 y.device_ptr_mut(&st).0 as *mut f32,
1228 );
1229 let (sr, h, dd, it) = (s_rows as i32, hc as i32, d as i32, iters as i32);
1230 let cs = st.cu_stream() as *mut c_void;
1231 let rc = match arm {
1232 0 => crate::dsv4_ffi::memra_dsv4_hc_pre_fused_v2(
1233 xp, mp, sp, bp, pp, qp, cp, yp, sr, h, dd, it, eps, np, cs,
1234 ),
1235 1 => crate::dsv4_ffi::memra_dsv4_hc_pre_fused_v3(
1236 xp, mp, sp, bp, pp, qp, cp, yp, sr, h, dd, it, eps, np, block, 0, cs,
1237 ),
1238 _ => crate::dsv4_ffi::memra_dsv4_hc_pre_fused_v3(
1239 xp, mp, sp, bp, pp, qp, cp, yp, sr, h, dd, it, eps, np, block, 1, cs,
1240 ),
1241 };
1242 ck("hc_pre_raw", rc)
1243 }
1244 }
1245
1246 #[allow(clippy::too_many_arguments)]
1247 /// The BF16-plane arm of [`Engine::mla_absorb_q`] for the served `_wp` partition (door
1248 /// `MEMRA_MLA_ABSORB_BF16`): the same split the coalesce door chooses, on the BF16 copy of
1249 /// `wk_b`. `Ok(false)` when that partition is not the one in force (the caller runs the f32
1250 /// dispatch unchanged).
1251 #[allow(clippy::too_many_arguments)]
1252 pub fn mla_absorb_q_bf16(
1253 &self,
1254 q_nope: &CudaSlice<f32>,
1255 wk_b16: &CudaSlice<u16>,
1256 q_lat: &mut CudaSlice<f32>,
1257 t_q: usize,
1258 n_head: usize,
1259 d_nope: usize,
1260 kv_rank: usize,
1261 ) -> Res<bool> {
1262 if !mla_coalesce_on() {
1263 return Ok(false);
1264 }
1265 let split = mla_b200_split_for(MlaB200Kernel::AbsorbQ, t_q, kv_rank)
1266 .or_else(|| mla_decode_split_for(t_q * n_head, kv_rank))
1267 .unwrap_or(1);
1268 if split <= 1 {
1269 return Ok(false);
1270 }
1271 let s = self.stream();
1272 unsafe {
1273 ck(
1274 "absorb_q_wp_bf16",
1275 memra_mla_absorb_q_wp_bf16(
1276 q_nope.device_ptr(&s).0 as *const f32,
1277 wk_b16.device_ptr(&s).0 as *const u16,
1278 q_lat.device_ptr_mut(&s).0 as *mut f32,
1279 t_q as i32,
1280 n_head as i32,
1281 d_nope as i32,
1282 kv_rank as i32,
1283 split,
1284 s.cu_stream() as *mut c_void,
1285 ),
1286 )?;
1287 }
1288 Ok(true)
1289 }
1290
1291 /// The BF16-plane arm of [`Engine::mla_decompress_v`]; see [`Engine::mla_absorb_q_bf16`].
1292 #[allow(clippy::too_many_arguments)]
1293 pub fn mla_decompress_v_bf16(
1294 &self,
1295 o_lat: &CudaSlice<f32>,
1296 wv_b16: &CudaSlice<u16>,
1297 out: &mut CudaSlice<f32>,
1298 t_q: usize,
1299 n_head: usize,
1300 d_v: usize,
1301 kv_rank: usize,
1302 ) -> Res<bool> {
1303 if !mla_coalesce_on() {
1304 return Ok(false);
1305 }
1306 let split = mla_b200_split_for(MlaB200Kernel::DecompressV, t_q, d_v)
1307 .or_else(|| mla_decode_split_for(t_q * n_head, d_v))
1308 .unwrap_or(1);
1309 if split <= 1 {
1310 return Ok(false);
1311 }
1312 let s = self.stream();
1313 unsafe {
1314 ck(
1315 "decompress_v_wp_bf16",
1316 memra_mla_decompress_v_wp_bf16(
1317 o_lat.device_ptr(&s).0 as *const f32,
1318 wv_b16.device_ptr(&s).0 as *const u16,
1319 out.device_ptr_mut(&s).0 as *mut f32,
1320 t_q as i32,
1321 n_head as i32,
1322 d_v as i32,
1323 kv_rank as i32,
1324 split,
1325 s.cu_stream() as *mut c_void,
1326 ),
1327 )?;
1328 }
1329 Ok(true)
1330 }
1331
1332 /// The coalesce-arm split the `_wp` decompress launch takes at this shape, when the fused
1333 /// q8_1 epilogue can ride it: `MEMRA_MLA_COALESCE=1`, split > 1, and `d_v / split` a whole
1334 /// number of q8 blocks (<= 256 wide). `None` means the plain kernels run unchanged.
1335 fn mla_decompress_v_zq8_split(&self, t_q: usize, n_head: usize, d_v: usize) -> Option<i32> {
1336 if !mla_coalesce_on() {
1337 return None;
1338 }
1339 let split = mla_b200_split_for(MlaB200Kernel::DecompressV, t_q, d_v)
1340 .or_else(|| mla_decode_split_for(t_q * n_head, d_v))
1341 .unwrap_or(1);
1342 if split <= 1 || !d_v.is_multiple_of(split as usize) {
1343 return None;
1344 }
1345 let per = d_v / split as usize;
1346 (per.is_multiple_of(32) && per <= 256).then_some(split)
1347 }
1348
1349 /// [`Engine::mla_decompress_v`]'s coalesce arm emitting `wo`'s q8_1 pair beside `out`
1350 /// (`memra_mla_decompress_v_wp_zq8_kernel`, MEMRA_MLA_WO_ZQ8): `out` bit-identical to the plain
1351 /// launch, the pair bit-identical to `quantize_q8_1(out, t_q, n_head * d_v)`. `Ok(None)` when
1352 /// the shape or the arm does not fit (the caller then runs the plain sequence).
1353 #[allow(clippy::too_many_arguments)]
1354 pub fn mla_decompress_v_zq8(
1355 &self,
1356 o_lat: &CudaSlice<f32>,
1357 wv_b: &CudaSlice<f32>,
1358 out: &mut CudaSlice<f32>,
1359 t_q: usize,
1360 n_head: usize,
1361 d_v: usize,
1362 kv_rank: usize,
1363 ) -> Res<Option<(CudaSlice<i8>, CudaSlice<f32>)>> {
1364 let Some(split) = self.mla_decompress_v_zq8_split(t_q, n_head, d_v) else {
1365 return Ok(None);
1366 };
1367 let n = t_q * n_head * d_v;
1368 let mut q = self.alloc_i8_uninit(n)?;
1369 let mut d = self.uninit(n / 32)?;
1370 let s = self.stream();
1371 mla_coalesce_announce("decompress_v_zq8", t_q, n_head, split);
1372 unsafe {
1373 ck(
1374 "decompress_v_wp_zq8",
1375 memra_mla_decompress_v_wp_zq8_f32(
1376 o_lat.device_ptr(&s).0 as *const f32,
1377 wv_b.device_ptr(&s).0 as *const f32,
1378 out.device_ptr_mut(&s).0 as *mut f32,
1379 q.device_ptr_mut(&s).0 as *mut i8,
1380 d.device_ptr_mut(&s).0 as *mut f32,
1381 t_q as i32,
1382 n_head as i32,
1383 d_v as i32,
1384 kv_rank as i32,
1385 split,
1386 s.cu_stream() as *mut c_void,
1387 ),
1388 )?;
1389 }
1390 Ok(Some((q, d)))
1391 }
1392
1393 /// BF16-plane twin of [`Engine::mla_decompress_v_zq8`] (the `MEMRA_MLA_ABSORB_BF16` arm).
1394 #[allow(clippy::too_many_arguments)]
1395 pub fn mla_decompress_v_bf16_zq8(
1396 &self,
1397 o_lat: &CudaSlice<f32>,
1398 wv_b16: &CudaSlice<u16>,
1399 out: &mut CudaSlice<f32>,
1400 t_q: usize,
1401 n_head: usize,
1402 d_v: usize,
1403 kv_rank: usize,
1404 ) -> Res<Option<(CudaSlice<i8>, CudaSlice<f32>)>> {
1405 let Some(split) = self.mla_decompress_v_zq8_split(t_q, n_head, d_v) else {
1406 return Ok(None);
1407 };
1408 let n = t_q * n_head * d_v;
1409 let mut q = self.alloc_i8_uninit(n)?;
1410 let mut d = self.uninit(n / 32)?;
1411 let s = self.stream();
1412 unsafe {
1413 ck(
1414 "decompress_v_wp_bf16_zq8",
1415 memra_mla_decompress_v_wp_bf16_zq8(
1416 o_lat.device_ptr(&s).0 as *const f32,
1417 wv_b16.device_ptr(&s).0 as *const u16,
1418 out.device_ptr_mut(&s).0 as *mut f32,
1419 q.device_ptr_mut(&s).0 as *mut i8,
1420 d.device_ptr_mut(&s).0 as *mut f32,
1421 t_q as i32,
1422 n_head as i32,
1423 d_v as i32,
1424 kv_rank as i32,
1425 split,
1426 s.cu_stream() as *mut c_void,
1427 ),
1428 )?;
1429 }
1430 Ok(Some((q, d)))
1431 }
1432
1433 #[allow(clippy::too_many_arguments)]
1434 pub fn mla_absorb_q(
1435 &self,
1436 q_nope: &CudaSlice<f32>,
1437 wk_b: &CudaSlice<f32>,
1438 q_lat: &mut CudaSlice<f32>,
1439 t_q: usize,
1440 n_head: usize,
1441 d_nope: usize,
1442 kv_rank: usize,
1443 ) -> Res<()> {
1444 let s = self.stream();
1445 // MEMRA_MLA_COALESCE door, checked FIRST because it changes how a row is READ, which
1446 // is orthogonal to every door below (they choose the output-range partition). It reuses
1447 // their policy rather than replacing it: ask the B200 arm, then the generic split, and
1448 // pass whatever split they choose (1 = unsplit). Consulting `mla_decode_split_for` also
1449 // ticks that door's own dispatch counter, which is correct — it WAS consulted and its
1450 // partition IS the one running.
1451 // MEASURED 2026-09-03, 2x B200: at split 1 (64 blocks) warp-per-row REGRESSES -11%
1452 // (51.01 vs 57.34): one row in flight per warp with a serial shuffle reduction after
1453 // each replaces 16,384 threads x 1 row with 512 warps x 1 row, a 32x loss of memory
1454 // parallelism that outweighs the coalescing. At split 16 (1,024 blocks) it is +1.9% on
1455 // top of the split. So the door only engages when a split door gave it a grid to spend
1456 // the coalescing on; at split 1 it falls through to the dispatch below, unchanged.
1457 let coalesce_split = if mla_coalesce_on() {
1458 mla_b200_split_for(MlaB200Kernel::AbsorbQ, t_q, kv_rank)
1459 .or_else(|| mla_decode_split_for(t_q * n_head, kv_rank))
1460 .unwrap_or(1)
1461 } else {
1462 1
1463 };
1464 if coalesce_split > 1 {
1465 let split = coalesce_split;
1466 mla_coalesce_announce("absorb_q", t_q, n_head, split);
1467 return unsafe {
1468 ck(
1469 "absorb_q_wp",
1470 memra_mla_absorb_q_wp_f32(
1471 q_nope.device_ptr(&s).0 as *const f32,
1472 wk_b.device_ptr(&s).0 as *const f32,
1473 q_lat.device_ptr_mut(&s).0 as *mut f32,
1474 t_q as i32,
1475 n_head as i32,
1476 d_nope as i32,
1477 kv_rank as i32,
1478 split,
1479 s.cu_stream() as *mut c_void,
1480 ),
1481 )
1482 };
1483 }
1484 // MEMRA_B200_MLA_DECODE_ARM door (checked first; split from the t_q-keyed table
1485 // MLA_B200_ABSORB_Q_SPLIT, a 1 cell falls through to the doors below; the split twin is
1486 // the same kernel the generic door launches, so this is only a policy pick).
1487 if let Some(split) = mla_b200_split_for(MlaB200Kernel::AbsorbQ, t_q, kv_rank) {
1488 mla_b200_split_announce("absorb_q", t_q, n_head, split);
1489 return unsafe {
1490 ck(
1491 "absorb_q_split_b200",
1492 memra_mla_absorb_q_split_f32(
1493 q_nope.device_ptr(&s).0 as *const f32,
1494 wk_b.device_ptr(&s).0 as *const f32,
1495 q_lat.device_ptr_mut(&s).0 as *mut f32,
1496 t_q as i32,
1497 n_head as i32,
1498 d_nope as i32,
1499 kv_rank as i32,
1500 split,
1501 s.cu_stream() as *mut c_void,
1502 ),
1503 )
1504 };
1505 }
1506 // MEMRA_MLA_DECODE_SPLIT door: same bytes at any split (see mla_decode_split_for).
1507 if let Some(split) = mla_decode_split_for(t_q * n_head, kv_rank) {
1508 mla_split_announce("absorb_q", t_q, n_head, split);
1509 return unsafe {
1510 ck(
1511 "absorb_q_split",
1512 memra_mla_absorb_q_split_f32(
1513 q_nope.device_ptr(&s).0 as *const f32,
1514 wk_b.device_ptr(&s).0 as *const f32,
1515 q_lat.device_ptr_mut(&s).0 as *mut f32,
1516 t_q as i32,
1517 n_head as i32,
1518 d_nope as i32,
1519 kv_rank as i32,
1520 split,
1521 s.cu_stream() as *mut c_void,
1522 ),
1523 )
1524 };
1525 }
1526 unsafe {
1527 ck(
1528 "absorb_q",
1529 memra_mla_absorb_q_f32(
1530 q_nope.device_ptr(&s).0 as *const f32,
1531 wk_b.device_ptr(&s).0 as *const f32,
1532 q_lat.device_ptr_mut(&s).0 as *mut f32,
1533 t_q as i32,
1534 n_head as i32,
1535 d_nope as i32,
1536 kv_rank as i32,
1537 s.cu_stream() as *mut c_void,
1538 ),
1539 )
1540 }
1541 }
1542
1543 /// Decompress: `out[i][h][:] = w_uv[h] · o_lat[i][h][:]`.
1544 #[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
1545 pub fn mla_decompress_v(
1546 &self,
1547 o_lat: &CudaSlice<f32>,
1548 wv_b: &CudaSlice<f32>,
1549 out: &mut CudaSlice<f32>,
1550 t_q: usize,
1551 n_head: usize,
1552 d_v: usize,
1553 kv_rank: usize,
1554 ) -> Res<()> {
1555 let s = self.stream();
1556 // MEMRA_MLA_COALESCE door, checked FIRST because it changes how a row is READ, which
1557 // is orthogonal to every door below (they choose the output-range partition). It reuses
1558 // their policy rather than replacing it: ask the B200 arm, then the generic split, and
1559 // pass whatever split they choose (1 = unsplit). Consulting `mla_decode_split_for` also
1560 // ticks that door's own dispatch counter, which is correct — it WAS consulted and its
1561 // partition IS the one running.
1562 // MEASURED 2026-09-03, 2x B200: at split 1 (64 blocks) warp-per-row REGRESSES -11%
1563 // (51.01 vs 57.34): one row in flight per warp with a serial shuffle reduction after
1564 // each replaces 16,384 threads x 1 row with 512 warps x 1 row, a 32x loss of memory
1565 // parallelism that outweighs the coalescing. At split 16 (1,024 blocks) it is +1.9% on
1566 // top of the split. So the door only engages when a split door gave it a grid to spend
1567 // the coalescing on; at split 1 it falls through to the dispatch below, unchanged.
1568 let coalesce_split = if mla_coalesce_on() {
1569 mla_b200_split_for(MlaB200Kernel::DecompressV, t_q, d_v)
1570 .or_else(|| mla_decode_split_for(t_q * n_head, d_v))
1571 .unwrap_or(1)
1572 } else {
1573 1
1574 };
1575 if coalesce_split > 1 {
1576 let split = coalesce_split;
1577 mla_coalesce_announce("decompress_v", t_q, n_head, split);
1578 return unsafe {
1579 ck(
1580 "decompress_v_wp",
1581 memra_mla_decompress_v_wp_f32(
1582 o_lat.device_ptr(&s).0 as *const f32,
1583 wv_b.device_ptr(&s).0 as *const f32,
1584 out.device_ptr_mut(&s).0 as *mut f32,
1585 t_q as i32,
1586 n_head as i32,
1587 d_v as i32,
1588 kv_rank as i32,
1589 split,
1590 s.cu_stream() as *mut c_void,
1591 ),
1592 )
1593 };
1594 }
1595 // MEMRA_B200_MLA_DECODE_ARM door (checked first, table MLA_B200_DECOMPRESS_V_SPLIT; see
1596 // mla_absorb_q above).
1597 if let Some(split) = mla_b200_split_for(MlaB200Kernel::DecompressV, t_q, d_v) {
1598 mla_b200_split_announce("decompress_v", t_q, n_head, split);
1599 return unsafe {
1600 ck(
1601 "decompress_v_split_b200",
1602 memra_mla_decompress_v_split_f32(
1603 o_lat.device_ptr(&s).0 as *const f32,
1604 wv_b.device_ptr(&s).0 as *const f32,
1605 out.device_ptr_mut(&s).0 as *mut f32,
1606 t_q as i32,
1607 n_head as i32,
1608 d_v as i32,
1609 kv_rank as i32,
1610 split,
1611 s.cu_stream() as *mut c_void,
1612 ),
1613 )
1614 };
1615 }
1616 // MEMRA_MLA_DECODE_SPLIT door: same bytes at any split (see mla_decode_split_for).
1617 if let Some(split) = mla_decode_split_for(t_q * n_head, d_v) {
1618 mla_split_announce("decompress_v", t_q, n_head, split);
1619 return unsafe {
1620 ck(
1621 "decompress_v_split",
1622 memra_mla_decompress_v_split_f32(
1623 o_lat.device_ptr(&s).0 as *const f32,
1624 wv_b.device_ptr(&s).0 as *const f32,
1625 out.device_ptr_mut(&s).0 as *mut f32,
1626 t_q as i32,
1627 n_head as i32,
1628 d_v as i32,
1629 kv_rank as i32,
1630 split,
1631 s.cu_stream() as *mut c_void,
1632 ),
1633 )
1634 };
1635 }
1636 unsafe {
1637 ck(
1638 "decompress_v",
1639 memra_mla_decompress_v_f32(
1640 o_lat.device_ptr(&s).0 as *const f32,
1641 wv_b.device_ptr(&s).0 as *const f32,
1642 out.device_ptr_mut(&s).0 as *mut f32,
1643 t_q as i32,
1644 n_head as i32,
1645 d_v as i32,
1646 kv_rank as i32,
1647 s.cu_stream() as *mut c_void,
1648 ),
1649 )
1650 }
1651 }
1652
1653 /// Absorbed-form MQA attention over the latent cache. `q_pe` is ignored when
1654 /// `d_rope == 0`; callers on the NoPE path may pass any allocated slice.
1655 #[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
1656 pub fn mla_attn_absorbed(
1657 &self,
1658 q_lat: &CudaSlice<f32>,
1659 q_pe: &CudaSlice<f32>,
1660 cache: &CudaSlice<f32>,
1661 o_lat: &mut CudaSlice<f32>,
1662 n_head: usize,
1663 kv_rank: usize,
1664 d_rope: usize,
1665 t_q: usize,
1666 t_kv: usize,
1667 scale: f32,
1668 ) -> Res<()> {
1669 let s = self.stream();
1670 unsafe {
1671 ck(
1672 "attn_absorbed",
1673 memra_mla_attn_absorbed_f32(
1674 q_lat.device_ptr(&s).0 as *const f32,
1675 q_pe.device_ptr(&s).0 as *const f32,
1676 cache.device_ptr(&s).0 as *const f32,
1677 o_lat.device_ptr_mut(&s).0 as *mut f32,
1678 n_head as i32,
1679 kv_rank as i32,
1680 d_rope as i32,
1681 t_q as i32,
1682 t_kv as i32,
1683 scale,
1684 s.cu_stream() as *mut c_void,
1685 ),
1686 )
1687 }
1688 }
1689
1690 /// Live-length twin of [`Engine::mla_attn_absorbed`]: `t_kv = pos_d[0] + t_q` read on the
1691 /// device (the decode-graph door's position word), fixed launch geometry, bit-identical to the
1692 /// scalar launch at the same length (`tests/mla_live_len_gpu.rs`). The caller owns the
1693 /// `t_q <= t_kv` invariant the scalar launcher checks on the host.
1694 #[allow(clippy::too_many_arguments)]
1695 pub fn mla_attn_absorbed_live(
1696 &self,
1697 q_lat: &CudaSlice<f32>,
1698 q_pe: &CudaSlice<f32>,
1699 cache: &CudaSlice<f32>,
1700 o_lat: &mut CudaSlice<f32>,
1701 n_head: usize,
1702 kv_rank: usize,
1703 d_rope: usize,
1704 t_q: usize,
1705 pos_d: &CudaSlice<i32>,
1706 scale: f32,
1707 ) -> Res<()> {
1708 let s = self.stream();
1709 unsafe {
1710 ck(
1711 "attn_absorbed_live",
1712 memra_mla_attn_absorbed_live_f32(
1713 q_lat.device_ptr(&s).0 as *const f32,
1714 q_pe.device_ptr(&s).0 as *const f32,
1715 cache.device_ptr(&s).0 as *const f32,
1716 o_lat.device_ptr_mut(&s).0 as *mut f32,
1717 n_head as i32,
1718 kv_rank as i32,
1719 d_rope as i32,
1720 t_q as i32,
1721 pos_d.device_ptr(&s).0 as *const i32,
1722 scale,
1723 s.cu_stream() as *mut c_void,
1724 ),
1725 )
1726 }
1727 }
1728
1729 /// Live-slot twin of [`Engine::mla_append_latent`]: the row offset is `pos_d[0]` on the
1730 /// device. Bit-identical to the scalar launch at `slot == pos_d[0]`.
1731 #[allow(clippy::too_many_arguments)]
1732 pub fn mla_append_latent_live(
1733 &self,
1734 cache: &mut CudaSlice<f32>,
1735 c_kv: &CudaSlice<f32>,
1736 k_pe: &CudaSlice<f32>,
1737 pos_d: &CudaSlice<i32>,
1738 t: usize,
1739 kv_rank: usize,
1740 d_rope: usize,
1741 ) -> Res<()> {
1742 let s = self.stream();
1743 unsafe {
1744 ck(
1745 "append_latent_live",
1746 memra_mla_append_latent_live_f32(
1747 cache.device_ptr_mut(&s).0 as *mut f32,
1748 c_kv.device_ptr(&s).0 as *const f32,
1749 k_pe.device_ptr(&s).0 as *const f32,
1750 pos_d.device_ptr(&s).0 as *const i32,
1751 t as i32,
1752 kv_rank as i32,
1753 d_rope as i32,
1754 s.cu_stream() as *mut c_void,
1755 ),
1756 )
1757 }
1758 }
1759}
1760
1761/// Safe wrappers for the DSA k-pool indexer (`cu/mla_attn.cu`, "DSA k-pool indexer" section).
1762/// Numeric truth is `memra_reference::kpool_allowed_tokens`; the gate is
1763/// `tests/glm5_kpool_indexer_gpu.rs`.
1764impl Engine {
1765 /// Collapse pools `[pool_begin, n_pools)` of `pool` cached indexer rows each into one key by a
1766 /// learned per-channel softmax over (gate score + positional embedding).
1767 /// `state` rows are `[k | gate]`, `2 * d` wide; `ape` is `[pool][d]` row-major.
1768 ///
1769 /// `pool_begin` is the RESIDENCY seam: a pool's key depends only on its own `pool` state rows
1770 /// (append-only, never rewritten) and the constant `ape`, so it is final the instant the
1771 /// pool's last row lands. Pools below `pool_begin` are already resident and are left alone —
1772 /// bit-identically to what rebuilding them would produce. Pass 0 for a full rebuild.
1773 ///
1774 /// `state_rows` is the indexer plane's TAIL-RING size in rows (0 = flat, absolute
1775 /// addressing). It is always a multiple of `pool`, so a pool's members stay contiguous
1776 /// across the wrap and the collapse reads the same values in the same order either way.
1777 #[allow(clippy::too_many_arguments)]
1778 pub fn mla_kpool_pool_keys(
1779 &self,
1780 state: &CudaSlice<f32>,
1781 ape: &CudaSlice<f32>,
1782 pool_keys: &mut CudaSlice<f32>,
1783 pool_begin: usize,
1784 n_pools: usize,
1785 pool: usize,
1786 d: usize,
1787 state_rows: usize,
1788 ) -> Res<()> {
1789 let s = self.stream();
1790 unsafe {
1791 ck(
1792 "kpool_pool_keys",
1793 memra_mla_kpool_pool_keys_f32(
1794 state.device_ptr(&s).0 as *const f32,
1795 ape.device_ptr(&s).0 as *const f32,
1796 pool_keys.device_ptr_mut(&s).0 as *mut f32,
1797 pool_begin as i32,
1798 n_pools as i32,
1799 pool as i32,
1800 d as i32,
1801 state_rows as i32,
1802 s.cu_stream() as *mut c_void,
1803 ),
1804 )
1805 }
1806 }
1807
1808 /// Append `t` packed indexer rows `[k_norm | gate]` at absolute row `slot`, wrapping mod
1809 /// `rows` when the plane is a TAIL RING (`rows == 0` is the flat plane).
1810 ///
1811 /// SEPARATE from [`Engine::mla_append_latent`] on purpose: the latent plane is re-read by
1812 /// every later query through the gathered attention walk and is NOT a ring, so the two planes
1813 /// must not share a row-addressing contract even though they share a row shape.
1814 #[allow(clippy::too_many_arguments)]
1815 ///
1816 /// `src_row` is the first SOURCE row of `a`/`b` to append: the call's `k_norm`/`gate` are
1817 /// computed once for the whole call, and the tail-ring drain (`mla_kpool_indices`) walks them
1818 /// in sub-ranges. `src_row` 0 is the whole-call append.
1819 pub fn mla_index_append(
1820 &self,
1821 plane: &mut CudaSlice<f32>,
1822 a: &CudaSlice<f32>,
1823 b: &CudaSlice<f32>,
1824 src_row: usize,
1825 slot: usize,
1826 t: usize,
1827 wa: usize,
1828 wb: usize,
1829 rows: usize,
1830 ) -> Res<()> {
1831 let s = self.stream();
1832 unsafe {
1833 ck(
1834 "index_append_ring",
1835 memra_mla_index_append_ring_f32(
1836 plane.device_ptr_mut(&s).0 as *mut f32,
1837 (a.device_ptr(&s).0 as *const f32).add(src_row * wa),
1838 (b.device_ptr(&s).0 as *const f32).add(src_row * wb),
1839 slot as i32,
1840 t as i32,
1841 wa as i32,
1842 wb as i32,
1843 rows as i32,
1844 s.cu_stream() as *mut c_void,
1845 ),
1846 )
1847 }
1848 }
1849
1850 /// Head-mixed pool scores, `-inf` on pools whose last token is not visible to the query.
1851 /// `first_pos` is the absolute cache row of query 0 (queries are the cache's last `t_q` rows).
1852 ///
1853 /// Register-tiled fused GEMM+head-reduce: the pool-key tile stays resident in shared memory
1854 /// across the head loop, so `pool_keys` is read once per query TILE instead of once per
1855 /// query, and the head mix lands in the accumulator instead of costing a second pass over a
1856 /// `[t_q * heads, n_pools]` plane (17 GB at the shipped 1M/512 shape). BIT-IDENTICAL to
1857 /// [`Engine::mla_kpool_score_ref`] by construction — same six-step rounding sequence, spelled
1858 /// with explicit intrinsics — and gated so
1859 /// (`gpu_kpool_scoring_is_byte_identical_to_the_reference_kernel`). See the scoring section
1860 /// of `cu/mla_attn.cu` for why that identity is the requirement and not a nicety.
1861 #[allow(clippy::too_many_arguments)]
1862 pub fn mla_kpool_score(
1863 &self,
1864 q: &CudaSlice<f32>,
1865 pool_keys: &CudaSlice<f32>,
1866 head_weights: &CudaSlice<f32>,
1867 score: &mut CudaSlice<f32>,
1868 t_q: usize,
1869 heads: usize,
1870 d: usize,
1871 n_pools: usize,
1872 pool: usize,
1873 first_pos: usize,
1874 qk_scale: f32,
1875 head_scale: f32,
1876 ) -> Res<()> {
1877 let s = self.stream();
1878 // MEMRA_B200_DSA_DECODE door (level >= 1): the head-blocked decode scorer. Engages only
1879 // at decode widths and only from MLA_DSA_SCORE_MIN_POOLS up, where the block count can
1880 // fill the die; below that the shipped dispatch's own measured crossover already sends
1881 // decode to the reference kernel, which wins there. Bit-identical, so this is a speed
1882 // choice and nothing else. See research/b200-dsa-decode-20260902/ROOFLINE.md §2.
1883 if mla_dsa_decode_level() >= 1
1884 && (1..=MLA_DSA_ARM_T_MAX).contains(&t_q)
1885 && n_pools >= MLA_DSA_SCORE_MIN_POOLS
1886 {
1887 let rc = unsafe {
1888 memra_mla_kpool_score_dsa_f32(
1889 q.device_ptr(&s).0 as *const f32,
1890 pool_keys.device_ptr(&s).0 as *const f32,
1891 head_weights.device_ptr(&s).0 as *const f32,
1892 score.device_ptr_mut(&s).0 as *mut f32,
1893 t_q as i32,
1894 heads as i32,
1895 d as i32,
1896 n_pools as i32,
1897 pool as i32,
1898 first_pos as i32,
1899 qk_scale,
1900 head_scale,
1901 s.cu_stream() as *mut c_void,
1902 )
1903 };
1904 if !mla_dsa_geometry_refusal(rc) {
1905 mla_dsa_announce(
1906 "kpool_score",
1907 t_q,
1908 &format!("arm=head-blocked heads={heads} pools={n_pools} class=bit-identical"),
1909 );
1910 return ck("kpool_score_dsa", rc);
1911 }
1912 }
1913 unsafe {
1914 ck(
1915 "kpool_score",
1916 memra_mla_kpool_score_f32(
1917 q.device_ptr(&s).0 as *const f32,
1918 pool_keys.device_ptr(&s).0 as *const f32,
1919 head_weights.device_ptr(&s).0 as *const f32,
1920 score.device_ptr_mut(&s).0 as *mut f32,
1921 t_q as i32,
1922 heads as i32,
1923 d as i32,
1924 n_pools as i32,
1925 pool as i32,
1926 first_pos as i32,
1927 qk_scale,
1928 head_scale,
1929 s.cu_stream() as *mut c_void,
1930 ),
1931 )
1932 }
1933 }
1934
1935 /// The RETAINED reference scorer: block per (query, pool), one thread per head, head sum
1936 /// walked sequentially by thread 0. It defines the arithmetic [`Engine::mla_kpool_score`]
1937 /// reproduces, and it is the only consumer-visible reason this crate still builds the slow
1938 /// kernel. Not a serving path — `O(t_q * n_pools)` blocks of `heads` threads.
1939 #[allow(clippy::too_many_arguments)]
1940 pub fn mla_kpool_score_ref(
1941 &self,
1942 q: &CudaSlice<f32>,
1943 pool_keys: &CudaSlice<f32>,
1944 head_weights: &CudaSlice<f32>,
1945 score: &mut CudaSlice<f32>,
1946 t_q: usize,
1947 heads: usize,
1948 d: usize,
1949 n_pools: usize,
1950 pool: usize,
1951 first_pos: usize,
1952 qk_scale: f32,
1953 head_scale: f32,
1954 ) -> Res<()> {
1955 let s = self.stream();
1956 unsafe {
1957 ck(
1958 "kpool_score_ref",
1959 memra_mla_kpool_score_ref_f32(
1960 q.device_ptr(&s).0 as *const f32,
1961 pool_keys.device_ptr(&s).0 as *const f32,
1962 head_weights.device_ptr(&s).0 as *const f32,
1963 score.device_ptr_mut(&s).0 as *mut f32,
1964 t_q as i32,
1965 heads as i32,
1966 d as i32,
1967 n_pools as i32,
1968 pool as i32,
1969 first_pos as i32,
1970 qk_scale,
1971 head_scale,
1972 s.cu_stream() as *mut c_void,
1973 ),
1974 )
1975 }
1976 }
1977
1978 /// Top-`select_k` pools per query expanded to ascending cache rows, tail appended, -1 padded.
1979 ///
1980 /// Radix select on the 64-bit order key `(desc32(score) << 32) | pool_index`, whose ascending
1981 /// order IS the oracle's "score descending, pool index ascending" — see the ORDER contract
1982 /// block in `cu/mla_attn.cu`. `O(8 * n_pools / threads)` per query, independent of `select_k`.
1983 #[allow(clippy::too_many_arguments)]
1984 /// Live-count twin of the DSA decode scorer (`MEMRA_B200_DSA_DECODE` level >= 1): `n_pools`
1985 /// read from the door's device word, launch grid from `n_pools_cap`, t_q must be 1 (one
1986 /// score row, so the row stride does not depend on the count). Bit-identical to
1987 /// `memra_mla_kpool_score_dsa_f32` at the same count (`tests/mla_kpool_live_gpu.rs`).
1988 #[allow(clippy::too_many_arguments)]
1989 pub fn mla_kpool_score_dsa_live(
1990 &self,
1991 q: &CudaSlice<f32>,
1992 pool_keys: &CudaSlice<f32>,
1993 head_weights: &CudaSlice<f32>,
1994 score: &mut CudaSlice<f32>,
1995 heads: usize,
1996 d: usize,
1997 n_pools_d: &CudaSlice<i32>,
1998 n_pools_cap: usize,
1999 pool: usize,
2000 first_pos: usize,
2001 qk_scale: f32,
2002 head_scale: f32,
2003 ) -> Res<()> {
2004 let s = self.stream();
2005 unsafe {
2006 ck(
2007 "kpool_score_dsa_live",
2008 memra_mla_kpool_score_dsa_live_f32(
2009 q.device_ptr(&s).0 as *const f32,
2010 pool_keys.device_ptr(&s).0 as *const f32,
2011 head_weights.device_ptr(&s).0 as *const f32,
2012 score.device_ptr_mut(&s).0 as *mut f32,
2013 1,
2014 heads as i32,
2015 d as i32,
2016 n_pools_d.device_ptr(&s).0 as *const i32,
2017 n_pools_cap as i32,
2018 pool as i32,
2019 first_pos as i32,
2020 qk_scale,
2021 head_scale,
2022 s.cu_stream() as *mut c_void,
2023 ),
2024 )
2025 }
2026 }
2027
2028 /// Live-count twin of the single-CTA selector: `n_pools` from the device word, grid t_q.
2029 /// Bit-identical to `memra_mla_kpool_select_f32` at the same count.
2030 #[allow(clippy::too_many_arguments)]
2031 pub fn mla_kpool_select_live(
2032 &self,
2033 score: &CudaSlice<f32>,
2034 idx: &mut CudaSlice<i32>,
2035 t_q: usize,
2036 n_pools_d: &CudaSlice<i32>,
2037 pool: usize,
2038 select_k: usize,
2039 width: usize,
2040 first_pos: usize,
2041 always_tail: bool,
2042 ) -> Res<()> {
2043 let s = self.stream();
2044 unsafe {
2045 ck(
2046 "kpool_select_live",
2047 memra_mla_kpool_select_live_f32(
2048 score.device_ptr(&s).0 as *const f32,
2049 idx.device_ptr_mut(&s).0 as *mut i32,
2050 t_q as i32,
2051 n_pools_d.device_ptr(&s).0 as *const i32,
2052 pool as i32,
2053 select_k as i32,
2054 width as i32,
2055 first_pos as i32,
2056 always_tail as i32,
2057 s.cu_stream() as *mut c_void,
2058 ),
2059 )
2060 }
2061 }
2062
2063 #[allow(clippy::too_many_arguments)]
2064 pub fn mla_kpool_select(
2065 &self,
2066 score: &CudaSlice<f32>,
2067 idx: &mut CudaSlice<i32>,
2068 t_q: usize,
2069 n_pools: usize,
2070 pool: usize,
2071 select_k: usize,
2072 width: usize,
2073 first_pos: usize,
2074 always_tail: bool,
2075 ) -> Res<()> {
2076 let s = self.stream();
2077 // MEMRA_B200_DSA_SELECT door: the exact multi-CTA selector. Byte-identical output, so
2078 // this is a speed choice and nothing else; it engages only where the single-CTA kernel
2079 // has parallelism to gain (see MLA_DSA_SELECT_MIN_POOLS).
2080 if mla_dsa_select_on() && mla_dsa_select_engages(t_q, n_pools) {
2081 let n_ctas = unsafe { memra_mla_kpool_select_ctas(n_pools as i32) };
2082 let stride = unsafe { memra_mla_kpool_select_ws_ints(n_ctas) };
2083 let mut ws = self.uninit_i32(t_q * stride as usize)?;
2084 mla_dsa_select_announce(t_q, n_pools, n_ctas);
2085 return unsafe {
2086 ck(
2087 "kpool_select_dsa",
2088 memra_mla_kpool_select_dsa_f32(
2089 score.device_ptr(&s).0 as *const f32,
2090 idx.device_ptr_mut(&s).0 as *mut i32,
2091 ws.device_ptr_mut(&s).0 as *mut i32,
2092 t_q as i32,
2093 n_pools as i32,
2094 pool as i32,
2095 select_k as i32,
2096 width as i32,
2097 first_pos as i32,
2098 i32::from(always_tail),
2099 s.cu_stream() as *mut c_void,
2100 ),
2101 )
2102 };
2103 }
2104 unsafe {
2105 ck(
2106 "kpool_select",
2107 memra_mla_kpool_select_f32(
2108 score.device_ptr(&s).0 as *const f32,
2109 idx.device_ptr_mut(&s).0 as *mut i32,
2110 t_q as i32,
2111 n_pools as i32,
2112 pool as i32,
2113 select_k as i32,
2114 width as i32,
2115 first_pos as i32,
2116 i32::from(always_tail),
2117 s.cu_stream() as *mut c_void,
2118 ),
2119 )
2120 }
2121 }
2122
2123 /// The `select_k`-rounds reference selection — the DEFINITION of the order the radix kernel
2124 /// above must reproduce. NOT a serving path: it is `O(select_k * n_pools / threads)` and
2125 /// exists so `gpu_kpool_radix_selection_is_byte_identical_to_the_reference_kernel` can hold
2126 /// the fast kernel to it at shapes the micro fixture cannot reach.
2127 #[allow(clippy::too_many_arguments)]
2128 pub fn mla_kpool_select_ref(
2129 &self,
2130 score: &CudaSlice<f32>,
2131 idx: &mut CudaSlice<i32>,
2132 t_q: usize,
2133 n_pools: usize,
2134 pool: usize,
2135 select_k: usize,
2136 width: usize,
2137 first_pos: usize,
2138 always_tail: bool,
2139 ) -> Res<()> {
2140 let s = self.stream();
2141 unsafe {
2142 ck(
2143 "kpool_select_ref",
2144 memra_mla_kpool_select_ref_f32(
2145 score.device_ptr(&s).0 as *const f32,
2146 idx.device_ptr_mut(&s).0 as *mut i32,
2147 t_q as i32,
2148 n_pools as i32,
2149 pool as i32,
2150 select_k as i32,
2151 width as i32,
2152 first_pos as i32,
2153 i32::from(always_tail),
2154 s.cu_stream() as *mut c_void,
2155 ),
2156 )
2157 }
2158 }
2159
2160 /// Strided-batched BF16 tensor-core GEMM over per-head planes — the
2161 /// MEMRA_MLA_TC_PREFILL absorb/decompress engine. Per head `b` in `0..batch`:
2162 /// `y_b[m, n] = x_b[m, k] @ w_b[n, k]^T`, f32 accumulate.
2163 ///
2164 /// `w` is the bf16 conversion-split weight plane: per-head `[n, k]` row-major,
2165 /// batch stride `n * k` (baked into the C side). `x` is a bf16 VIEW of a
2166 /// `[m, batch, k]` activation plane: per-head row stride `x_rs`, per-head base
2167 /// offset `x_bs` — for the canonical `[t, n_head, d]` layout that is
2168 /// `x_rs = batch * k`, `x_bs = k`. `y` mirrors that with `y_rs`/`y_bs` over `n`.
2169 ///
2170 /// `y_bf16` selects the output dtype: `true` writes bf16 (feeds the TC attention
2171 /// kernel directly, one fewer convert), `false` writes f32 (re-enters the f32
2172 /// stream). The caller passes `y` as raw bytes either way; an f32 output slice
2173 /// is viewed through its byte layout by the caller (`mla_bf16_gemm_sb_f32out`).
2174 ///
2175 /// rc 2xxxx (no cuBLASLt heuristic for the shape) is a DECLINE class the caller
2176 /// may fall back on; everything else is a hard error.
2177 #[allow(clippy::too_many_arguments)]
2178 pub fn mla_bf16_gemm_sb_raw(
2179 &self,
2180 w_bf16: &CudaSlice<u8>,
2181 x_bf16: &CudaSlice<u8>,
2182 y_ptr: u64,
2183 m: usize,
2184 n: usize,
2185 k: usize,
2186 x_rs: usize,
2187 x_bs: usize,
2188 y_rs: usize,
2189 y_bs: usize,
2190 batch: usize,
2191 y_bf16: bool,
2192 ) -> Res<i32> {
2193 // Workspace from the shared f16/bf16 Lt scratch (bf16_tc_gemm pattern).
2194 let mut guard = self.f16_scratch.lock().unwrap();
2195 if guard.is_none() {
2196 *guard = Some(crate::f16_ffi::F16Scratch::with_capacity(self, 2)?);
2197 }
2198 let s_scr = guard.as_mut().unwrap();
2199 let s = self.stream();
2200 let rc = unsafe {
2201 memra_bf16_gemm_sb(
2202 w_bf16.device_ptr(&s).0 as *const c_void,
2203 x_bf16.device_ptr(&s).0 as *const c_void,
2204 y_ptr as *mut c_void,
2205 m as i32,
2206 n as i32,
2207 k as i32,
2208 x_rs as i64,
2209 x_bs as i64,
2210 y_rs as i64,
2211 y_bs as i64,
2212 batch as i32,
2213 i32::from(y_bf16),
2214 s_scr.ws.device_ptr_mut(&s).0 as *mut c_void,
2215 crate::f16_ffi::F16_WS_BYTES,
2216 s.cu_stream() as *mut c_void,
2217 )
2218 };
2219 Ok(rc)
2220 }
2221
2222 /// [`Engine::mla_bf16_gemm_sb_raw`] with a bf16 output plane (absorb: feeds the TC
2223 /// attention kernel). Non-decline errors are named; a 2xxxx decline is returned as
2224 /// `Ok(false)` so the door can fall back to the per-position kernels.
2225 #[allow(clippy::too_many_arguments)]
2226 pub fn mla_bf16_gemm_sb_bf16out(
2227 &self,
2228 w_bf16: &CudaSlice<u8>,
2229 x_bf16: &CudaSlice<u8>,
2230 y_bf16: &mut CudaSlice<u8>,
2231 m: usize,
2232 n: usize,
2233 k: usize,
2234 x_rs: usize,
2235 x_bs: usize,
2236 y_rs: usize,
2237 y_bs: usize,
2238 batch: usize,
2239 ) -> Res<bool> {
2240 let s = self.stream();
2241 let (y_ptr, _gy) = y_bf16.device_ptr_mut(&s);
2242 let rc = self.mla_bf16_gemm_sb_raw(
2243 w_bf16, x_bf16, y_ptr, m, n, k, x_rs, x_bs, y_rs, y_bs, batch, true,
2244 )?;
2245 match rc {
2246 0 => Ok(true),
2247 r if (20000..30000).contains(&r) => Ok(false),
2248 r => Err(format!(
2249 "mla bf16 strided-batched GEMM (bf16 out) failed: rc {r} \
2250 (m={m} n={n} k={k} batch={batch})"
2251 )
2252 .into()),
2253 }
2254 }
2255
2256 /// [`Engine::mla_bf16_gemm_sb_raw`] with an f32 output plane (decompress: re-enters
2257 /// the f32 stream). Same decline contract as the bf16-out twin.
2258 #[allow(clippy::too_many_arguments)]
2259 pub fn mla_bf16_gemm_sb_f32out(
2260 &self,
2261 w_bf16: &CudaSlice<u8>,
2262 x_bf16: &CudaSlice<u8>,
2263 y_f32: &mut CudaSlice<f32>,
2264 m: usize,
2265 n: usize,
2266 k: usize,
2267 x_rs: usize,
2268 x_bs: usize,
2269 y_rs: usize,
2270 y_bs: usize,
2271 batch: usize,
2272 ) -> Res<bool> {
2273 let s = self.stream();
2274 let (y_ptr, _gy) = y_f32.device_ptr_mut(&s);
2275 let rc = self.mla_bf16_gemm_sb_raw(
2276 w_bf16, x_bf16, y_ptr, m, n, k, x_rs, x_bs, y_rs, y_bs, batch, false,
2277 )?;
2278 match rc {
2279 0 => Ok(true),
2280 r if (20000..30000).contains(&r) => Ok(false),
2281 r => Err(format!(
2282 "mla bf16 strided-batched GEMM (f32 out) failed: rc {r} \
2283 (m={m} n={n} k={k} batch={batch})"
2284 )
2285 .into()),
2286 }
2287 }
2288
2289 /// Absorbed-form MQA attention over a GATHERED index list (one list per query, shared across
2290 /// heads). Same body as `mla_attn_absorbed`; only the cache walk differs.
2291 #[allow(clippy::too_many_arguments)]
2292 pub fn mla_attn_gathered(
2293 &self,
2294 q_lat: &CudaSlice<f32>,
2295 q_pe: &CudaSlice<f32>,
2296 cache: &CudaSlice<f32>,
2297 idx: &CudaSlice<i32>,
2298 o_lat: &mut CudaSlice<f32>,
2299 n_head: usize,
2300 kv_rank: usize,
2301 d_rope: usize,
2302 t_q: usize,
2303 n_slots: usize,
2304 scale: f32,
2305 ) -> Res<()> {
2306 let s = self.stream();
2307 // MEMRA_B200_DSA_DECODE door, checked FIRST: its arms fight the same 64-CTA t_q=1
2308 // geometry the output-range split below does, without repeating the slot walk.
2309 // THE TWO LEVELS DIFFER TODAY, and the difference is this PR's headline: the shipped
2310 // table is [0, 32, 0, 0, 1, ...], so at t_q=1 level 1 takes arm 0 (falls through to
2311 // the sibling split door) while level 2 takes warp-online chunks=32 -- +9.7% vs
2312 // +43.1% in the 256k serving A/B. The `a >= 2 && dsa_level < 2` guard below exists
2313 // precisely because they differ, and the level boundary IS the numeric-class
2314 // admission boundary. See research/b200-dsa-decode-20260902/ROOFLINE.md.
2315 let dsa_level = mla_dsa_decode_level();
2316 let dsa_arm = if dsa_level >= 1 && t_q <= MLA_DSA_ARM_T_MAX {
2317 // `_effective` already enforces the named-class width rule (plain decode only, so
2318 // the spec-verify batch never sees `dsa-warp-online-f32`); level 2 is the second,
2319 // independent admission for the same class.
2320 let a = mla_dsa_attn_arm_effective(t_q);
2321 if a >= 2 && dsa_level < 2 { 0 } else { a }
2322 } else {
2323 0
2324 };
2325 if dsa_arm >= 2 {
2326 let cells = t_q * n_head * dsa_arm as usize;
2327 let mut part_m = self.uninit(cells)?;
2328 let mut part_d = self.uninit(cells)?;
2329 let mut part_acc = self.uninit(cells * kv_rank)?;
2330 let rc = unsafe {
2331 memra_mla_dsa_attn_split_f32(
2332 q_lat.device_ptr(&s).0 as *const f32,
2333 q_pe.device_ptr(&s).0 as *const f32,
2334 cache.device_ptr(&s).0 as *const f32,
2335 idx.device_ptr(&s).0 as *const i32,
2336 o_lat.device_ptr_mut(&s).0 as *mut f32,
2337 part_m.device_ptr_mut(&s).0 as *mut f32,
2338 part_d.device_ptr_mut(&s).0 as *mut f32,
2339 part_acc.device_ptr_mut(&s).0 as *mut f32,
2340 n_head as i32,
2341 kv_rank as i32,
2342 d_rope as i32,
2343 t_q as i32,
2344 n_slots as i32,
2345 dsa_arm,
2346 scale,
2347 s.cu_stream() as *mut c_void,
2348 )
2349 };
2350 if !mla_dsa_geometry_refusal(rc) {
2351 mla_dsa_announce(
2352 "attn_gathered",
2353 t_q,
2354 &format!("arm=warp-online chunks={dsa_arm} class=dsa-warp-online-f32"),
2355 );
2356 return ck("attn_gathered_dsa_warp", rc);
2357 }
2358 } else if dsa_arm == 1 {
2359 let rc = unsafe {
2360 memra_mla_attn_gathered_dsa_f32(
2361 q_lat.device_ptr(&s).0 as *const f32,
2362 q_pe.device_ptr(&s).0 as *const f32,
2363 cache.device_ptr(&s).0 as *const f32,
2364 idx.device_ptr(&s).0 as *const i32,
2365 o_lat.device_ptr_mut(&s).0 as *mut f32,
2366 n_head as i32,
2367 kv_rank as i32,
2368 d_rope as i32,
2369 t_q as i32,
2370 n_slots as i32,
2371 scale,
2372 s.cu_stream() as *mut c_void,
2373 )
2374 };
2375 if !mla_dsa_geometry_refusal(rc) {
2376 mla_dsa_announce("attn_gathered", t_q, "arm=single-pass class=bit-identical");
2377 return ck("attn_gathered_dsa", rc);
2378 }
2379 }
2380 // MEMRA_B200_MLA_DECODE_ARM door: output-range split from the t_q-keyed table
2381 // MLA_B200_ATTN_GATHERED_SPLIT. This twin repeats the score/softmax walk per split
2382 // block, unlike the absorb/decompress splits, which is why the B200 run found it a win
2383 // at t_q=1 only (see the table comment); every other cell is the shipped kernel.
2384 if let Some(split) = mla_b200_split_for(MlaB200Kernel::AttnGathered, t_q, kv_rank) {
2385 mla_b200_split_announce("attn_gathered", t_q, n_head, split);
2386 return unsafe {
2387 ck(
2388 "attn_gathered_split_b200",
2389 memra_mla_attn_gathered_split_f32(
2390 q_lat.device_ptr(&s).0 as *const f32,
2391 q_pe.device_ptr(&s).0 as *const f32,
2392 cache.device_ptr(&s).0 as *const f32,
2393 idx.device_ptr(&s).0 as *const i32,
2394 o_lat.device_ptr_mut(&s).0 as *mut f32,
2395 n_head as i32,
2396 kv_rank as i32,
2397 d_rope as i32,
2398 t_q as i32,
2399 n_slots as i32,
2400 scale,
2401 split,
2402 s.cu_stream() as *mut c_void,
2403 ),
2404 )
2405 };
2406 }
2407 unsafe {
2408 ck(
2409 "attn_gathered",
2410 memra_mla_attn_gathered_f32(
2411 q_lat.device_ptr(&s).0 as *const f32,
2412 q_pe.device_ptr(&s).0 as *const f32,
2413 cache.device_ptr(&s).0 as *const f32,
2414 idx.device_ptr(&s).0 as *const i32,
2415 o_lat.device_ptr_mut(&s).0 as *mut f32,
2416 n_head as i32,
2417 kv_rank as i32,
2418 d_rope as i32,
2419 t_q as i32,
2420 n_slots as i32,
2421 scale,
2422 s.cu_stream() as *mut c_void,
2423 ),
2424 )
2425 }
2426 }
2427}
2428
2429#[cfg(test)]
2430mod dsa_select_default_tests {
2431 use super::{mla_dsa_select_engages, mla_dsa_select_on_from};
2432
2433 #[test]
2434 fn default_on_only_zero_disarms_and_the_floors_still_gate() {
2435 assert!(mla_dsa_select_on_from(None));
2436 assert!(mla_dsa_select_on_from(Some("1")));
2437 assert!(!mla_dsa_select_on_from(Some("0")));
2438 assert!(!mla_dsa_select_on_from(Some(" 0 ")));
2439 // The flip does not reach short context: the floors are unchanged.
2440 assert!(!mla_dsa_select_engages(1, 65_535));
2441 assert!(mla_dsa_select_engages(1, 65_536));
2442 assert!(!mla_dsa_select_engages(2, 65_536));
2443 assert!(mla_dsa_select_engages(2, 262_144));
2444 assert!(!mla_dsa_select_engages(9, 1_000_000));
2445 }
2446}