1#![allow(unused_imports)]
6
7use anyhow::{Context, Result, bail, ensure};
8use spark_runtime::gpu::{DevicePtr, GpuBackend};
9use spark_runtime::weights::{WeightDtype, WeightStore};
10
11use super::*;
12
13pub struct NemotronSsmWeights {
17 pub in_proj: QuantizedWeight,
19 pub out_proj: QuantizedWeight,
21 pub conv1d_weight: DenseWeight,
23 pub conv1d_bias: DenseWeight,
25 pub a_log: DenseWeight,
27 pub d_param: DenseWeight,
29 pub dt_bias: DenseWeight,
31 pub ssm_norm: DenseWeight,
33}
34
35#[derive(Debug, Clone, Copy)]
37pub struct NemotronExpertWeight {
38 pub up_proj: QuantizedWeight,
39 pub down_proj: QuantizedWeight,
40}
41
42impl NemotronExpertWeight {
43 pub fn null() -> Self {
44 Self {
45 up_proj: QuantizedWeight::null(),
46 down_proj: QuantizedWeight::null(),
47 }
48 }
49}
50
51pub struct NemotronMoeWeights {
53 pub gate: DenseWeight,
55 pub e_score_correction_bias: DenseWeight,
57 pub experts: Vec<NemotronExpertWeight>,
59 pub shared_up: QuantizedWeight,
61 pub shared_up_fp8: Option<Fp8Weight>,
69 pub shared_down: QuantizedWeight,
71 pub shared_down_fp8: Option<Fp8Weight>,
77 pub fc1_latent_proj: Option<DenseWeight>,
80 pub fc2_latent_proj: Option<DenseWeight>,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum NemotronSsmQuant {
88 Nvfp4,
90 Fp8,
92 Bf16,
94}
95
96pub(crate) fn load_nemotron_ssm(
101 store: &WeightStore,
102 _layer: usize,
103 gpu: &dyn GpuBackend,
104 layer_prefix: &str,
105) -> Result<(NemotronSsmWeights, NemotronSsmQuant)> {
106 let p = format!("{layer_prefix}.mixer");
107 let has_scale = store.contains(&format!("{p}.in_proj.weight_scale"));
108 let has_scale2 = store.contains(&format!("{p}.in_proj.weight_scale_2"));
109 let quant = if has_scale && has_scale2 {
110 NemotronSsmQuant::Nvfp4
111 } else if has_scale {
112 NemotronSsmQuant::Fp8
113 } else {
114 NemotronSsmQuant::Bf16
115 };
116 let in_proj = if quant == NemotronSsmQuant::Nvfp4 {
117 quantized(store, &format!("{p}.in_proj"), gpu)?
118 } else {
119 QuantizedWeight::null()
120 };
121 let out_proj = if quant == NemotronSsmQuant::Nvfp4 {
122 quantized(store, &format!("{p}.out_proj"), gpu)?
123 } else {
124 QuantizedWeight::null()
125 };
126 Ok((
128 NemotronSsmWeights {
129 in_proj,
130 out_proj,
131 conv1d_weight: dense(store, &format!("{p}.conv1d.weight"))?,
132 conv1d_bias: dense_bf16_as_f32(store, &format!("{p}.conv1d.bias"), gpu)?,
133 a_log: dense_bf16_as_f32(store, &format!("{p}.A_log"), gpu)?,
134 d_param: dense_bf16_as_f32(store, &format!("{p}.D"), gpu)?,
135 dt_bias: dense_bf16_as_f32(store, &format!("{p}.dt_bias"), gpu)?,
136 ssm_norm: dense(store, &format!("{p}.norm.weight"))?,
137 },
138 quant,
139 ))
140}
141
142pub(crate) fn load_nemotron_attention(
148 store: &WeightStore,
149 layer: usize,
150 gpu: &dyn GpuBackend,
151 layer_prefix: &str,
152) -> Result<(
153 AttentionWeights,
154 Option<QuantizedWeight>,
155 Option<QuantizedWeight>,
156 Option<QuantizedWeight>,
157 DenseWeight,
158 bool,
159)> {
160 let p = format!("{layer_prefix}.mixer");
161 let is_nvfp4 = store.contains(&format!("{p}.q_proj.weight_scale_2"));
163 let is_fp8 = !is_nvfp4 && store.contains(&format!("{p}.q_proj.weight_scale"));
164 let dummy = DenseWeight {
165 weight: DevicePtr::NULL,
166 };
167
168 let (q_dense, k_dense, v_dense, o_dense, o_proj, q_nvfp4, k_nvfp4, v_nvfp4) = if is_nvfp4 {
169 let q = quantized(store, &format!("{p}.q_proj"), gpu)?;
170 let k = quantized(store, &format!("{p}.k_proj"), gpu)?;
171 let v = quantized(store, &format!("{p}.v_proj"), gpu)?;
172 let o = quantized(store, &format!("{p}.o_proj"), gpu)?;
173 (dummy, dummy, dummy, dummy, o, Some(q), Some(k), Some(v))
174 } else {
175 let load_proj = |name: &str| -> Result<DenseWeight> {
177 let prefix = format!("{p}.{name}");
178 if store.contains(&format!("{prefix}.weight_scale")) {
179 dequant_fp8_to_bf16(store, &prefix, gpu)
180 } else {
181 dense(store, &format!("{prefix}.weight"))
182 }
183 };
184 let q = load_proj("q_proj")?;
185 let k = load_proj("k_proj")?;
186 let v = load_proj("v_proj")?;
187 let o = load_proj("o_proj")?;
188 if is_fp8 && layer < 2 {
189 tracing::info!("L{layer} Attention: FP8 → BF16 (runtime quantization to NVFP4)");
190 }
191 (q, k, v, o, QuantizedWeight::null(), None, None, None)
192 };
193
194 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu);
195 let attn = AttentionWeights {
196 q_proj: q_dense,
197 k_proj: k_dense,
198 v_proj: v_dense,
199 o_proj,
200 q_norm: dummy,
201 k_norm: dummy,
202 q_norm_full: None,
203 k_norm_full: None,
204 k_scale,
205 v_scale,
206 };
207 Ok((attn, q_nvfp4, k_nvfp4, v_nvfp4, o_dense, is_nvfp4))
208}
209
210pub(crate) fn load_nemotron_moe(
215 store: &WeightStore,
216 layer: usize,
217 num_experts: usize,
218 gpu: &dyn GpuBackend,
219 config: &atlas_core::config::ModelConfig,
220 absmax_k: Option<spark_runtime::gpu::KernelHandle>,
221 quantize_k: Option<spark_runtime::gpu::KernelHandle>,
222 stream: u64,
223 scratch: Option<DevicePtr>,
224 layer_prefix: &str,
225) -> Result<NemotronMoeWeights> {
226 let p = format!("{layer_prefix}.mixer");
227 let gate_name = format!("{p}.gate.weight");
230 let gate_w = store.get(&gate_name)?;
231 let gate = if gate_w.dtype == WeightDtype::FP32 {
232 dense_f32_as_bf16(store, &gate_name, gpu)?
233 } else {
234 DenseWeight { weight: gate_w.ptr }
235 };
236 let e_score_correction_bias = dense(store, &format!("{p}.gate.e_score_correction_bias"))?;
238
239 let shared_up_prefix = format!("{p}.shared_experts.up_proj");
241 let shared_up_has_s2 = store.contains(&format!("{shared_up_prefix}.weight_scale_2"));
242 let shared_up_has_s = store.contains(&format!("{shared_up_prefix}.weight_scale"));
243 let native_fp8_mode =
252 std::env::var("ATLAS_NEMOTRON_NATIVE_FP8_SSM").unwrap_or_else(|_| "1".to_string());
253 let want_native_fp8 = matches!(native_fp8_mode.as_str(), "1" | "both" | "decode");
254 let shared_up_fp8 = if want_native_fp8 && !shared_up_has_s2 && shared_up_has_s {
255 match load_fp8_block_scaled_as_fp8weight(store, &shared_up_prefix, gpu) {
256 Ok(mut w) => {
257 let bytes = (w.n as usize) * (w.k as usize);
262 let owned = gpu.alloc(bytes)?;
263 gpu.copy_d2d(w.weight, owned, bytes)?;
264 w.weight = owned;
265 Some(w)
266 }
267 Err(e) => {
268 tracing::warn!("shared_up native FP8 unavailable ({e}) — using NVFP4 requant");
269 None
270 }
271 }
272 } else {
273 None
274 };
275 let shared_up = if shared_up_fp8.is_some() {
281 QuantizedWeight::null()
282 } else if shared_up_has_s2 {
283 quantized(store, &shared_up_prefix, gpu)?
284 } else {
285 let bf16 = if shared_up_has_s {
287 if let Some(s) = scratch {
288 dequant_fp8_to_bf16_into(store, &shared_up_prefix, gpu, s)?
289 } else {
290 dequant_fp8_to_bf16(store, &shared_up_prefix, gpu)?
291 }
292 } else {
293 dense(store, &format!("{shared_up_prefix}.weight"))?
294 };
295 quantize_to_nvfp4(
296 &bf16,
297 config.shared_expert_intermediate_size,
298 config.hidden_size,
299 gpu,
300 absmax_k.unwrap(),
301 quantize_k.unwrap(),
302 stream,
303 )?
304 };
305
306 let shared_down_prefix = format!("{p}.shared_experts.down_proj");
307 let shared_down_has_s2 = store.contains(&format!("{shared_down_prefix}.weight_scale_2"));
308 let shared_down_has_s = store.contains(&format!("{shared_down_prefix}.weight_scale"));
309 let shared_down_fp8 = if want_native_fp8 && !shared_down_has_s2 && shared_down_has_s {
310 match load_fp8_block_scaled_as_fp8weight(store, &shared_down_prefix, gpu) {
311 Ok(mut w) => {
312 let bytes = (w.n as usize) * (w.k as usize);
314 let owned = gpu.alloc(bytes)?;
315 gpu.copy_d2d(w.weight, owned, bytes)?;
316 w.weight = owned;
317 Some(w)
318 }
319 Err(e) => {
320 tracing::warn!("shared_down native FP8 unavailable ({e}) — using NVFP4 requant");
321 None
322 }
323 }
324 } else {
325 None
326 };
327 let shared_down = if shared_down_fp8.is_some() {
328 QuantizedWeight::null()
329 } else if shared_down_has_s2 {
330 quantized(store, &shared_down_prefix, gpu)?
331 } else {
332 let bf16 = if shared_down_has_s {
333 if let Some(s) = scratch {
334 dequant_fp8_to_bf16_into(store, &shared_down_prefix, gpu, s)?
335 } else {
336 dequant_fp8_to_bf16(store, &shared_down_prefix, gpu)?
337 }
338 } else {
339 dense(store, &format!("{shared_down_prefix}.weight"))?
340 };
341 quantize_to_nvfp4(
342 &bf16,
343 config.hidden_size,
344 config.shared_expert_intermediate_size,
345 gpu,
346 absmax_k.unwrap(),
347 quantize_k.unwrap(),
348 stream,
349 )?
350 };
351
352 let (fc1_latent_proj, fc2_latent_proj) = if config.moe_latent_size > 0 {
355 let fc1_prefix = format!("{p}.fc1_latent_proj");
356 let fc1 = if store.contains(&format!("{fc1_prefix}.weight_scale")) {
357 dequant_fp8_to_bf16(store, &fc1_prefix, gpu)?
358 } else {
359 dense(store, &format!("{fc1_prefix}.weight"))?
360 };
361 let fc2_prefix = format!("{p}.fc2_latent_proj");
362 let fc2 = if store.contains(&format!("{fc2_prefix}.weight_scale")) {
363 dequant_fp8_to_bf16(store, &fc2_prefix, gpu)?
364 } else {
365 dense(store, &format!("{fc2_prefix}.weight"))?
366 };
367 (Some(fc1), Some(fc2))
368 } else {
369 (None, None)
370 };
371
372 let moe_input = config.moe_input_size();
375 let moe_inter = config.moe_intermediate_size_for(layer);
376 let first_local = (0..num_experts).find(|e| config.is_local_expert(*e));
377 let experts_are_nvfp4 = first_local
378 .is_none_or(|e| store.contains(&format!("{p}.experts.{e}.up_proj.weight_scale_2")));
379 let experts_are_fp8 = !experts_are_nvfp4
380 && first_local
381 .is_some_and(|e| store.contains(&format!("{p}.experts.{e}.up_proj.weight_scale")));
382 if !experts_are_nvfp4 && layer < 2 {
383 tracing::info!(
384 "L{layer} MoE experts: {} → NVFP4 (runtime quantization, {} experts)",
385 if experts_are_fp8 { "FP8" } else { "BF16" },
386 num_experts,
387 );
388 }
389
390 let mut experts = Vec::with_capacity(num_experts);
391 for e in 0..num_experts {
392 if config.is_local_expert(e) {
393 let up_prefix = format!("{p}.experts.{e}.up_proj");
394 let down_prefix = format!("{p}.experts.{e}.down_proj");
395 let (up_proj, down_proj) = if experts_are_nvfp4 {
396 (
397 quantized(store, &up_prefix, gpu)?,
398 quantized(store, &down_prefix, gpu)?,
399 )
400 } else {
401 let up_bf16 = if experts_are_fp8 {
402 if let Some(s) = scratch {
403 dequant_fp8_to_bf16_into(store, &up_prefix, gpu, s)?
404 } else {
405 dequant_fp8_to_bf16(store, &up_prefix, gpu)?
406 }
407 } else {
408 dense(store, &format!("{up_prefix}.weight"))?
409 };
410 let up = quantize_to_nvfp4(
411 &up_bf16,
412 moe_inter,
413 moe_input,
414 gpu,
415 absmax_k.unwrap(),
416 quantize_k.unwrap(),
417 stream,
418 )?;
419 let down_bf16 = if experts_are_fp8 {
420 if let Some(s) = scratch {
421 dequant_fp8_to_bf16_into(store, &down_prefix, gpu, s)?
422 } else {
423 dequant_fp8_to_bf16(store, &down_prefix, gpu)?
424 }
425 } else {
426 dense(store, &format!("{down_prefix}.weight"))?
427 };
428 let down = quantize_to_nvfp4(
429 &down_bf16,
430 moe_input,
431 moe_inter,
432 gpu,
433 absmax_k.unwrap(),
434 quantize_k.unwrap(),
435 stream,
436 )?;
437 (up, down)
438 };
439 experts.push(NemotronExpertWeight { up_proj, down_proj });
440 } else {
441 experts.push(NemotronExpertWeight::null());
442 }
443 }
444
445 Ok(NemotronMoeWeights {
446 gate,
447 e_score_correction_bias,
448 experts,
449 shared_up,
450 shared_up_fp8,
451 shared_down_fp8,
452 shared_down,
453 fc1_latent_proj,
454 fc2_latent_proj,
455 })
456}