spark_model/layers/moe/mod.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! MoE (Mixture of Experts) FFN component.
4//!
5//! Batched expert dispatch: top-K experts run in 2 fused kernel launches
6//! (gate+up, silu+down) instead of 10 × 5 individual launches. Expert indices
7//! and weights stay on device — zero D2H synchronization.
8
9use anyhow::Result;
10use spark_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
11
12use crate::layer::ForwardContext;
13use crate::layers::ops;
14use crate::weight_map::{DenseWeight, Fp8ExpertWeight, MoeWeights, QuantizedWeight};
15
16/// MoE feed-forward network component.
17///
18/// Not a `TransformerLayer` — used as a component inside layers
19/// for the FFN/MoE block after post-attention norm.
20#[allow(dead_code)]
21pub struct MoeLayer {
22 pub weights: MoeWeights,
23 /// Quant format of the ROUTED experts as landed in GPU memory. `Nvfp4`
24 /// (default) = packed E2M1 + FP8-E4M3 per-16 block scales + f32 per-tensor
25 /// global. Set to `Mxfp4E8m0` by the DeepSeek-V4 native-MXFP4 loader
26 /// (transcode-free: E8M0 per-32 scales, no global) so the Phase-K E8M0
27 /// GEMM variants dispatch on it instead of the NVFP4 kernels. Consumed at
28 /// the grouped/decode GEMM call sites (assert via `WeightQuantFormat::expect`).
29 // Written by the loader (Phase L); READ at the GEMM dispatch sites in Phase K.
30 // Until Phase K wires the read, `deny(warnings)` would flag it never-read.
31 #[allow(dead_code)]
32 pub(crate) experts_scale_kind: crate::weight_map::WeightQuantFormat,
33 /// Quant format of the SHARED expert (ARM-2 Phase-K RIDER A1). The native
34 /// V4 ckpt is heterogeneous: routed experts `Mxfp4E8m0` but the shared
35 /// expert is FP8→`Nvfp4`. Keyed off the weight tag (not `is_shared`
36 /// positionality) so the dual-format decode kernel's `expect` net fires if
37 /// a future ckpt ships a different shared format. Default `Nvfp4`.
38 #[allow(dead_code)]
39 pub(crate) shared_experts_scale_kind: crate::weight_map::WeightQuantFormat,
40 // NVFP4-quantized gate weight (quarters bandwidth for routing)
41 gate_nvfp4: Option<QuantizedWeight>,
42 /// Pre-expert norm: applied to input AFTER routing but BEFORE expert dispatch.
43 /// Gemma-4 26B: router sees raw residual, experts see pre_feedforward_layernorm_2(residual).
44 pub pre_expert_norm: Option<crate::weight_map::DenseWeight>,
45 pre_expert_norm_k: spark_runtime::gpu::KernelHandle,
46 dense_gemv: KernelHandle,
47 w4a16_gemv: KernelHandle,
48 /// Single-warp `w4a16_gemv_sw`. `KernelHandle(0)` on miss → base GEMV.
49 w4a16_gemv_sw: KernelHandle,
50 w4a16_gemm: KernelHandle,
51 dense_gemm: KernelHandle,
52 /// Order-preserving register-blocked router GEMM (`dense_gemm_bf16_router`):
53 /// bit-identical to the scalar `dense_gemm` (same per-output FP32 k-order,
54 /// `--fmad=false` build) at ~2x speed. `KernelHandle(0)` on miss → the
55 /// pinned scalar kernel. Used ONLY by `router_gate_gemm_dense`.
56 dense_gemm_router: KernelHandle,
57 dense_gemm_pipelined: KernelHandle,
58 /// FP32-output router GEMM + FP32-input top-K for the ATLAS_FP32_GATE path.
59 /// Zero (unresolved) when the kernels are absent; dispatch falls back to BF16.
60 dense_gemm_f32out: KernelHandle,
61 /// FP32-in/FP32-out router GEMM for ATLAS_FP32_ROUTING (reads the FP32
62 /// router_in from residual_add_rms_norm_gatef32). Zero if absent.
63 dense_gemm_f32in: KernelHandle,
64 moe_topk_f32: KernelHandle,
65 moe_expert_gate_up_shared: KernelHandle,
66 moe_expert_silu_down_shared: KernelHandle,
67 moe_topk: KernelHandle,
68 moe_weighted_sum_blend: KernelHandle,
69 residual_add: KernelHandle,
70 moe_topk_batched: KernelHandle,
71 // K=2 fused MoE kernel handles
72 moe_expert_gate_up_shared_batch2: KernelHandle,
73 moe_expert_silu_down_shared_batch2: KernelHandle,
74 moe_weighted_sum_blend_batch2: KernelHandle,
75 w4a16_gemv_batch2: KernelHandle,
76 // K=3 fused MoE kernel handles
77 moe_expert_gate_up_shared_batch3: KernelHandle,
78 moe_expert_silu_down_shared_batch3: KernelHandle,
79 moe_weighted_sum_blend_batch3: KernelHandle,
80 w4a16_gemv_batch3: KernelHandle,
81 // Generic token-major NVFP4 MoE kernels. Used as an opt-in decode
82 // concurrency experiment for N>=4 without grouped-GEMM sorting.
83 moe_expert_gate_up_shared_token_major: KernelHandle,
84 moe_expert_silu_down_shared_token_major: KernelHandle,
85 moe_weighted_sum_blend_token_major: KernelHandle,
86 moe_decode_atomic_c4_silu_down_accum_k: KernelHandle,
87 moe_decode_atomic_c4_finalize_k: KernelHandle,
88 // Sorted/grouped prefill path
89 moe_sort_by_expert: KernelHandle,
90 moe_sorted_gate_up: KernelHandle,
91 moe_sorted_silu_down: KernelHandle,
92 moe_grouped_gemm: KernelHandle,
93 /// Wider-K, grouped-dequant twin of `moe_grouped_gemm`, bit-exact with
94 /// it. `try_kernel` — absent on targets whose shadow predates it.
95 /// Opt-in via ATLAS_MOE_GROUPED_K32=1: measured on qwen4_exp only, and
96 /// the win is shape-dependent, so it is not switched on for every model
97 /// that happens to compile it.
98 moe_grouped_gemm_k32: KernelHandle,
99 /// M_TILE=256 twin: ONE pass over the expert weights instead of three
100 /// when rows/expert <= 256. Bit-exact. Opt-in via ATLAS_MOE_GROUPED_M256
101 /// because the win inverts for models with many rows per expert.
102 moe_grouped_gemm_m256: KernelHandle,
103 moe_silu_mul: KernelHandle,
104 /// Activation kernel for sorted/unfused path. SiLU by default, GeGLU for Gemma-4.
105 moe_act_mul: KernelHandle,
106 /// When true, decode uses the sorted prefill path (avoids fused SiLU kernels).
107 gelu_activation: bool,
108 moe_unpermute_reduce: KernelHandle,
109 moe_batched_blend: KernelHandle,
110 /// Pointer tables for batched expert dispatch.
111 gate_ptrs: ExpertPtrTable,
112 up_ptrs: ExpertPtrTable,
113 down_ptrs: ExpertPtrTable,
114 /// Transposed pointer tables for coalesced prefill GEMM.
115 gate_ptrs_t: Option<ExpertPtrTable>,
116 up_ptrs_t: Option<ExpertPtrTable>,
117 down_ptrs_t: Option<ExpertPtrTable>,
118 /// CUTLASS grouped-NVFP4 host tables (`ATLAS_HOLO_MOE_GROUPED_CUTLASS`).
119 /// Per-expert packed/SFB pointer values + scale2, snapshotted ONCE at load
120 /// by `build_cutlass_grouped_sfb` (the SFB swizzle is built there from the
121 /// `gate_ptrs_t`/`up_ptrs_t` `[K/16,N]` scales via `pack_weight_sfb`). The
122 /// grouped C entry consumes these host-side, so the snapshot lives here —
123 /// owned by the layer, dying with the model — rather than in any global
124 /// cache keyed on device addresses, which a model swap's free/realloc
125 /// would turn stale. `None` => the CUTLASS grouped path is unavailable.
126 cutlass_grouped_host: Option<ops::MoeCutlassHostTables>,
127 /// Keeps the per-expert SFB buffers alive.
128 _cutlass_sfb_owned: Vec<DevicePtr>,
129 /// Lazy down_proj transpose scratch — populated at the start of each
130 /// prefill call when the persistent transpose pass couldn't fit
131 /// down_proj. Decode keeps using `down_ptrs` (untransposed); prefill
132 /// uses `down_ptrs_t` pointing into this scratch. Shared across all
133 /// MoE layers (the same scratch is overwritten layer-by-layer during
134 /// the sequential forward).
135 ///
136 /// `down_t_scratch_packed`: contiguous `[num_experts × N × K/2]` bytes.
137 /// `down_t_scratch_scale`: contiguous `[num_experts × N × K/16]` bytes.
138 /// Both `None` when the persistent transpose pass already covered
139 /// down (full-fits path) or when the layer doesn't need scratch
140 /// transpose (FP8 experts, etc.).
141 down_t_scratch_packed: Option<DevicePtr>,
142 down_t_scratch_scale: Option<DevicePtr>,
143 /// Kernel handle for the batched per-expert uint8 transpose.
144 moe_transpose_u8_batched_k: KernelHandle,
145 // ── Phase 8a transposed-layout decode kernels (unified-layout MoE).
146 // Loaded eagerly at construction. Currently NOT wired into the
147 // dispatch — Phase 8a part 3/3 will route decode through these once
148 // the weight loader produces transposed-only pointer tables.
149 moe_expert_gate_up_shared_t_k: KernelHandle,
150 moe_expert_silu_down_shared_t_k: KernelHandle,
151 // ARM-2 Phase-K: native-MXFP4 (E8M0 routed / NVFP4 shared) dual-format
152 // decode variants. KernelHandle(0) on models that don't ship them.
153 moe_expert_gate_up_shared_t_e8m0_k: KernelHandle,
154 moe_expert_silu_down_shared_t_e8m0_k: KernelHandle,
155 // ── sqrtsoftplus routing (DeepSeek-V4) ──
156 moe_topk_sqrtsoftplus_k: KernelHandle,
157 moe_topk_sqrtsoftplus_batched_k: KernelHandle,
158 // ── hash routing (DeepSeek-V4 first `num_hash_layers` MoE layers) ──
159 moe_hash_route_k: KernelHandle,
160 moe_hash_route_batched_k: KernelHandle,
161 // ── LongCat softmax+bias routing with zero-computation experts ──
162 /// Router logit width = num_experts + zero_expert_num. Equal to
163 /// num_experts on every non-LongCat model (behavior-neutral).
164 pub(crate) router_logits_n: u32,
165 moe_topk_softmax_bias_k: KernelHandle,
166 moe_topk_softmax_bias_batched_k: KernelHandle,
167 moe_zero_expert_add_k: KernelHandle,
168 /// Per-token folded zero-expert weight (f32, written by the softmax+bias
169 /// router kernels). Fixed-size allocation (16K tokens) — graph-safe.
170 zero_accum_dev: DevicePtr,
171 /// Static `tid2eid` table [vocab_size, top_k] i64 — present ONLY for the
172 /// hash-routed layers (the loader supplies it only for those). `Some`
173 /// here is the SSOT that this layer routes via the static hash table
174 /// instead of the learned gate's top-K.
175 tid2eid_dev: Option<DevicePtr>,
176 moe_expert_gate_up_shared_batch2_t_k: KernelHandle,
177 moe_expert_silu_down_shared_batch2_t_k: KernelHandle,
178 moe_expert_gate_up_shared_batch3_t_k: KernelHandle,
179 moe_expert_silu_down_shared_batch3_t_k: KernelHandle,
180 moe_expert_gate_up_shared_fp8_t_k: KernelHandle,
181 moe_expert_silu_down_shared_fp8_t_k: KernelHandle,
182 moe_expert_gate_up_shared_fp8_batch2_t_k: KernelHandle,
183 moe_expert_silu_down_shared_fp8_batch2_t_k: KernelHandle,
184 moe_expert_gate_up_shared_fp8_batch3_t_k: KernelHandle,
185 moe_expert_silu_down_shared_fp8_batch3_t_k: KernelHandle,
186 /// `ATLAS_UNIFIED_MOE_LAYOUT=1` opts in to the unified-layout decode
187 /// path: gate/up/down all use transposed `[K/2, N]` layout, decode
188 /// dispatches to `moe_expert_*_shared_t` kernels. Default off — the
189 /// dispatch falls through to the original `[N, K/2]` kernels.
190 /// Resolved once at construction.
191 unified_layout: bool,
192 /// `ATLAS_NVFP4_GATE_UP_M128=1` opts in to the M=128 fused gate+up
193 /// kernel (Block D #3, Avarok tile-shape rewrite). Halves block count
194 /// at large prefill — better SM amortization on GB10's 25-SM budget.
195 /// Currently only minimax-m2-229b ships the kernel; other models keep
196 /// `moe_fused_gate_up_t_k64_m128 == KernelHandle(0)` and dispatch
197 /// falls through to the M=64 path even when the env var is set.
198 nvfp4_gate_up_m128: bool,
199 /// `ATLAS_HOLO_MOE_GATEUP_FP4=1` opts the prefill fused gate_up onto the
200 /// block-scaled FP4 kernel. Reads the SHARED FAST_MOE=full `gate_ptrs_t`/
201 /// `up_ptrs_t` `[K/2,N]` tables (no extra MoE memory); dispatch also requires
202 /// those tables present + the FP4 kernel handle != 0.
203 gateup_fp4: bool,
204 /// `ATLAS_HOLO_MOE_DOWN_FP4=1` — same, for the prefill down projection over
205 /// the shared `down_ptrs_t` table.
206 down_fp4: bool,
207 /// `ATLAS_HYBRID_MOE_LAYOUT=1` opts in to the hybrid-layout path:
208 /// keep BOTH original `[N, K/2]` weights (for decode + MTP verify) AND
209 /// transposed `[K/2, N]` weights (for prefill). Doubles MoE-weight
210 /// memory but recovers the ~15 % decode regression that pure unified
211 /// layout suffers from. Resolved once at construction; mutually
212 /// exclusive with `unified_layout` at the dispatch level (hybrid wins
213 /// on decode paths since it preserves untransposed warp-reduction
214 /// parallelism).
215 hybrid_layout: bool,
216 /// Transposed shared expert weights for prefill.
217 shared_gate_t: Option<QuantizedWeight>,
218 shared_up_t: Option<QuantizedWeight>,
219 shared_down_t: Option<QuantizedWeight>,
220 moe_grouped_gemm_t: KernelHandle,
221 moe_grouped_gemm_t_k64: KernelHandle,
222 moe_fused_gate_up_t: KernelHandle,
223 moe_fused_gate_up_t_k64: KernelHandle,
224 // ARM-2 Phase-K: native-MXFP4 (E8M0 per-32) prefill variants of the W4A16
225 // routed-expert GEMMs. KernelHandle(0) on models that don't ship them
226 // (only the deepseek-v4-flash target compiles the `_e8m0` entries).
227 moe_grouped_gemm_e8m0: KernelHandle,
228 moe_grouped_gemm_t_e8m0: KernelHandle,
229 moe_grouped_gemm_t_k64_e8m0: KernelHandle,
230 moe_fused_gate_up_t_e8m0: KernelHandle,
231 moe_fused_gate_up_t_k64_e8m0: KernelHandle,
232 /// M=128 variant of the K64 fused gate+up kernel (Block D #3, Avarok
233 /// tile-shape rewrite). Loaded with `try_kernel` — falls back to
234 /// `KernelHandle(0)` on models that don't ship the kernel; dispatch
235 /// gates on `nvfp4_gate_up_m128` AND handle non-zero.
236 moe_fused_gate_up_t_k64_m128: KernelHandle,
237 /// FUSED FP4 (block-scaled e2m1) variant of the K64 fused gate+up kernel
238 /// (`ATLAS_HOLO_MOE_GATEUP_FP4`). Same signature as `moe_fused_gate_up_t_k64`
239 /// but runs one `mma.sync.kind::mxf4nvf4.scale_vec::4X.m16n8k64` per k64
240 /// tile (vs 2× m16n8k32 e4m3). `try_kernel` — `KernelHandle(0)` on images
241 /// lacking it; the dispatch in `forward_prefill_routed` only fires when this
242 /// handle != 0, `gateup_fp4` is set, and the shared `gate_ptrs_t`/`up_ptrs_t`
243 /// tables are present (FAST_MOE=full).
244 moe_fused_gate_up_t_k64_fp4: KernelHandle,
245 moe_fp8_grouped_gemm_t: KernelHandle,
246 w4a16_gemm_t: KernelHandle,
247 bf16_to_fp8_k: KernelHandle,
248 /// Pre-dequanted FP8 weights for zero-overhead prefill GEMMs.
249 gate_fp8: Option<DevicePtr>,
250 shared_gate_fp8: Option<DevicePtr>,
251 shared_up_fp8: Option<DevicePtr>,
252 shared_down_fp8: Option<DevicePtr>,
253 fp8_gemm_k: KernelHandle,
254 /// Secondary CUDA stream for overlapping shared expert with routed experts.
255 prefill_stream: u64,
256 /// Event pair for stream synchronization (input_ready, shared_done).
257 event_a: u64,
258 event_b: u64,
259 // ── Sigmoid + correction-bias routing (DeepSeek-V3 / MiniMax-M2 style) ──
260 /// Device pointer to `[num_experts]` correction bias. Populated from
261 /// `MoeWeights.correction_bias` in `new()` when the loader sets it.
262 /// `None` = Atlas's default softmax path. When `Some`, every top-k
263 /// dispatch site branches to `moe_topk_sigmoid` with this bias arg.
264 correction_bias_dev: Option<DevicePtr>,
265 /// Handle to `moe_topk_sigmoid` kernel. Lazy-loaded in `new()` even
266 /// when bias is `None` (harmless if kernel isn't used).
267 moe_topk_sigmoid_k: KernelHandle,
268 /// Batched variant for prefill / MTP-verify (one block per token).
269 /// Loaded via `try_kernel` — returns KernelHandle(0) on models whose
270 /// KERNEL.toml doesn't register the sigmoid kernels (e.g. Mistral).
271 /// Never dispatched on those paths because `correction_bias_dev` is
272 /// `None` there.
273 moe_topk_sigmoid_batched_k: KernelHandle,
274 // FP8 fused MoE kernels (used when experts are FP8)
275 moe_expert_gate_up_shared_fp8: KernelHandle,
276 moe_expert_silu_down_shared_fp8: KernelHandle,
277 // FP8 batch2/3 fused MoE kernels (for MTP K=2/K=3 verify)
278 moe_expert_gate_up_shared_fp8_batch2: KernelHandle,
279 moe_expert_silu_down_shared_fp8_batch2: KernelHandle,
280 moe_weighted_sum_blend_fp8_batch2: KernelHandle,
281 moe_expert_gate_up_shared_fp8_batch3: KernelHandle,
282 moe_expert_silu_down_shared_fp8_batch3: KernelHandle,
283 moe_weighted_sum_blend_fp8_batch3: KernelHandle,
284 // THE routed-expert FP8 grouped GEMM for sorted MoE prefill: grid-compaction
285 // (persistent 96-CTA grid over a COMPACTED (expert, m_tile, n_tile) work-list
286 // built by `moe_build_tile_worklist`). Handle may be 0 on images that don't
287 // ship the kernel.
288 moe_fp8_grouped_gemm_k: KernelHandle,
289 // Builds the grouped-GEMM work-list (moe_build_tile_worklist, module "moe").
290 // Launched on the SAME stream as the grouped GEMM (read-after-write of
291 // total_tiles). Handle may be 0 on older images.
292 moe_build_tile_worklist_k: KernelHandle,
293 // W8A8 + FP32 epilogue MoE GEMM (vLLM-equivalent). Opt-in via
294 // ATLAS_FP8_W8A8=1. Requires per-token-quanted A_fp8 + a_scale.
295 moe_w8a8_grouped_gemm_k: KernelHandle,
296 // PM4-geometry W8A8 grouped GEMM over the compacted work-list (kernel
297 // `moe_w8a8_grouped_gemm_pm4`, same module). Bit-identical numerics to
298 // the dense kernel; preferred when present (gb10). Handle may be 0 on
299 // targets/images that don't ship it — dispatch falls back to the dense
300 // 3D-grid `moe_w8a8_grouped_gemm_k`.
301 moe_w8a8_grouped_gemm_pm4_k: KernelHandle,
302 per_token_group_quant_fp8_k: KernelHandle,
303 /// Fused SiLU·mul + per-token-group FP8 quant (bit-identical replacement
304 /// for the `silu_mul` → `per_token_group_quant_fp8` pair on the W8A8
305 /// prefill down-path). Optional: handle 0 (e.g. a model shadowing
306 /// moe_silu_mul.cu without this entry point) falls back to the pair.
307 silu_mul_quant_fp8_k: KernelHandle,
308 // Dense W8A8 (same kernel used by attention QKV/O proj) for shared-expert path.
309 fp8_gemm_t_blockscaled_k: KernelHandle,
310 // BF16 grouped GEMM — for FP8-source models dequanted to BF16 at load.
311 // Activates the high-precision MoE path that closes the per-layer
312 // 0.989 FP8 cosine ceiling. Handle may be 0 on images that don't ship
313 // the kernel; dispatch site is gated on Some(bf16_*_weight_ptrs).
314 moe_bf16_grouped_gemm_k: KernelHandle,
315 // Fused BF16 decode kernels (mirror moe_expert_*_shared_fp8 layout).
316 moe_expert_gate_up_shared_bf16_k: KernelHandle,
317 moe_expert_silu_down_shared_bf16_k: KernelHandle,
318 // Fused BF16 K=2 batch kernels for MTP verify (mirror the FP8 batch2 layout).
319 // Handle may be 0 on images that don't ship the kernel; the K=2 BF16
320 // dispatch site is gated on this being non-null and falls back to the
321 // per-token batched path otherwise.
322 moe_expert_gate_up_shared_bf16_batch2_k: KernelHandle,
323 moe_expert_silu_down_shared_bf16_batch2_k: KernelHandle,
324 w8a16_gemm_k: KernelHandle, // for shared expert FP8 prefill
325 w8a16_gemm_pipelined_k: KernelHandle, // ATLAS_W8A16_PIPELINED shared-expert variant
326 // Fused gate GEMV + topK softmax (saves 1 kernel launch per layer)
327 moe_gate_topk_fused_k: KernelHandle,
328 // FP8 expert pointer tables (None when experts are NVFP4)
329 fp8_gate_weight_ptrs: Option<Fp8ExpertPtrTable>,
330 fp8_up_weight_ptrs: Option<Fp8ExpertPtrTable>,
331 fp8_down_weight_ptrs: Option<Fp8ExpertPtrTable>,
332 // BF16 expert pointer tables — populated by the FP8-dequant-on-load
333 // path. When Some, the routed-expert dispatch in `forward_prefill_fp8`
334 // routes through `moe_bf16_grouped_gemm` instead of the FP8 grouped
335 // GEMM, eliminating the per-layer FP8 quantization ceiling.
336 bf16_gate_weight_ptrs: Option<DevicePtr>,
337 bf16_up_weight_ptrs: Option<DevicePtr>,
338 bf16_down_weight_ptrs: Option<DevicePtr>,
339 // Checkpoint-native BF16 shared expert. Independent of routed-expert
340 // precision so mixed NVFP4-routed/BF16-shared checkpoints stay faithful.
341 bf16_shared_expert: Option<Bf16SharedExpert>,
342 // FP8 shared expert weights (None when shared expert is NVFP4)
343 fp8_shared_expert: Option<Fp8ExpertWeight>,
344 /// FP4 down kernel handle (`moe_w4a16_down_t_k64_fp4`). `try_kernel` =>
345 /// `KernelHandle(0)` on images lacking it; the FP4-down dispatch checks this
346 /// handle != 0, `down_fp4` is set, and the shared `down_ptrs_t` table is present.
347 pub(crate) moe_down_t_k64_fp4: KernelHandle,
348 /// `moe_permute_tokens` gather kernel — only needed by the FP4 escape-hatch
349 /// (which consumes expert-sorted contiguous rows, unlike the FP8 fused
350 /// kernel that gathers via `sorted_token_ids` internally). `try_kernel`
351 /// (handle may be 0 on images lacking it). Now unused — the CUTLASS grouped
352 /// path fuses the gather into its A-pack — kept for potential reuse.
353 #[allow(dead_code)]
354 pub(crate) moe_permute_tokens_k: KernelHandle,
355 // Phase 2.7 Tier C — Frankenstein dispatch flag.
356 // True when this layer's index is in `config.dflash_capture_layers`.
357 // When the env var `ATLAS_FRANKENSTEIN_DECODE_VIA_PREFILL=1` is set,
358 // `forward()` (single-token decode) will route through `forward_prefill`
359 // (tensor-core grouped GEMM kernel) on this layer only, so the captured
360 // hidden states use a different numerical recipe than the scalar GEMV
361 // path. Used to test whether the kernel choice is the dominant cause
362 // of low DFlash drafter acceptance on FP4/FP8 targets.
363 pub is_dflash_capture_layer: bool,
364 /// Feature-1 (MoE expert + router LoRA): this layer's installed router +
365 /// routed-expert deltas + apply scratch. `None` = no adapter / feature off
366 /// → the base MoE path is byte-identical. Set by
367 /// [`MoeLayer::set_lora_weights`] (`moe/lora.rs`); applied in the prefill
368 /// forward. Decode/verify paths are a phase-1 followup (they REFUSE rather
369 /// than silently drop the delta — see `reject_decode_lora`).
370 pub(crate) lora: Option<MoeLoraWeights>,
371}
372
373impl MoeLayer {
374 /// ARM-2 Phase-K routed-expert kernel-handle select. Returns the E8M0
375 /// variant when the routed experts are native MXFP4 (`Mxfp4E8m0`), else the
376 /// NVFP4 handle. Panics if E8M0 is selected but the `_e8m0` kernel is
377 /// absent from this target (`try_kernel` gave 0) — that means a native
378 /// checkpoint reached a build that never compiled the variant, which must
379 /// be loud, not silent NVFP4-on-E8M0 garbage (the straggler net).
380 #[inline]
381 fn e8m0_or(
382 &self,
383 nvfp4: spark_runtime::gpu::KernelHandle,
384 e8m0: spark_runtime::gpu::KernelHandle,
385 site: &str,
386 ) -> spark_runtime::gpu::KernelHandle {
387 if self.experts_scale_kind == crate::weight_map::WeightQuantFormat::Mxfp4E8m0 {
388 assert!(
389 e8m0.0 != 0,
390 "ARM-2 Phase-K: routed experts tagged Mxfp4E8m0 at {site}, but the \
391 _e8m0 kernel handle is unresolved (not compiled into this target)."
392 );
393 e8m0
394 } else {
395 nvfp4
396 }
397 }
398}
399
400mod tables;
401// Re-exported so every `moe::ExpertPtrTable`-style path in the sub-files keeps
402// resolving; the split is invisible to them.
403pub(crate) use tables::{Bf16SharedExpert, ExpertPtrTable, Fp8ExpertPtrTable};
404
405// ── Sub-files (split for ≤500 LoC) ────────────────────────────────────────
406mod dump;
407mod forward;
408mod lora;
409mod lora_gateup;
410mod lora_router;
411pub(crate) use lora::MoeLoraWeights;
412mod forward_atomic_c4;
413mod forward_batched;
414mod forward_batched_gate;
415mod forward_ep;
416mod forward_k2;
417mod forward_k3;
418mod forward_phase;
419mod forward_prefill;
420mod forward_prefill_bf16;
421mod forward_prefill_fp8;
422mod forward_prefill_phase;
423mod forward_prefill_routed;
424mod forward_prefill_router;
425mod forward_token_major;
426mod helpers_a;
427mod helpers_b;
428mod helpers_c;
429mod init;
430#[cfg(test)]
431mod mod_tests;
432mod ptr_table_build;
433mod union_stats;
434pub(crate) use ptr_table_build::*;