1use zsfm_gguf::GGMLType;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum SrcDtype {
11 F32,
12 F16,
13 BF16,
14}
15
16impl SrcDtype {
17 pub fn bytes_per_elem(self) -> usize {
18 match self {
19 SrcDtype::F32 => 4,
20 SrcDtype::F16 | SrcDtype::BF16 => 2,
21 }
22 }
23
24 pub fn name(self) -> &'static str {
25 match self {
26 SrcDtype::F32 => "F32",
27 SrcDtype::F16 => "F16",
28 SrcDtype::BF16 => "BF16",
29 }
30 }
31}
32
33pub fn cast_data(data: &[u8], src: SrcDtype, dst: GGMLType) -> anyhow::Result<Vec<u8>> {
39 match (src, dst) {
41 (SrcDtype::F32, GGMLType::F32)
42 | (SrcDtype::F16, GGMLType::F16)
43 | (SrcDtype::BF16, GGMLType::BF16) => return Ok(data.to_vec()),
44 _ => {}
45 }
46
47 let f32_values = decode_to_f32(data, src)?;
48 match dst {
49 GGMLType::F32 => Ok(f32_to_bytes(&f32_values)),
50 GGMLType::F16 => Ok(f32_values
51 .iter()
52 .flat_map(|&v| f32_to_f16_bits(v).to_le_bytes())
53 .collect()),
54 GGMLType::BF16 => Ok(f32_values
55 .iter()
56 .flat_map(|&v| f32_to_bf16_bits(v).to_le_bytes())
57 .collect()),
58 GGMLType::Q8_0 => quantize_q8_0(&f32_values),
59 }
60}
61
62pub fn f32_to_bf16_bits(v: f32) -> u16 {
64 let bits = v.to_bits();
65 if (bits & 0x7f80_0000) == 0x7f80_0000 && (bits & 0x007f_ffff) != 0 {
67 return 0x7fc0;
68 }
69 let rounding = 0x7fff + ((bits >> 16) & 1);
71 ((bits + rounding) >> 16) as u16
72}
73
74pub fn decode_to_f32(data: &[u8], src: SrcDtype) -> anyhow::Result<Vec<f32>> {
76 match src {
77 SrcDtype::F32 => parse_f32_le(data),
78 SrcDtype::F16 => {
79 if data.len() % 2 != 0 {
80 anyhow::bail!("f16 data length not divisible by 2");
81 }
82 Ok(data
83 .chunks_exact(2)
84 .map(|c| f16_to_f32(u16::from_le_bytes([c[0], c[1]])))
85 .collect())
86 }
87 SrcDtype::BF16 => {
88 if data.len() % 2 != 0 {
89 anyhow::bail!("bf16 data length not divisible by 2");
90 }
91 Ok(data
92 .chunks_exact(2)
93 .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
94 .collect())
95 }
96 }
97}
98
99pub fn f32_to_bytes(vals: &[f32]) -> Vec<u8> {
100 vals.iter().flat_map(|v| v.to_le_bytes()).collect()
101}
102
103pub fn q8_dst_or_fallback(shape: &[u64], out_dtype: GGMLType) -> (GGMLType, bool) {
108 if out_dtype == GGMLType::Q8_0 {
109 let n_elems: u64 = shape.iter().product();
110 let innermost = shape.last().copied().unwrap_or(1);
111 if innermost % 32 != 0 || n_elems % 32 != 0 {
112 return (GGMLType::F32, true);
113 }
114 }
115 (out_dtype, false)
116}
117
118pub fn cast_with_q8_fallback(
120 data: &[u8],
121 src: SrcDtype,
122 shape: &[u64],
123 out_dtype: GGMLType,
124) -> anyhow::Result<(Vec<u8>, GGMLType, bool)> {
125 let (dst, fell_back) = q8_dst_or_fallback(shape, out_dtype);
126 Ok((cast_data(data, src, dst)?, dst, fell_back))
127}
128
129pub fn quantize_q8_0(values: &[f32]) -> anyhow::Result<Vec<u8>> {
130 const BLOCK: usize = 32;
131 if values.len() % BLOCK != 0 {
132 anyhow::bail!(
133 "Q8_0 requires element count divisible by {BLOCK}, got {}",
134 values.len()
135 );
136 }
137 let n_blocks = values.len() / BLOCK;
138 let mut out = vec![0u8; n_blocks * 34];
139 for b in 0..n_blocks {
140 let blk = &values[b * BLOCK..(b + 1) * BLOCK];
141 let amax = blk.iter().copied().map(f32::abs).fold(0.0f32, f32::max);
142 let d = if amax == 0.0 { 0.0f32 } else { amax / 127.0 };
143 let d_inv = if d == 0.0 { 0.0f32 } else { 1.0 / d };
144 let base = b * 34;
145 out[base..base + 2].copy_from_slice(&f32_to_f16_bits(d).to_le_bytes());
146 for i in 0..BLOCK {
147 out[base + 2 + i] = (blk[i] * d_inv).round().clamp(-127.0, 127.0) as i8 as u8;
148 }
149 }
150 Ok(out)
151}
152
153fn parse_f32_le(data: &[u8]) -> anyhow::Result<Vec<f32>> {
154 if data.len() % 4 != 0 {
155 anyhow::bail!("f32 data length not divisible by 4");
156 }
157 Ok(data
158 .chunks_exact(4)
159 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
160 .collect())
161}
162
163pub fn f32_to_f16_bits(v: f32) -> u16 {
164 let bits = v.to_bits();
165 let sign = ((bits >> 16) & 0x8000) as u16;
166 let exp = ((bits >> 23) & 0xFF) as i32;
167 let mantissa = bits & 0x007F_FFFF;
168 if exp == 0xFF {
169 return sign | 0x7C00 | if mantissa != 0 { 0x0200 } else { 0 };
170 }
171 let new_exp = exp - 127 + 15;
172 if new_exp >= 31 {
173 return sign | 0x7C00;
174 }
175 if new_exp <= 0 {
176 if new_exp < -10 {
177 return sign;
178 }
179 let shift = 1 - new_exp;
181 let m = mantissa | 0x0080_0000;
182 let remainder = m & ((1 << shift) - 1);
183 let halfway = 1 << (shift - 1);
184 let mut rounded = m >> shift;
185 if remainder > halfway as u32 || (remainder == halfway as u32 && (rounded & 1) == 1) {
186 rounded += 1;
187 }
188 return sign | (rounded >> 13) as u16;
189 }
190 let lsb = (mantissa >> 13) & 1;
192 let rounded = mantissa + 0xFFF + lsb;
193 if rounded & 0x0080_0000 != 0 {
194 let carried_exp = new_exp + 1;
196 if carried_exp >= 31 {
197 return sign | 0x7C00;
198 }
199 return sign | ((carried_exp as u16) << 10);
200 }
201 sign | ((new_exp as u16) << 10) | (rounded >> 13) as u16
202}
203
204pub fn f16_to_f32(bits: u16) -> f32 {
205 let sign = ((bits & 0x8000) as u32) << 16;
206 let exp = ((bits >> 10) & 0x1F) as i32;
207 let mantissa = (bits & 0x03FF) as u32;
208 let f32_bits = if exp == 0 {
209 if mantissa == 0 {
210 sign
211 } else {
212 let mut m = mantissa;
213 let mut e = 0i32;
214 while m & 0x0400 == 0 {
215 m <<= 1;
216 e += 1;
217 }
218 sign | ((127 - 15 - e + 1) as u32) << 23 | (m & 0x03FF) << 13
219 }
220 } else if exp == 31 {
221 sign | 0x7F80_0000 | (mantissa << 13)
222 } else {
223 sign | ((exp + 127 - 15) as u32) << 23 | (mantissa << 13)
224 };
225 f32::from_bits(f32_bits)
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn f16_roundtrip_exact_values() {
234 for v in [0.0f32, 1.0, -1.0, 0.5, 65504.0, -65504.0, 0.099975586] {
235 assert_eq!(f16_to_f32(f32_to_f16_bits(v)), v, "roundtrip {v}");
236 }
237 }
238
239 #[test]
240 fn passthrough_same_dtype() {
241 let bytes: Vec<u8> = (0..16).collect();
242 assert_eq!(
243 cast_data(&bytes, SrcDtype::F32, GGMLType::F32).unwrap(),
244 bytes
245 );
246 assert_eq!(
247 cast_data(&bytes, SrcDtype::F16, GGMLType::F16).unwrap(),
248 bytes
249 );
250 assert_eq!(
251 cast_data(&bytes, SrcDtype::BF16, GGMLType::BF16).unwrap(),
252 bytes
253 );
254 }
255
256 #[test]
257 fn f32_to_bf16_rounds() {
258 let v = 1.2345678f32;
259 let out = cast_data(&v.to_le_bytes(), SrcDtype::F32, GGMLType::BF16).unwrap();
260 let bits = u16::from_le_bytes([out[0], out[1]]);
261 assert_eq!(bits, f32_to_bf16_bits(v));
262 let nan_bits = f32_to_bf16_bits(f32::NAN);
264 assert_eq!(nan_bits & 0x7f80, 0x7f80);
265 assert_ne!(nan_bits & 0x007f, 0);
266 }
267
268 #[test]
269 fn bf16_to_f32_exact_widening() {
270 let bits: u16 = 0x3FA0; let out = cast_data(&bits.to_le_bytes(), SrcDtype::BF16, GGMLType::F32).unwrap();
272 let v = f32::from_le_bytes([out[0], out[1], out[2], out[3]]);
273 assert_eq!(v, 1.25);
274 }
275
276 #[test]
277 fn q8_0_block_layout() {
278 let vals: Vec<f32> = (0..32).map(|i| i as f32).collect();
279 let out = quantize_q8_0(&vals).unwrap();
280 assert_eq!(out.len(), 34);
281 let d = f16_to_f32(u16::from_le_bytes([out[0], out[1]]));
282 assert!((d - 31.0 / 127.0).abs() < 1e-3);
283 assert_eq!(out[2] as i8, 0); assert_eq!(out[33] as i8, 127); }
286
287 #[test]
288 fn q8_0_rejects_partial_block() {
289 assert!(quantize_q8_0(&[1.0f32; 31]).is_err());
290 }
291}