metrale_model_layers/weight_map/
quant_helpers.rs1#![allow(unused_imports)]
9
10use anyhow::{Context, Result, bail, ensure};
11use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
12use metrale_model_weights::weights::{WeightDtype, WeightStore};
13
14use super::*;
15
16pub(super) fn dequant_fp8_bytes_to_bf16(fp8_buf: &[u8], scale: f32) -> Vec<u8> {
19 fp8_buf
20 .iter()
21 .flat_map(|&byte| {
22 let val = fp8_e4m3_to_f32(byte) * scale;
23 f32_to_bf16(val).to_le_bytes()
24 })
25 .collect()
26}
27
28pub fn dequant_fp8_blockscaled_to_bf16(
38 store: &WeightStore,
39 prefix: &str,
40 gpu: &dyn GpuBackend,
41) -> Result<DenseWeight> {
42 use metrale_gpu_runtime::kernel_args::{KernelLaunch, div_ceil};
43
44 let w = store.get(&format!("{prefix}.weight"))?;
45 ensure!(
46 w.dtype == WeightDtype::FP8E4M3,
47 "Expected FP8E4M3 for {prefix}.weight, got {:?}",
48 w.dtype,
49 );
50 ensure!(
51 w.shape.len() == 2,
52 "Expected 2D weight for {prefix}, got {:?}",
53 w.shape
54 );
55 let n = w.shape[0];
56 let k = w.shape[1];
57 let total = n * k;
58 let byte_size = w.byte_size();
59 ensure!(
60 total == byte_size,
61 "FP8 size mismatch: total={total} byte_size={byte_size}"
62 );
63
64 let gpu_scale = store
68 .get(&format!("{prefix}.weight_scale_inv"))
69 .ok()
70 .or_else(|| {
71 store
72 .get(&format!("{prefix}.weight_scale"))
73 .ok()
74 .filter(|s| s.shape.len() == 2)
75 });
76 if let Some(s) = gpu_scale {
77 ensure!(
78 s.dtype == WeightDtype::BF16 || s.dtype == WeightDtype::FP32,
79 "Expected BF16 or FP32 for {prefix} GPU block scale, got {:?}",
80 s.dtype,
81 );
82 let sn = s.shape[0];
83 let sk = s.shape[1];
84 let block_n = (n / sn) as u32;
85 let block_k = (k / sk) as u32;
86 let scale_is_f32 = s.dtype == WeightDtype::FP32;
87
88 let out = gpu.alloc(total * 2)?;
89
90 let stream = gpu.default_stream();
91 let kernel = gpu.kernel(
92 "dequant_fp8_blockscaled_bf16",
93 "dequant_fp8_blockscaled_bf16",
94 )?;
95 KernelLaunch::new(gpu, kernel)
96 .grid([div_ceil(k as u32, 64), div_ceil(n as u32, 4), 1])
97 .block([64, 4, 1])
98 .arg_ptr(w.ptr)
99 .arg_ptr(s.ptr)
100 .arg_ptr(out)
101 .arg_u32(n as u32)
102 .arg_u32(k as u32)
103 .arg_u32(block_n)
104 .arg_u32(block_k)
105 .arg_u32(sk as u32)
106 .arg_u32(scale_is_f32 as u32)
107 .launch(stream)?;
108 tracing::debug!(
112 "GPU-dequanted FP8 blockscaled {prefix}: [{n}, {k}] block=[{block_n}, {block_k}] → BF16",
113 );
114 return Ok(DenseWeight { weight: out });
115 }
116
117 enum ScaleDtype {
120 Fp32,
121 Bf16,
122 E8M0,
123 }
124 let mut fp8_buf = vec![0u8; total];
125 gpu.copy_d2h(w.ptr, &mut fp8_buf).with_context(|| {
126 format!(
127 "D2H failed for {prefix}.weight: ptr={}, size={total}",
128 w.ptr.0
129 )
130 })?;
131
132 let (scale_buf, _sn, sk, block_n, block_k, scale_dtype) = if let Ok(s) =
133 store.get(&format!("{prefix}.weight_scale"))
134 {
135 ensure!(
136 s.dtype == WeightDtype::BF16 || s.dtype == WeightDtype::FP32,
137 "Expected BF16 or FP32 2-D block scale for {prefix}.weight_scale, got {:?}",
138 s.dtype,
139 );
140 let rank = s.shape.len();
141 let (sn, sk) = if rank == 2 {
142 (s.shape[0], s.shape[1])
143 } else if rank == 1 {
144 (s.shape[0], 1)
145 } else {
146 bail!(
147 "Expected 1-D or 2-D scale for {prefix}.weight_scale, got shape {:?}",
148 s.shape
149 );
150 };
151 let block_n = if sn > 1 { n / sn } else { n };
152 let block_k = if sk > 1 { k / sk } else { k };
153 let scale_is_f32 = s.dtype == WeightDtype::FP32;
154 let scale_bytes_per = if scale_is_f32 { 4 } else { 2 };
155 let mut buf = vec![0u8; sn * sk * scale_bytes_per];
156 gpu.copy_d2h(s.ptr, &mut buf).with_context(|| {
157 format!(
158 "D2H failed for {prefix}.weight_scale: ptr={}, size={}",
159 s.ptr.0,
160 sn * sk * scale_bytes_per
161 )
162 })?;
163 let sd = if scale_is_f32 {
164 ScaleDtype::Fp32
165 } else {
166 ScaleDtype::Bf16
167 };
168 (buf, sn, sk, block_n, block_k, sd)
169 } else if let Ok(s) = store.get(&format!("{prefix}.scale")) {
170 let rank = s.shape.len();
171 let (sn, sk) = if rank == 2 {
172 (s.shape[0], s.shape[1])
173 } else if rank == 1 {
174 (s.shape[0], 1)
175 } else {
176 bail!(
177 "Expected 1-D or 2-D scale for {prefix}.scale, got shape {:?}",
178 s.shape
179 );
180 };
181 let block_n = if sn > 1 { n / sn } else { n };
182 let block_k = if sk > 1 { k / sk } else { k };
183 let sd = match s.dtype {
184 WeightDtype::FP32 => ScaleDtype::Fp32,
185 WeightDtype::BF16 => ScaleDtype::Bf16,
186 WeightDtype::FP8E8M0 => ScaleDtype::E8M0,
187 other => bail!(
188 "Expected FP32, BF16, or FP8E8M0 for {prefix}.scale, got {:?}",
189 other,
190 ),
191 };
192 let scale_bytes_per = s.dtype.byte_size();
193 let mut buf = vec![0u8; sn * sk * scale_bytes_per];
194 gpu.copy_d2h(s.ptr, &mut buf).with_context(|| {
195 format!(
196 "D2H failed for {prefix}.scale: ptr={}, size={}",
197 s.ptr.0,
198 sn * sk * scale_bytes_per
199 )
200 })?;
201 (buf, sn, sk, block_n, block_k, sd)
202 } else {
203 bail!(
204 "FP8 tensor {prefix}: no .weight_scale_inv, .weight_scale, or .scale found for dequant"
205 );
206 };
207
208 let mut bf16_out = vec![0u8; total * 2];
209 for row in 0..n {
210 let scale_row = row / block_n;
211 for col in 0..k {
212 let scale_col = col / block_k;
213 let scale_idx = scale_row * sk + scale_col;
214 let scale_f32 = match scale_dtype {
215 ScaleDtype::E8M0 => fp8_e8m0_to_f32(scale_buf[scale_idx]),
216 ScaleDtype::Fp32 => {
217 let b = [
218 scale_buf[scale_idx * 4],
219 scale_buf[scale_idx * 4 + 1],
220 scale_buf[scale_idx * 4 + 2],
221 scale_buf[scale_idx * 4 + 3],
222 ];
223 f32::from_le_bytes(b)
224 }
225 ScaleDtype::Bf16 => {
226 let b = [scale_buf[scale_idx * 2], scale_buf[scale_idx * 2 + 1]];
227 bf16_bytes_to_f32(b)
228 }
229 };
230
231 let fp8_byte = fp8_buf[row * k + col];
232 let val = fp8_e4m3_to_f32(fp8_byte) * scale_f32;
233 let bf16_val = f32_to_bf16(val);
234 let out_idx = (row * k + col) * 2;
235 let [lo, hi] = bf16_val.to_le_bytes();
236 bf16_out[out_idx] = lo;
237 bf16_out[out_idx + 1] = hi;
238 }
239 }
240
241 let out = gpu.alloc(bf16_out.len())?;
242 gpu.copy_h2d(&bf16_out, out)?;
243 tracing::debug!(
244 "CPU-dequanted FP8 blockscaled {prefix}: [{n}, {k}] block=[{block_n}, {block_k}] → BF16",
245 );
246 Ok(DenseWeight { weight: out })
247}
248
249pub(super) fn bf16_bytes_to_f32(bytes: [u8; 2]) -> f32 {
251 let bits = u16::from_le_bytes(bytes);
252 f32::from_bits((bits as u32) << 16)
253}
254
255pub fn dense_auto(store: &WeightStore, name: &str, gpu: &dyn GpuBackend) -> Result<DenseWeight> {
265 let w = store.get(name)?;
266 match w.dtype {
267 WeightDtype::BF16 => Ok(DenseWeight { weight: w.ptr }),
268 WeightDtype::FP32 => dense_f32_safe(store, name, gpu),
269 WeightDtype::FP8E4M3 => {
270 let prefix = name
271 .strip_suffix(".weight")
272 .ok_or_else(|| anyhow::anyhow!("FP8 tensor {name} doesn't end with .weight"))?;
273 let has_blockscale = store.contains(&format!("{prefix}.weight_scale_inv"));
276 let has_per_row_scale = store
277 .get(&format!("{prefix}.weight_scale"))
278 .map(|s| s.num_elements() > 1)
279 .unwrap_or(false);
280 let has_v4_scale = store.contains(&format!("{prefix}.scale"));
281 if has_blockscale || has_per_row_scale || has_v4_scale {
282 dequant_fp8_blockscaled_to_bf16(store, prefix, gpu)
283 } else {
284 dequant_fp8_to_bf16(store, prefix, gpu)
285 }
286 }
287 WeightDtype::UInt8 => {
288 let prefix = name
289 .strip_suffix(".weight")
290 .ok_or_else(|| anyhow::anyhow!("NVFP4 tensor {name} doesn't end with .weight"))?;
291 if w.shape.len() != 2 {
292 anyhow::bail!(
293 "dense_auto: packed NVFP4 {name} must be 2-D, got {:?}",
294 w.shape
295 );
296 }
297 crate::weight_map::dequant_nvfp4_to_bf16(store, prefix, w.shape[0], w.shape[1] * 2, gpu)
298 }
299 other => anyhow::bail!("dense_auto: unsupported dtype {:?} for {name}", other),
300 }
301}
302
303pub fn quantized_v2(
308 store: &WeightStore,
309 prefix: &str,
310 gpu: &dyn GpuBackend,
311) -> Result<QuantizedWeight> {
312 let raw_global_scale = scalar_f32(store, &format!("{prefix}.weight_global_scale"), gpu)?;
313 if !raw_global_scale.is_finite() || raw_global_scale.abs() < f32::MIN_POSITIVE {
316 anyhow::bail!(
317 "{prefix}.weight_global_scale is non-finite or zero ({raw_global_scale}); \
318 checkpoint likely corrupted"
319 );
320 }
321 Ok(QuantizedWeight {
322 weight: ptr(store, &format!("{prefix}.weight_packed"))?,
323 weight_scale: ptr(store, &format!("{prefix}.weight_scale"))?,
324 weight_scale_2: 1.0 / raw_global_scale,
325 input_scale: ptr(store, &format!("{prefix}.input_global_scale")).unwrap_or(DevicePtr::NULL),
327 weight_scale_2_vec: DevicePtr::NULL,
328 act: metrale_config::Nvfp4Act::Unstamped,
329 })
330}