metrale_model_layers/layers/dense_ffn.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Dense gated FFN layer, `down(act(gate(x)) * up(x))` with SiLU or GELU, for
4//! single-token decode, small verify batches and prefill.
5//!
6//! Owner: model-layers (dense FFN).
7//! Invariants:
8//! - On success, every forward entry point leaves its `[rows, hidden]` BF16 output in
9//! `ctx.buffers.moe_output()`; decode `forward` also returns that pointer.
10//! - `forward`, `forward_k2`, `forward_k3` and `forward_prefill` serve an installed
11//! packed-Q2 overlay first, then native FP8, then BF16, and use the NVFP4 weights only
12//! when none is installed.
13
14use anyhow::Result;
15use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
16
17// 2026-09-26: Imported so the moved bodies in `dense_ffn_init.rs` and `dense_ffn_overlays.rs` keep
18// calling these as `super::`.
19use super::{k64_kernel, tgemm_kernel, try_kernel, try_target_kernel, w4a16_v2_kernel};
20use crate::layer::ForwardContext;
21use crate::layers::ops;
22use crate::layers::w4a16_gemv_tiers::W4a16BatchmTiers;
23use crate::weight_map::Fp8Weight;
24
25/// 2026-09-26: The public weight sets and `FfnActivation`.
26#[path = "dense_ffn_weights.rs"]
27mod weights;
28pub use weights::{
29 DenseFfnWeights, DenseFfnWeightsBf16, DenseFfnWeightsFp8, DenseFfnWeightsQ2, FfnActivation,
30};
31use weights::{Fp4MmqWeight, Int8Weight, Q4kWeight};
32
33pub struct DenseFfnLayer {
34 pub weights: DenseFfnWeights,
35 activation: FfnActivation,
36 w4a16_gemv: KernelHandle,
37 /// 2026-09-25: Single-warp `w4a16_gemv_sw`. `KernelHandle(0)` on miss, which selects the base
38 /// GEMV. `examples/w4a16_gemv_sw_microtest.rs` requires it to match `w4a16_gemv` bit for bit.
39 w4a16_gemv_sw: KernelHandle,
40 w4a16_gemv_dual: KernelHandle,
41 w4a16_gemv_silu_input: KernelHandle,
42 // 2026-09-25: Single-warp-per-output decode variants, used when `ModelLevers::gemv_sw` is on
43 // (unless `METRALE_NO_GEMV_SW=1`) and the handle resolved; otherwise the 64-thread
44 // kernels run.
45 w4a16_gemv_dual_sw: KernelHandle,
46 w4a16_gemv_silu_input_sw: KernelHandle,
47 w4a16_gemv_dual_batch2: KernelHandle,
48 w4a16_gemv_dual_batch3: KernelHandle,
49 w4a16_gemv_batch2: KernelHandle,
50 w4a16_gemv_batch3: KernelHandle,
51 /// 2026-09-25: The `w4a16_gemv_batch{M}` tiers `forward_km` launches.
52 /// `W4a16BatchmTiers::kernel` picks the tier for `m` and returns a zero handle when none serves
53 /// it.
54 w4a16_batchm: W4a16BatchmTiers,
55 w4a16_gemm: KernelHandle,
56 w4a16_gemm_t_m128_k: KernelHandle,
57 // 2026-09-25: 8-warp variant of `w4a16_gemm_t_m128`, preferred over it when resolved.
58 // `w4a16_v2_kernel` resolves it only under `METRALE_W4A16_VARIANT=v2` or `v3`.
59 w4a16_gemm_t_m128_v2_k: KernelHandle,
60 // 2026-09-25: BF16 tensor-core variant of `w4a16_gemm_t_m128`, used for prefill only when
61 // `METRALE_BF16_TC_PREFILL` is set. `examples/w4a16_bf16_v2_microtest.rs` compares it with
62 // the base `w4a16_gemm` by cosine, not bit for bit.
63 w4a16_gemm_t_m128_bf16_k: KernelHandle,
64 // 2026-09-25: Second BF16 tensor-core variant, chosen over `w4a16_gemm_t_m128_bf16` when it
65 // resolved unless `METRALE_DISABLE_PREFILL_V2` is set. `examples/w4a16_bf16_v2_microtest.rs`
66 // requires its output to equal that kernel's bit for bit.
67 w4a16_gemm_t_m128_bf16_v2_k: KernelHandle,
68 // 2026-09-25: `w4a16_gemm_t` (M tile 64; `tgemm_kernel` prefers the `_p3` variant).
69 // `w4a16_prefill_gemm` uses it for m <= 64, and `METRALE_FP8_M64_PREFILL` sends every
70 // prefill projection that has a transposed copy through it.
71 w4a16_gemm_t_k: KernelHandle,
72 // 2026-09-25: int8 prefill (`METRALE_INT8_PREFILL`, and the down projection under
73 // `METRALE_FFN_MMQ`): `requant_w_nvfp4_int8` converts each NVFP4 weight once into the
74 // `OnceLock`s below, and `requant_a_bf16_int8` converts the BF16 activation on every call
75 // into the arena's `ffn_act_a` / `ffn_act_scale`. A zero `int8_faith2_k` disables both.
76 int8_faith2_k: KernelHandle,
77 // 2026-09-25: `int8_gemm_i32acc`, which replaces `int8_gemm_faith2` in the int8 arms when
78 // `METRALE_INT8_FAITH5` is set.
79 int8_faith5_k: KernelHandle,
80 requant_w_int8_k: KernelHandle,
81 requant_a_int8_k: KernelHandle,
82 int8_gate: std::sync::OnceLock<Int8Weight>,
83 int8_up: std::sync::OnceLock<Int8Weight>,
84 int8_down: std::sync::OnceLock<Int8Weight>,
85 // 2026-09-25: W4A4 prefill (`METRALE_FP4_PREFILL`): `w4a4_gemm` reads the NVFP4 weights as they
86 // are, and `quantize_bf16_to_nvfp4` writes the activation into the arena's `ffn_act_a` /
87 // `ffn_act_scale`. Taken only when both handles resolved.
88 w4a4_gemm_k: KernelHandle,
89 quantize_nvfp4_k: KernelHandle,
90 // 2026-09-25: Q4_K MMQ prefill (`METRALE_FFN_MMQ`): each NVFP4 weight is dequantized to BF16
91 // and quantized to `block_q4_K` once (`ensure_q4k_weight`), and the activation is
92 // quantized to q8_1 on every call into the arena's `ffn_act_q8`. All four handles must
93 // resolve.
94 q4k_mmq_nc_k: KernelHandle,
95 q4k_mmq_wc_k: KernelHandle,
96 q4k_quant_act_k: KernelHandle,
97 q4k_quant_w_k: KernelHandle,
98 dequant_nvfp4_bf16_k: KernelHandle,
99 q4k_gate: std::sync::OnceLock<Q4kWeight>,
100 q4k_up: std::sync::OnceLock<Q4kWeight>,
101 q4k_down: std::sync::OnceLock<Q4kWeight>,
102 // 2026-09-25: NVFP4 MMQ prefill, on by default (`ModelLevers::ffn_nvfp4_mmq`, off with
103 // `METRALE_NO_FFN_NVFP4_MMQ`) for SiLU layers without a LoRA adapter.
104 // `finalize_nvfp4_mmq_load` repacks the weights to `block_nvfp4` at load, and the
105 // activation is quantized on every call into the arena's `ffn_act_q8`. Each projection's
106 // `weight_scale_2` is applied after the GEMM (`nvfp4_silu_mul_scaled`,
107 // `nvfp4_silu_mul_quant` or `nvfp4_scale_bf16`).
108 nvfp4_mmq_nc_k: KernelHandle,
109 nvfp4_mmq_wc_k: KernelHandle,
110 /// 2026-09-25: The 16-, 32- and 64-row MMQ tiles, chosen for m <= 16, 32 or 64
111 /// (`mmq_small_tile_enabled`, `mmq_tile64_enabled`). A zero handle keeps the 128-row tile.
112 nvfp4_mmq16_nc_k: KernelHandle,
113 nvfp4_mmq16_wc_k: KernelHandle,
114 nvfp4_mmq32_nc_k: KernelHandle,
115 nvfp4_mmq32_wc_k: KernelHandle,
116 nvfp4_mmq64_nc_k: KernelHandle,
117 nvfp4_mmq64_wc_k: KernelHandle,
118 nvfp4_quant_act_k: KernelHandle,
119 nvfp4_repack_k: KernelHandle,
120 nvfp4_silu_scaled_k: KernelHandle,
121 nvfp4_silu_quant_k: KernelHandle,
122 nvfp4_scale_k: KernelHandle,
123 fp4mmq_gate: std::sync::OnceLock<Fp4MmqWeight>,
124 fp4mmq_up: std::sync::OnceLock<Fp4MmqWeight>,
125 fp4mmq_down: std::sync::OnceLock<Fp4MmqWeight>,
126 // 2026-09-25: Deep-K variant of `w4a16_gemm_t` (`k64_kernel` prefers `_p3`).
127 // `w4a16_prefill_gemm` uses it for m <= 64 when k >= `w4a16_k64_min_k()` and k % 64 == 0.
128 w4a16_gemm_t_k64_k: KernelHandle,
129 /// 2026-09-25: SiLU(gate)*up or GELU(gate)*up, depending on the activation.
130 act_mul: KernelHandle,
131 /// 2026-09-25: Set by `set_bf16_weights`. When present and no packed-Q2 or FP8 overlay is
132 /// installed, every forward path uses the BF16 kernels.
133 bf16_weights: Option<DenseFfnWeightsBf16>,
134 dense_gemv_bf16_k: KernelHandle,
135 dense_gemm_bf16_k: KernelHandle,
136 // 2026-09-25: Tensor-core BF16 GEMM for BF16 and packed-Q2 prefill; a zero handle falls back to
137 // `dense_gemm_bf16`. For BF16 prefill, cuBLASLt comes first when `METRALE_CUBLAS_GEMM`
138 // arms the `ffn` family.
139 dense_gemm_tc_k: KernelHandle,
140 /// 2026-09-25: Set by `set_fp8_weights`. When present (and no packed-Q2 overlay), every forward
141 /// path runs FP8 kernels: `forward_k2`, `forward_k3` and `forward_km` hand the layer to
142 /// `forward_prefill`.
143 fp8_weights: Option<DenseFfnWeightsFp8>,
144 /// 2026-09-28: W8A8 gate, up and down (`set_w8a8_decode_weights`), for a checkpoint that
145 /// declares them FP8 W8A8; run ahead of every other arm at 1..=64 decode rows.
146 pub(crate) w8a8: Option<crate::layers::W8a8Ffn>,
147 w8a16_gemv_k: KernelHandle,
148 w8a16_gemm_k: KernelHandle,
149 w8a16_gemv_batch4_k: KernelHandle,
150 /// 2026-09-25: `w8a16_gemv_batch16` for the opt-in 5..=32-row tier; zero when the target lacks
151 /// it. Rule: `batch16_decode::batch16_plan`.
152 w8a16_gemv_batch16_k: KernelHandle,
153 /// 2026-09-25: Whether the batch16 tier is armed (`METRALE_FFN_BATCH16=1`), copied from
154 /// `ffn_batch16_enabled()` at construction. A field, so the dispatch tests can set either
155 /// value without touching the process-wide `OnceLock`.
156 batch16_enabled: bool,
157 /// 2026-09-25: `w8a16_gemm_m16`, the tensor-core tier `w8_gemm!` tries ahead of batch16 at
158 /// 5..=32 rows when `m16_tc` is on; zero when the target lacks the module. Rule:
159 /// `m16_tc::m16_tc_plan`.
160 w8a16_gemm_m16_k: KernelHandle,
161 /// 2026-09-25: The 64-wide N tile twin, used when `METRALE_FFN_M16_TC_NTILE=64` and it
162 /// resolved; otherwise the 32-wide kernel runs (`m16_tc::m16_tc_kernel`).
163 w8a16_gemm_m16_n64_k: KernelHandle,
164 /// 2026-09-25: `m16_tc::m16_tc_levers().ffn`, copied at construction: the target's `[defaults]
165 /// ffn_m16_tc`, overridden by `METRALE_FFN_M16_TC`, or by `METRALE_M16_TC` when that is
166 /// unset. A field for the same reason as `batch16_enabled`.
167 m16_tc: bool,
168 /// 2026-09-25: The CTA N width this layer asks for, 32 or 64.
169 m16_tc_n_tile: u32,
170 w8a16_gemm_pipelined_k: KernelHandle,
171 // 2026-09-25: Fused FP8 decode GEMVs: gate+up in one launch, and `silu(gate)*up` computed
172 // inside the down GEMV. `fp8_down::fp8_down_arm` decides which run.
173 w8a16_gemv_dual_k: KernelHandle,
174 w8a16_gemv_silu_input_k: KernelHandle,
175 // 2026-09-25: Transposed-weight FP8 prefill GEMM. `forward_prefill_inner` passes no transposed
176 // FP8 copy (`gate_t` / `up_t` / `down_t` are `None`), so its `w8_gemm!` arm never matches.
177 w8a16_gemm_t_m128_k: KernelHandle,
178 // 2026-09-25: The W8A8 prefill pair: per-token, 128-group FP8 activation quantization and the
179 // block-scaled FP8 x FP8 GEMM. Rule: `w8a8_prefill::w8a8_prefill_selected`.
180 per_token_group_quant_fp8_k: ops::Fp8ActQuant,
181 fp8_gemm_t_blockscaled_k: KernelHandle,
182 // 2026-09-25: Activation-scale layout adapter for the cuBLASLt arm of `w8a8_gemm`. When
183 // `ops::cublas_scale_layout_kmajor()` holds, a zero handle makes that arm decline and the
184 // in-tree GEMM run.
185 fp8_act_scale_kmajor_k: KernelHandle,
186 /// 2026-09-25: The `[2*inter, hidden]` FP8 gate+up weight installed by `set_fp8_gate_up_fused`;
187 /// `None` unless the loader built it. The installer's contract is that `gate_proj` and
188 /// `up_proj` are views into it.
189 fp8_gate_up_fused: Option<Fp8Weight>,
190 /// 2026-09-25: `gateup_fused::ffn_gateup_fused()` at construction: the target's `[defaults]
191 /// ffn_gateup_fused`, overridden by `METRALE_FFN_GATEUP_FUSED`. It also decides whether
192 /// `silu_mul_strided_k` is looked up.
193 gateup_fused: bool,
194 /// 2026-09-25: The strided SiLU·mul that reads the fused `[m, 2*inter]` output. Its source
195 /// exists only under `kernels/hopper/common/`, so the handle is zero on other targets, and it
196 /// is looked up only when `gateup_fused` is on.
197 silu_mul_strided_k: KernelHandle,
198 /// 2026-09-25: LoRA overlay for gate/up/down, set by `set_lora_weights` (NVFP4 layers only).
199 /// `apply_lora_gate_up` runs after the gate/up projections and before the activation;
200 /// `apply_lora_down` runs after the down projection. `forward` (split-SiLU path),
201 /// `forward_k2`, `forward_k3`, `forward_km` and the NVFP4 part of `forward_prefill` call
202 /// both. The `ModelLevers::decode_ffn_via_gemm` branch of `forward` calls neither.
203 lora: Option<ops::lora_delta::LoraFfnWeights>,
204
205 /// 2026-09-25: Set by `set_q2_weights`. Checked first by `forward`, `forward_k2`, `forward_k3`
206 /// and `forward_prefill`.
207 q2_weights: Option<DenseFfnWeightsQ2>,
208 q2_0_gemv_k: KernelHandle,
209 // 2026-09-25: Batched packed-Q2 decode GEMV, used by `forward_km_q2`.
210 #[allow(dead_code)]
211 q2_0_gemv_batchm_k: KernelHandle,
212 // 2026-09-25: Packed-Q2 to BF16 dequant for packed-Q2 prefill when the Q2_0 MMQ arm is not
213 // taken: each projection is dequantized into the arena's `q2_dequant_scratch` and run
214 // through the BF16 GEMM.
215 dequant_q2_0_gn_k: KernelHandle,
216 // 2026-09-25: Q2_0 MMQ prefill (`METRALE_GGUF_NATIVE_Q2_MMQ=1`, group-128 weights) against a
217 // q8_1 activation from `q4k_quant_act_k`. Looked up by `set_q2_weights`; a zero handle leaves
218 // prefill on the dequant path.
219 q2_0_mmq_nc_k: KernelHandle,
220 q2_0_mmq_wc_k: KernelHandle,
221}
222
223/// 2026-09-25: The 16/32/64-row MMQ tiles: on unless `METRALE_NO_MMQ_SMALL_TILE` is exactly `1`.
224/// Read once per process.
225fn mmq_small_tile_enabled() -> bool {
226 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
227 *ON.get_or_init(|| std::env::var("METRALE_NO_MMQ_SMALL_TILE").as_deref() != Ok("1"))
228}
229
230/// 2026-09-25: The 64-row MMQ tile: on unless `METRALE_NO_MMQ_TILE64` is exactly `1`;
231/// `METRALE_NO_MMQ_SMALL_TILE=1` also turns it off. Read once per process.
232fn mmq_tile64_enabled() -> bool {
233 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
234 *ON.get_or_init(|| std::env::var("METRALE_NO_MMQ_TILE64").as_deref() != Ok("1"))
235}
236impl DenseFfnLayer {
237 pub fn new(weights: DenseFfnWeights, gpu: &dyn GpuBackend) -> Result<Self> {
238 Self::new_with_activation(weights, FfnActivation::SiLU, gpu)
239 }
240
241 /// 2026-09-25: Load-time setup for the Q4_K MMQ prefill arm. Returns at once for packed-Q2 or
242 /// FP8 layers, when one of the four Q4_K kernels is missing, or when `METRALE_FFN_MMQ` is
243 /// unset. Otherwise it builds the gate/up Q4_K copies and the down copy (int8 when
244 /// `int8_gemm_faith2` and `requant_a_bf16_int8` resolved and `METRALE_FFN_MMQ_DOWN_Q4K` is
245 /// unset, Q4_K otherwise), synchronizes `stream`, and frees the transposed `_t` copies.
246 pub fn finalize_q4k_load(
247 &mut self,
248 gpu: &dyn GpuBackend,
249 h: u32,
250 inter: u32,
251 stream: u64,
252 ) -> Result<()> {
253 // 2026-09-25: Packed-Q2 prefill never reaches the Q4_K arm (`forward_prefill_inner`), so
254 // the copies would be unused.
255 if self.q2_weights.is_some() {
256 return Ok(());
257 }
258 // 2026-09-25: FP8 prefill returns before the Q4_K arm, so the copies would be unused.
259 if self.fp8_weights.is_some() {
260 return Ok(());
261 }
262 let q4k_active = self.q4k_mmq_nc_k.0 != 0
263 && self.q4k_quant_act_k.0 != 0
264 && self.q4k_quant_w_k.0 != 0
265 && self.dequant_nvfp4_bf16_k.0 != 0
266 && std::env::var_os("METRALE_FFN_MMQ").is_some();
267 if !q4k_active {
268 return Ok(());
269 }
270 // 2026-09-25: Build the copies before freeing the `_t` weights.
271 self.build_q4k_gate_up(gpu, h, inter, stream)?;
272 let down_faith2 = self.int8_faith2_k.0 != 0
273 && self.requant_a_int8_k.0 != 0
274 && std::env::var_os("METRALE_FFN_MMQ_DOWN_Q4K").is_none();
275 self.finish_q4k_load(gpu, h, inter, stream, down_faith2)
276 }
277
278 /// 2026-09-25: Load-time setup for the NVFP4 MMQ prefill arm. Returns at once for packed-Q2 or
279 /// FP8 layers, GELU layers, when a kernel it needs is missing, or when
280 /// `METRALE_NO_FFN_NVFP4_MMQ` is set. Otherwise it repacks gate and up, and down unless
281 /// `METRALE_NO_FFN_NVFP4_MMQ_DOWN` is set, synchronizes `stream`, and frees the transposed `_t`
282 /// copies of the repacked projections.
283 pub fn finalize_nvfp4_mmq_load(
284 &mut self,
285 gpu: &dyn GpuBackend,
286 h: u32,
287 inter: u32,
288 stream: u64,
289 ) -> Result<()> {
290 // 2026-09-25: Packed-Q2 prefill never reaches the MMQ arm, so a repack would be unused.
291 if self.q2_weights.is_some() {
292 return Ok(());
293 }
294 // 2026-09-25: FP8 prefill returns before the MMQ arm, so a repack would be unused.
295 if self.fp8_weights.is_some() {
296 return Ok(());
297 }
298 let active = self.nvfp4_mmq_nc_k.0 != 0
299 && self.nvfp4_quant_act_k.0 != 0
300 && self.nvfp4_repack_k.0 != 0
301 && self.nvfp4_silu_scaled_k.0 != 0
302 && matches!(self.activation, FfnActivation::SiLU)
303 && self.mmq_gate_up_declared()
304 && std::env::var_os("METRALE_NO_FFN_NVFP4_MMQ").is_none();
305 if !active {
306 return Ok(());
307 }
308 self.build_nvfp4_mmq_gate_up(gpu, h, inter, stream)?;
309 let down_mmq = std::env::var_os("METRALE_NO_FFN_NVFP4_MMQ_DOWN").is_none();
310 self.finish_nvfp4_mmq_load(gpu, h, inter, stream, down_mmq)
311 }
312
313 /// 2026-09-26: The BF16 branch of `forward_prefill_inner` (`dense_ffn_prefill.rs`). It reads
314 /// `dispatch.cublas.ffn`, which `ops/dispatch_config_routing_tests.rs` allows only in this
315 /// file, `dense_ffn_w8a8_prefill.rs` and `moe/`.
316 fn prefill_bf16(
317 &self,
318 bf16w: &DenseFfnWeightsBf16,
319 input: DevicePtr,
320 ctx: &ForwardContext,
321 m: u32,
322 h: u32,
323 inter: u32,
324 gate_out: DevicePtr,
325 up_out: DevicePtr,
326 stream: u64,
327 ) -> Result<()> {
328 let tc = self.dense_gemm_tc_k.0 != 0;
329 // 2026-09-25: BF16 prefill GEMM: cuBLASLt when `METRALE_CUBLAS_GEMM` arms the `ffn`
330 // family, else `dense_gemm_tc` when it resolved, else `dense_gemm_bf16`.
331 macro_rules! ffn_gemm {
332 ($a:expr, $b:expr, $c:expr, $n:expr, $k:expr) => {
333 if ctx.dispatch.cublas.ffn {
334 ops::cublas_bf16_proj_dense($a, $b.weight, $c, m, $n, $k, stream)?;
335 } else if tc {
336 ops::dense_gemm_tc(
337 ctx.gpu,
338 self.dense_gemm_tc_k,
339 $a,
340 $b,
341 $c,
342 m,
343 $n,
344 $k,
345 stream,
346 )?;
347 } else {
348 ops::dense_gemm(
349 ctx.gpu,
350 self.dense_gemm_bf16_k,
351 $a,
352 $b,
353 $c,
354 m,
355 $n,
356 $k,
357 stream,
358 )?;
359 }
360 };
361 }
362 ffn_gemm!(input, &bf16w.gate_proj, gate_out, inter, h);
363 ffn_gemm!(input, &bf16w.up_proj, up_out, inter, h);
364 ops::silu_mul(
365 ctx.gpu,
366 self.act_mul,
367 gate_out,
368 up_out,
369 gate_out,
370 m * inter,
371 stream,
372 )?;
373 let output = ctx.buffers.moe_output();
374 ffn_gemm!(gate_out, &bf16w.down_proj, output, h, inter);
375 Ok(())
376 }
377
378 /// 2026-09-25: Same as `forward_prefill`.
379 pub fn forward_batched(
380 &self,
381 input: DevicePtr,
382 num_tokens: usize,
383 ctx: &ForwardContext,
384 stream: u64,
385 ) -> Result<()> {
386 self.forward_prefill(input, num_tokens, ctx, stream)
387 }
388}
389
390/// 2026-09-25: Arm choice for the native-FP8 decode FFN.
391#[path = "dense_ffn_fp8_down.rs"]
392pub mod fp8_down;
393/// 2026-09-25: The W8A8 block-scaled FP8 prefill arm.
394#[path = "dense_ffn_w8a8_prefill.rs"]
395pub mod w8a8_prefill;
396
397/// 2026-09-25: The opt-in 5..=32-row native-FP8 decode tier.
398#[path = "dense_ffn_batch16_decode.rs"]
399pub mod batch16_decode;
400
401/// 2026-09-25: The tensor-core 5..=32-row FP8 tier (`m16_tc`).
402#[path = "dense_ffn_m16_tc.rs"]
403pub mod m16_tc;
404
405/// 2026-09-25: The fused FP8 gate+up GEMM (`ffn_gateup_fused`).
406#[path = "dense_ffn_gateup_fused.rs"]
407pub mod gateup_fused;
408
409/// 2026-09-26: `new_with_activation`, which resolves every kernel handle.
410#[path = "dense_ffn_init.rs"]
411mod init;
412#[path = "dense_ffn_w8a8.rs"]
413mod w8a8;
414
415/// 2026-09-26: The `ensure_*_weight` builders and the steps of the `finalize_*_load` functions.
416#[path = "dense_ffn_load.rs"]
417mod load;
418
419/// 2026-09-26: The weight-overlay installers and the LoRA delta launches.
420#[path = "dense_ffn_overlays.rs"]
421mod overlays;
422
423/// 2026-09-30: The fixed `--activation-quantization` path (`fixed_ok`, `forward_fixed`).
424#[path = "dense_ffn_fixed.rs"]
425mod fixed;
426
427/// 2026-09-26: Single-token decode (`forward`).
428#[path = "dense_ffn_decode.rs"]
429mod decode;
430
431/// 2026-09-26: Small-batch decode (`forward_k2`, `forward_k3`, `forward_km`).
432#[path = "dense_ffn_decode_batch.rs"]
433mod decode_batch;
434
435/// 2026-09-26: Prefill entry point, its packed-Q2 branch, and `w4a16_prefill_gemm`.
436#[path = "dense_ffn_prefill.rs"]
437mod prefill;
438
439/// 2026-09-26: The native-FP8 prefill branch (the `w8_gemm!` ladder).
440#[path = "dense_ffn_prefill_fp8.rs"]
441mod prefill_fp8;
442
443/// 2026-09-26: The NVFP4 prefill branch (the `w4_gemm!` ladder).
444#[path = "dense_ffn_prefill_nvfp4.rs"]
445mod prefill_nvfp4;
446
447/// 2026-09-26: The per-call arm choices of the NVFP4 prefill branch.
448#[path = "dense_ffn_nvfp4_plan.rs"]
449mod nvfp4_plan;
450
451/// 2026-09-25: Whether `forward_k2`, `forward_k3` or `forward_km` must hand the layer to
452/// `forward_prefill`: true when a BF16 or FP8 overlay is installed.
453fn native_small_batch_uses_prefill(has_bf16: bool, has_fp8: bool) -> bool {
454 has_bf16 || has_fp8
455}
456
457#[cfg(test)]
458#[path = "dense_ffn_mmq_tests.rs"]
459mod mmq_tests;
460
461#[cfg(test)]
462#[path = "dense_ffn_native_batch_tests.rs"]
463mod native_batch_tests;
464
465#[cfg(test)]
466#[path = "dense_ffn_kernel_tests.rs"]
467mod kernel_tests;
468
469#[cfg(test)]
470#[path = "dense_ffn_fp8_residency_tests.rs"]
471mod fp8_residency_tests;
472
473#[cfg(test)]
474#[path = "dense_ffn_fp8_down_tests.rs"]
475mod fp8_down_tests;
476
477#[cfg(test)]
478mod tests {
479 use super::native_small_batch_uses_prefill;
480
481 #[test]
482 fn native_weight_presence_requires_prefill_dispatch() {
483 assert!(native_small_batch_uses_prefill(true, false));
484 assert!(native_small_batch_uses_prefill(false, true));
485 assert!(native_small_batch_uses_prefill(true, true));
486 assert!(!native_small_batch_uses_prefill(false, false));
487 }
488}