spark_model/layers/
dense_ffn.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Dense SwiGLU FFN component for non-MoE models.
4//!
5//! Forward: gate = gate_proj(x), up = up_proj(x), out = down_proj(SiLU(gate) * up)
6//! 2 fused kernel launches per decode token (dual GEMV + SiLU-fused down GEMV).
7
8use anyhow::Result;
9use spark_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
10
11use crate::layer::ForwardContext;
12use crate::layers::ops;
13use crate::layers::w4a16_gemv_tiers::W4a16BatchmTiers;
14use crate::weight_map::{
15    DenseWeight, Fp8Weight, Fp8WeightTransposed, PackedQ2Weight, QuantizedWeight,
16};
17
18pub struct DenseFfnWeights {
19    pub gate_proj: QuantizedWeight,
20    pub up_proj: QuantizedWeight,
21    pub down_proj: QuantizedWeight,
22    /// Transposed ([K/2, N]) copies for the fast `w4a16_gemm_t_m128` prefill
23    /// kernel. `None` → prefill falls back to the slow M64xN64 base kernel.
24    /// The non-transposed copies above are kept for the decode gemv path.
25    pub gate_proj_t: Option<QuantizedWeight>,
26    pub up_proj_t: Option<QuantizedWeight>,
27    pub down_proj_t: Option<QuantizedWeight>,
28}
29
30/// BF16 dense MLP weights — alternative to NVFP4 for precision-sensitive
31/// models (Gemma-4-31B). Each is `[N, K]` row-major BF16. When installed
32/// on a `DenseFfnLayer` via `set_bf16_weights`, the forward paths
33/// dispatch to `dense_gemv_bf16` / `dense_gemm_bf16` instead of the
34/// w4a16 NVFP4 kernels. Costs ~3.4 GB extra GPU memory on Gemma-4-31B
35/// (3 × hidden×intermediate × 2 bytes) vs NVFP4's 0.5 bytes/weight.
36pub struct DenseFfnWeightsBf16 {
37    pub gate_proj: DenseWeight,
38    pub up_proj: DenseWeight,
39    pub down_proj: DenseWeight,
40}
41
42/// Native block-scaled FP8 dense MLP weights — loaded directly from an FP8
43/// checkpoint (no NVFP4 requant). When installed via `set_fp8_weights`, decode
44/// dispatches `w8a16_gemv` and prefill `w8a16_gemm` per projection (BF16 act ×
45/// FP8 E4M3 weight with 2D block scales), mirroring the SSM/attention FP8 path.
46pub struct DenseFfnWeightsFp8 {
47    pub gate_proj: Fp8Weight,
48    pub up_proj: Fp8Weight,
49    pub down_proj: Fp8Weight,
50}
51
52/// Native keep-packed ternary Q2_0 dense MLP weights — loaded directly from a
53/// PrismML Q2_0 GGUF (`ATLAS_GGUF_NATIVE_Q2=1`) with NO dequant / NVFP4 requant.
54/// Each projection is a raw `block_q2_0` buffer (2-bit codes + inline fp16 scale
55/// per group). When installed via `set_q2_weights`, decode dispatches
56/// `q2_0_gemv` (BF16 act × 2-bit weight, dequant-in-dot-product), mirroring the
57/// FP8 path but with the weights ~4× smaller resident.
58pub struct DenseFfnWeightsQ2 {
59    pub gate_proj: PackedQ2Weight,
60    pub up_proj: PackedQ2Weight,
61    pub down_proj: PackedQ2Weight,
62}
63
64/// Activation function for gated FFN (SiLU for Qwen/Llama, GELU for Gemma-4).
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum FfnActivation {
67    SiLU,
68    GeLU,
69}
70
71/// A per-projection int8 W4A8 weight, built lazily from the NVFP4 weight on the
72/// first `ATLAS_INT8_PREFILL` prefill (see `DenseFfnLayer::ensure_int8_weight`).
73/// `w_i8` is `[N, K]` signed int8; `w_scale` is `[N, K/32]` F32. Cached for the
74/// process lifetime in a `OnceLock`, so the requant kernel runs once per weight.
75#[derive(Debug, Clone, Copy)]
76struct Int8Weight {
77    w_i8: DevicePtr,
78    w_scale: DevicePtr,
79}
80
81/// Q4_K-quantized FFN weight (GGML block_q4_K layout), materialized once at first
82/// `ATLAS_FFN_MMQ` prefill and cached for process lifetime in a `OnceLock`.
83#[derive(Debug, Clone, Copy)]
84struct Q4kWeight {
85    w_q4k: DevicePtr,
86}
87
88/// block_nvfp4-repacked FFN weight for the `ATLAS_FFN_NVFP4_MMQ` W4A4 prefill arm.
89/// Raw bit shuffle of the checkpoint's NVFP4 (same e2m1 codes + e4m3 scale bytes,
90/// same total bytes) — materialized once and cached for process lifetime.
91#[derive(Debug, Clone, Copy)]
92struct Fp4MmqWeight {
93    w: DevicePtr,
94}
95
96pub struct DenseFfnLayer {
97    pub weights: DenseFfnWeights,
98    activation: FfnActivation,
99    w4a16_gemv: KernelHandle,
100    /// Single-warp `w4a16_gemv_sw`. `KernelHandle(0)` on miss → base GEMV.
101    w4a16_gemv_sw: KernelHandle,
102    w4a16_gemv_dual: KernelHandle,
103    w4a16_gemv_silu_input: KernelHandle,
104    // LOSSLESS single-warp-per-output decode variants (8 outputs/block, no smem
105    // cross-warp reduce). Bit-identical to the 64-thread kernels (proven by the
106    // w4a16_gemv_sw microtest). Default ON via `ModelLevers::gemv_sw`;
107    // `ATLAS_NO_GEMV_SW=1` restores the 64-thread kernels. KernelHandle(0) on
108    // miss → fall back to base kernels.
109    w4a16_gemv_dual_sw: KernelHandle,
110    w4a16_gemv_silu_input_sw: KernelHandle,
111    w4a16_gemv_dual_batch2: KernelHandle,
112    w4a16_gemv_dual_batch3: KernelHandle,
113    w4a16_gemv_batch2: KernelHandle,
114    w4a16_gemv_batch3: KernelHandle,
115    /// Narrow `w4a16_gemv_batch{M}` family (M=4..8) for the K=4 verify FFN and
116    /// the K=5..8 chain verify. SSOT for the M -> tier decision; individual
117    /// tiers are 0-handles when the target did not load them.
118    w4a16_batchm: W4a16BatchmTiers,
119    w4a16_gemm: KernelHandle,
120    // 128x128 2-stage cp.async pipelined w4a16 GEMM — the fast prefill kernel
121    // attention/SSM already use. The base `w4a16_gemm` (M64xN64) only hits
122    // ~10 TFLOPS at M=8k and was the flat ~155 tok/s dense-FFN prefill
123    // bottleneck on Qwen3.6-27B. KernelHandle(0) on miss → scalar-tile fallback.
124    w4a16_gemm_t_m128_k: KernelHandle,
125    // v2: 8-warp (256-thread) variant of t_m128 — parallel chunk MMAs, 3 CTAs/SM.
126    // Preferred over t_m128 for dense-FFN prefill when present. KernelHandle(0) → use t_m128.
127    w4a16_gemm_t_m128_v2_k: KernelHandle,
128    // LOSSLESS BF16 variant of t_m128: same 128x128 cp.async tiling, but FP4→BF16
129    // dequant + BF16 m16n8k16 MMA (FP32 accum) instead of the FP8-E4M3 crush the
130    // default NVIDIA t_m128 uses. The FP8 path perturbs generation (measured
131    // length-truncations / accuracy risk on Qwen3.6-27B); this kernel keeps prefill
132    // outputs bit-for-bit vs the base `w4a16_gemm`. OPT-IN only, gated by
133    // ATLAS_BF16_TC_PREFILL (default off → dispatch unchanged). KernelHandle(0) on miss.
134    w4a16_gemm_t_m128_bf16_k: KernelHandle,
135    // v2 of the LOSSLESS BF16 128x128 prefill kernel: same MMA instruction order
136    // (so BIT-IDENTICAL to bf16_k, proven by w4a16_bf16_v2_microtest) but a
137    // smaller A-tile smem pad lifts occupancy from 2→3 CTAs/SM (~+50% resident
138    // warps), giving a measured ~3-8% faster prefill GEMM on this latency-bound
139    // kernel. Preferred over bf16_k when present. KernelHandle(0) on miss → bf16_k.
140    w4a16_gemm_t_m128_bf16_v2_k: KernelHandle,
141    // FP8 M64 prefill (w4a16_gemm_t): m16n8k32 e4m3 MMA + M_TILE=64. Packed 1-byte
142    // operands cut shared-memory load instructions ~4x (the v2 BF16 path is
143    // smem-bandwidth-bound, L1/TEX 90% per ncu), and M64's lower register pressure
144    // lifts occupancy → measured ~44 TFLOP/s vs ~30 for v2 (~1.47x prefill) on dgx1.
145    // LOSSY (FP8 E4M3, cosine ~0.9997) — OPT-IN via ATLAS_FP8_M64_PREFILL, gated on
146    // quality. KernelHandle(0) on miss → dispatch unchanged.
147    w4a16_gemm_t_k: KernelHandle,
148    // int8 W4A8 prefill (ATLAS_INT8_PREFILL): the validated requant→faith2
149    // pipeline (cosine 0.999978). `int8_gemm_faith2` is an int8×int8 MMA with
150    // per-32 block scales, so BOTH operands must be int8 — unlike the FP8 path
151    // (mixed BF16×FP8). At first int8 prefill we requant the NVFP4 gate/up/down
152    // weights to int8 once (`requant_w_nvfp4_int8`, cached in the OnceLocks
153    // below) and requant the BF16 activations every call (`requant_a_bf16_int8`,
154    // into `int8_a_scratch`). KernelHandle(0) on miss → arm never taken.
155    int8_faith2_k: KernelHandle,
156    // faith5: int32 per-sb accumulation (breaks the MMA→scale dependency chain).
157    // Opt-in via ATLAS_INT8_FAITH5=1 (replaces faith2 for int8 prefill GEMMs).
158    int8_faith5_k: KernelHandle,
159    requant_w_int8_k: KernelHandle,
160    requant_a_int8_k: KernelHandle,
161    // Lazily-built, process-lifetime int8 weight copies (one per projection),
162    // requanted from `self.weights.{gate,up,down}_proj`. Only ever touched when
163    // ATLAS_INT8_PREFILL is set → default-off path is byte-identical.
164    int8_gate: std::sync::OnceLock<Int8Weight>,
165    int8_up: std::sync::OnceLock<Int8Weight>,
166    int8_down: std::sync::OnceLock<Int8Weight>,
167    // Activation-requant scratch for the int8/NVFP4/Q4_K prefill GEMMs is now
168    // shared, arena-owned (BufferArena::ffn_act_{q8,a,scale}), sized once for
169    // max_batch_tokens × max(h, inter) — no per-layer allocation.
170    // W4A4 native-FP4 prefill (ATLAS_FP4_PREFILL): NVFP4 weights consumed directly
171    // (no requant), BF16 activations quantized to NVFP4 each call into ffn_act_a/scale.
172    // KernelHandle(0) on miss → arm never taken (default-off byte-identical).
173    w4a4_gemm_k: KernelHandle,
174    quantize_nvfp4_k: KernelHandle,
175    // Q4_K MMQ prefill (ATLAS_FFN_MMQ): vendored llama Q4_K W4A8 GEMM. Weights
176    // materialized NVFP4→bf16→Q4_K once (lazy, cached in the OnceLocks); activations
177    // quantized to q8_1_mmq each call into ffn_act_q8. KernelHandle(0) → arm skipped.
178    q4k_mmq_nc_k: KernelHandle,
179    q4k_mmq_wc_k: KernelHandle,
180    q4k_quant_act_k: KernelHandle,
181    q4k_quant_w_k: KernelHandle,
182    dequant_nvfp4_bf16_k: KernelHandle,
183    q4k_gate: std::sync::OnceLock<Q4kWeight>,
184    q4k_up: std::sync::OnceLock<Q4kWeight>,
185    q4k_down: std::sync::OnceLock<Q4kWeight>,
186    // NVFP4 W4A4 MMQ prefill (ATLAS_FFN_NVFP4_MMQ): vendored llama Blackwell block-scale
187    // FP4 MMA (80 TFLOP/s vs t_m128 ~51 on GB10). Gate/up weights repacked ONCE at load
188    // (raw bit shuffle, checkpoint layout → block_nvfp4, zero requantization); activations
189    // quantized per call into the shared ffn_act_q8 scratch; the per-tensor scale2 is
190    // folded in the scaled SiLU-mul. KernelHandle(0) → arm skipped.
191    nvfp4_mmq_nc_k: KernelHandle,
192    nvfp4_mmq_wc_k: KernelHandle,
193    /// M-sized MMQ tiles for DECODE. The 128 tile issues MMAs for all 128 columns
194    /// regardless of m, so at m=16 it discards 112 of them; these size the tile to
195    /// the batch. try_kernel: 0-handle -> dispatch keeps the 128 tile.
196    nvfp4_mmq16_nc_k: KernelHandle,
197    nvfp4_mmq16_wc_k: KernelHandle,
198    nvfp4_mmq32_nc_k: KernelHandle,
199    nvfp4_mmq32_wc_k: KernelHandle,
200    nvfp4_mmq64_nc_k: KernelHandle,
201    nvfp4_mmq64_wc_k: KernelHandle,
202    nvfp4_quant_act_k: KernelHandle,
203    nvfp4_repack_k: KernelHandle,
204    nvfp4_silu_scaled_k: KernelHandle,
205    nvfp4_silu_quant_k: KernelHandle,
206    nvfp4_scale_k: KernelHandle,
207    fp4mmq_gate: std::sync::OnceLock<Fp4MmqWeight>,
208    fp4mmq_up: std::sync::OnceLock<Fp4MmqWeight>,
209    fp4mmq_down: std::sync::OnceLock<Fp4MmqWeight>,
210    // Small-M (DFlash verify M=17) routing companion to `w4a16_gemm_t_k`
211    // (declared above): deep-K variant. w4a16_m17_bench: `w4a16_gemm_t_k64`
212    // wins deep-K down_proj (554 vs 810us at K=17408); the M64-tile
213    // `w4a16_gemm_t` beats M128 tiles at M<=64 (283 vs 324us on gate/up).
214    // KernelHandle(0) → m128 dispatch.
215    w4a16_gemm_t_k64_k: KernelHandle,
216    /// SiLU(gate)*up or GELU(gate)*up depending on activation.
217    act_mul: KernelHandle,
218    /// BF16 dense MLP weights — when `Some`, all forward paths use the
219    /// `dense_gemv_bf16` / `dense_gemm_bf16` kernels instead of w4a16
220    /// NVFP4. Falls back to the NVFP4 weights when `None`. Set via
221    /// `set_bf16_weights`. Used by Gemma-4 dense to avoid the structural
222    /// NVFP4 attention drift on greedy code generation (the fib test's
223    /// broken-indentation pattern).
224    bf16_weights: Option<DenseFfnWeightsBf16>,
225    dense_gemv_bf16_k: KernelHandle,
226    dense_gemm_bf16_k: KernelHandle,
227    // Tensor-core BF16 GEMM (m16n8k16 MMA) for the dense-FFN PREFILL path.
228    // The scalar `dense_gemm_bf16` is ~10x too slow on long prefills (it was
229    // the flat ~155 tok/s prefill bottleneck on Qwen3.6-27B dense NVFP4).
230    // KernelHandle(0) on miss → forward_prefill falls back to the scalar path.
231    // Decode (gemv, M=1) is untouched, so TPOT is unaffected.
232    dense_gemm_tc_k: KernelHandle,
233    /// Native FP8 dense MLP weights — when `Some`, decode/prefill dispatch the
234    /// block-scaled FP8 kernels (`w8a16_gemv` / `w8a16_gemm`) instead of w4a16
235    /// NVFP4. Set via `set_fp8_weights` for native FP8 checkpoints (Qwythos /
236    /// Ornith-FP8). Spec-decode batched paths fall back to dequant — dense
237    /// qwen3_5 has no MTP, so they're never reached.
238    fp8_weights: Option<DenseFfnWeightsFp8>,
239    w8a16_gemv_k: KernelHandle,
240    w8a16_gemm_k: KernelHandle,
241    w8a16_gemv_batch4_k: KernelHandle,
242    /// MAX_M=16 sibling of `w8a16_gemv_batch4_k` — the 5..=32-row decode tier
243    /// (#927). KernelHandle(0) on a shadow that lacks the entry point, which
244    /// puts those widths back on the tile GEMMs. Rule: `batch16_decode.rs`.
245    w8a16_gemv_batch16_k: KernelHandle,
246    /// Whether that tier is ARMED — `ATLAS_FFN_BATCH16=1`, latched once here
247    /// at construction. DEFAULT FALSE: the tier measured a net LOSS in serving
248    /// on H100 (-5.4% aggregate at C=16, +50 ms TTFT) and has never been
249    /// measured on GB10, where the handle resolves just the same. Receipt and
250    /// the full rule: `batch16_decode.rs`.
251    ///
252    /// A FIELD rather than a call into `ffn_batch16_enabled()` at dispatch
253    /// time, for two reasons: the route is then fixed before any CUDA-graph
254    /// capture, and the dispatch tests can pin BOTH polarities without a
255    /// process-global `OnceLock` that latches on whichever test runs first.
256    batch16_enabled: bool,
257    /// Tensor-core 16-row-M-tile GEMM (#927) — the `ATLAS_FFN_M16_TC` tier
258    /// that sits AHEAD of `w8a16_gemv_batch16_k` at 5..=32 rows when the lever
259    /// is set. KernelHandle(0) on a shadow that lacks the entry point, which
260    /// leaves the ladder exactly as #927 shipped it. Rule: `dense_ffn_m16_tc.rs`.
261    w8a16_gemm_m16_k: KernelHandle,
262    /// The `N_TILE=64` twin (`ATLAS_FFN_M16_TC_NTILE=64`). KernelHandle(0) on a
263    /// shadow built before the wide arm existed, which silently keeps the
264    /// 32-wide kernel. Rule: `m16_tc::m16_tc_kernel`.
265    w8a16_gemm_m16_n64_k: KernelHandle,
266    /// `ATLAS_FFN_M16_TC` (or the `ATLAS_M16_TC` umbrella), cached at
267    /// construction. Since round 6 this lever reaches ONLY the FFN arm — the
268    /// attention tiers have their own, because the H100 measured them moving in
269    /// opposite directions. A FIELD rather than a per-call accessor for two
270    /// reasons: the selector runs per projection per layer per step, and the
271    /// dispatch tests drive both arms without racing the process-global
272    /// `OnceLock` the env read lives in. Grammar: `dense_ffn_m16_tc.rs`.
273    m16_tc: bool,
274    /// The CTA N width this layer asks for, 32 or 64. Same field-not-accessor
275    /// reasoning as `m16_tc`.
276    m16_tc_n_tile: u32,
277    w8a16_gemm_pipelined_k: KernelHandle,
278    // Fused FP8 decode GEMVs (gate+up in one launch / silu+down in one launch),
279    // mirroring the NVFP4 w4a16_gemv_dual / w4a16_gemv_silu_input. KernelHandle(0)
280    // on miss → fall back to the 3-launch w8a16_gemv path. Module = .cu file stem.
281    w8a16_gemv_dual_k: KernelHandle,
282    w8a16_gemv_silu_input_k: KernelHandle,
283    // Fast transposed FP8 prefill GEMM (128x128 / 8-warp / two-level FP32 fold).
284    // Preferred over w8a16_gemm when a transposed FP8 weight copy is present.
285    // KernelHandle(0) → fall back to non-transposed w8a16_gemm.
286    w8a16_gemm_t_m128_k: KernelHandle,
287    // W8A8 block-scaled prefill pair (#917/#928): per-token 1x128 FP8
288    // activation quant + the FP8xFP8 GEMM with both scale sets folded in an
289    // FP32 epilogue. Same two entry points the attention Q/K/V/O prefill
290    // already resolves, so no model shadow needs a new kernel; KernelHandle(0)
291    // on a shadow that lacks them -> the W8A16 branches still run.
292    // Dispatch rule + rationale live in `dense_ffn_w8a8_prefill.rs` (SSOT).
293    per_token_group_quant_fp8_k: ops::Fp8ActQuant,
294    fp8_gemm_t_blockscaled_k: KernelHandle,
295    // VEC128 activation-scale layout adapter for the cuBLASLt arm of the pair
296    // above: cuBLASLt reads those scales with the TOKEN index contiguous, the
297    // quantizer writes them K-group-contiguous. KernelHandle(0) -> the cuBLASLt
298    // arm is not selectable and the in-tree GEMM runs (SSOT for the rule:
299    // `dense_ffn_w8a8_prefill.rs::w8a8_gemm`).
300    fp8_act_scale_kmajor_k: KernelHandle,
301    /// The FUSED `[2*inter, hidden]` block-scaled FP8 gate+up weight (#927),
302    /// installed by `set_fp8_gate_up_fused`. `gate_proj` and `up_proj` above
303    /// are VIEWS inside it whenever it is `Some`, so every un-fused rung reads
304    /// the same bytes and no arm needs a second copy. `None` on every route
305    /// that did not build it — the arm is optional at runtime, not assumed.
306    fp8_gate_up_fused: Option<Fp8Weight>,
307    /// Whether the compiled target ARMS the fused arm (`[defaults]
308    /// ffn_gateup_fused`, overridable with `ATLAS_FFN_GATEUP_FUSED`), cached at
309    /// construction for the two reasons `batch16_tier` is: the selector runs
310    /// per layer per step, and the dispatch tests drive both polarities without
311    /// racing the process-global `OnceLock`.
312    gateup_fused: bool,
313    /// The strided SiLU·mul that reads the fused `[m, 2*inter]` output.
314    /// `silu_mul_strided.cu` is a HOPPER-OWNED source (`[kernels] overrides`),
315    /// so KernelHandle(0) on every other target — and the probe is GATED on the
316    /// resolved lever, like `w8a16_gemm_m16_k`, because an unresolved lookup
317    /// nothing declared fails the boot audit CLOSED.
318    silu_mul_strided_k: KernelHandle,
319    /// v0 LoRA overlay for gate/up/down. `set_lora_weights` REJECTS layers
320    /// where `fp8_weights`, `bf16_weights` or `q2_weights` are installed (v0
321    /// supports the NVFP4 dispatch path only — those branches early-return
322    /// before the NVFP4 tail where the deltas land; holo is NVFP4 so it is
323    /// unaffected).
324    ///
325    /// M1 (2026-08-19): the deltas are APPLIED. `apply_lora_gate_up` runs
326    /// after the gate/up projection and before `silu_mul`; `apply_lora_down`
327    /// runs after the down projection. Every NVFP4 dispatch this layer can
328    /// take — decode `forward`, `forward_k2`/`k3`/`km`, `forward_prefill`,
329    /// `forward_batched` — calls both, because an adapter that applies on one
330    /// path and not another produces a model that contradicts itself between
331    /// prefill and decode.
332    ///
333    /// Until M1 this field was written by `set_lora_weights` and never read,
334    /// so an adapter targeting gate/up/down loaded successfully and changed
335    /// nothing. On a hybrid like Qwen3.8-27B that is most of the adapter:
336    /// community LoRAs for it put 67-78% of their parameter mass in the FFN.
337    lora: Option<ops::lora_delta::LoraFfnWeights>,
338
339    /// Native keep-packed ternary Q2_0 dense MLP weights (`ATLAS_GGUF_NATIVE_Q2`).
340    /// When installed via `set_q2_weights`, decode dispatches `q2_0_gemv_vec`
341    /// (BF16 activation × packed 2-bit weight, dequant-in-dot-product) — the
342    /// weights stay 2-bit resident (no NVFP4 requant). Highest-priority forward
343    /// branch. Prefill/batched paths for packed-Q2 are a deferred (Tier-2) phase
344    /// and currently bail — dense qwen35 has no MTP so k2/k3 are never reached.
345    q2_weights: Option<DenseFfnWeightsQ2>,
346    q2_0_gemv_k: KernelHandle,
347    // Batched (M=1..8) packed-Q2 decode GEMV handle. The kernel + wrapper are
348    // built and validated (CPU math test), but the batched-decode call site
349    // (spec-decode verify rows) is deferred to the same Tier-2 phase as prefill;
350    // dense qwen35 has no MTP so no batched decode reaches the FFN today.
351    #[allow(dead_code)]
352    q2_0_gemv_batchm_k: KernelHandle,
353    // Load-time packed-Q2 → BF16 dequant kernel (`dequant_gguf_bf16` module).
354    // Used by packed-Q2 PREFILL: dequant each proj into a TRANSIENT BF16 scratch
355    // buffer, run the normal BF16 GEMM, free the scratch — the resident weight
356    // stays 2-bit. Decode uses the native `q2_0_gemv` (no dequant). Tier-1 path.
357    dequant_q2_0_gn_k: KernelHandle,
358    // Native Q2_0 MMQ prefill (Tier-2, `ATLAS_GGUF_NATIVE_Q2_MMQ=1`): keeps the
359    // 2-bit weight packed and runs a tensor-core int8 MMA (dequant-in-register)
360    // against a q8_1 activation — no BF16 weight scratch, no dequant tax, no race.
361    // The q8_1 activation quantizer is SHARED with Q4_K (`q4k_quant_act_k`).
362    // KernelHandle(0) when absent → falls back to the transient-dequant path.
363    q2_0_mmq_nc_k: KernelHandle,
364    q2_0_mmq_wc_k: KernelHandle,
365}
366
367/// M-sized MMQ tiles: **ON by default**, disabled by `ATLAS_NO_MMQ_SMALL_TILE=1`.
368/// Strict `== "1"` on an `ATLAS_NO_*` name — presence flags here are enabled by `=0`.
369fn mmq_small_tile_enabled() -> bool {
370    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
371    *ON.get_or_init(|| std::env::var("ATLAS_NO_MMQ_SMALL_TILE").as_deref() != Ok("1"))
372}
373
374/// The m=64 MMQ tile: **ON by default**, disabled by `ATLAS_NO_MMQ_TILE64=1`. Separate
375/// from `ATLAS_NO_MMQ_SMALL_TILE` so this arm can be A/B'd without also reverting the
376/// already-shipped 16/32 tiles. Strict `== "1"`, matching the sibling above.
377fn mmq_tile64_enabled() -> bool {
378    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
379    *ON.get_or_init(|| std::env::var("ATLAS_NO_MMQ_TILE64").as_deref() != Ok("1"))
380}
381impl DenseFfnLayer {
382    pub fn new(weights: DenseFfnWeights, gpu: &dyn GpuBackend) -> Result<Self> {
383        Self::new_with_activation(weights, FfnActivation::SiLU, gpu)
384    }
385
386    pub fn new_with_activation(
387        weights: DenseFfnWeights,
388        activation: FfnActivation,
389        gpu: &dyn GpuBackend,
390    ) -> Result<Self> {
391        let act_mul = match activation {
392            FfnActivation::SiLU => gpu.kernel("moe_silu_mul", "moe_silu_mul")?,
393            FfnActivation::GeLU => gpu.kernel("gelu", "gelu_mul")?,
394        };
395        // Resolved ONCE for the layer: it gates both the kernel probe below
396        // and the per-step selector, and those two must never disagree —
397        // a probe skipped while the selector says yes is a NULL launch.
398        // Rule: `dense_ffn_gateup_fused.rs`.
399        let gateup_fused = gateup_fused::ffn_gateup_fused();
400        // BF16 path kernels — optional (only loaded if available; gemma4
401        // is the only consumer today). `try_kernel` returns
402        // `KernelHandle(0)` on miss so we don't break NVFP4-only models
403        // that were built without these kernels. Module names per
404        // `kernels/gb10/{target}/nvfp4/KERNEL.toml`:
405        //   `dense_gemv_bf16 = "gemv"`, `dense_gemm_bf16 = "gemm"`.
406        let dense_gemv_bf16_k = super::try_kernel(gpu, "gemv", "dense_gemv_bf16");
407        let dense_gemm_bf16_k = super::try_kernel(gpu, "gemm", "dense_gemm_bf16");
408        let dense_gemm_tc_k = super::try_kernel(gpu, "gemm_tc", "dense_gemm_tc");
409
410        let layer = Self {
411            weights,
412            activation,
413            w4a16_gemv: gpu.kernel("w4a16_gemv", "w4a16_gemv")?,
414            w4a16_gemv_sw: super::try_kernel(gpu, "w4a16_gemv", "w4a16_gemv_sw"),
415            w4a16_gemv_dual: gpu.kernel("w4a16_gemv_fused", "w4a16_gemv_dual")?,
416            w4a16_gemv_silu_input: gpu.kernel("w4a16_gemv_fused", "w4a16_gemv_silu_input")?,
417            w4a16_gemv_dual_sw: super::try_kernel(gpu, "w4a16_gemv_fused", "w4a16_gemv_dual_sw"),
418            w4a16_gemv_silu_input_sw: super::try_kernel(
419                gpu,
420                "w4a16_gemv_fused",
421                "w4a16_gemv_silu_input_sw",
422            ),
423            w4a16_gemv_dual_batch2: gpu.kernel("w4a16_gemv", "w4a16_gemv_dual_batch2")?,
424            w4a16_gemv_dual_batch3: gpu.kernel("w4a16_gemv", "w4a16_gemv_dual_batch3")?,
425            w4a16_gemv_batch2: gpu.kernel("w4a16_gemv", "w4a16_gemv_batch2")?,
426            w4a16_gemv_batch3: gpu.kernel("w4a16_gemv", "w4a16_gemv_batch3")?,
427            w4a16_batchm: W4a16BatchmTiers::resolve(gpu),
428            w4a16_gemm: gpu.kernel("w4a16", "w4a16_gemm")?,
429            w4a16_gemm_t_m128_k: super::try_kernel(gpu, "w4a16", "w4a16_gemm_t_m128"),
430            w4a16_gemm_t_m128_v2_k: super::w4a16_v2_kernel(gpu),
431            w4a16_gemm_t_m128_bf16_k: super::try_kernel(gpu, "w4a16", "w4a16_gemm_t_m128_bf16"),
432            w4a16_gemm_t_m128_bf16_v2_k: super::try_kernel(
433                gpu,
434                "w4a16",
435                "w4a16_gemm_t_m128_bf16_v2",
436            ),
437            w4a16_gemm_t_k: super::tgemm_kernel(gpu),
438            int8_faith2_k: super::try_kernel(gpu, "w4a16", "int8_gemm_faith2"),
439            int8_faith5_k: super::try_kernel(gpu, "w4a16", "int8_gemm_i32acc"),
440            requant_w_int8_k: super::try_kernel(gpu, "w4a16", "requant_w_nvfp4_int8"),
441            requant_a_int8_k: super::try_kernel(gpu, "w4a16", "requant_a_bf16_int8"),
442            int8_gate: std::sync::OnceLock::new(),
443            int8_up: std::sync::OnceLock::new(),
444            int8_down: std::sync::OnceLock::new(),
445            w4a4_gemm_k: super::try_kernel(gpu, "w4a4", "w4a4_gemm"),
446            quantize_nvfp4_k: super::try_kernel(gpu, "quantize_nvfp4", "quantize_bf16_to_nvfp4"),
447            q4k_mmq_nc_k: super::try_kernel(gpu, "q4k_mmq", "atlas_q4k_mmq128_nc"),
448            q4k_mmq_wc_k: super::try_kernel(gpu, "q4k_mmq", "atlas_q4k_mmq128_wc"),
449            q4k_quant_act_k: super::try_kernel(gpu, "q4k_mmq", "atlas_q8_1_quantize_ds4_bf16"),
450            q4k_quant_w_k: super::try_kernel(gpu, "q4k_quantize", "q4k_quantize"),
451            dequant_nvfp4_bf16_k: super::try_kernel(
452                gpu,
453                "dequant_nvfp4_bf16",
454                "dequant_nvfp4_to_bf16",
455            ),
456            q4k_gate: std::sync::OnceLock::new(),
457            q4k_up: std::sync::OnceLock::new(),
458            q4k_down: std::sync::OnceLock::new(),
459            nvfp4_mmq_nc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq128_nc"),
460            nvfp4_mmq_wc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq128_wc"),
461            nvfp4_mmq16_nc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq16_nc"),
462            nvfp4_mmq16_wc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq16_wc"),
463            nvfp4_mmq32_nc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq32_nc"),
464            nvfp4_mmq32_wc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq32_wc"),
465            nvfp4_mmq64_nc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq64_nc"),
466            nvfp4_mmq64_wc_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_mmq64_wc"),
467            nvfp4_quant_act_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_quantize_bf16"),
468            nvfp4_repack_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_repack"),
469            nvfp4_silu_scaled_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_silu_mul_scaled"),
470            nvfp4_silu_quant_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_silu_mul_quant"),
471            nvfp4_scale_k: super::try_kernel(gpu, "nvfp4_mmq", "atlas_nvfp4_scale_bf16"),
472            fp4mmq_gate: std::sync::OnceLock::new(),
473            fp4mmq_up: std::sync::OnceLock::new(),
474            fp4mmq_down: std::sync::OnceLock::new(),
475            w4a16_gemm_t_k64_k: super::k64_kernel(gpu).unwrap_or(KernelHandle(0)),
476            act_mul,
477            bf16_weights: None,
478            dense_gemv_bf16_k,
479            dense_gemm_bf16_k,
480            dense_gemm_tc_k,
481            fp8_weights: None,
482            w8a16_gemv_k: super::try_kernel(gpu, "w8a16_gemv", "w8a16_gemv"),
483            w8a16_gemm_k: super::try_kernel(gpu, "w8a16_gemm", "w8a16_gemm"),
484            w8a16_gemv_batch4_k: super::try_kernel(gpu, "w8a16_gemv_batch4", "w8a16_gemv_batch4"),
485            w8a16_gemv_batch16_k: super::try_kernel(gpu, "w8a16_gemv_batch4", "w8a16_gemv_batch16"),
486            batch16_enabled: batch16_decode::ffn_batch16_enabled(),
487            w8a16_gemm_m16_k: super::try_target_kernel(gpu, "w8a16_gemm_m16", "w8a16_gemm_m16"),
488            w8a16_gemm_m16_n64_k: super::try_target_kernel(
489                gpu,
490                "w8a16_gemm_m16",
491                "w8a16_gemm_m16_n64",
492            ),
493            m16_tc: m16_tc::m16_tc_levers().ffn,
494            m16_tc_n_tile: m16_tc::m16_tc_levers().ffn_n_tile,
495            w8a16_gemm_pipelined_k: super::try_kernel(
496                gpu,
497                "w8a16_gemm_pipelined",
498                "w8a16_gemm_pipelined",
499            ),
500            w8a16_gemv_dual_k: super::try_kernel(gpu, "w8a16_gemv_fused", "w8a16_gemv_dual"),
501            w8a16_gemv_silu_input_k: super::try_kernel(
502                gpu,
503                "w8a16_gemv_fused",
504                "w8a16_gemv_silu_input",
505            ),
506            w8a16_gemm_t_m128_k: super::try_kernel(gpu, "w8a16_gemm_t_m128", "w8a16_gemm_t_m128"),
507            per_token_group_quant_fp8_k: ops::Fp8ActQuant::resolve(gpu),
508            fp8_gemm_t_blockscaled_k: super::try_kernel(
509                gpu,
510                "fp8_gemm_t_blockscaled",
511                "fp8_gemm_t_blockscaled",
512            ),
513            fp8_act_scale_kmajor_k: super::try_kernel(
514                gpu,
515                "fp8_scale_transpose",
516                "fp8_act_scale_to_kmajor",
517            ),
518            fp8_gate_up_fused: None,
519            gateup_fused,
520            // ★ PROBED ONLY WHEN THE ARM IS ARMED: an unresolved lookup
521            // nothing declared fails the boot audit CLOSED, and
522            // `silu_mul_strided.cu` is HOPPER-OWNED — no other hardware tree
523            // carries it. Gating the lookup on the resolved lever is the fix
524            // `ptx_set.rs` prescribes.
525            silu_mul_strided_k: if gateup_fused {
526                super::try_target_kernel(gpu, "silu_mul_strided", "silu_mul_strided")
527            } else {
528                KernelHandle(0)
529            },
530            lora: None,
531            q2_weights: None,
532            // Winner of the decode-GEMV bench: candidate B (vectorized code loads
533            // + smem A-stage, 1 warp/row × 8 rows/CTA). ~268 GB/s (98% of the
534            // 273 GB/s LPDDR5X peak) at gate/up M=1 — 9.5× the original
535            // whole-block-strided `q2_0_gemv`. Same `(code-1)*d` FP32 numerics.
536            q2_0_gemv_k: super::try_kernel(gpu, "q2_0_gemv_vec", "q2_0_gemv_vec"),
537            q2_0_gemv_batchm_k: super::try_kernel(gpu, "q2_0_gemv_vec", "q2_0_gemv_vec_batchm"),
538            dequant_q2_0_gn_k: super::try_kernel(
539                gpu,
540                "dequant_gguf_bf16",
541                "dequant_q2_0_gn_to_bf16",
542            ),
543            // Resolved by `set_q2_weights`, never here: q2_0_mmq ships only
544            // in GGUF-serving targets, and an unconditional probe fails the
545            // boot audit on every dense-FFN model that never installs
546            // packed-Q2 weights.
547            q2_0_mmq_nc_k: KernelHandle(0),
548            q2_0_mmq_wc_k: KernelHandle(0),
549        };
550        Ok(layer)
551    }
552
553    /// Load-time finalize for the Q4_K MMQ prefill path (`ATLAS_FFN_MMQ`). MUST run at
554    /// load, BEFORE the KV cache is sized, so the net FFN footprint is correct when the KV
555    /// cache claims free memory. Order is critical: (1) eagerly materialize the Q4_K weights
556    /// (+9.63 GB) so they are accounted for now rather than lazily on first prefill (which
557    /// would over-subscribe AFTER the KV cache already grabbed the freed `_t` space → decode
558    /// OOM-throttle); (2) free the transposed `_proj_t` copies (−9.63 GB, dead under Q4_K
559    /// prefill — only the unreachable `Some(wt)` arms read them). Net FFN = baseline; decode
560    /// untouched (NVFP4 gemv on the non-`_t` copies). No-op unless Q4_K is active.
561    pub fn finalize_q4k_load(
562        &mut self,
563        gpu: &dyn GpuBackend,
564        h: u32,
565        inter: u32,
566        stream: u64,
567    ) -> Result<()> {
568        // Packed-Q2 (ATLAS_GGUF_NATIVE_Q2) FFN keeps its NVFP4 source weights
569        // NULL — the Q4_K prefill copy is built by dequant-ing those (NULL) NVFP4
570        // blocks, so running it here is a null-ptr kernel launch (CUDA 700).
571        // Packed-Q2 has its own prefill path (transient dequant), so skip.
572        if self.q2_weights.is_some() {
573            return Ok(());
574        }
575        // Native FP8 (#915): `forward_prefill_inner` returns from inside its
576        // `self.fp8_weights` arm long before the Q4_K/MMQ arm, so this repack
577        // is dead work — and since #915 the loader installs NULL NVFP4 source
578        // weights on that route, over which `ensure_q4k_weight`'s dequant is a
579        // CUDA-700 illegal access. Same reason as the packed-Q2 guard above.
580        if self.fp8_weights.is_some() {
581            return Ok(());
582        }
583        let q4k_active = self.q4k_mmq_nc_k.0 != 0
584            && self.q4k_quant_act_k.0 != 0
585            && self.q4k_quant_w_k.0 != 0
586            && self.dequant_nvfp4_bf16_k.0 != 0
587            && std::env::var_os("ATLAS_FFN_MMQ").is_some();
588        if !q4k_active {
589            return Ok(());
590        }
591        // (1) eagerly materialize the prefill weights BEFORE freeing `_t`, so the KV cache
592        // (sized after load) can't claim the freed space before the weights exist.
593        // gate/up: Q4_K (N=inter,K=h). down: HYBRID → int8 faith2 (N=h,K=inter) for accuracy,
594        // else Q4_K. ensure_int8_weight reads the non-`_t` NVFP4 down_proj (kept for decode gemv).
595        self.ensure_q4k_weight(
596            &self.q4k_gate,
597            gpu,
598            &self.weights.gate_proj,
599            inter,
600            h,
601            stream,
602        )?;
603        self.ensure_q4k_weight(&self.q4k_up, gpu, &self.weights.up_proj, inter, h, stream)?;
604        let down_faith2 = self.int8_faith2_k.0 != 0
605            && self.requant_a_int8_k.0 != 0
606            && std::env::var_os("ATLAS_FFN_MMQ_DOWN_Q4K").is_none();
607        if down_faith2 {
608            self.ensure_int8_weight(
609                &self.int8_down,
610                gpu,
611                &self.weights.down_proj,
612                h,
613                inter,
614                stream,
615            )?;
616        } else {
617            self.ensure_q4k_weight(
618                &self.q4k_down,
619                gpu,
620                &self.weights.down_proj,
621                h,
622                inter,
623                stream,
624            )?;
625        }
626        gpu.synchronize(stream)?;
627        // (2) free the dead transposed copies
628        let mut freed = 0usize;
629        for wt in [
630            &mut self.weights.gate_proj_t,
631            &mut self.weights.up_proj_t,
632            &mut self.weights.down_proj_t,
633        ] {
634            if let Some(w) = wt.as_ref()
635                && !w.weight.is_null()
636            {
637                gpu.free(w.weight)?;
638                gpu.free(w.weight_scale)?;
639                freed += 1;
640            }
641            *wt = None;
642        }
643        if freed > 0 {
644            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
645            // value — the message is rebuilt from the arguments every call — so a
646            // stale entry cannot produce a wrong answer, only a suppressed duplicate
647            // line after a model swap. Scoping it would thread a logging concern
648            // through the call path to prevent one repeated INFO line.
649            // Latched on the BACKEND (`OpCache::once`), which exists by load
650            // time: `finalize_q4k_load` takes the `gpu` it is loading onto. A
651            // static meant only the first model in the process reported the
652            // decision.
653            if gpu.op_cache().once("log:ffn_mmq_freed_twins") {
654                tracing::info!(
655                    "[atlas] ATLAS_FFN_MMQ: freed transposed FFN `_t` copies (dead under Q4_K prefill) — Q4_K weights net to ~0 vs NVFP4 baseline"
656                );
657            }
658        }
659        Ok(())
660    }
661
662    /// Eagerly materialize the block_nvfp4 gate/up copies for the `ATLAS_FFN_NVFP4_MMQ`
663    /// W4A4 prefill arm at LOAD time (before KV sizing), then free the now-dead gate/up
664    /// transposed `_t` copies so net FFN footprint stays at the NVFP4 baseline. Down is
665    /// untouched (hybrid: it stays on the default t_m128 path for accuracy → keeps its
666    /// `_t` copy). No-op unless the env + kernels are present.
667    pub fn finalize_nvfp4_mmq_load(
668        &mut self,
669        gpu: &dyn GpuBackend,
670        h: u32,
671        inter: u32,
672        stream: u64,
673    ) -> Result<()> {
674        // Packed-Q2 (ATLAS_GGUF_NATIVE_Q2) FFN keeps its NVFP4 source weights
675        // NULL. This W4A4-MMQ finalize is active by DEFAULT (SiLU + kernels
676        // present) and repacks the NVFP4 gate/up — over NULL pointers that's a
677        // CUDA-700 illegal access. Packed-Q2 uses its own decode/prefill path.
678        if self.q2_weights.is_some() {
679            return Ok(());
680        }
681        // Native FP8 (#915): same reasoning as `finalize_q4k_load`, and this
682        // one is active by DEFAULT wherever the W4A4-MMQ kernels exist — so on
683        // a target that ships them it would repack NULL NVFP4 gate/up AND free
684        // `_t` copies for a prefill arm the FP8 early-return never reaches.
685        if self.fp8_weights.is_some() {
686            return Ok(());
687        }
688        let active = self.nvfp4_mmq_nc_k.0 != 0
689            && self.nvfp4_quant_act_k.0 != 0
690            && self.nvfp4_repack_k.0 != 0
691            && self.nvfp4_silu_scaled_k.0 != 0
692            && matches!(self.activation, FfnActivation::SiLU)
693            && std::env::var_os("ATLAS_NO_FFN_NVFP4_MMQ").is_none();
694        if !active {
695            return Ok(());
696        }
697        self.ensure_nvfp4_mmq_weight(
698            &self.fp4mmq_gate,
699            gpu,
700            &self.weights.gate_proj,
701            inter,
702            h,
703            stream,
704        )?;
705        self.ensure_nvfp4_mmq_weight(
706            &self.fp4mmq_up,
707            gpu,
708            &self.weights.up_proj,
709            inter,
710            h,
711            stream,
712        )?;
713        let down_mmq = std::env::var_os("ATLAS_NO_FFN_NVFP4_MMQ_DOWN").is_none();
714        if down_mmq {
715            self.ensure_nvfp4_mmq_weight(
716                &self.fp4mmq_down,
717                gpu,
718                &self.weights.down_proj,
719                h,
720                inter,
721                stream,
722            )?;
723        }
724        gpu.synchronize(stream)?;
725        // Free the dead transposed copies (prefill for those projections now runs on the
726        // MMQ arm; decode reads the non-transposed originals). down_proj_t is freed only
727        // when the down A/B gate is on.
728        let mut down_t = if down_mmq {
729            Some(&mut self.weights.down_proj_t)
730        } else {
731            None
732        };
733        let mut freed = 0usize;
734        for wt in [&mut self.weights.gate_proj_t, &mut self.weights.up_proj_t]
735            .into_iter()
736            .chain(down_t.take())
737        {
738            if let Some(w) = wt.as_ref()
739                && !w.weight.is_null()
740            {
741                gpu.free(w.weight)?;
742                gpu.free(w.weight_scale)?;
743                freed += 1;
744            }
745            *wt = None;
746        }
747        if freed > 0 {
748            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
749            // value — the message is rebuilt from the arguments every call — so a
750            // stale entry cannot produce a wrong answer, only a suppressed duplicate
751            // line after a model swap. Scoping it would thread a logging concern
752            // through the call path to prevent one repeated INFO line.
753            // Latched on the BACKEND (`OpCache::once`), which exists by load
754            // time: `finalize_q4k_load` takes the `gpu` it is loading onto. A
755            // static meant only the first model in the process reported the
756            // decision.
757            if gpu.op_cache().once("log:ffn_fp4mmq_freed_twins") {
758                tracing::info!(
759                    "[atlas] ATLAS_FFN_NVFP4_MMQ: freed gate/up `_t` copies (dead under FP4-MMQ prefill) — block_nvfp4 copies net to ~0 vs NVFP4 baseline"
760                );
761            }
762        }
763        Ok(())
764    }
765
766    /// Ensure the block_nvfp4 copy of one NVFP4 projection exists (raw repack of the
767    /// checkpoint's packed E2M1 `[N, K/2]` + E4M3 `[N, K/16]` scales — zero numerics;
768    /// scale2 folded at the SiLU-mul). Cached in `cell` for process lifetime.
769    fn ensure_nvfp4_mmq_weight(
770        &self,
771        cell: &std::sync::OnceLock<Fp4MmqWeight>,
772        gpu: &dyn GpuBackend,
773        src: &QuantizedWeight,
774        n: u32,
775        k: u32,
776        stream: u64,
777    ) -> Result<Fp4MmqWeight> {
778        if let Some(w) = cell.get() {
779            return Ok(*w);
780        }
781        let w = gpu.alloc(ops::nvfp4_mmq_weight_bytes(n, k))?;
782        ops::nvfp4_mmq_repack(
783            gpu,
784            self.nvfp4_repack_k,
785            src.weight,
786            src.weight_scale,
787            w,
788            n,
789            k,
790            stream,
791        )?;
792        let built = Fp4MmqWeight { w };
793        if let Err(dup) = cell.set(built) {
794            gpu.synchronize(stream)?;
795            let _ = gpu.free(dup.w);
796        }
797        Ok(*cell.get().expect("fp4mmq weight cell set above"))
798    }
799
800    /// Install native block-scaled FP8 dense MLP weights. After this call the
801    /// forward paths dispatch `w8a16_gemv` (decode) / `w8a16_gemm` (prefill)
802    /// instead of w4a16 NVFP4. Caller must ensure those kernels are present in
803    /// the target (they are for the qwen3_5/ornith nvfp4 bundle).
804    pub fn set_fp8_weights(&mut self, gate: Fp8Weight, up: Fp8Weight, down: Fp8Weight) {
805        self.fp8_weights = Some(DenseFfnWeightsFp8 {
806            gate_proj: gate,
807            up_proj: up,
808            down_proj: down,
809        });
810    }
811
812    /// Install the FUSED `[2*inter, hidden]` block-scaled FP8 gate+up weight
813    /// (#927), enabling the one-GEMM decode arm.
814    ///
815    /// 🪤 CONTRACT, and the loader is the only caller: `gate` and `up` as
816    /// passed to [`Self::set_fp8_weights`] must ALREADY be views inside
817    /// `fused` (`gate.weight == fused.weight`, `up.weight ==
818    /// fused.weight + inter*hidden`, and the same for the scale grid). Passing
819    /// a fused weight built from DIFFERENT bytes than the two projections
820    /// would make the fused and un-fused rungs of one layer disagree, silently,
821    /// at whichever `m` moved the dispatch between them. Rule and residency
822    /// argument: `dense_ffn_gateup_fused.rs`.
823    pub fn set_fp8_gate_up_fused(&mut self, fused: Fp8Weight) {
824        debug_assert!(
825            self.fp8_weights
826                .as_ref()
827                .is_some_and(|w| w.gate_proj.weight == fused.weight),
828            "the fused gate+up weight must be the buffer gate_proj is a view into"
829        );
830        self.fp8_gate_up_fused = Some(fused);
831    }
832
833    /// Install the startup-static LoRA FFN overlay (gate/up/down deltas).
834    /// Hard-rejects when FP8/BF16 weight overlays are installed — those
835    /// decode branches early-return before the NVFP4 tail where the M1
836    /// delta insertions land, so a permissive install would silently skip
837    /// deltas. holo is NVFP4, so it is unaffected.
838    pub fn set_lora_weights(&mut self, w: ops::lora_delta::LoraFfnWeights) -> Result<()> {
839        anyhow::ensure!(
840            self.fp8_weights.is_none() && self.bf16_weights.is_none(),
841            "LoRA v0 supports only the NVFP4 dense-FFN path (FP8/BF16 weight \
842             overlays installed on this layer)"
843        );
844        // Packed-Q2 has its own gemv/batchm branches that early-return before
845        // the NVFP4 tail where the deltas land, exactly like FP8/BF16. Refusing
846        // here keeps the invariant the M1 apply relies on: if `self.lora` is
847        // Some, EVERY dispatch this layer can take applies it. A silently
848        // skipping path is worse than a refused load — it makes the adapter
849        // active in prefill and absent in decode, which reads as model
850        // weirdness rather than as a missing feature.
851        anyhow::ensure!(
852            self.q2_weights.is_none(),
853            "LoRA v0 supports only the NVFP4 dense-FFN path (packed-Q2 weights \
854             installed on this layer)"
855        );
856        // The decode down delta contracts over silu(gate)*up, which only the
857        // split-SiLU path materialises; `forward` pins that path whenever an
858        // adapter is installed. Refuse here if the layer cannot take it, so
859        // the pin is a guarantee rather than a hope.
860        anyhow::ensure!(
861            self.activation == FfnActivation::SiLU && self.act_mul.0 != 0 && self.w4a16_gemv.0 != 0,
862            "LoRA v0 needs the split-SiLU decode path (SiLU activation + \
863             act_mul + w4a16_gemv kernels); this layer resolved activation \
864             {:?}, act_mul={}, w4a16_gemv={}",
865            self.activation,
866            self.act_mul.0,
867            self.w4a16_gemv.0,
868        );
869        self.lora = Some(w);
870        Ok(())
871    }
872
873    /// M1 gate/up delta: `gate_out += ΔW_gate · x`, `up_out += ΔW_up · x`.
874    ///
875    /// Call AFTER the gate/up projection and BEFORE `silu_mul` — the deltas
876    /// belong to the projections, so they must land while gate/up are still
877    /// separate. Both buffers are the arena's dedicated `expert_gate_out` /
878    /// `expert_up_out` regions, contiguous with row stride `inter*2`, which is
879    /// what `apply_lora_delta`'s contiguity contract requires.
880    ///
881    /// No-op (and no launches) when the layer carries no adapter, so the
882    /// non-LoRA path stays byte-identical.
883    fn apply_lora_gate_up(
884        &self,
885        ctx: &ForwardContext,
886        input: DevicePtr,
887        gate_out: DevicePtr,
888        up_out: DevicePtr,
889        m: u32,
890        stream: u64,
891    ) -> Result<()> {
892        if ops::lora_delta::lora_no_ffn() {
893            return Ok(());
894        }
895        let Some(ref lw) = self.lora else {
896            return Ok(());
897        };
898        for (pair, base) in [(&lw.gate, gate_out), (&lw.up, up_out)] {
899            if let Some(pair) = pair.as_ref() {
900                ops::lora_delta::apply_lora_delta(
901                    ctx.gpu,
902                    &lw.kernels,
903                    pair,
904                    input,
905                    base,
906                    m,
907                    ctx.buffers.lora_xa(),
908                    ctx.buffers.lora_delta(),
909                    stream,
910                )?;
911            }
912        }
913        Ok(())
914    }
915
916    /// M1 down delta: `output += ΔW_down · act`.
917    ///
918    /// Call AFTER the down projection. `act` is the SiLU(gate)*up activation —
919    /// the same tensor the base down projection contracted over, NOT the
920    /// layer input. Every dense path leaves it in the `expert_gate_out`
921    /// region (silu_mul writes in place over gate), contiguous at row stride
922    /// `inter*2`.
923    fn apply_lora_down(
924        &self,
925        ctx: &ForwardContext,
926        act: DevicePtr,
927        output: DevicePtr,
928        m: u32,
929        stream: u64,
930    ) -> Result<()> {
931        if ops::lora_delta::lora_no_ffn() {
932            return Ok(());
933        }
934        let Some(ref lw) = self.lora else {
935            return Ok(());
936        };
937        let Some(ref pair) = lw.down else {
938            return Ok(());
939        };
940        ops::lora_delta::apply_lora_delta(
941            ctx.gpu,
942            &lw.kernels,
943            pair,
944            act,
945            output,
946            m,
947            ctx.buffers.lora_xa(),
948            ctx.buffers.lora_delta(),
949            stream,
950        )
951    }
952
953    /// Install native keep-packed ternary Q2_0 dense MLP weights. After this
954    /// call, decode `forward` dispatches `q2_0_gemv` per projection (weights
955    /// stay 2-bit resident, no NVFP4 requant) as the highest-priority path.
956    /// Caller must ensure the `q2_0_gemv` kernel is present in the target
957    /// (checked at forward time; falls through to a clear error otherwise).
958    /// Prefill for packed-Q2 is a deferred phase — see `forward_prefill`.
959    pub fn set_q2_weights(
960        &mut self,
961        gate: PackedQ2Weight,
962        up: PackedQ2Weight,
963        down: PackedQ2Weight,
964        gpu: &dyn GpuBackend,
965    ) {
966        self.q2_weights = Some(DenseFfnWeightsQ2 {
967            gate_proj: gate,
968            up_proj: up,
969            down_proj: down,
970        });
971        // Resolved here, not in the constructor: these ship only in
972        // GGUF-serving targets and the boot audit fails closed on an
973        // unconditional probe everywhere else.
974        self.q2_0_mmq_nc_k = super::try_kernel(gpu, "q2_0_mmq", "atlas_q2_0_mmq128_nc");
975        self.q2_0_mmq_wc_k = super::try_kernel(gpu, "q2_0_mmq", "atlas_q2_0_mmq128_wc");
976    }
977
978    /// Install BF16 dense MLP weights. After this call, the forward paths
979    /// dispatch to the BF16 GEMV/GEMM kernels instead of w4a16. The
980    /// caller must ensure the BF16 kernels are loaded (see
981    /// `dense_gemv_bf16_k` / `dense_gemm_bf16_k` checks). Small-batch
982    /// paths reuse `forward_prefill` so they cannot enter NVFP4 kernels
983    /// with the null placeholder weights used by BF16-native layers.
984    pub fn set_bf16_weights(&mut self, gate: DenseWeight, up: DenseWeight, down: DenseWeight) {
985        self.bf16_weights = Some(DenseFfnWeightsBf16 {
986            gate_proj: gate,
987            up_proj: up,
988            down_proj: down,
989        });
990    }
991
992    /// Ensure the int8 W4A8 copy of one NVFP4 projection weight exists, building
993    /// it once via `requant_w_nvfp4_int8` and caching it in `cell`. Reads the
994    /// NON-transposed NVFP4 layout (`weight` = packed E2M1 `[N, K/2]`,
995    /// `weight_scale` = per-16 E4M3 `[N, K/16]`, `weight_scale_2` = per-tensor
996    /// F32) — so it is independent of the `*_proj_t` transposed copies. The
997    /// requant launches on `stream`; the subsequent faith2 read is stream-ordered
998    /// after it, so no host sync is needed.
999    fn ensure_int8_weight(
1000        &self,
1001        cell: &std::sync::OnceLock<Int8Weight>,
1002        gpu: &dyn GpuBackend,
1003        src: &QuantizedWeight,
1004        n: u32,
1005        k: u32,
1006        stream: u64,
1007    ) -> Result<Int8Weight> {
1008        if let Some(w) = cell.get() {
1009            return Ok(*w);
1010        }
1011        let (nn, kk) = (n as usize, k as usize);
1012        let w_i8 = gpu.alloc(nn * kk)?; // [N, K] int8
1013        let w_scale = gpu.alloc(nn * (kk / 32) * 4)?; // [N, K/32] F32
1014        ops::requant_w_nvfp4_int8(
1015            gpu,
1016            self.requant_w_int8_k,
1017            src.weight,
1018            src.weight_scale,
1019            src.weight_scale_2,
1020            w_i8,
1021            w_scale,
1022            n,
1023            k,
1024            stream,
1025        )?;
1026        let built = Int8Weight { w_i8, w_scale };
1027        // Lost a race (another thread built first): free our duplicate buffers.
1028        if let Err(dup) = cell.set(built) {
1029            let _ = gpu.free(dup.w_i8);
1030            let _ = gpu.free(dup.w_scale);
1031        }
1032        Ok(*cell.get().expect("int8 weight cell set above"))
1033    }
1034
1035    /// Lazily materialize a Q4_K FFN weight from the NVFP4 source: dequant NVFP4→bf16
1036    /// (transient buffer, freed) then quantize bf16→GGML block_q4_K (cached for the
1037    /// process lifetime). `src` is the non-transposed NVFP4 weight `[n, k]`.
1038    fn ensure_q4k_weight(
1039        &self,
1040        cell: &std::sync::OnceLock<Q4kWeight>,
1041        gpu: &dyn GpuBackend,
1042        src: &QuantizedWeight,
1043        n: u32,
1044        k: u32,
1045        stream: u64,
1046    ) -> Result<Q4kWeight> {
1047        if let Some(w) = cell.get() {
1048            return Ok(*w);
1049        }
1050        // transient bf16 [n, k] (freed after quantize); persistent Q4_K bytes.
1051        let bf16_tmp = gpu.alloc((n as usize) * (k as usize) * 2)?;
1052        ops::dequant_nvfp4_to_bf16(
1053            gpu,
1054            self.dequant_nvfp4_bf16_k,
1055            src.weight,
1056            src.weight_scale,
1057            bf16_tmp,
1058            src.weight_scale_2,
1059            n,
1060            k,
1061            stream,
1062        )?;
1063        let w_q4k = gpu.alloc(ops::q4k_weight_bytes(n, k))?;
1064        ops::quantize_weight_q4k(gpu, self.q4k_quant_w_k, bf16_tmp, w_q4k, n, k, stream)?;
1065        // bf16_tmp consumed by the quantize on `stream`; sync before freeing it.
1066        gpu.synchronize(stream)?;
1067        let _ = gpu.free(bf16_tmp);
1068        let built = Q4kWeight { w_q4k };
1069        if let Err(dup) = cell.set(built) {
1070            let _ = gpu.free(dup.w_q4k);
1071        }
1072        Ok(*cell.get().expect("q4k weight cell set above"))
1073    }
1074
1075    /// Single-token decode: 2-3 kernel launches depending on activation.
1076    /// SiLU: dual GEMV + SiLU-fused down GEMV (2 launches).
1077    /// GELU: dual GEMV + gelu_mul + down GEMV (3 launches, no fused GELU down kernel).
1078    pub fn forward(
1079        &self,
1080        input: DevicePtr,
1081        ctx: &ForwardContext,
1082        stream: u64,
1083    ) -> Result<DevicePtr> {
1084        let h = ctx.config.hidden_size as u32;
1085        let inter = ctx.config.intermediate_size as u32;
1086
1087        let gate_out = ctx.buffers.expert_gate_out();
1088        let up_out = ctx.buffers.expert_up_out();
1089
1090        // Native keep-packed Q2_0 dispatch (highest priority). Per-projection
1091        // `q2_0_gemv`: BF16 activation × packed 2-bit weight, dequant in the
1092        // dot-product — weights never expand to BF16/NVFP4. No fused dual/silu
1093        // kernel yet, so this is gate + up + silu_mul + down (4 launches),
1094        // mirroring the FP8 non-fused fallback. SiLU only (Ternary-Bonsai is a
1095        // Qwen-family SwiGLU); GeLU packed-Q2 is a follow-up.
1096        if let Some(ref q2w) = self.q2_weights {
1097            if self.q2_0_gemv_k.0 == 0 {
1098                anyhow::bail!(
1099                    "q2_0_gemv kernel missing in this target build — packed-Q2 decode \
1100                     (ATLAS_GGUF_NATIVE_Q2) is unavailable"
1101                );
1102            }
1103            if self.activation != FfnActivation::SiLU {
1104                anyhow::bail!(
1105                    "packed-Q2 FFN decode supports SiLU only (got {:?})",
1106                    self.activation
1107                );
1108            }
1109            let output = ctx.buffers.moe_output();
1110            ops::q2_0_gemv_vec(
1111                ctx.gpu,
1112                self.q2_0_gemv_k,
1113                input,
1114                &q2w.gate_proj,
1115                gate_out,
1116                stream,
1117            )?;
1118            ops::q2_0_gemv_vec(
1119                ctx.gpu,
1120                self.q2_0_gemv_k,
1121                input,
1122                &q2w.up_proj,
1123                up_out,
1124                stream,
1125            )?;
1126            ops::silu_mul(
1127                ctx.gpu,
1128                self.act_mul,
1129                gate_out,
1130                up_out,
1131                gate_out,
1132                inter,
1133                stream,
1134            )?;
1135            ops::q2_0_gemv_vec(
1136                ctx.gpu,
1137                self.q2_0_gemv_k,
1138                gate_out,
1139                &q2w.down_proj,
1140                output,
1141                stream,
1142            )?;
1143            return Ok(output);
1144        }
1145
1146        // FP8 dispatch: the fused FP8 dual-GEMV (gate+up in one launch) then the
1147        // down projection, mirroring the NVFP4 path. Falls back to the 4-launch
1148        // per-projection `w8a16_gemv` path when the fused kernels or a non-SiLU
1149        // activation make the fast path unavailable.
1150        //
1151        // SPLIT SiLU+down (DEFAULT; kill-switch ATLAS_NO_DECODE_SPLIT_SILU) —
1152        // #928. The NVFP4 arm below has staged `silu(gate)*up` once since the
1153        // ncu receipt quoted there; the FP8 arm never got the same treatment
1154        // and paid for it. `w8a16_gemv_silu_input` recomputes the SwiGLU PER
1155        // OUTPUT, not per block: each of the N/4 CTAs gives every one of its 4
1156        // outputs a 64-lane team that walks all of K, so the launch evaluates
1157        // N*K = 5,120 x 17,408 = 89.1 M silu(gate)*up — 5,120x the 17,408 the
1158        // token actually needs — and each one is an `__expf` plus a true FP32
1159        // divide (`--fmad=false`, no `-use_fast_math`, so `g/(1+e^-g)` is the
1160        // IEEE division sequence, not a reciprocal). nsys, 1xH100,
1161        // Qwen/Qwen3.8-27B-FP8, 2026-09-11 round 7, C=1 step 21.891 ms: 64
1162        // launches x 103.9 us = 6.65 ms/step = 30.4% of the step at 858 GB/s,
1163        // against the `w8a16_gemv_dual` that reads the SAME 89.1 MB of weights
1164        // per layer at 1,979 GB/s. Staging the activation once (one elementwise
1165        // launch over K, microseconds, and CUDA graphs amortise the launch)
1166        // leaves the down GEMV a pure weight-streaming kernel.
1167        //
1168        // NUMERICS, stated rather than implied: this is NOT bit-identical to
1169        // the fused kernel. `moe_silu_mul` rounds `g*(1/(1+e^-g))*u` to BF16
1170        // before the GEMV consumes it, where the fused kernel keeps
1171        // `(g/(1+e^-g))*u` in FP32 all the way into the dot product — a BF16
1172        // round plus a reciprocal-vs-divide difference on the activation. It
1173        // IS the numerics prefill runs, and the same trade the NVFP4 arm has
1174        // shipped by default; `ATLAS_NO_DECODE_SPLIT_SILU` restores the fused
1175        // kernel bit-for-bit.
1176        if let Some(ref fp8w) = self.fp8_weights {
1177            let output = ctx.buffers.moe_output();
1178            let arm = fp8_down::fp8_down_arm(
1179                self.activation == FfnActivation::SiLU,
1180                self.w8a16_gemv_dual_k.0 != 0,
1181                self.w8a16_gemv_silu_input_k.0 != 0,
1182                self.act_mul.0 != 0,
1183                self.w8a16_gemv_k.0 != 0,
1184                ctx.levers.decode_split_silu,
1185            );
1186            if arm != fp8_down::Fp8DownArm::PerProjection {
1187                ops::w8a16_gemv_dual(
1188                    ctx.gpu,
1189                    self.w8a16_gemv_dual_k,
1190                    input,
1191                    fp8w.gate_proj.weight,
1192                    fp8w.gate_proj.row_scale,
1193                    gate_out,
1194                    fp8w.up_proj.weight,
1195                    fp8w.up_proj.row_scale,
1196                    up_out,
1197                    inter,
1198                    h,
1199                    stream,
1200                )?;
1201                if arm == fp8_down::Fp8DownArm::SplitSilu {
1202                    ops::silu_mul(
1203                        ctx.gpu,
1204                        self.act_mul,
1205                        gate_out,
1206                        up_out,
1207                        gate_out,
1208                        inter,
1209                        stream,
1210                    )?;
1211                    ops::w8a16_gemv(
1212                        ctx.gpu,
1213                        self.w8a16_gemv_k,
1214                        gate_out,
1215                        fp8w.down_proj.weight,
1216                        fp8w.down_proj.row_scale,
1217                        output,
1218                        h,
1219                        inter,
1220                        stream,
1221                    )?;
1222                } else {
1223                    ops::w8a16_gemv_silu_input(
1224                        ctx.gpu,
1225                        self.w8a16_gemv_silu_input_k,
1226                        gate_out,
1227                        up_out,
1228                        fp8w.down_proj.weight,
1229                        fp8w.down_proj.row_scale,
1230                        output,
1231                        h,
1232                        inter,
1233                        stream,
1234                    )?;
1235                }
1236                return Ok(output);
1237            }
1238            ops::w8a16_gemv(
1239                ctx.gpu,
1240                self.w8a16_gemv_k,
1241                input,
1242                fp8w.gate_proj.weight,
1243                fp8w.gate_proj.row_scale,
1244                gate_out,
1245                inter,
1246                h,
1247                stream,
1248            )?;
1249            ops::w8a16_gemv(
1250                ctx.gpu,
1251                self.w8a16_gemv_k,
1252                input,
1253                fp8w.up_proj.weight,
1254                fp8w.up_proj.row_scale,
1255                up_out,
1256                inter,
1257                h,
1258                stream,
1259            )?;
1260            ops::silu_mul(
1261                ctx.gpu,
1262                self.act_mul,
1263                gate_out,
1264                up_out,
1265                gate_out,
1266                inter,
1267                stream,
1268            )?;
1269            ops::w8a16_gemv(
1270                ctx.gpu,
1271                self.w8a16_gemv_k,
1272                gate_out,
1273                fp8w.down_proj.weight,
1274                fp8w.down_proj.row_scale,
1275                output,
1276                h,
1277                inter,
1278                stream,
1279            )?;
1280            return Ok(output);
1281        }
1282
1283        // BF16 dispatch: per-projection GEMV via `dense_gemv_bf16`. We
1284        // don't have a fused dual-BF16-GEMV kernel today; two sequential
1285        // launches are still BF16-precision-correct and only ~10% slower
1286        // than the fused w4a16 path on Gemma-4-31B (the cost is dominated
1287        // by the bigger BF16 weight reads, not launch overhead).
1288        if let Some(ref bf16w) = self.bf16_weights {
1289            ops::dense_gemv(
1290                ctx.gpu,
1291                self.dense_gemv_bf16_k,
1292                input,
1293                &bf16w.gate_proj,
1294                gate_out,
1295                inter,
1296                h,
1297                stream,
1298            )?;
1299            ops::dense_gemv(
1300                ctx.gpu,
1301                self.dense_gemv_bf16_k,
1302                input,
1303                &bf16w.up_proj,
1304                up_out,
1305                inter,
1306                h,
1307                stream,
1308            )?;
1309            ops::silu_mul(
1310                ctx.gpu,
1311                self.act_mul,
1312                gate_out,
1313                up_out,
1314                gate_out,
1315                inter,
1316                stream,
1317            )?;
1318            let output = ctx.buffers.moe_output();
1319            ops::dense_gemv(
1320                ctx.gpu,
1321                self.dense_gemv_bf16_k,
1322                gate_out,
1323                &bf16w.down_proj,
1324                output,
1325                h,
1326                inter,
1327                stream,
1328            )?;
1329            return Ok(output);
1330        }
1331
1332        // ATLAS_DECODE_FFN_VIA_GEMM=1: route decode's M=1 FFN projections
1333        // through the SAME transposed-weight GEMM kernels the DFlash verify
1334        // path uses (`w4a16_prefill_gemm` → w4a16_gemm_t / _t_k64), instead
1335        // of the dedicated GEMV kernels. Purpose: bit-identical FFN numerics
1336        // between serial decode and batched verify — the batch-K vs batch-1
1337        // divergence #218's bisect isolated ("FFN non-associativity") and the
1338        // root cause of the T=0 spec trajectory flips (2026-07-07 session).
1339        // Split SiLU staging already matches prefill SiLU numerics (swiglu
1340        // clamp), so with this arm the whole FFN block is kernel-identical to
1341        // a verify row. Requires the *_proj_t transposed copies (the NVFP4-MMQ
1342        // prefill arm FREES them — disable it if the warn below fires).
1343        // The `OnceLock<bool>` static that lived here is now a field on
1344        // `layers::ops::ModelLevers` — resolved when the model is built and carried
1345        // on `ForwardContext`, because a static outlives the model whose flags it
1346        // encodes.
1347        if ctx.levers.decode_ffn_via_gemm
1348            && self.activation == FfnActivation::SiLU
1349            && self.act_mul.0 != 0
1350        {
1351            let wt_alive =
1352                |w: &Option<QuantizedWeight>| w.as_ref().is_some_and(|w| !w.weight.is_null());
1353            if wt_alive(&self.weights.gate_proj_t) && wt_alive(&self.weights.up_proj_t) {
1354                // Log-once latch (see `atlas_core::scope`). It holds no model-derived
1355                // value — the message is rebuilt from the arguments every call — so a
1356                // stale entry cannot produce a wrong answer, only a suppressed duplicate
1357                // line after a model swap. Scoping it would thread a logging concern
1358                // through the call path to prevent one repeated INFO line.
1359                if ctx.stats.once("log:decode_ffn_via_gemm") {
1360                    tracing::info!(
1361                        "decode FFN via verify GEMM path (ATLAS_DECODE_FFN_VIA_GEMM=1): \
1362                         gate/up/down through w4a16_prefill_gemm at M=1"
1363                    );
1364                }
1365                self.w4a16_prefill_gemm(
1366                    ctx,
1367                    &self.weights.gate_proj,
1368                    self.weights.gate_proj_t.as_ref(),
1369                    input,
1370                    gate_out,
1371                    1,
1372                    inter,
1373                    h,
1374                    stream,
1375                )?;
1376                self.w4a16_prefill_gemm(
1377                    ctx,
1378                    &self.weights.up_proj,
1379                    self.weights.up_proj_t.as_ref(),
1380                    input,
1381                    up_out,
1382                    1,
1383                    inter,
1384                    h,
1385                    stream,
1386                )?;
1387                ops::silu_mul(
1388                    ctx.gpu,
1389                    self.act_mul,
1390                    gate_out,
1391                    up_out,
1392                    gate_out,
1393                    inter,
1394                    stream,
1395                )?;
1396                let output = ctx.buffers.moe_output();
1397                self.w4a16_prefill_gemm(
1398                    ctx,
1399                    &self.weights.down_proj,
1400                    self.weights.down_proj_t.as_ref(),
1401                    gate_out,
1402                    output,
1403                    1,
1404                    h,
1405                    inter,
1406                    stream,
1407                )?;
1408                return Ok(output);
1409            }
1410            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
1411            // value — the message is rebuilt from the arguments every call — so a
1412            // stale entry cannot produce a wrong answer, only a suppressed duplicate
1413            // line after a model swap. Scoping it would thread a logging concern
1414            // through the call path to prevent one repeated INFO line.
1415            if ctx.stats.once("log:decode_ffn_no_twins") {
1416                tracing::warn!(
1417                    "ATLAS_DECODE_FFN_VIA_GEMM=1 requested but transposed FFN copies \
1418                     are freed/absent (NVFP4-MMQ prefill arm?) — falling back to GEMV; \
1419                     the unification experiment is NOT active"
1420                );
1421            }
1422        }
1423
1424        // Fused gate_proj + up_proj: [1, H] → [1, inter] × 2.
1425        // Single-warp variant (lossless) when the lever is on and the kernel
1426        // resolved; otherwise the 64-thread kernel. Dual and silu-input SW
1427        // are independent — missing silu_input_sw must not skip dual_sw on
1428        // the default split-SiLU path.
1429        let use_dual_sw = ops::use_gemv_sw(ctx.levers.gemv_sw, self.w4a16_gemv_dual_sw);
1430        let use_silu_sw = ops::use_gemv_sw(ctx.levers.gemv_sw, self.w4a16_gemv_silu_input_sw);
1431        if use_dual_sw {
1432            ops::w4a16_gemv_dual_sw(
1433                ctx.gpu,
1434                self.w4a16_gemv_dual_sw,
1435                input,
1436                &self.weights.gate_proj,
1437                gate_out,
1438                &self.weights.up_proj,
1439                up_out,
1440                inter,
1441                h,
1442                stream,
1443            )?;
1444        } else {
1445            ops::w4a16_gemv_dual(
1446                ctx.gpu,
1447                self.w4a16_gemv_dual,
1448                input,
1449                &self.weights.gate_proj,
1450                gate_out,
1451                &self.weights.up_proj,
1452                up_out,
1453                inter,
1454                h,
1455                stream,
1456            )?;
1457        }
1458
1459        let output = ctx.buffers.moe_output();
1460        // Split SiLU+down (DEFAULT; kill-switch ATLAS_NO_DECODE_SPLIT_SILU): the fused
1461        // silu_input kernel recomputes the SiLU transcendentals per OUTPUT ROW (N/4
1462        // blocks × redundant __expf) and measures COMPUTE-bound — ncu: SM 57% vs
1463        // memory 23%, 186 GB/s vs the dual GEMV's 266. Staging silu(gate)*up once
1464        // (one elementwise launch, CUDA graphs amortize it) lets the down GEMV run
1465        // memory-bound like the dual. Also aligns decode with the prefill SiLU
1466        // numerics (swiglu clamp), which the fused kernel lacked.
1467        //
1468        // An installed LoRA adapter PINS this path. The fused `silu_input`
1469        // alternative never materialises silu(gate)*up — it consumes gate and
1470        // up straight into the down GEMV — and the down delta has to contract
1471        // over exactly that activation. Reproducing it into `lora_hact` just
1472        // to feed the delta would compute the SiLU twice for a path that is
1473        // already the default and already the numerically preferred one, so
1474        // the adapter pins it instead. `set_lora_weights` refuses an adapter
1475        // when this path is unavailable on the layer, which makes
1476        // `lora.is_some()` imply the three conditions above.
1477        let split_silu = self.activation == FfnActivation::SiLU
1478            && self.act_mul.0 != 0
1479            && self.w4a16_gemv.0 != 0
1480            && (ctx.levers.decode_split_silu || self.lora.is_some());
1481        if split_silu {
1482            self.apply_lora_gate_up(ctx, input, gate_out, up_out, 1, stream)?;
1483            ops::silu_mul(
1484                ctx.gpu,
1485                self.act_mul,
1486                gate_out,
1487                up_out,
1488                gate_out,
1489                inter,
1490                stream,
1491            )?;
1492            ops::w4a16_decode_gemv(
1493                ctx.gpu,
1494                self.w4a16_gemv,
1495                self.w4a16_gemv_sw,
1496                ctx.levers.gemv_sw,
1497                gate_out,
1498                &self.weights.down_proj,
1499                output,
1500                h,
1501                inter,
1502                stream,
1503            )?;
1504            self.apply_lora_down(ctx, gate_out, output, 1, stream)?;
1505            return Ok(output);
1506        }
1507        debug_assert!(
1508            self.lora.is_none(),
1509            "LoRA installed but decode took the fused silu_input path, which \
1510             never materialises the activation the down delta contracts over; \
1511             set_lora_weights is supposed to make this unreachable"
1512        );
1513        match self.activation {
1514            FfnActivation::SiLU => {
1515                // Fused SiLU(gate)*up + down_proj: [1, inter] → [1, H]
1516                if use_silu_sw {
1517                    ops::w4a16_gemv_silu_input_sw(
1518                        ctx.gpu,
1519                        self.w4a16_gemv_silu_input_sw,
1520                        gate_out,
1521                        up_out,
1522                        &self.weights.down_proj,
1523                        output,
1524                        h,
1525                        inter,
1526                        stream,
1527                    )?;
1528                } else {
1529                    ops::w4a16_gemv_silu_input(
1530                        ctx.gpu,
1531                        self.w4a16_gemv_silu_input,
1532                        gate_out,
1533                        up_out,
1534                        &self.weights.down_proj,
1535                        output,
1536                        h,
1537                        inter,
1538                        stream,
1539                    )?;
1540                }
1541            }
1542            FfnActivation::GeLU => {
1543                // GELU(gate)*up → gate_out, then down_proj GEMV
1544                ops::silu_mul(
1545                    ctx.gpu,
1546                    self.act_mul,
1547                    gate_out,
1548                    up_out,
1549                    gate_out,
1550                    inter,
1551                    stream,
1552                )?;
1553                ops::w4a16_decode_gemv(
1554                    ctx.gpu,
1555                    self.w4a16_gemv,
1556                    self.w4a16_gemv_sw,
1557                    ctx.levers.gemv_sw,
1558                    gate_out,
1559                    &self.weights.down_proj,
1560                    output,
1561                    h,
1562                    inter,
1563                    stream,
1564                )?;
1565            }
1566        }
1567
1568        Ok(output)
1569    }
1570
1571    /// Packed-Q2 batched decode FFN for `m` concurrent rows (`m >= 2`). Mirrors
1572    /// the single-token `forward` packed-Q2 arm — per-projection keep-packed
1573    /// `q2_0_gemv_vec_batchm` (BF16 `[m,·]` activation × 2-bit weight, dequant in
1574    /// the dot-product, no BF16/NVFP4 expansion), SiLU-mul between gate/up, then
1575    /// down — but with `m` activation rows staged per weight read. This is the
1576    /// correctness path for concurrent decode (C>=2): the NVFP4 `forward_k2/k3`
1577    /// GEMVs read the NULL NVFP4 fallback weights, so packed-Q2 must route here.
1578    /// The wrapper chunks internally for `m > 8`. SiLU only (Ternary-Bonsai is a
1579    /// SwiGLU); output lands in `moe_output` as `[m, h]` row-major.
1580    fn forward_km_q2(
1581        &self,
1582        q2w: &DenseFfnWeightsQ2,
1583        input: DevicePtr,
1584        ctx: &ForwardContext,
1585        m: u32,
1586        stream: u64,
1587    ) -> Result<()> {
1588        if self.q2_0_gemv_batchm_k.0 == 0 {
1589            anyhow::bail!(
1590                "q2_0_gemv_vec_batchm kernel missing in this target build — packed-Q2 \
1591                 batched decode (ATLAS_GGUF_NATIVE_Q2, C>=2) is unavailable"
1592            );
1593        }
1594        if self.activation != FfnActivation::SiLU {
1595            anyhow::bail!(
1596                "packed-Q2 FFN batched decode supports SiLU only (got {:?})",
1597                self.activation
1598            );
1599        }
1600        let inter = ctx.config.intermediate_size as u32;
1601        let gate_out = ctx.buffers.expert_gate_out();
1602        let up_out = ctx.buffers.expert_up_out();
1603        let batchm = |w: &PackedQ2Weight, inp: DevicePtr, out: DevicePtr| -> Result<()> {
1604            ops::q2_0_gemv_vec_batchm(ctx.gpu, self.q2_0_gemv_batchm_k, inp, w, out, m, stream)
1605        };
1606        batchm(&q2w.gate_proj, input, gate_out)?;
1607        batchm(&q2w.up_proj, input, up_out)?;
1608        ops::silu_mul(
1609            ctx.gpu,
1610            self.act_mul,
1611            gate_out,
1612            up_out,
1613            gate_out,
1614            m * inter,
1615            stream,
1616        )?;
1617        let output = ctx.buffers.moe_output();
1618        batchm(&q2w.down_proj, gate_out, output)?;
1619        Ok(())
1620    }
1621
1622    /// K=2 speculative: batched GEMV for 2 tokens.
1623    /// 3 launches: dual batch2 (gate+up) + silu_mul + batch2 (down).
1624    pub fn forward_k2(&self, input: DevicePtr, ctx: &ForwardContext, stream: u64) -> Result<()> {
1625        // Packed-Q2: NVFP4 fallback weights are NULL, so the NVFP4 batch2 GEMVs
1626        // below would fault. Route to the keep-packed batchm FFN (m=2).
1627        if let Some(ref q2w) = self.q2_weights {
1628            return self.forward_km_q2(q2w, input, ctx, 2, stream);
1629        }
1630        if native_small_batch_uses_prefill(self.bf16_weights.is_some(), self.fp8_weights.is_some())
1631        {
1632            return self.forward_prefill(input, 2, ctx, stream);
1633        }
1634
1635        let h = ctx.config.hidden_size as u32;
1636        let inter = ctx.config.intermediate_size as u32;
1637
1638        let gate_out = ctx.buffers.expert_gate_out();
1639        let up_out = ctx.buffers.expert_up_out();
1640
1641        // Fused gate+up for 2 tokens
1642        ops::w4a16_gemv_dual_batch2(
1643            ctx.gpu,
1644            self.w4a16_gemv_dual_batch2,
1645            input,
1646            &self.weights.gate_proj,
1647            gate_out,
1648            &self.weights.up_proj,
1649            up_out,
1650            inter,
1651            h,
1652            stream,
1653        )?;
1654        self.apply_lora_gate_up(ctx, input, gate_out, up_out, 2, stream)?;
1655        ops::silu_mul(
1656            ctx.gpu,
1657            self.act_mul,
1658            gate_out,
1659            up_out,
1660            gate_out,
1661            2 * inter,
1662            stream,
1663        )?;
1664        let output = ctx.buffers.moe_output();
1665        ops::w4a16_gemv_batch2(
1666            ctx.gpu,
1667            self.w4a16_gemv_batch2,
1668            gate_out,
1669            &self.weights.down_proj,
1670            output,
1671            h,
1672            inter,
1673            stream,
1674        )?;
1675        self.apply_lora_down(ctx, gate_out, output, 2, stream)?;
1676
1677        Ok(())
1678    }
1679
1680    /// K=3 speculative: batched GEMV for 3 tokens.
1681    /// 3 launches: dual batch3 (gate+up) + silu_mul + batch3 (down).
1682    pub fn forward_k3(&self, input: DevicePtr, ctx: &ForwardContext, stream: u64) -> Result<()> {
1683        // Packed-Q2: route to the keep-packed batchm FFN (m=3); NVFP4 weights null.
1684        if let Some(ref q2w) = self.q2_weights {
1685            return self.forward_km_q2(q2w, input, ctx, 3, stream);
1686        }
1687        if native_small_batch_uses_prefill(self.bf16_weights.is_some(), self.fp8_weights.is_some())
1688        {
1689            return self.forward_prefill(input, 3, ctx, stream);
1690        }
1691
1692        let h = ctx.config.hidden_size as u32;
1693        let inter = ctx.config.intermediate_size as u32;
1694
1695        let gate_out = ctx.buffers.expert_gate_out();
1696        let up_out = ctx.buffers.expert_up_out();
1697
1698        // Fused gate+up for 3 tokens
1699        ops::w4a16_gemv_dual_batch3(
1700            ctx.gpu,
1701            self.w4a16_gemv_dual_batch3,
1702            input,
1703            &self.weights.gate_proj,
1704            gate_out,
1705            &self.weights.up_proj,
1706            up_out,
1707            inter,
1708            h,
1709            stream,
1710        )?;
1711        self.apply_lora_gate_up(ctx, input, gate_out, up_out, 3, stream)?;
1712        ops::silu_mul(
1713            ctx.gpu,
1714            self.act_mul,
1715            gate_out,
1716            up_out,
1717            gate_out,
1718            3 * inter,
1719            stream,
1720        )?;
1721        let output = ctx.buffers.moe_output();
1722        ops::w4a16_gemv_batch3(
1723            ctx.gpu,
1724            self.w4a16_gemv_batch3,
1725            gate_out,
1726            &self.weights.down_proj,
1727            output,
1728            h,
1729            inter,
1730            stream,
1731        )?;
1732        self.apply_lora_down(ctx, gate_out, output, 3, stream)?;
1733
1734        Ok(())
1735    }
1736
1737    /// Batchm-GEMV kernel for `m` verify rows: the narrowest resolved tier in
1738    /// `w4a16_gemv_batch{4,5,6,7,8}` that covers `m`. 0-handle when out of
1739    /// range or absent. See `layers::w4a16_gemv_tiers` for the decision and
1740    /// the `ATLAS_NO_GEMV_EXACT_M_TIERS=1` kill switch.
1741    fn batchm_kernel(&self, m: u32) -> KernelHandle {
1742        self.w4a16_batchm.kernel(m)
1743    }
1744
1745    /// Whether the M-row batched-GEMV verify path is available for `m` rows
1746    /// (batchm kernel present AND NVFP4 weights loaded — the batchm GEMV
1747    /// reads the non-transposed NVFP4 layout).
1748    pub fn can_forward_km(&self, m: u32) -> bool {
1749        self.batchm_kernel(m).0 != 0
1750            && (!self.weights.gate_proj.weight.is_null()
1751                // Native FP8 (#915): the loader no longer builds the NVFP4
1752                // fallback, but `forward_km` redirects to `forward_prefill` for
1753                // an FP8 layer anyway (`native_small_batch_uses_prefill`), so
1754                // the answer must stay `true` or the n=4..8 verify arm in
1755                // `multi_seq/ffn.rs:145` would fall through to a DIFFERENT
1756                // branch and quietly change the routing this fix must not touch.
1757                || self.fp8_weights.is_some())
1758    }
1759
1760    /// K=m (m<=8) speculative verify: batched GEMV for m tokens.
1761    /// 4 launches: batchm gate + batchm up + silu_mul + batchm down — each
1762    /// projection weight is read ONCE for all m rows at near-peak stream
1763    /// bandwidth. nsys (2026-07-18, M=4): the `forward_prefill` MMQ arm this
1764    /// replaces for the K=4 verify cost 54.8 ms/step across the 64-layer
1765    /// dense FFN stack (~156 GB/s effective at M=4); the batch GEMV family
1766    /// measures ~290 GB/s on the same shapes (w8a16_gemv_batch4 sibling),
1767    /// putting this path at the ~31 ms weight-traffic floor. m=5..8 uses
1768    /// `w4a16_gemv_batch8` (batchm_bench: same weight-streaming bandwidth,
1769    /// removing the M>4 tile-GEMM cliff for chain-verify K=5..8).
1770    pub fn forward_km(
1771        &self,
1772        input: DevicePtr,
1773        m: u32,
1774        ctx: &ForwardContext,
1775        stream: u64,
1776    ) -> Result<()> {
1777        // As in k2/k3, concurrent decode must preserve an installed native
1778        // overlay even when valid, lower-precision NVFP4 fallbacks coexist.
1779        if native_small_batch_uses_prefill(self.bf16_weights.is_some(), self.fp8_weights.is_some())
1780        {
1781            return self.forward_prefill(input, m as usize, ctx, stream);
1782        }
1783        let h = ctx.config.hidden_size as u32;
1784        let inter = ctx.config.intermediate_size as u32;
1785        let kh = self.batchm_kernel(m);
1786
1787        let gate_out = ctx.buffers.expert_gate_out();
1788        let up_out = ctx.buffers.expert_up_out();
1789
1790        ops::w4a16_gemv_batchm(
1791            ctx.gpu,
1792            kh,
1793            input,
1794            &self.weights.gate_proj,
1795            gate_out,
1796            m,
1797            inter,
1798            h,
1799            stream,
1800        )?;
1801        ops::w4a16_gemv_batchm(
1802            ctx.gpu,
1803            kh,
1804            input,
1805            &self.weights.up_proj,
1806            up_out,
1807            m,
1808            inter,
1809            h,
1810            stream,
1811        )?;
1812        self.apply_lora_gate_up(ctx, input, gate_out, up_out, m, stream)?;
1813        ops::silu_mul(
1814            ctx.gpu,
1815            self.act_mul,
1816            gate_out,
1817            up_out,
1818            gate_out,
1819            m * inter,
1820            stream,
1821        )?;
1822        let output = ctx.buffers.moe_output();
1823        ops::w4a16_gemv_batchm(
1824            ctx.gpu,
1825            kh,
1826            gate_out,
1827            &self.weights.down_proj,
1828            output,
1829            m,
1830            h,
1831            inter,
1832            stream,
1833        )?;
1834        self.apply_lora_down(ctx, gate_out, output, m, stream)?;
1835
1836        Ok(())
1837    }
1838
1839    /// N-token prefill: GEMM for all projections.
1840    /// W4A16 prefill/verify GEMM dispatch, routed by (M, K) per
1841    /// w4a16_m17_bench measurements on GB10:
1842    ///   - M<=64 (DFlash verify M=17): the M64-tile `w4a16_gemm_t` beats the
1843    ///     M128-tile kernels (283 vs 324us on gate/up — 87% of an M128 tile
1844    ///     is padding at M=17), and `w4a16_gemm_t_k64` wins deep-K down_proj
1845    ///     (554 vs 810us at K=17408, where N/128 CTAs can't fill the GPU and
1846    ///     the halved K-loop matters).
1847    ///   - M>64 (real prefill): v2 (8-warp) > t_m128 (4-warp), unchanged.
1848    ///   - No transposed copy: base `w4a16_gemm` (9-12x the bandwidth floor —
1849    ///     last resort).
1850    ///
1851    /// Kill-switch: ATLAS_FFN_SMALLM=0 restores the m128-only dispatch for A/B.
1852    #[allow(clippy::too_many_arguments)]
1853    fn w4a16_prefill_gemm(
1854        &self,
1855        ctx: &ForwardContext,
1856        w: &QuantizedWeight,
1857        wt: Option<&QuantizedWeight>,
1858        input: DevicePtr,
1859        output: DevicePtr,
1860        m: u32,
1861        n: u32,
1862        k: u32,
1863        stream: u64,
1864    ) -> Result<()> {
1865        // The `OnceLock<bool>` static that lived here is now a field on
1866        // `layers::ops::ModelLevers` — resolved when the model is built and carried
1867        // on `ForwardContext`, because a static outlives the model whose flags it
1868        // encodes.
1869        if let Some(wt) = wt {
1870            if m <= 64 && k.is_multiple_of(32) && ctx.levers.ffn_small_m {
1871                if k >= crate::layers::w4a16_k64_min_k()
1872                    && k.is_multiple_of(64)
1873                    && self.w4a16_gemm_t_k64_k.0 != 0
1874                {
1875                    return ops::w4a16_gemm_n128(
1876                        ctx.gpu,
1877                        self.w4a16_gemm_t_k64_k,
1878                        input,
1879                        wt,
1880                        output,
1881                        m,
1882                        n,
1883                        k,
1884                        stream,
1885                    );
1886                }
1887                if self.w4a16_gemm_t_k.0 != 0 {
1888                    return ops::w4a16_gemm_n128(
1889                        ctx.gpu,
1890                        self.w4a16_gemm_t_k,
1891                        input,
1892                        wt,
1893                        output,
1894                        m,
1895                        n,
1896                        k,
1897                        stream,
1898                    );
1899                }
1900            }
1901            if self.w4a16_gemm_t_m128_v2_k.0 != 0 {
1902                return ops::w4a16_gemm_n128_m128_v2(
1903                    ctx.gpu,
1904                    self.w4a16_gemm_t_m128_v2_k,
1905                    input,
1906                    wt,
1907                    output,
1908                    m,
1909                    n,
1910                    k,
1911                    stream,
1912                );
1913            }
1914            if self.w4a16_gemm_t_m128_k.0 != 0 {
1915                return ops::w4a16_gemm_n128_m128(
1916                    ctx.gpu,
1917                    self.w4a16_gemm_t_m128_k,
1918                    input,
1919                    wt,
1920                    output,
1921                    m,
1922                    n,
1923                    k,
1924                    stream,
1925                );
1926            }
1927        }
1928        ops::w4a16_gemm(ctx.gpu, self.w4a16_gemm, input, w, output, m, n, k, stream)
1929    }
1930
1931    /// Timed wrapper around the dense-FFN prefill.
1932    ///
1933    /// ★ THIS PATH HAD NO TIMERS AT ALL, and that hid the largest unexplained
1934    /// number on the board. Profiling nvidia/Gemma-4-31B-IT-NVFP4 at a
1935    /// 4096-token prompt: wall 28,180 ms, while EVERY profiled phase across
1936    /// `ATTN prefill [...]` and `MoE prefill [...]` summed to 3,269.8 ms. 88% of
1937    /// the prefill was invisible — not attributed to something slow, simply not
1938    /// instrumented. `forward_prefill` dispatches ~20 quantization arms and none
1939    /// of them reported elapsed time; only one-shot "which arm was chosen" INFO
1940    /// lines existed.
1941    ///
1942    /// One coarse timer first, deliberately: it answers whether the missing time
1943    /// is here at all before anyone threads timers through twenty arms. Same
1944    /// `<AREA> prefill [phase] N=<n>: <us>µs` shape the attention and MoE paths
1945    /// already emit, so the existing log-summing one-liners pick it up unchanged.
1946    pub fn forward_prefill(
1947        &self,
1948        input: DevicePtr,
1949        num_tokens: usize,
1950        ctx: &ForwardContext,
1951        stream: u64,
1952    ) -> Result<()> {
1953        if !ctx.profile {
1954            return self.forward_prefill_inner(input, num_tokens, ctx, stream);
1955        }
1956        let t0 = std::time::Instant::now();
1957        let r = self.forward_prefill_inner(input, num_tokens, ctx, stream);
1958        // Sync so the figure is the kernel's, not the launch queue's — the
1959        // attention and MoE timers do the same under `ctx.profile`.
1960        ctx.gpu.synchronize(stream)?;
1961        tracing::info!(
1962            "  FFN prefill [dense_total] N={}: {}µs",
1963            num_tokens,
1964            t0.elapsed().as_micros()
1965        );
1966        r
1967    }
1968
1969    fn forward_prefill_inner(
1970        &self,
1971        input: DevicePtr,
1972        num_tokens: usize,
1973        ctx: &ForwardContext,
1974        stream: u64,
1975    ) -> Result<()> {
1976        let h = ctx.config.hidden_size as u32;
1977        let inter = ctx.config.intermediate_size as u32;
1978        let m = num_tokens as u32;
1979
1980        let gate_out = ctx.buffers.expert_gate_out();
1981        let up_out = ctx.buffers.expert_up_out();
1982
1983        // Native keep-packed Q2_0 prefill (Tier-1): the resident weight stays
1984        // 2-bit, but prefill has no packed-MMQ kernel yet (that's Tier-2). So we
1985        // dequant each projection into a TRANSIENT BF16 scratch `[N, K]` via the
1986        // load-time `dequant_q2_0_gn_to_bf16` kernel, run the normal BF16
1987        // prefill GEMM (tensor-core when present), then free the scratch. Only a
1988        // per-matmul scratch is BF16 — the WeightStore blocks stay 2-bit. Decode
1989        // still uses the native `q2_0_gemv` (no dequant). SiLU only.
1990        if let Some(ref q2w) = self.q2_weights {
1991            if self.activation != FfnActivation::SiLU {
1992                anyhow::bail!(
1993                    "packed-Q2 FFN prefill supports SiLU only (got {:?})",
1994                    self.activation
1995                );
1996            }
1997
1998            // Tier-2 native MMQ prefill (ATLAS_GGUF_NATIVE_Q2_MMQ=1): quantize the
1999            // activation to q8_1 ONCE per projection-input (gate/up share `input`;
2000            // down re-quantizes `gate_out`), then run the packed 2-bit MMQ GEMM —
2001            // no BF16 weight dequant, no shared `q2_dequant_scratch`. Requires the
2002            // MMQ kernel + the shared q8_1 quantizer + group-128 weights.
2003            let q2_mmq = self.q2_0_mmq_nc_k.0 != 0
2004                && self.q4k_quant_act_k.0 != 0
2005                && ops::native_q2_mmq_enabled()
2006                && q2w.gate_proj.group == 128
2007                && q2w.up_proj.group == 128
2008                && q2w.down_proj.group == 128;
2009            if q2_mmq {
2010                static Q2MMQ_LOG: std::sync::Once = std::sync::Once::new();
2011                Q2MMQ_LOG.call_once(|| {
2012                    eprintln!(
2013                        "[atlas] ATLAS_GGUF_NATIVE_Q2_MMQ=1: dense-FFN prefill via native packed Q2_0 MMQ (W2A8, keep-packed)"
2014                    );
2015                });
2016                let a_q8 = ctx.buffers.q2_act_q8();
2017                let mmq = |w: &PackedQ2Weight, out: DevicePtr| -> Result<()> {
2018                    ops::q2_0_mmq_gemm(
2019                        ctx.gpu,
2020                        self.q2_0_mmq_nc_k,
2021                        self.q2_0_mmq_wc_k,
2022                        a_q8,
2023                        w.weight,
2024                        out,
2025                        m,
2026                        w.n,
2027                        w.k,
2028                        stream,
2029                    )
2030                };
2031                // gate/up: quantize `input` [m,h] once, feed both.
2032                ops::quantize_act_q8_1(ctx.gpu, self.q4k_quant_act_k, input, a_q8, m, h, stream)?;
2033                mmq(&q2w.gate_proj, gate_out)?;
2034                mmq(&q2w.up_proj, up_out)?;
2035                ops::silu_mul(
2036                    ctx.gpu,
2037                    self.act_mul,
2038                    gate_out,
2039                    up_out,
2040                    gate_out,
2041                    m * inter,
2042                    stream,
2043                )?;
2044                // down: quantize `gate_out` [m,inter] (same-stream after silu_mul).
2045                let output = ctx.buffers.moe_output();
2046                ops::quantize_act_q8_1(
2047                    ctx.gpu,
2048                    self.q4k_quant_act_k,
2049                    gate_out,
2050                    a_q8,
2051                    m,
2052                    inter,
2053                    stream,
2054                )?;
2055                mmq(&q2w.down_proj, output)?;
2056                return Ok(());
2057            }
2058
2059            // Transient-dequant stopgap (Tier-1): requires the load-time dequant kernel.
2060            if self.dequant_q2_0_gn_k.0 == 0 {
2061                anyhow::bail!(
2062                    "dequant_q2_0_gn_to_bf16 kernel missing in this target build — \
2063                     packed-Q2 (ATLAS_GGUF_NATIVE_Q2) prefill is unavailable"
2064                );
2065            }
2066            let tc = self.dense_gemm_tc_k.0 != 0;
2067            // Dequant one packed-Q2 projection into the PERSISTENT arena BF16
2068            // scratch `[n, k]`, run the BF16 GEMM (A=`in` [m,k] → out [m,n]). No
2069            // per-matmul alloc/sync/free: the arena buffer is sized to the
2070            // largest packed projection and reused. Gate → up → down run
2071            // sequentially on `stream`, so each GEMM consumes the scratch before
2072            // the next projection's dequant overwrites it (same-stream order).
2073            let scratch = ctx.buffers.q2_dequant_scratch();
2074            let q2_gemm = |w: &PackedQ2Weight, input: DevicePtr, out: DevicePtr| -> Result<()> {
2075                let (n, k) = (w.n, w.k);
2076                debug_assert!(
2077                    (n as usize) * (k as usize) * 2 <= ctx.buffers.q2_dequant_scratch_bytes(),
2078                    "packed-Q2 FFN dequant scratch too small for [{n},{k}] BF16"
2079                );
2080                ops::dequant_q2_0_gn_to_bf16(
2081                    ctx.gpu,
2082                    self.dequant_q2_0_gn_k,
2083                    w.weight,
2084                    scratch,
2085                    n,
2086                    k,
2087                    w.group as u32,
2088                    stream,
2089                )?;
2090                let dw = DenseWeight { weight: scratch };
2091                if tc {
2092                    ops::dense_gemm_tc(
2093                        ctx.gpu,
2094                        self.dense_gemm_tc_k,
2095                        input,
2096                        &dw,
2097                        out,
2098                        m,
2099                        n,
2100                        k,
2101                        stream,
2102                    )?;
2103                } else {
2104                    ops::dense_gemm(
2105                        ctx.gpu,
2106                        self.dense_gemm_bf16_k,
2107                        input,
2108                        &dw,
2109                        out,
2110                        m,
2111                        n,
2112                        k,
2113                        stream,
2114                    )?;
2115                }
2116                Ok(())
2117            };
2118            q2_gemm(&q2w.gate_proj, input, gate_out)?;
2119            q2_gemm(&q2w.up_proj, input, up_out)?;
2120            ops::silu_mul(
2121                ctx.gpu,
2122                self.act_mul,
2123                gate_out,
2124                up_out,
2125                gate_out,
2126                m * inter,
2127                stream,
2128            )?;
2129            let output = ctx.buffers.moe_output();
2130            q2_gemm(&q2w.down_proj, gate_out, output)?;
2131            return Ok(());
2132        }
2133
2134        // Native FP8: small batches stream each weight once via the existing
2135        // batched GEMVs, avoiding padded MMA tiles. Larger prefills prefer a
2136        // transposed copy when available, then the same-format pipelined GEMM.
2137        // Every fallback retains the original E4M3 bytes and FP32 block scales.
2138        //
2139        // 🔴 ARM ORDER IS THE DISPATCH RULE — decode rungs first, widest last:
2140        //
2141        //   1. m <= 4      w8a16_gemv_batch4        one weight pass, 4-row tier
2142        //   2. m 5..=16    w8a16_gemm_m16           MMA, ATLAS_FFN_M16_TC only
2143        //   3. m 17..=32   w8a16_gemm_m16 x2        MMA, ATLAS_FFN_M16_TC only
2144        //      (ATLAS_FFN_M16_TC reaches THIS arm only; the attention tiers
2145        //       take ATLAS_ATTN_M16_TC, and ATLAS_M16_TC is both. Round 6
2146        //       measured them moving in opposite directions: attention -21.7%,
2147        //       this arm +13.7%. WHY: `dense_ffn_m16_tc.rs`.)
2148        //   4. m 5..=16    w8a16_gemv_batch16       OPT-IN, off by default
2149        //   5. m 17..=32   w8a16_gemv_batch16 x2    OPT-IN, off by default
2150        //   6. W8A8 block-scaled prefill            (#917/#928)
2151        //   7. transposed / pipelined / base W8A16 tile GEMMs
2152        //
2153        // Rungs 2-3 are the TENSOR-CORE tier and are OFF unless
2154        // `ATLAS_FFN_M16_TC` is set. `ATLAS_FFN_M16_TC` reaches THIS arm only;
2155        // the attention tiers take `ATLAS_ATTN_M16_TC`, and `ATLAS_M16_TC` is
2156        // both. They are the only arm here that does NOT reproduce the scalar
2157        // `w8a16_gemv` bit-for-bit: an m16n8k16 MMA reassociates the K
2158        // reduction (<= 2 BF16 ULP), which is the same reassociation the
2159        // pre-#927 tile GEMMs had at these widths. WHY, the H100 numbers and
2160        // the seam: `dense_ffn_m16_tc.rs`.
2161        //
2162        // Rungs 4-5 are #927, and they are DISARMED unless `ATLAS_FFN_BATCH16=1`
2163        // — so a stock serve runs rungs 1, 6, 7 exactly as it did before #927.
2164        // The cliff they answer is real (the rung-7 tile GEMMs pad M to a
2165        // 128-row MMA tile, 5-12 TFLOP/s on these shapes), but the tier as a
2166        // whole lost the only end-to-end A/B it has: H100, 2026-09-11,
2167        // Qwen3.8-27B-FP8, C=16 aggregate 121.4 ON -> 128.0 tok/s OFF, because
2168        // it dispatches by row count and so catches a chunked prefill's tail
2169        // chunk. It has never been measured on GB10. Receipt, opt-in and the
2170        // consequence for rung 6's lower edge: `dense_ffn_batch16_decode.rs`.
2171        if let Some(ref fp8w) = self.fp8_weights {
2172            // Resolved ONCE for the whole FFN, not per projection: gate, up and
2173            // down share `m`, so a per-arm call would re-run the same match
2174            // three times and could not be read as one rule.
2175            let batch16 = self.ffn_batch16_plan(m);
2176            // Resolved per PROJECTION depth, not once: gate/up reduce over `h`
2177            // and down over `inter`, and the tier declines a K that is not a
2178            // whole number of 128-wide scale blocks.
2179            let m16_tc = |k: u32| self.ffn_m16_tc_plan(m, k);
2180            macro_rules! w8_gemm {
2181                ($w:expr, $wt:expr, $in:expr, $out:expr, $n:expr, $k:expr, $a8:expr, $cap:expr) => {
2182                    match $wt {
2183                        // 1. m <= 4.
2184                        _ if (1..=4).contains(&m) && self.w8a16_gemv_batch4_k.0 != 0 => {
2185                            ops::w8a16_gemv_batch4(
2186                                ctx.gpu,
2187                                self.w8a16_gemv_batch4_k,
2188                                $in,
2189                                $w.weight,
2190                                $w.row_scale,
2191                                $out,
2192                                m,
2193                                $n,
2194                                $k,
2195                                stream,
2196                            )?
2197                        }
2198                        // 2-3. TENSOR-CORE tier, 5..=16 (one launch) and
2199                        // 17..=32 (two halves). `Some(plan)` already encodes the
2200                        // handle, the `ATLAS_FFN_M16_TC` lever (default OFF) and
2201                        // the K % 128 guard, so this arm is inert until an
2202                        // operator opts in. It REASSOCIATES — see the rule above.
2203                        _ if m16_tc($k).is_some() => self.w8a16_m16_tc_proj(
2204                            ctx,
2205                            m16_tc($k).expect("guarded by is_some"),
2206                            &$w,
2207                            $in,
2208                            $out,
2209                            m,
2210                            $n,
2211                            $k,
2212                            stream,
2213                        )?,
2214                        // 4-5. m 5..=16 (one launch) and 17..=32 (two halves).
2215                        // `Some(plan)` already encodes the handle and the
2216                        // `ATLAS_FFN_BATCH16=1` opt-in, so this is `None` on a
2217                        // stock serve and the match falls through to rung 4.
2218                        _ if batch16.is_some() => self.w8a16_batch16_proj(
2219                            ctx,
2220                            batch16.expect("guarded by is_some"),
2221                            &$w,
2222                            $in,
2223                            $out,
2224                            m,
2225                            $n,
2226                            $k,
2227                            stream,
2228                        )?,
2229                        // 6. W8A8 block-scaled (#917/#928) — ahead of the W8A16
2230                        // arms below, which run the BF16 MMA at ~12 TFLOP/s on
2231                        // these shapes. `$a8` is `Some` exactly when
2232                        // `prefill_w8a8_selected` held for this projection.
2233                        _ if $a8.is_some() => {
2234                            let (a_fp8, a_scale) = $a8.expect("guarded by is_some");
2235                            self.w8a8_gemm(ctx, a_fp8, a_scale, &$w, $out, $cap, m, $n, $k, stream)?
2236                        }
2237                        // 7. Tile GEMMs — prefill widths (m > 32) and any
2238                        // shape the rungs above declined.
2239                        Some(wt) if self.w8a16_gemm_t_m128_k.0 != 0 => {
2240                            let wt: Fp8WeightTransposed = wt;
2241                            ops::w8a16_gemm_n128_m128(
2242                                ctx.gpu,
2243                                self.w8a16_gemm_t_m128_k,
2244                                $in,
2245                                wt.weight_t,
2246                                wt.scale_t,
2247                                $out,
2248                                m,
2249                                $n,
2250                                $k,
2251                                stream,
2252                            )?
2253                        }
2254                        _ if self.w8a16_gemm_pipelined_k.0 != 0 => ops::w8a16_gemm_pipelined(
2255                            ctx.gpu,
2256                            self.w8a16_gemm_pipelined_k,
2257                            $in,
2258                            $w.weight,
2259                            $w.row_scale,
2260                            $out,
2261                            m,
2262                            $n,
2263                            $k,
2264                            stream,
2265                        )?,
2266                        _ => ops::w8a16_gemm(
2267                            ctx.gpu,
2268                            self.w8a16_gemm_k,
2269                            $in,
2270                            $w.weight,
2271                            $w.row_scale,
2272                            $out,
2273                            m,
2274                            $n,
2275                            $k,
2276                            stream,
2277                        )?,
2278                    }
2279                };
2280            }
2281            let gate_t: Option<Fp8WeightTransposed> = None;
2282            let up_t: Option<Fp8WeightTransposed> = None;
2283            let down_t: Option<Fp8WeightTransposed> = None;
2284            // W8A8 activation quant (#917/#928), ONCE per shared input: gate and
2285            // up both read `input`, so quantizing per projection would pay the
2286            // per-token E4M3 cast twice per layer for identical bytes. down
2287            // quantizes separately because its input is the post-SiLU product.
2288            // `None` => that projection keeps today's W8A16 dispatch.
2289            // Selection rule + WHY: `dense_ffn_w8a8_prefill.rs` (SSOT).
2290            //
2291            // `batch16.is_none()` is part of the condition and not only of the
2292            // match: rungs 2-3 sit AHEAD of the W8A8 arm, so at m=5..=32 the
2293            // quantizer launch below would be dead work whose result no arm
2294            // reads. This keeps the W8A8 lower edge honest at m > 32.
2295            // `m16_tc` is checked at BOTH projection depths: gate/up reduce
2296            // over `h` and down over `inter`, and either claiming the width
2297            // makes the quantizer launch below dead work for that projection.
2298            let w8a8_reachable =
2299                batch16.is_none() && m16_tc(h).is_none() && m16_tc(inter).is_none();
2300            let gate_up_w8a8 = w8a8_reachable
2301                && self.prefill_w8a8_selected(ctx, m, inter, h, &fp8w.gate_proj)
2302                && self.prefill_w8a8_selected(ctx, m, inter, h, &fp8w.up_proj);
2303            let down_w8a8 =
2304                w8a8_reachable && self.prefill_w8a8_selected(ctx, m, h, inter, &fp8w.down_proj);
2305            if !gate_up_w8a8 && !down_w8a8 && w8a8_reachable {
2306                self.log_w8a16_prefill_route(ctx);
2307            }
2308            let gu_a8 = if gate_up_w8a8 {
2309                Some(self.w8a8_quant_act(ctx, input, m, h, stream)?)
2310            } else {
2311                None
2312            };
2313            let gu_cap = ctx.buffers.expert_gate_out_bytes();
2314            // FUSED gate+up (#927): ONE cuBLASLt W8A8 GEMM at N=2*inter, then
2315            // the strided SiLU straight out of its `[m, 2*inter]` rows. It sits
2316            // HERE, ahead of the two `w8_gemm!` calls, because it is the SAME
2317            // W8A8 arm those calls would take (`gate_up_w8a8` is a clause of
2318            // its rule) with the two N's concatenated — not a new rung of the
2319            // ladder. Declines to the pair below at every width, target and
2320            // checkpoint it does not claim. Rule and the round-13 receipt:
2321            // `dense_ffn_gateup_fused.rs` (SSOT).
2322            if let Some((a_fp8, a_scale)) = gu_a8
2323                && let Some(fused) = self.gateup_fused_plan(ctx, m, inter, gate_up_w8a8)
2324            {
2325                self.w8a8_gate_up_fused(ctx, a_fp8, a_scale, fused, gate_out, m, inter, h, stream)?;
2326            } else {
2327                w8_gemm!(
2328                    fp8w.gate_proj,
2329                    gate_t,
2330                    input,
2331                    gate_out,
2332                    inter,
2333                    h,
2334                    gu_a8,
2335                    gu_cap
2336                );
2337                w8_gemm!(fp8w.up_proj, up_t, input, up_out, inter, h, gu_a8, gu_cap);
2338                ops::silu_mul(
2339                    ctx.gpu,
2340                    self.act_mul,
2341                    gate_out,
2342                    up_out,
2343                    gate_out,
2344                    m * inter,
2345                    stream,
2346                )?;
2347            }
2348            let output = ctx.buffers.moe_output();
2349            // `gate_out` (the SiLU product) is fully written by the launch above
2350            // and re-read here; the quant lands in the SAME scratch the gate/up
2351            // quant used, which is safe because every launch is on `stream`.
2352            let down_a8 = if down_w8a8 {
2353                Some(self.w8a8_quant_act(ctx, gate_out, m, inter, stream)?)
2354            } else {
2355                None
2356            };
2357            let down_cap = ctx.buffers.moe_output_bytes();
2358            w8_gemm!(
2359                fp8w.down_proj,
2360                down_t,
2361                gate_out,
2362                output,
2363                h,
2364                inter,
2365                down_a8,
2366                down_cap
2367            );
2368            return Ok(());
2369        }
2370
2371        // BF16 prefill dispatch. Prefer the tensor-core m16n8k16 MMA kernel
2372        // (`dense_gemm_tc`, 3-5x+ over scalar) — the scalar `dense_gemm_bf16`
2373        // was the flat ~155 tok/s prefill bottleneck on Qwen3.6-27B dense
2374        // NVFP4 (FFN = ~83% of prefill). Falls back to scalar if the TC
2375        // kernel isn't loaded for this target. Decode (gemv, M=1) is a
2376        // separate path, so TPOT is unaffected; BF16 MMA preserves coherence.
2377        if let Some(ref bf16w) = self.bf16_weights {
2378            let tc = self.dense_gemm_tc_k.0 != 0;
2379            // helper: cuBLASLt when enabled (the big win at prefill M), else the
2380            // tensor-core MMA kernel, else scalar. dense_gemm_tc is ~1.4 TFLOP/s
2381            // on the large dense-FFN shapes (e.g. Laguna layer-0 gate/up/down at
2382            // N=12288/3072, K=3072) — nsys measured its 3 launches at ~100 ms
2383            // EACH = 33% of the whole C=1 prefill. cuBLASLt runs the identical
2384            // BF16×BF16→FP32 GEMM at 90+ TFLOP/s (~65× faster), the same path
2385            // q/k/v/o and the head-gate already use. Gated on ATLAS_CUBLAS_GEMM.
2386            macro_rules! ffn_gemm {
2387                ($a:expr, $b:expr, $c:expr, $n:expr, $k:expr) => {
2388                    if ctx.dispatch.cublas.ffn {
2389                        ops::cublas_bf16_proj_dense($a, $b.weight, $c, m, $n, $k, stream)?;
2390                    } else if tc {
2391                        ops::dense_gemm_tc(
2392                            ctx.gpu,
2393                            self.dense_gemm_tc_k,
2394                            $a,
2395                            $b,
2396                            $c,
2397                            m,
2398                            $n,
2399                            $k,
2400                            stream,
2401                        )?;
2402                    } else {
2403                        ops::dense_gemm(
2404                            ctx.gpu,
2405                            self.dense_gemm_bf16_k,
2406                            $a,
2407                            $b,
2408                            $c,
2409                            m,
2410                            $n,
2411                            $k,
2412                            stream,
2413                        )?;
2414                    }
2415                };
2416            }
2417            ffn_gemm!(input, &bf16w.gate_proj, gate_out, inter, h);
2418            ffn_gemm!(input, &bf16w.up_proj, up_out, inter, h);
2419            ops::silu_mul(
2420                ctx.gpu,
2421                self.act_mul,
2422                gate_out,
2423                up_out,
2424                gate_out,
2425                m * inter,
2426                stream,
2427            )?;
2428            let output = ctx.buffers.moe_output();
2429            ffn_gemm!(gate_out, &bf16w.down_proj, output, h, inter);
2430            return Ok(());
2431        }
2432
2433        // Prefill: prefer the 128x128 cp.async-pipelined `w4a16_gemm_t_m128`
2434        // (the kernel attention/SSM use) over the M64xN64 base `w4a16_gemm`
2435        // (~10 TFLOPS, the flat ~155 tok/s bottleneck). That kernel needs the
2436        // TRANSPOSED weight layout, so we use the `*_proj_t` copies built at
2437        // load (decode keeps the non-transposed weights via gemv → TPOT/
2438        // coherence unaffected). Falls back to base when no transposed copy /
2439        // kernel is present.
2440        // LOSSLESS prefill opt-in: when ATLAS_BF16_TC_PREFILL is set AND the
2441        // BF16 128x128 kernel is present, route prefill GEMMs through the
2442        // bit-equivalent BF16 tensor-core path instead of the default FP8-E4M3
2443        // `t_m128`. The FP8 crush is fast but perturbs generation (measured
2444        // length-truncations / accuracy risk on Qwen3.6-27B); the BF16 variant
2445        // keeps the same 128x128 cp.async speed at base-kernel precision.
2446        // Unset (default) → every arm below is byte-for-byte the prior behavior
2447        // (PCND: explicit opt-in, no silent default change). Read once per call.
2448        // Env read only here; the usable gate (`bf16_tc_prefill`) is derived
2449        // below AFTER v1/v2 selection, from the handle actually launched.
2450        // Gating on v1's handle while dispatching v2 admitted launches of a
2451        // kernel this target may not carry.
2452        let bf16_tc_env = ctx.levers.bf16_tc_prefill;
2453        // FP8 M64 fast-prefill opt-in: route prefill GEMMs through the m16n8k32
2454        // e4m3 M64 kernel (~1.47x vs v2 BF16, smem-relieved). Lossy (cosine 0.9997)
2455        // → highest priority when set, so it overrides the BF16/FP8 t_m128 arms.
2456        // PCND: explicit opt-in, default off = byte-for-byte prior behavior.
2457        let fp8_m64_prefill = self.w4a16_gemm_t_k.0 != 0 && ctx.levers.fp8_m64_prefill;
2458        // int8 W4A8 fast-prefill opt-in (ATLAS_INT8_PREFILL): route prefill GEMMs
2459        // through the validated requant→`int8_gemm_faith2` pipeline (cosine
2460        // 0.999978 vs the host full-precision dequant GEMM). HIGHEST priority when
2461        // set, so it overrides every other prefill arm. Needs both operands int8:
2462        // the NVFP4 weights are requanted to int8 once (cached, see
2463        // `ensure_int8_weight`) and the BF16 activations are requanted every call
2464        // into the shared scratch (`ensure_int8_scratch`). LOSSY (perf gate, not
2465        // bit-identical) — the _2.5h IoU gate is the final arbiter.
2466        // PCND: explicit opt-in, default off = byte-for-byte prior behavior; the
2467        // arm is a no-op (and no buffers are built) unless the kernels are loaded.
2468        let int8_prefill = self.int8_faith2_k.0 != 0 && ctx.levers.int8_prefill;
2469        if int8_prefill {
2470            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
2471            // value — the message is rebuilt from the arguments every call — so a
2472            // stale entry cannot produce a wrong answer, only a suppressed duplicate
2473            // line after a model swap. Scoping it would thread a logging concern
2474            // through the call path to prevent one repeated INFO line.
2475            if ctx.stats.once("log:ffn_int8_prefill") {
2476                tracing::info!(
2477                    "[atlas] ATLAS_INT8_PREFILL=1: dense-FFN prefill via int8_gemm_faith2 (W4A8 requant→int8 MMA, lossy ~0.99998 cosine)"
2478                );
2479            }
2480        }
2481        // NVFP4 W4A4 MMQ prefill (ATLAS_FFN_NVFP4_MMQ) — vendored llama Blackwell
2482        // block-scale FP4 MMA, gate/up ONLY (hybrid: down stays on the default t_m128
2483        // path — SiLU(gate)*up is heavy-tailed and accuracy-critical). SiLU models only
2484        // (the scale2 fold lives in the scaled SiLU-mul). Mutually exclusive with
2485        // ATLAS_FFN_MMQ (both use the shared ffn_act_q8 scratch); this arm wins.
2486        //
2487        // An installed LoRA adapter turns this arm OFF. The MMQ path leaves
2488        // gate_out/up_out holding UNSCALED products and folds each
2489        // projection's `weight_scale_2` later, inside the SiLU-mul kernel. A
2490        // LoRA delta is a true-valued quantity, so adding it to those buffers
2491        // would put it through a scale2 multiply that does not belong to it —
2492        // silently wrong output rather than a failure. Folding scale2 into the
2493        // delta instead would mean reproducing the quant layout's arithmetic
2494        // in the adapter path, which is a much larger commitment than the
2495        // ~8% prefill this arm is worth (measured 831 vs 767 tok/s at 8K).
2496        // Correctness first; making LoRA and MMQ coexist is its own change.
2497        let fp4mmq_prefill = self.nvfp4_mmq_nc_k.0 != 0
2498            && self.nvfp4_quant_act_k.0 != 0
2499            && self.nvfp4_silu_scaled_k.0 != 0
2500            && matches!(self.activation, FfnActivation::SiLU)
2501            && self.lora.is_none()
2502            && ctx.levers.ffn_nvfp4_mmq;
2503        if fp4mmq_prefill {
2504            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
2505            // value — the message is rebuilt from the arguments every call — so a
2506            // stale entry cannot produce a wrong answer, only a suppressed duplicate
2507            // line after a model swap. Scoping it would thread a logging concern
2508            // through the call path to prevent one repeated INFO line.
2509            if ctx.stats.once("log:ffn_fp4_mmq_prefill") {
2510                tracing::info!(
2511                    "[atlas] ATLAS_FFN_NVFP4_MMQ=1: dense-FFN gate/up prefill via vendored llama NVFP4 W4A4 MMQ (block-scale FP4 MMA, ~80 TFLOP/s vs t_m128 ~51)"
2512                );
2513            }
2514        }
2515        // Down-projection MMQ arm (DEFAULT ON; kill-switch ATLAS_NO_FFN_NVFP4_MMQ_DOWN=1): route down through
2516        // the same MMQ arm (t_m128 runs the narrow-N down at only ~34 TFLOP/s in-model).
2517        // Accuracy note: down W4A4 cosine 0.9961 (random) — better than the previously
2518        // coherence-validated all-W4A4 config (0.991) — but still the heavy-tailed
2519        // projection, so it stays a SEPARATE opt-in gate.
2520        let fp4mmq_down =
2521            fp4mmq_prefill && self.nvfp4_scale_k.0 != 0 && ctx.levers.ffn_nvfp4_mmq_down;
2522        // HYBRID: route the accuracy-critical down_proj OFF Q4_K onto the near-lossless faith2
2523        // NVFP4 path (W4A8 requant, cos 0.99998). down=SiLU(gate)*up is heavy-tailed; Q4_K
2524        // superblock scaling clips it (BFCL `multiple` -4.0%; llama promotes only down→Q6_K for
2525        // this reason). gate/up stay on Q4_K. Default ON when MMQ active; ATLAS_FFN_MMQ_DOWN_Q4K=1
2526        // = lossy all-Q4_K (A/B only). Defined here (self-fields+env, no q4k_prefill var dep) so the
2527        // int8 scratch below can size for the hybrid down.
2528        let down_faith2 = self.q4k_mmq_nc_k.0 != 0
2529            && self.q4k_quant_act_k.0 != 0
2530            && self.q4k_quant_w_k.0 != 0
2531            && self.dequant_nvfp4_bf16_k.0 != 0
2532            && self.int8_faith2_k.0 != 0
2533            && self.requant_a_int8_k.0 != 0
2534            && !fp4mmq_prefill
2535            && ctx.levers.ffn_mmq
2536            && !ctx.levers.ffn_mmq_down_q4k;
2537        // Pre-allocate (or reuse) the activation-requant scratch once per call,
2538        // sized to the largest projection K (= max(h, inter)) so the per-GEMM
2539        // arms never trigger a mid-call grow/sync. NULL when the int8 path is off.
2540        // Shared, arena-owned activation-requant scratch (sized once for
2541        // max_batch_tokens × max(h, inter) in BufferSizes::from_config). Replaces
2542        // the former per-DenseFfnLayer grow-on-demand allocator that leaked
2543        // ~286MB × 64 layers on the MMQ prefill path.
2544        let (int8_a_i8, int8_a_scale) = if int8_prefill || down_faith2 {
2545            (ctx.buffers.ffn_act_a(), ctx.buffers.ffn_act_scale())
2546        } else {
2547            (DevicePtr::NULL, DevicePtr::NULL)
2548        };
2549        // W4A4 native-FP4 prefill (ATLAS_FP4_PREFILL) — HIGHEST priority. NVFP4 weights
2550        // used directly (no requant); BF16 activations quantized to NVFP4 each GEMM into
2551        // the shared scratch. Native FP4 tensor cores (sm_121a). Lossy (cos ~0.99 vs fp32).
2552        let fp4_prefill =
2553            self.w4a4_gemm_k.0 != 0 && self.quantize_nvfp4_k.0 != 0 && ctx.levers.fp4_prefill;
2554        if fp4_prefill {
2555            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
2556            // value — the message is rebuilt from the arguments every call — so a
2557            // stale entry cannot produce a wrong answer, only a suppressed duplicate
2558            // line after a model swap. Scoping it would thread a logging concern
2559            // through the call path to prevent one repeated INFO line.
2560            if ctx.stats.once("log:ffn_fp4_prefill") {
2561                tracing::info!(
2562                    "[atlas] ATLAS_FP4_PREFILL=1: dense-FFN prefill via w4a4_gemm (native FP4 MMA sm_121a, W4A4)"
2563                );
2564            }
2565        }
2566        // NVFP4 packed [m,K/2] + scale [m,K/16] both fit within the shared int8
2567        // buffers (a_i8 [m,K] ⊇ packed; a_scale [m,(K/32)*4] ⊇ scale). FP4-prefill
2568        // is a standalone A/B flag, never co-active with the int8/Q4_K down path.
2569        let (nvfp4_a_packed, nvfp4_a_scale) = if fp4_prefill {
2570            (ctx.buffers.ffn_act_a(), ctx.buffers.ffn_act_scale())
2571        } else {
2572            (DevicePtr::NULL, DevicePtr::NULL)
2573        };
2574        // Q4_K MMQ prefill (ATLAS_FFN_MMQ) — vendored llama Q4_K W4A8 GEMM. Highest priority
2575        // when enabled. Lossy (Q4_K weight format ≠ NVFP4); gate via BFCL before relying on it.
2576        let q4k_prefill = self.q4k_mmq_nc_k.0 != 0
2577            && self.q4k_quant_act_k.0 != 0
2578            && self.q4k_quant_w_k.0 != 0
2579            && self.dequant_nvfp4_bf16_k.0 != 0
2580            && !fp4mmq_prefill
2581            && ctx.levers.ffn_mmq;
2582        if q4k_prefill {
2583            // Log-once latch (see `atlas_core::scope`). It holds no model-derived
2584            // value — the message is rebuilt from the arguments every call — so a
2585            // stale entry cannot produce a wrong answer, only a suppressed duplicate
2586            // line after a model swap. Scoping it would thread a logging concern
2587            // through the call path to prevent one repeated INFO line.
2588            if ctx.stats.once("log:ffn_q4k_prefill") {
2589                tracing::info!(
2590                    "[atlas] ATLAS_FFN_MMQ=1: dense-FFN prefill via vendored llama Q4_K MMQ (W4A8, +25%/+10% gate·down vs faith2)"
2591                );
2592            }
2593        }
2594        let q4k_a = if q4k_prefill {
2595            ctx.buffers.ffn_act_q8()
2596        } else {
2597            DevicePtr::NULL
2598        };
2599        // FP4-MMQ y scratch: block_fp4_mmq activations, in the SAME shared arena buffer
2600        // (fp4_act_scratch_bytes ≤ q8_1_scratch_bytes; mutually exclusive with q4k_prefill).
2601        let fp4_y = if fp4mmq_prefill {
2602            ctx.buffers.ffn_act_q8()
2603        } else {
2604            DevicePtr::NULL
2605        };
2606        // A/B escape hatch (benchmark only): force the proven v1 BF16 kernel even
2607        // when v2 is loaded, so v1-vs-v2 prefill TTFT can be compared in one
2608        // binary. Default unset → prefer v2 (the faster, bit-identical variant).
2609        let use_v2 = self.w4a16_gemm_t_m128_bf16_v2_k.0 != 0 && ctx.levers.prefill_v2;
2610        let bf16_kernel = if use_v2 {
2611            self.w4a16_gemm_t_m128_bf16_v2_k
2612        } else {
2613            self.w4a16_gemm_t_m128_bf16_k
2614        };
2615        // Final gate: the flag is honored only when the SELECTED kernel is
2616        // loaded (v2 when preferred, else v1) — not v1's handle unconditionally.
2617        let bf16_tc_prefill = bf16_kernel.0 != 0 && bf16_tc_env;
2618
2619        macro_rules! w4_gemm {
2620            ($w:expr, $wt:expr, $cell:expr, $qcell:expr, $fp4cell:expr, $allow_fp4:expr, $in:expr, $out:expr, $n:expr, $k:expr, $allow_q4k:expr) => {
2621                match $wt {
2622                    // NVFP4 W4A4 MMQ prefill (ATLAS_FFN_NVFP4_MMQ) — HIGHEST priority.
2623                    // `$allow_fp4` = fp4mmq_prefill for gate/up, fp4mmq_down for down.
2624                    // Activation pre-quantized into `fp4_y` by the caller; the output is
2625                    // missing ×scale2, folded downstream (scaled SiLU-mul / scale_bf16).
2626                    _ if $allow_fp4 => {
2627                        let _ = $in;
2628                        let qw =
2629                            self.ensure_nvfp4_mmq_weight($fp4cell, ctx.gpu, $w, $n, $k, stream)?;
2630                        // Size the M tile to the batch when the batch is small and
2631                        // the small-tile entries are present. m must be <= mmq_x or
2632                        // grid.y>1 re-streams the weights per tile.
2633                        let (tk_nc, tk_wc, tile) = if m <= 16
2634                            && self.nvfp4_mmq16_nc_k.0 != 0
2635                            && mmq_small_tile_enabled()
2636                        {
2637                            (self.nvfp4_mmq16_nc_k, self.nvfp4_mmq16_wc_k, 16u32)
2638                        } else if m <= 32
2639                            && self.nvfp4_mmq32_nc_k.0 != 0
2640                            && mmq_small_tile_enabled()
2641                        {
2642                            (self.nvfp4_mmq32_nc_k, self.nvfp4_mmq32_wc_k, 32u32)
2643                        } else if m <= 64
2644                            && self.nvfp4_mmq64_nc_k.0 != 0
2645                            && mmq_small_tile_enabled()
2646                            && mmq_tile64_enabled()
2647                        {
2648                            (self.nvfp4_mmq64_nc_k, self.nvfp4_mmq64_wc_k, 64u32)
2649                        } else {
2650                            (self.nvfp4_mmq_nc_k, self.nvfp4_mmq_wc_k, 128u32)
2651                        };
2652                        ops::nvfp4_mmq_gemm_tiled(
2653                            ctx.gpu, tk_nc, tk_wc, tile, fp4_y, qw.w, $out, m, $n, $k, stream,
2654                        )?;
2655                    }
2656                    // Q4_K MMQ prefill (ATLAS_FFN_MMQ) — next priority, gated per-GEMM by
2657                    // `$allow_q4k` (false for down in the hybrid → falls to the faith2 arm).
2658                    // Activation `$in` is pre-quantized to q8_1 in `q4k_a` by the caller.
2659                    _ if q4k_prefill && $allow_q4k => {
2660                        let qw = self.ensure_q4k_weight($qcell, ctx.gpu, $w, $n, $k, stream)?;
2661                        ops::q4k_mmq_gemm(
2662                            ctx.gpu,
2663                            self.q4k_mmq_nc_k,
2664                            self.q4k_mmq_wc_k,
2665                            q4k_a,
2666                            qw.w_q4k,
2667                            $out,
2668                            m,
2669                            $n,
2670                            $k,
2671                            stream,
2672                        )?;
2673                    }
2674                    // W4A4 native-FP4 prefill (ATLAS_FP4_PREFILL) — HIGHEST priority.
2675                    // The activation is PRE-quantized into the NVFP4 scratch by the caller
2676                    // (`input` once for gate+up which share it; `gate_out` for down) — opt #1,
2677                    // avoids the redundant re-quant. This arm just runs w4a4_gemm against the
2678                    // native NVFP4 weight `$w` (no requant). sm_121a FP4 MMA.
2679                    _ if fp4_prefill => {
2680                        let _ = $in;
2681                        ops::w4a4_gemm(
2682                            ctx.gpu,
2683                            self.w4a4_gemm_k,
2684                            nvfp4_a_packed,
2685                            nvfp4_a_scale,
2686                            $w,
2687                            $out,
2688                            m,
2689                            $n,
2690                            $k,
2691                            stream,
2692                        )?;
2693                    }
2694                    // int8 W4A8 fast prefill (ATLAS_INT8_PREFILL) — next priority.
2695                    // Independent of `$wt`/the transposed copies: requant reads the
2696                    // non-transposed NVFP4 `$w` directly. Builds (once) + caches the
2697                    // int8 weight in `$cell`, then requant_a + faith2 via the shared
2698                    // scratch. Lossy (cosine ~0.99998). Also the HYBRID down path
2699                    // (down_faith2 && !$allow_q4k): down falls here instead of Q4_K.
2700                    _ if int8_prefill || (down_faith2 && !$allow_q4k) => {
2701                        let iw = self.ensure_int8_weight($cell, ctx.gpu, $w, $n, $k, stream)?;
2702                        // faith5 (ATLAS_INT8_FAITH5=1): int32 per-sb accumulation
2703                        // breaks the MMA→scale dependency chain. Same kernel signature
2704                        // + grid/block as faith2 — just a different KernelHandle.
2705                        let int8_kernel = if self.int8_faith5_k.0 != 0 && ctx.levers.int8_faith5 {
2706                            self.int8_faith5_k
2707                        } else {
2708                            self.int8_faith2_k
2709                        };
2710                        ops::int8_gemm_faith2_prefill(
2711                            ctx.gpu,
2712                            int8_kernel,
2713                            self.requant_a_int8_k,
2714                            $in,
2715                            iw.w_i8,
2716                            iw.w_scale,
2717                            int8_a_i8,
2718                            int8_a_scale,
2719                            $out,
2720                            m,
2721                            $n,
2722                            $k,
2723                            stream,
2724                        )?;
2725                    }
2726                    // Lossless opt-in: BF16 128x128 tensor-core prefill (bit-equivalent
2727                    // to base `w4a16_gemm`). Preferred over the FP8 t_m128/v2 paths only
2728                    // when ATLAS_BF16_TC_PREFILL is set and the kernel is loaded. Within
2729                    // the lossless path, prefer the higher-occupancy v2 kernel (3 CTAs/SM,
2730                    // bit-identical to v1) when it is loaded; else the proven v1 kernel.
2731                    // Both go through the same launch helper (identical grid/block/args).
2732                    // FP8 M64 fast prefill (ATLAS_FP8_M64_PREFILL) — highest priority,
2733                    // M64 grid via the w4a16_gemm_n128 launcher.
2734                    Some(wt) if fp8_m64_prefill => ops::w4a16_gemm_n128(
2735                        ctx.gpu,
2736                        self.w4a16_gemm_t_k,
2737                        $in,
2738                        &wt,
2739                        $out,
2740                        m,
2741                        $n,
2742                        $k,
2743                        stream,
2744                    )?,
2745                    // v2's COMPILED signature carries a 9th param, `ldb`
2746                    // (transposed-B row stride; == N for the FFN twins, which
2747                    // are built unpadded). It MUST go through the `_ldb`
2748                    // launcher: the 8-arg helper leaves cuLaunchKernel reading
2749                    // one-past-the-end of the param array for `ldb` —
2750                    // CUDA_ERROR_INVALID_VALUE or a host SIGSEGV depending on
2751                    // the neighboring heap word. v1 takes exactly 8 params and
2752                    // stays on the 8-arg helper.
2753                    Some(wt) if bf16_tc_prefill && use_v2 => ops::w4a16_gemm_n128_m128_bf16_ldb(
2754                        ctx.gpu,
2755                        bf16_kernel,
2756                        $in,
2757                        &wt,
2758                        $out,
2759                        m,
2760                        $n,
2761                        $k,
2762                        $n,
2763                        stream,
2764                    )?,
2765                    Some(wt) if bf16_tc_prefill => ops::w4a16_gemm_n128_m128_bf16(
2766                        ctx.gpu,
2767                        bf16_kernel,
2768                        $in,
2769                        &wt,
2770                        $out,
2771                        m,
2772                        $n,
2773                        $k,
2774                        stream,
2775                    )?,
2776                    // Small-M routing (DFlash verify, M<=64): delegate to
2777                    // `w4a16_prefill_gemm`, which picks `w4a16_gemm_t` /
2778                    // `w4a16_gemm_t_k64` per the w4a16_m17_bench numbers and
2779                    // falls back to the same v2/m128 kernels below.
2780                    // ATLAS_FFN_SMALLM=0 disables. Sits after the opt-in
2781                    // quant arms so explicit MMQ/int8/FP8 experiments keep
2782                    // priority.
2783                    Some(wt) if m <= 64 => {
2784                        self.w4a16_prefill_gemm(ctx, $w, Some(&wt), $in, $out, m, $n, $k, stream)?
2785                    }
2786                    // Prefer v2 (8-warp) > t_m128 (4-warp) > scalar-tile base.
2787                    Some(wt) if self.w4a16_gemm_t_m128_v2_k.0 != 0 => ops::w4a16_gemm_n128_m128_v2(
2788                        ctx.gpu,
2789                        self.w4a16_gemm_t_m128_v2_k,
2790                        $in,
2791                        &wt,
2792                        $out,
2793                        m,
2794                        $n,
2795                        $k,
2796                        stream,
2797                    )?,
2798                    Some(wt) if self.w4a16_gemm_t_m128_k.0 != 0 => ops::w4a16_gemm_n128_m128(
2799                        ctx.gpu,
2800                        self.w4a16_gemm_t_m128_k,
2801                        $in,
2802                        &wt,
2803                        $out,
2804                        m,
2805                        $n,
2806                        $k,
2807                        stream,
2808                    )?,
2809                    _ => {
2810                        ops::w4a16_gemm(ctx.gpu, self.w4a16_gemm, $in, $w, $out, m, $n, $k, stream)?
2811                    }
2812                }
2813            };
2814        }
2815
2816        // W4A4 opt #1: quantize the gate/up SHARED input `[M, H]` to NVFP4 ONCE
2817        // (gate and up both read it) instead of per-GEMM. Reused by both arms below.
2818        if fp4_prefill {
2819            ops::quantize_bf16_to_nvfp4(
2820                ctx.gpu,
2821                self.quantize_nvfp4_k,
2822                input,
2823                nvfp4_a_packed,
2824                nvfp4_a_scale,
2825                m,
2826                h,
2827                stream,
2828            )?;
2829        }
2830        // Q4_K opt: quantize the gate/up SHARED input `[M, H]` to q8_1 ONCE (both read it).
2831        if q4k_prefill {
2832            ops::quantize_act_q8_1(ctx.gpu, self.q4k_quant_act_k, input, q4k_a, m, h, stream)?;
2833        }
2834        // FP4-MMQ: quantize the gate/up SHARED input `[M, H]` to block_fp4_mmq ONCE.
2835        if fp4mmq_prefill {
2836            ops::nvfp4_mmq_quantize_act(
2837                ctx.gpu,
2838                self.nvfp4_quant_act_k,
2839                input,
2840                fp4_y,
2841                m,
2842                h,
2843                stream,
2844            )?;
2845        }
2846        // Per-projection timers. `dense_total` localised 23.7 s of a 28.2 s
2847        // Gemma-4-31B prefill to this function; these say WHICH of the three
2848        // projections it is. Roofline for that shape: 3 x 5376 x 21504 x 4012 tok
2849        // x 60 layers = 167 TFLOP, so 23.7 s is ~7 TFLOP/s against a bf16
2850        // tensor-core peak two orders higher — a hypothesis these numbers test
2851        // rather than assume.
2852        // ★ PER-STEP, NOT CUMULATIVE. The first version of this timer measured
2853        // elapsed-since-one-start at each of the three call sites, so `up_proj`
2854        // included `gate_proj` and `down_proj` included both — and the summed
2855        // "total profiled" then exceeded the wall clock, which is the tell.
2856        macro_rules! ffn_step {
2857            ($label:expr, $t0:expr) => {
2858                if ctx.profile {
2859                    ctx.gpu.synchronize(stream)?;
2860                    tracing::info!(
2861                        "  FFN prefill [{}] N={}: {}µs",
2862                        $label,
2863                        num_tokens,
2864                        $t0.elapsed().as_micros()
2865                    );
2866                    #[allow(unused_assignments)]
2867                    {
2868                        $t0 = std::time::Instant::now();
2869                    }
2870                }
2871            };
2872        }
2873        #[allow(unused_mut, unused_assignments)]
2874        let mut t_ffn = std::time::Instant::now();
2875        // gate_proj GEMM: [M, H] → [M, inter]
2876        w4_gemm!(
2877            &self.weights.gate_proj,
2878            self.weights.gate_proj_t,
2879            &self.int8_gate,
2880            &self.q4k_gate,
2881            &self.fp4mmq_gate,
2882            fp4mmq_prefill,
2883            input,
2884            gate_out,
2885            inter,
2886            h,
2887            true
2888        );
2889        ffn_step!("gate_proj", t_ffn);
2890        // up_proj GEMM: [M, H] → [M, inter]
2891        w4_gemm!(
2892            &self.weights.up_proj,
2893            self.weights.up_proj_t,
2894            &self.int8_up,
2895            &self.q4k_up,
2896            &self.fp4mmq_up,
2897            fp4mmq_prefill,
2898            input,
2899            up_out,
2900            inter,
2901            h,
2902            true
2903        );
2904        ffn_step!("up_proj", t_ffn);
2905
2906        // LoRA gate/up deltas land here: the projections are complete and, with
2907        // the MMQ arm disabled above, gate_out/up_out hold true-valued BF16 —
2908        // so the delta adds in the same units it was trained in.
2909        self.apply_lora_gate_up(ctx, input, gate_out, up_out, m, stream)?;
2910        // activation(gate) * up for all M tokens (SiLU or GELU)
2911        let fused_down_quant = fp4mmq_down && self.nvfp4_silu_quant_k.0 != 0;
2912        if fused_down_quant {
2913            // Fused SiLU-mul + quantize straight into the down MMQ's y-format: the
2914            // [M, inter] bf16 intermediate is never written or re-read (that round-trip
2915            // is why the unfused down arm measured neutral). scale2 folds happen inside,
2916            // pre-clamp — identical math to the two-step path below.
2917            ops::nvfp4_silu_mul_quant(
2918                ctx.gpu,
2919                self.nvfp4_silu_quant_k,
2920                gate_out,
2921                up_out,
2922                fp4_y,
2923                self.weights.gate_proj.weight_scale_2,
2924                self.weights.up_proj.weight_scale_2,
2925                m,
2926                inter,
2927                stream,
2928            )?;
2929        } else if fp4mmq_prefill {
2930            // FP4-MMQ outputs are missing the per-tensor FP32 scale2 (the hardware MMA
2931            // applies only the per-16 e4m3 scales) — fold it here, before the nonlinearity.
2932            ops::nvfp4_silu_mul_scaled(
2933                ctx.gpu,
2934                self.nvfp4_silu_scaled_k,
2935                gate_out,
2936                up_out,
2937                gate_out,
2938                self.weights.gate_proj.weight_scale_2,
2939                self.weights.up_proj.weight_scale_2,
2940                m * inter,
2941                stream,
2942            )?;
2943        } else {
2944            ops::silu_mul(
2945                ctx.gpu,
2946                self.act_mul,
2947                gate_out,
2948                up_out,
2949                gate_out,
2950                m * inter,
2951                stream,
2952            )?;
2953        }
2954
2955        // W4A4 opt #1: quantize the down input (SiLU(gate)*up, `[M, inter]`) to NVFP4.
2956        if fp4_prefill {
2957            ops::quantize_bf16_to_nvfp4(
2958                ctx.gpu,
2959                self.quantize_nvfp4_k,
2960                gate_out,
2961                nvfp4_a_packed,
2962                nvfp4_a_scale,
2963                m,
2964                inter,
2965                stream,
2966            )?;
2967        }
2968        // Q4_K opt: quantize the down input (SiLU(gate)*up, `[M, inter]`) to q8_1.
2969        // Skip when the hybrid routes down to faith2 (it does its own int8 requant).
2970        if q4k_prefill && !down_faith2 {
2971            ops::quantize_act_q8_1(
2972                ctx.gpu,
2973                self.q4k_quant_act_k,
2974                gate_out,
2975                q4k_a,
2976                m,
2977                inter,
2978                stream,
2979            )?;
2980        }
2981        // FP4-MMQ down (two-step fallback, only when the fused kernel is absent):
2982        // quantize the down input (SiLU(gate)*up, `[M, inter]`) to block_fp4_mmq.
2983        if fp4mmq_down && !fused_down_quant {
2984            ops::nvfp4_mmq_quantize_act(
2985                ctx.gpu,
2986                self.nvfp4_quant_act_k,
2987                gate_out,
2988                fp4_y,
2989                m,
2990                inter,
2991                stream,
2992            )?;
2993        }
2994        // down_proj GEMM: [M, inter] → [M, H]
2995        // ($fp4cell is a placeholder — the FP4-MMQ arm is gated off by `false` below;
2996        // down stays on the default path in the FP4-MMQ hybrid.)
2997        let output = ctx.buffers.moe_output();
2998        w4_gemm!(
2999            &self.weights.down_proj,
3000            self.weights.down_proj_t,
3001            &self.int8_down,
3002            &self.q4k_down,
3003            &self.fp4mmq_down,
3004            fp4mmq_down,
3005            gate_out,
3006            output,
3007            h,
3008            inter,
3009            false
3010        );
3011        ffn_step!("down_proj", t_ffn);
3012        // FP4-MMQ down: fold the down-projection's per-tensor scale2 (no SiLU-mul here;
3013        // the consumer is the residual add).
3014        if fp4mmq_down {
3015            ops::nvfp4_scale_bf16(
3016                ctx.gpu,
3017                self.nvfp4_scale_k,
3018                output,
3019                self.weights.down_proj.weight_scale_2,
3020                m * h,
3021                stream,
3022            )?;
3023        }
3024        // AFTER the scale2 fold, not before: that fold scales the base
3025        // projection's output, and the delta is not part of that product.
3026        // `gate_out` holds silu(gate)*up — the activation the base down GEMM
3027        // just contracted over — because the fused silu+quant arm is off
3028        // whenever an adapter is installed.
3029        self.apply_lora_down(ctx, gate_out, output, m, stream)?;
3030
3031        Ok(())
3032    }
3033
3034    /// Batched forward (per-token loop). Used by forward_batched in model loop.
3035    pub fn forward_batched(
3036        &self,
3037        input: DevicePtr,
3038        num_tokens: usize,
3039        ctx: &ForwardContext,
3040        stream: u64,
3041    ) -> Result<()> {
3042        self.forward_prefill(input, num_tokens, ctx, stream)
3043    }
3044}
3045
3046/// The native-FP8 M=1 decode DOWN projection (#928) — the arm rule. A CHILD
3047/// module, not a sibling: this file is already at the CI size cap, and the
3048/// nsys attribution that motivates the arm needs room this file does not have.
3049#[path = "dense_ffn_fp8_down.rs"]
3050pub mod fp8_down;
3051/// W8A8 block-scaled prefill branch (#917/#928). A CHILD module, not a
3052/// sibling: it adds `impl DenseFfnLayer` methods that read this layer's
3053/// private kernel handles, and this file is already at the CI size cap.
3054#[path = "dense_ffn_w8a8_prefill.rs"]
3055pub mod w8a8_prefill;
3056
3057/// The 5..=32-row native-FP8 DECODE tier (#927) — same child-module reason as
3058/// `w8a8_prefill` above: it reads this layer's private kernel handles, and the
3059/// arm's rule, its opt-in and the measurements that made it opt-in do not fit
3060/// in this file's budget.
3061#[path = "dense_ffn_batch16_decode.rs"]
3062pub mod batch16_decode;
3063
3064/// The TENSOR-CORE 5..=32-row decode tier (`ATLAS_FFN_M16_TC`, #927) — same
3065/// child-module reason as the two above: it reads this layer's private kernel
3066/// handles, and its rule, lever and the numerics seam it opens need room.
3067#[path = "dense_ffn_m16_tc.rs"]
3068pub mod m16_tc;
3069
3070/// The FUSED gate+up DECODE GEMM (`ffn_gateup_fused`, #927) — a child module
3071/// because it reads this layer's private kernel handles, and the round-13
3072/// receipt, the layout decision and the residency-neutrality argument need
3073/// room this file does not have.
3074#[path = "dense_ffn_gateup_fused.rs"]
3075pub mod gateup_fused;
3076
3077/// Native BF16/FP8 overlays take precedence over any NVFP4 fallback weights.
3078/// Small batches must use the same format-aware dispatcher as prefill.
3079fn native_small_batch_uses_prefill(has_bf16: bool, has_fp8: bool) -> bool {
3080    has_bf16 || has_fp8
3081}
3082
3083#[cfg(test)]
3084#[path = "dense_ffn_mmq_tests.rs"]
3085mod mmq_tests;
3086
3087#[cfg(test)]
3088#[path = "dense_ffn_native_batch_tests.rs"]
3089mod native_batch_tests;
3090
3091#[cfg(test)]
3092#[path = "dense_ffn_kernel_tests.rs"]
3093mod kernel_tests;
3094
3095/// #915: the native-FP8 route must work with NO NVFP4 fallback weights, so the
3096/// loader can stop allocating 18.4 GiB of them.
3097#[cfg(test)]
3098#[path = "dense_ffn_fp8_residency_tests.rs"]
3099mod fp8_residency_tests;
3100
3101#[cfg(test)]
3102#[path = "dense_ffn_fp8_down_tests.rs"]
3103mod fp8_down_tests;
3104
3105#[cfg(test)]
3106mod tests {
3107    use super::native_small_batch_uses_prefill;
3108
3109    #[test]
3110    fn native_weight_presence_requires_prefill_dispatch() {
3111        assert!(native_small_batch_uses_prefill(true, false));
3112        assert!(native_small_batch_uses_prefill(false, true));
3113        assert!(native_small_batch_uses_prefill(true, true));
3114        assert!(!native_small_batch_uses_prefill(false, false));
3115    }
3116}