1use anyhow::Result;
22use metrale_cache::kv_cache::PagedKvCache;
23use metrale_config::ModelConfig;
24use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
25
26use metrale_model_layers::layer::{EmptyLayerState, ForwardContext, LayerState, TransformerLayer};
27use metrale_model_layers::layer::{
28 LayerAuxState, LayerCapabilities, LayerGraphHooks, LayerSplitPrefill, LayerWeightSetup,
29 LayerWriteOnAccept,
30};
31use metrale_model_layers::layers::ops;
32use metrale_model_layers::weight_map::{DenseWeight, NemotronMoeWeights, QuantizedWeight};
33
34struct ExpertPtrTable {
36 packed_ptrs: DevicePtr,
37 scale_ptrs: DevicePtr,
38 scale2_vals: DevicePtr,
39}
40
41pub struct NemotronMoeLayer {
43 weights: NemotronMoeWeights,
44 input_norm: DenseWeight,
45 moe_latent_size: usize,
47 moe_inter: usize,
50 top_k: usize,
53 rms_norm_residual_k: KernelHandle,
54 dense_gemv_k: KernelHandle,
55 topk_sigmoid_k: KernelHandle,
56 moe_expert_gemv_k: KernelHandle,
57 w4a16_gemv_k: KernelHandle,
58 w4a16_gemv_sw_k: KernelHandle,
61 w8a16_gemv_k: KernelHandle,
65 w8a16_gemm_k: KernelHandle,
67 w8a16_gemm_pipelined_k: KernelHandle,
68 relu2_down_shared_k: KernelHandle,
69 weighted_sum_scale_k: KernelHandle,
70 residual_add_k: KernelHandle,
71 dense_gemm_k: KernelHandle,
72 dense_gemm_pipelined_k: KernelHandle,
75 w4a16_gemm_k: KernelHandle,
76 topk_sigmoid_batched_k: KernelHandle,
77 moe_up_prefill_k: KernelHandle,
78 moe_relu2_down_prefill_k: KernelHandle,
79 moe_weighted_sum_prefill_k: KernelHandle,
80 moe_sort_k: KernelHandle,
81 moe_grouped_gemm_k: KernelHandle,
82 moe_relu2_elementwise_k: KernelHandle,
83 moe_grouped_gemm_relu2_k: KernelHandle,
84 moe_w4a4_grouped_k: KernelHandle,
85 moe_unpermute_reduce_k: KernelHandle,
86 moe_grouped_gemm_n128_k: KernelHandle,
87 up_ptrs: ExpertPtrTable,
88 down_ptrs: ExpertPtrTable,
89 up_ptrs_t: Option<ExpertPtrTable>,
91 down_ptrs_t: Option<ExpertPtrTable>,
92 shared_up_t: Option<QuantizedWeight>,
94 shared_down_t: Option<QuantizedWeight>,
95 shared_up_pd_fp8: Option<DevicePtr>,
98 shared_down_pd_fp8: Option<DevicePtr>,
99 fc1_pd_fp8: Option<DevicePtr>,
102 fc2_pd_fp8: Option<DevicePtr>,
103 w4a16_gemm_t_k: KernelHandle,
106 w4a16_gemm_t_m128_k: KernelHandle,
107 fp8_gemm_m128_k: KernelHandle,
108 w4a4_gemm_k: KernelHandle,
109 quantize_nvfp4_k: KernelHandle,
110}
111
112impl NemotronMoeLayer {
113 pub fn new(
114 weights: NemotronMoeWeights,
115 input_norm: DenseWeight,
116 config: &ModelConfig,
117 gpu: &dyn GpuBackend,
118 moe_inter: usize,
119 top_k: usize,
120 ) -> Result<Self> {
121 let up_ptrs = build_ptr_table(&weights.experts, |e| &e.up_proj, gpu)?;
122 let down_ptrs = build_ptr_table(&weights.experts, |e| &e.down_proj, gpu)?;
123 let moe_inter = if moe_inter > 0 {
124 moe_inter
125 } else {
126 config.moe_intermediate_size
127 };
128 let top_k = if top_k > 0 {
129 top_k
130 } else {
131 config.num_experts_per_tok
132 };
133 let num_experts = weights.experts.len();
137 anyhow::ensure!(
138 top_k > 0
139 && top_k <= num_experts
140 && top_k <= metrale_model_layers::layers::ops::MOE_TOPK_SIGMOID_MAX_TOP_K
141 && num_experts <= metrale_model_layers::layers::ops::MOE_TOPK_SIGMOID_MAX_EXPERTS,
142 "Nemotron MoE config invalid: top_k={} must be in 1..={} and within \
143 the routing kernels' bounds (top_k max {}, num_experts={} max {})",
144 top_k,
145 num_experts,
146 metrale_model_layers::layers::ops::MOE_TOPK_SIGMOID_MAX_TOP_K,
147 num_experts,
148 metrale_model_layers::layers::ops::MOE_TOPK_SIGMOID_MAX_EXPERTS,
149 );
150
151 Ok(Self {
152 weights,
153 input_norm,
154 moe_latent_size: config.moe_latent_size,
155 moe_inter,
156 top_k,
157 rms_norm_residual_k: gpu.kernel("norm", "rms_norm_residual")?,
158 dense_gemv_k: gpu.kernel("gemv", "dense_gemv_bf16")?,
159 topk_sigmoid_k: gpu.kernel("moe_topk_sig", "moe_topk_sigmoid")?,
160 moe_expert_gemv_k: gpu.kernel("moe_expert_gemv", "moe_expert_gemv")?,
161 w4a16_gemv_k: gpu.kernel("w4a16_gemv", "w4a16_gemv")?,
162 w4a16_gemv_sw_k: metrale_model_layers::layers::try_kernel(
163 gpu,
164 "w4a16_gemv",
165 "w4a16_gemv_sw",
166 ),
167 w8a16_gemv_k: metrale_model_layers::layers::try_kernel(gpu, "w8a16_gemv", "w8a16_gemv"),
168 w8a16_gemm_k: metrale_model_layers::layers::try_kernel(gpu, "w8a16_gemm", "w8a16_gemm"),
169 w8a16_gemm_pipelined_k: metrale_model_layers::layers::try_kernel(
170 gpu,
171 "w8a16_gemm_pipelined",
172 "w8a16_gemm_pipelined",
173 ),
174 relu2_down_shared_k: gpu.kernel("moe_relu2_fused", "moe_expert_relu2_down_shared")?,
175 weighted_sum_scale_k: gpu.kernel("relu2", "moe_weighted_sum_scale")?,
176 residual_add_k: gpu.kernel("residual_add", "bf16_residual_add")?,
177 dense_gemm_k: gpu.kernel("gemm", "dense_gemm_bf16")?,
178 dense_gemm_pipelined_k: metrale_model_layers::layers::try_kernel(
179 gpu,
180 "gemm",
181 "dense_gemm_bf16_pipelined",
182 ),
183 w4a16_gemm_k: gpu.kernel("w4a16", "w4a16_gemm")?,
184 topk_sigmoid_batched_k: metrale_model_layers::layers::try_kernel(
185 gpu,
186 "nemotron_moe_prefill",
187 "nemotron_moe_topk_sigmoid_batched",
188 ),
189 moe_up_prefill_k: metrale_model_layers::layers::try_kernel(
190 gpu,
191 "nemotron_moe_prefill",
192 "nemotron_moe_up_prefill",
193 ),
194 moe_relu2_down_prefill_k: metrale_model_layers::layers::try_kernel(
195 gpu,
196 "nemotron_moe_prefill",
197 "nemotron_moe_relu2_down_prefill",
198 ),
199 moe_weighted_sum_prefill_k: metrale_model_layers::layers::try_kernel(
200 gpu,
201 "nemotron_moe_prefill",
202 "nemotron_moe_weighted_sum_prefill",
203 ),
204 moe_sort_k: metrale_model_layers::layers::try_kernel(gpu, "moe", "moe_sort_by_expert"),
205 moe_grouped_gemm_k: metrale_model_layers::layers::try_kernel(
206 gpu,
207 "moe_w4a16",
208 "moe_w4a16_grouped_gemm_ptrtable",
209 ),
210 moe_relu2_elementwise_k: metrale_model_layers::layers::try_kernel(
211 gpu,
212 "relu2",
213 "relu_squared_inplace",
214 ),
215 moe_grouped_gemm_relu2_k: metrale_model_layers::layers::try_kernel(
216 gpu,
217 "moe_w4a16",
218 "moe_w4a16_grouped_gemm_ptrtable_relu2",
219 ),
220 moe_w4a4_grouped_k: metrale_model_layers::layers::try_kernel(
221 gpu,
222 "moe_w4a4",
223 "moe_w4a4_grouped_gemm_relu2",
224 ),
225 moe_unpermute_reduce_k: metrale_model_layers::layers::try_kernel(
226 gpu,
227 "moe",
228 "moe_unpermute_reduce_indexed",
229 ),
230 moe_grouped_gemm_n128_k: metrale_model_layers::layers::try_kernel(
231 gpu,
232 "moe_w4a16",
233 "moe_w4a16_grouped_gemm_ptrtable_t",
234 ),
235 up_ptrs,
236 down_ptrs,
237 up_ptrs_t: None,
238 down_ptrs_t: None,
239 shared_up_t: None,
240 shared_down_t: None,
241 shared_up_pd_fp8: None,
242 shared_down_pd_fp8: None,
243 fc1_pd_fp8: None,
244 fc2_pd_fp8: None,
245 w4a16_gemm_t_k: metrale_model_layers::layers::try_kernel(gpu, "w4a16", "w4a16_gemm_t"),
246 w4a16_gemm_t_m128_k: metrale_model_layers::layers::try_kernel(
247 gpu,
248 "w4a16",
249 "w4a16_gemm_t_m128",
250 ),
251 fp8_gemm_m128_k: metrale_model_layers::layers::try_kernel(
252 gpu,
253 "w4a16",
254 "fp8_gemm_t_m128_mfast",
255 ),
256 w4a4_gemm_k: metrale_model_layers::layers::try_kernel(gpu, "w4a4", "w4a4_gemm_mfast"),
257 quantize_nvfp4_k: metrale_model_layers::layers::try_kernel(
258 gpu,
259 "quantize_nvfp4",
260 "quantize_bf16_to_nvfp4",
261 ),
262 })
263 }
264}
265
266mod decode_helpers;
267mod prefill_fallback;
268mod prefill_shared_up;
269mod prefill_sorted;
270mod prefill_weights;
271mod ptr_tables;
272mod shared_e4m3;
273pub use shared_e4m3::{set_shared_expert_e4m3_from_cli, shared_expert_e4m3};
274
275use prefill_sorted::SortedPrefillCtx;
276use ptr_tables::{build_ptr_table, build_ptr_table_from_weights};
277
278impl TransformerLayer for NemotronMoeLayer {
279 fn decode(
280 &self,
281 hidden: DevicePtr,
282 residual: DevicePtr,
283 _state: &mut dyn LayerState,
284 _kv_cache: &mut PagedKvCache,
285 _seq_len: usize,
286 _block_table: &mut Vec<u32>,
287 _disk_block_ids: &mut Vec<u32>,
288 _disk_last_offloaded_per_layer: &mut Vec<u32>,
289 ctx: &ForwardContext,
290 stream: u64,
291 ) -> Result<()> {
292 self.decode_inner(hidden, residual, ctx, stream)
293 }
294
295 #[allow(clippy::overly_complex_bool_expr)]
300 fn prefill(
301 &self,
302 hidden: DevicePtr,
303 residual: DevicePtr,
304 num_tokens: usize,
305 _state: &mut dyn LayerState,
306 _kv_cache: &mut PagedKvCache,
307 _seq_len_start: usize,
308 _block_table: &mut Vec<u32>,
309 _disk_block_ids: &mut Vec<u32>,
310 _disk_last_offloaded_per_layer: &mut Vec<u32>,
311 _kv_write_start: usize,
312 ctx: &ForwardContext,
313 stream: u64,
314 ) -> Result<()> {
315 let h = ctx.config.hidden_size;
316 let inter = self.moe_inter as u32;
317 let shared_inter = ctx.config.shared_expert_intermediate_size as u32;
318 let num_experts = ctx.config.num_experts as u32;
319 let top_k = self.top_k as u32;
320 let eps = ctx.config.rms_norm_eps as f32;
321 let scale = ctx.config.routed_scaling_factor as f32;
322 let n = num_tokens as u32;
323
324 let normed = ctx.buffers.norm_output();
325 ops::rms_norm_residual(
326 ctx.gpu,
327 self.rms_norm_residual_k,
328 hidden,
329 &self.input_norm,
330 normed,
331 residual,
332 n,
333 h as u32,
334 eps,
335 stream,
336 )?;
337
338 let gate_logits = ctx.buffers.gate_logits();
339 self.dense_gemm_prefill(
340 ctx.gpu,
341 normed,
342 &self.weights.gate,
343 gate_logits,
344 n,
345 num_experts,
346 h as u32,
347 stream,
348 )?;
349
350 let has_batched = self.topk_sigmoid_batched_k.0 != 0
351 && self.moe_up_prefill_k.0 != 0
352 && self.moe_relu2_down_prefill_k.0 != 0
353 && self.moe_weighted_sum_prefill_k.0 != 0;
354
355 let shared_up_out_base = ctx.buffers.ssm_qkvz();
356 let use_batched_moe = has_batched && num_tokens > 1;
357 self.prefill_shared_up(normed, shared_up_out_base, n, h, shared_inter, ctx, stream)?;
360
361 let latent = self.moe_latent_size as u32;
364 let latent_base = if latent > 0 {
365 let latent_buf = ctx.buffers.attn_output();
366 if let Some(w_fp8) = self.fc1_pd_fp8 {
367 ops::fp8_gemm_m128_mfast(
368 ctx.gpu,
369 self.fp8_gemm_m128_k,
370 normed,
371 w_fp8,
372 latent_buf,
373 n,
374 latent,
375 h as u32,
376 stream,
377 )?;
378 } else {
379 let fc1 = self.weights.fc1_latent_proj.as_ref().unwrap();
380 self.dense_gemm_prefill(
381 ctx.gpu, normed, fc1, latent_buf, n, latent, h as u32, stream,
382 )?;
383 }
384 Some(latent_buf)
385 } else {
386 None
387 };
388
389 let scratch = ctx.buffers.scratch();
390 let indices_dev = scratch;
391 let weights_dev = scratch.offset(n as usize * top_k as usize * 4);
392
393 let use_sorted = use_batched_moe
397 && self.moe_sort_k.0 != 0
398 && self.moe_grouped_gemm_k.0 != 0
399 && self.moe_unpermute_reduce_k.0 != 0;
400
401 let p = SortedPrefillCtx {
402 n,
403 num_tokens,
404 h,
405 inter,
406 shared_inter,
407 num_experts,
408 top_k,
409 scale,
410 latent,
411 gate_logits,
412 indices_dev,
413 weights_dev,
414 normed,
415 hidden,
416 latent_base,
417 shared_up_out_base,
418 };
419 if use_sorted {
420 self.prefill_sorted_path(&p, ctx, stream)?;
421 } else {
422 self.prefill_fallback_path(&p, ctx, stream)?;
423 }
424
425 Ok(())
426 }
427
428 fn alloc_state(&self, _gpu: &dyn GpuBackend) -> Result<Box<dyn LayerState>> {
429 Ok(Box::new(EmptyLayerState))
430 }
431}
432
433impl LayerCapabilities for NemotronMoeLayer {}
434impl LayerWeightSetup for NemotronMoeLayer {}
435impl LayerWriteOnAccept for NemotronMoeLayer {}
436impl LayerGraphHooks for NemotronMoeLayer {}
437impl LayerAuxState for NemotronMoeLayer {}
438impl LayerSplitPrefill for NemotronMoeLayer {}
439impl metrale_model_layers::circuit_exec::CircuitBindings for NemotronMoeLayer {}