metrale_model_layers/weight_map/
nvfp4_detect.rs1#![allow(unused_imports)]
11
12use anyhow::{Context, Result, bail, ensure};
13use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
14use metrale_model_weights::weights::{WeightDtype, WeightStore};
15
16use super::*;
17
18pub fn config_declared_variant(config: &metrale_config::ModelConfig) -> Option<Nvfp4Variant> {
24 let qc = config.quantization_config.as_ref()?;
25 match qc.quant_method.as_str() {
26 "modelopt" if qc.quant_algo.eq_ignore_ascii_case("NVFP4") => Some(Nvfp4Variant::Standard),
27 "modelopt" if qc.quant_algo.eq_ignore_ascii_case("FP8") => Some(Nvfp4Variant::Fp8Dequanted),
28 "compressed-tensors" => {
29 let fmt = qc.format.to_ascii_lowercase();
32 if fmt.contains("fp8") || fmt.contains("float-quant") {
33 Some(Nvfp4Variant::Fp8Dequanted)
34 } else {
35 Some(Nvfp4Variant::CompressedTensors)
36 }
37 }
38 "fp8" => Some(Nvfp4Variant::Fp8Dequanted),
39 _ => None,
40 }
41}
42
43pub fn detect_nvfp4_variant(
53 store: &WeightStore,
54 config: &metrale_config::ModelConfig,
55) -> Nvfp4Variant {
56 if let Some(declared) = config_declared_variant(config) {
57 return declared;
58 }
59
60 let lp = config.layer_prefix(0);
61
62 let local_expert = config.local_expert_range().0;
63 let moe_sehyo_key = format!("{lp}.mlp.experts.{local_expert}.gate_proj.weight_packed");
64 if store.contains(&moe_sehyo_key) {
65 return Nvfp4Variant::CompressedTensors;
66 }
67
68 let dense_sehyo_key = format!("{lp}.mlp.gate_proj.weight_packed");
69 if store.contains(&dense_sehyo_key) {
70 return Nvfp4Variant::CompressedTensors;
71 }
72
73 let mistral_key = format!("layers.0.experts.{local_expert}.w1.weight_packed");
74 if store.contains(&mistral_key) {
75 return Nvfp4Variant::CompressedTensors;
76 }
77
78 if store.names().any(|k| k.ends_with(".weight_packed")) {
79 return Nvfp4Variant::CompressedTensors;
80 }
81
82 const ALT_LAYER0_PREFIX: &str = "model.language_model.layers.0";
85 let prefixes_to_check = [lp.clone(), ALT_LAYER0_PREFIX.to_string()];
86 for pfx in &prefixes_to_check {
87 let fp8_key = format!("{pfx}.mlp.experts.{local_expert}.gate_proj.weight_scale_inv");
88 if store.contains(&fp8_key) {
89 return Nvfp4Variant::Fp8Dequanted;
90 }
91 let fp8_dense_key = format!("{pfx}.mlp.gate_proj.weight_scale_inv");
92 if store.contains(&fp8_dense_key) {
93 return Nvfp4Variant::Fp8Dequanted;
94 }
95 let fp8_attn_key = format!("{pfx}.self_attn.q_proj.weight_scale_inv");
96 if store.contains(&fp8_attn_key) {
97 return Nvfp4Variant::Fp8Dequanted;
98 }
99 for key in [
103 format!("{pfx}.mlp.experts.{local_expert}.gate_proj.weight"),
104 format!("{pfx}.mlp.gate_proj.weight"),
105 format!("{pfx}.self_attn.q_proj.weight"),
106 ] {
107 if store
108 .get(&key)
109 .map(|w| w.dtype == WeightDtype::FP8E4M3)
110 .unwrap_or(false)
111 {
112 return Nvfp4Variant::Fp8Dequanted;
113 }
114 }
115 }
116 if store.names().any(|k| k.ends_with(".weight_scale_inv")) {
117 return Nvfp4Variant::Fp8Dequanted;
118 }
119
120 let any_standard_scale = store.names().any(|k| k.ends_with(".weight_scale"));
121 if !any_standard_scale {
122 tracing::warn!(
123 "No NVFP4/FP8 quantization metadata found (no .weight_packed / .weight_scale_inv / .weight_scale). \
124 Falling back to runtime BF16→NVFP4 quantization. Quality will be inferior to a calibrated NVFP4 release."
125 );
126 return Nvfp4Variant::Bf16Raw;
127 }
128
129 let has_mlp_scale = {
133 let k_dense = format!("{lp}.mlp.gate_proj.weight_scale");
134 let k_moe = format!("{lp}.mlp.experts.{local_expert}.gate_proj.weight_scale");
135 store.contains(&k_dense) || store.contains(&k_moe)
136 };
137 if !has_mlp_scale {
138 tracing::warn!(
139 "Partial NVFP4 metadata: `.weight_scale` exists for some tensors (e.g. KV scales) \
140 but not for MLP/MoE projections. Falling back to runtime BF16→NVFP4 quantization. \
141 For best quality use a fully-quantized NVFP4 release (e.g. Sehyo/*-NVFP4)."
142 );
143 return Nvfp4Variant::Bf16Raw;
144 }
145
146 Nvfp4Variant::Standard
147}
148
149pub fn quantized_auto(
153 store: &WeightStore,
154 prefix: &str,
155 gpu: &dyn GpuBackend,
156 variant: Nvfp4Variant,
157) -> Result<QuantizedWeight> {
158 match variant {
159 Nvfp4Variant::Standard => quantized(store, prefix, gpu),
160 Nvfp4Variant::CompressedTensors => quantized_v2(store, prefix, gpu),
161 Nvfp4Variant::Fp8Dequanted => {
162 unreachable!("Fp8Dequanted must use quantized_auto_fp8 with quant context")
163 }
164 Nvfp4Variant::Bf16Raw => {
165 unreachable!("Bf16Raw must use quantized_any with quant context")
166 }
167 }
168}
169
170#[derive(Clone, Copy)]
172pub struct QuantizeCtx {
173 pub absmax_k: metrale_gpu_runtime::gpu::KernelHandle,
174 pub quantize_k: metrale_gpu_runtime::gpu::KernelHandle,
175 pub stream: u64,
176}
177
178pub fn quantized_any(
186 store: &WeightStore,
187 prefix: &str,
188 n: usize,
189 k: usize,
190 gpu: &dyn GpuBackend,
191 variant: Nvfp4Variant,
192 qctx: QuantizeCtx,
193) -> Result<QuantizedWeight> {
194 let _t_detect = std::time::Instant::now();
195 let has_packed = store.contains(&format!("{prefix}.weight_packed"));
196 let has_scale = store.contains(&format!("{prefix}.weight_scale"));
197 let has_scale_inv = store.contains(&format!("{prefix}.weight_scale_inv"));
198 let has_only_dense =
199 !has_packed && !has_scale && !has_scale_inv && store.contains(&format!("{prefix}.weight"));
200
201 let has_fp8_dense = !has_packed
205 && !store.contains(&format!("{prefix}.weight_global_scale"))
206 && !store.contains(&format!("{prefix}.weight_scale_2"))
207 && (has_scale || has_scale_inv)
208 && store
209 .get(&format!("{prefix}.weight"))
210 .map(|w| w.dtype == WeightDtype::FP8E4M3)
211 .unwrap_or(false);
212
213 let effective_variant = if has_only_dense && !matches!(variant, Nvfp4Variant::Bf16Raw) {
214 tracing::debug!("{prefix}: no quantization metadata; falling back to runtime BF16→NVFP4");
215 Nvfp4Variant::Bf16Raw
216 } else if has_fp8_dense
217 && !matches!(variant, Nvfp4Variant::Fp8Dequanted | Nvfp4Variant::Bf16Raw)
218 {
219 tracing::debug!("{prefix}: FP8 key in an NVFP4 checkpoint; dequant FP8→BF16→NVFP4");
228 Nvfp4Variant::Fp8Dequanted
229 } else {
230 variant
231 };
232
233 let _t_detect_ns = _t_detect.elapsed().as_nanos() as u64;
234 match effective_variant {
235 Nvfp4Variant::Standard => quantized(store, prefix, gpu),
236 Nvfp4Variant::CompressedTensors => quantized_v2(store, prefix, gpu),
237 Nvfp4Variant::Fp8Dequanted => quantized_from_fp8(
238 store,
239 prefix,
240 n,
241 k,
242 gpu,
243 qctx.absmax_k,
244 qctx.quantize_k,
245 qctx.stream,
246 ),
247 Nvfp4Variant::Bf16Raw => {
248 use std::sync::atomic::{AtomicU64, Ordering};
249 static T_DETECT: AtomicU64 = AtomicU64::new(0);
250 static T_GET: AtomicU64 = AtomicU64::new(0);
251 static T_QUANT: AtomicU64 = AtomicU64::new(0);
252 static T_FREE: AtomicU64 = AtomicU64::new(0);
253 static N: AtomicU64 = AtomicU64::new(0);
254 T_DETECT.fetch_add(_t_detect_ns, Ordering::Relaxed);
255 let _t = std::time::Instant::now();
256 let w = store.get(&format!("{prefix}.weight"))?;
257 let bf16 = DenseWeight { weight: w.ptr };
258 T_GET.fetch_add(_t.elapsed().as_nanos() as u64, Ordering::Relaxed);
259 let _t = std::time::Instant::now();
260 let q = quantize_to_nvfp4(
261 &bf16,
262 n,
263 k,
264 gpu,
265 qctx.absmax_k,
266 qctx.quantize_k,
267 qctx.stream,
268 )?;
269 T_QUANT.fetch_add(_t.elapsed().as_nanos() as u64, Ordering::Relaxed);
270 let _t = std::time::Instant::now();
271 gpu.free(w.ptr)?;
275 T_FREE.fetch_add(_t.elapsed().as_nanos() as u64, Ordering::Relaxed);
276 let c = N.fetch_add(1, Ordering::Relaxed) + 1;
277 if c.is_multiple_of(512) {
278 let ms = |a: &AtomicU64| a.load(Ordering::Relaxed) as f64 / 1.0e6;
279 tracing::info!(
280 "quantized_any(Bf16Raw) PROFILE after {c} calls (ms total): detect={:.1} \
281 store_get={:.1} quantize={:.1} free={:.1} | sum={:.1} per_call={:.3}ms",
282 ms(&T_DETECT),
283 ms(&T_GET),
284 ms(&T_QUANT),
285 ms(&T_FREE),
286 ms(&T_DETECT) + ms(&T_GET) + ms(&T_QUANT) + ms(&T_FREE),
287 (ms(&T_DETECT) + ms(&T_GET) + ms(&T_QUANT) + ms(&T_FREE)) / c as f64,
288 );
289 }
290 Ok(q)
291 }
292 }
293}
294
295pub fn quantized_from_fp8(
298 store: &WeightStore,
299 prefix: &str,
300 n: usize,
301 k: usize,
302 gpu: &dyn GpuBackend,
303 absmax_k: metrale_gpu_runtime::gpu::KernelHandle,
304 quantize_k: metrale_gpu_runtime::gpu::KernelHandle,
305 stream: u64,
306) -> Result<QuantizedWeight> {
307 let bf16 = dequant_fp8_blockscaled_to_bf16(store, prefix, gpu)?;
308 let result = quantize_to_nvfp4(&bf16, n, k, gpu, absmax_k, quantize_k, stream)?;
309 gpu.free(bf16.weight)?;
310 Ok(result)
311}
312
313#[allow(dead_code)]
315pub(crate) fn dense_from_fp8(
316 store: &WeightStore,
317 prefix: &str,
318 gpu: &dyn GpuBackend,
319) -> Result<DenseWeight> {
320 dequant_fp8_blockscaled_to_bf16(store, prefix, gpu)
321}
322
323#[allow(dead_code)]
326pub(crate) fn load_attention_qwen35(
327 store: &WeightStore,
328 layer_prefix: &str,
329 gpu: &dyn GpuBackend,
330) -> Result<AttentionWeights> {
331 let p = format!("{layer_prefix}.self_attn");
332 let (k_scale, v_scale) = load_kv_scales(store, &p, gpu)?;
333 Ok(AttentionWeights {
334 q_proj: dense(store, &format!("{p}.q_proj.weight_packed"))?,
335 k_proj: dense(store, &format!("{p}.k_proj.weight_packed"))?,
336 v_proj: dense(store, &format!("{p}.v_proj.weight_packed"))?,
337 o_proj: quantized_v2(store, &format!("{p}.o_proj"), gpu)?,
338 q_norm: dense(store, &format!("{p}.q_norm.weight"))?,
339 k_norm: dense(store, &format!("{p}.k_norm.weight"))?,
340 q_norm_full: None,
341 k_norm_full: None,
342 k_scale,
343 v_scale,
344 })
345}
346
347#[allow(dead_code)]
349pub(crate) fn load_quantized_proj_qwen35(
350 store: &WeightStore,
351 prefix: &str,
352 gpu: &dyn GpuBackend,
353) -> Result<QuantizedWeight> {
354 quantized_v2(store, prefix, gpu)
355}
356
357#[cfg(test)]
358mod ep_detection_tests {
359 use super::*;
360 use metrale_config::ModelConfig;
361 use metrale_model_weights::weights::WeightStore;
362
363 fn store_with(names: &[String]) -> WeightStore {
365 use std::collections::HashMap;
366 let map: HashMap<String, metrale_model_weights::weights::WeightTensor> = names
367 .iter()
368 .map(|n| {
369 (
370 n.clone(),
371 metrale_model_weights::weights::WeightTensor {
372 ptr: metrale_gpu_runtime::gpu::DevicePtr::NULL,
373 shape: vec![1],
374 dtype: metrale_model_weights::weights::WeightDtype::FP8E4M3,
375 },
376 )
377 })
378 .collect();
379 WeightStore::from_map(map)
380 }
381
382 #[test]
383 fn alternate_layer0_fp8_dtype_is_detected_on_every_ep_rank() {
384 let mut cfg = ModelConfig::qwen3_next_80b_nvfp4();
385 cfg.quantization_config = None;
386 let store =
387 store_with(&["model.language_model.layers.0.self_attn.q_proj.weight".to_string()]);
388
389 cfg.ep_world_size = 2;
390 for ep_rank in 0..2 {
391 cfg.ep_rank = ep_rank;
392 assert_eq!(
393 detect_nvfp4_variant(&store, &cfg),
394 Nvfp4Variant::Fp8Dequanted,
395 "EP rank {ep_rank} must inspect the same layer-zero checkpoint marker"
396 );
397 }
398 }
399
400 #[test]
401 fn scale_inv_suffix_fallback_detects_an_unexpected_prefix() {
402 let mut cfg = ModelConfig::qwen3_next_80b_nvfp4();
403 cfg.quantization_config = None;
404 let store =
405 store_with(&["third_party.transformer.blocks.17.attn.q.weight_scale_inv".to_string()]);
406 assert_eq!(
407 detect_nvfp4_variant(&store, &cfg),
408 Nvfp4Variant::Fp8Dequanted
409 );
410 }
411}