qualia_core_db/inference/
inference_kernel_parity.rs1pub fn max_abs_err(a: &[f32], b: &[f32]) -> f32 {
15 if a.len() != b.len() {
16 return f32::INFINITY;
17 }
18 a.iter()
19 .zip(b)
20 .map(|(x, y)| (x - y).abs())
21 .fold(0.0f32, f32::max)
22}
23
24pub fn mean_abs_err(a: &[f32], b: &[f32]) -> f64 {
26 if a.is_empty() || a.len() != b.len() {
27 return f64::INFINITY;
28 }
29 let s: f64 = a
30 .iter()
31 .zip(b)
32 .map(|(x, y)| (*x as f64 - *y as f64).abs())
33 .sum();
34 s / a.len() as f64
35}
36
37#[inline]
39fn ulp_key(x: f32) -> i64 {
40 let b = x.to_bits();
41 if b & 0x8000_0000 == 0 {
42 b as i64
43 } else {
44 -((b & 0x7fff_ffff) as i64)
45 }
46}
47
48pub fn max_ulp_diff(a: &[f32], b: &[f32]) -> u64 {
51 if a.len() != b.len() {
52 return u64::MAX;
53 }
54 let mut m = 0u64;
55 for (&x, &y) in a.iter().zip(b) {
56 if x.is_finite() && y.is_finite() {
57 let d = (ulp_key(x) - ulp_key(y)).unsigned_abs();
58 if d > m {
59 m = d;
60 }
61 }
62 }
63 m
64}
65
66const Q8_0_BLOCK_ELEMS: usize = 32;
67const Q8_0_BLOCK_BYTES: usize = 34; pub fn q8_0_bytes(n_elems: usize) -> usize {
71 n_elems.div_ceil(Q8_0_BLOCK_ELEMS) * Q8_0_BLOCK_BYTES
72}
73
74pub fn quantize_q8_0_from_f32(weights: &[f32], out: &mut [u8]) -> bool {
78 if out.len() < q8_0_bytes(weights.len()) {
79 return false;
80 }
81 let n_blocks = weights.len().div_ceil(Q8_0_BLOCK_ELEMS);
82 for b in 0..n_blocks {
83 let start = b * Q8_0_BLOCK_ELEMS;
84 let end = (start + Q8_0_BLOCK_ELEMS).min(weights.len());
85 let absmax = weights[start..end]
86 .iter()
87 .fold(0.0f32, |m, &w| m.max(w.abs()));
88 let scale = if absmax > 0.0 { absmax / 127.0 } else { 1.0 };
89 let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
90 let bs = b * Q8_0_BLOCK_BYTES;
91 let s16 = half::f16::from_f32(scale).to_le_bytes();
92 out[bs] = s16[0];
93 out[bs + 1] = s16[1];
94 for j in 0..Q8_0_BLOCK_ELEMS {
95 let q = if start + j < end {
96 (weights[start + j] * inv).round().clamp(-127.0, 127.0) as i8
97 } else {
98 0
99 };
100 out[bs + 2 + j] = q as u8;
101 }
102 }
103 true
104}
105
106pub fn f16_bytes(n_elems: usize) -> usize {
108 n_elems * 2
109}
110
111pub fn quantize_f16_from_f32(weights: &[f32], out: &mut [u8]) -> bool {
114 if out.len() < f16_bytes(weights.len()) {
115 return false;
116 }
117 for (i, &w) in weights.iter().enumerate() {
118 let h = half::f16::from_f32(w).to_le_bytes();
119 out[i * 2] = h[0];
120 out[i * 2 + 1] = h[1];
121 }
122 true
123}
124
125pub fn q4_0_bytes(n_elems: usize) -> usize {
127 n_elems.div_ceil(32) * 18
128}
129
130pub fn quantize_q4_0_from_f32(weights: &[f32], out: &mut [u8]) -> bool {
135 if out.len() < q4_0_bytes(weights.len()) {
136 return false;
137 }
138 let n_blocks = weights.len().div_ceil(32);
139 for b in 0..n_blocks {
140 let start = b * 32;
141 let end = (start + 32).min(weights.len());
142 let mut amax = 0.0f32;
143 let mut max_signed = 0.0f32;
144 for &x in &weights[start..end] {
145 if x.abs() > amax {
146 amax = x.abs();
147 max_signed = x;
148 }
149 }
150 let d = max_signed / -8.0;
151 let id = if d != 0.0 { 1.0 / d } else { 0.0 };
152 let bs = b * 18;
153 out[bs..bs + 2].copy_from_slice(&half::f16::from_f32(d).to_le_bytes());
154 let q = |k: usize| -> u8 {
155 let gk = start + k;
156 if gk >= end {
157 return 8; }
159 (weights[gk] * id + 8.5).floor().clamp(0.0, 15.0) as u8
160 };
161 for j in 0..16 {
162 out[bs + 2 + j] = (q(j) & 0x0F) | ((q(j + 16) & 0x0F) << 4);
163 }
164 }
165 true
166}
167
168pub fn q4_k_bytes(n_elems: usize) -> usize {
170 n_elems.div_ceil(256) * 144
171}
172
173pub fn quantize_q4_k_from_f32(weights: &[f32], out: &mut [u8]) -> bool {
181 if out.len() < q4_k_bytes(weights.len()) {
182 return false;
183 }
184 let n_super = weights.len().div_ceil(256);
185 for sb in 0..n_super {
186 let base = sb * 256;
187 let bb = sb * 144;
188 let mut scale_s = [0f32; 8];
189 let mut mt_s = [0f32; 8];
190 for s in 0..8 {
191 let s0 = base + s * 32;
192 if s0 >= weights.len() {
193 continue;
194 }
195 let s1 = (s0 + 32).min(weights.len());
196 let mut mn = f32::INFINITY;
197 let mut mx = f32::NEG_INFINITY;
198 for &x in &weights[s0..s1] {
199 mn = mn.min(x);
200 mx = mx.max(x);
201 }
202 let eff_min = mn.min(0.0); scale_s[s] = ((mx - eff_min) / 15.0).max(0.0);
204 mt_s[s] = -eff_min; }
206 let d = scale_s.iter().cloned().fold(0.0f32, f32::max) / 63.0;
207 let dmin = mt_s.iter().cloned().fold(0.0f32, f32::max) / 63.0;
208 let idd = if d > 0.0 { 1.0 / d } else { 0.0 };
209 let idm = if dmin > 0.0 { 1.0 / dmin } else { 0.0 };
210 let mut sc = [0u8; 8];
211 let mut m = [0u8; 8];
212 for s in 0..8 {
213 sc[s] = (scale_s[s] * idd).round().clamp(0.0, 63.0) as u8;
214 m[s] = (mt_s[s] * idm).round().clamp(0.0, 63.0) as u8;
215 }
216 out[bb..bb + 2].copy_from_slice(&half::f16::from_f32(d).to_le_bytes());
217 out[bb + 2..bb + 4].copy_from_slice(&half::f16::from_f32(dmin).to_le_bytes());
218 let mut scales = [0u8; 12];
220 for j in 0..4 {
221 scales[j] = sc[j] & 63;
222 scales[j + 4] = m[j] & 63;
223 }
224 for j in 4..8 {
225 scales[j + 4] = (sc[j] & 0xF) | ((m[j] & 0xF) << 4);
226 scales[j - 4] |= (sc[j] >> 4) << 6;
227 scales[j] |= (m[j] >> 4) << 6;
228 }
229 out[bb + 4..bb + 16].copy_from_slice(&scales);
230 for s in 0..8 {
232 let dq = d * sc[s] as f32;
233 let mq = dmin * m[s] as f32;
234 let idq = if dq > 0.0 { 1.0 / dq } else { 0.0 };
235 for l in 0..32 {
236 let gi = base + s * 32 + l;
237 let q = if gi < weights.len() {
238 ((weights[gi] + mq) * idq).round().clamp(0.0, 15.0) as u8
239 } else {
240 0
241 };
242 let byte = bb + 16 + (s / 2) * 32 + l;
243 if s % 2 == 0 {
244 out[byte] = (out[byte] & 0xF0) | (q & 0x0F);
245 } else {
246 out[byte] = (out[byte] & 0x0F) | ((q & 0x0F) << 4);
247 }
248 }
249 }
250 }
251 true
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn metrics_zero_for_identical() {
260 let a = [1.0f32, -2.0, 3.5, 0.0, -0.0];
261 assert_eq!(max_abs_err(&a, &a), 0.0);
262 assert_eq!(mean_abs_err(&a, &a), 0.0);
263 assert_eq!(max_ulp_diff(&a, &a), 0);
264 }
265
266 #[test]
267 fn ulp_one_step_is_one() {
268 let x = 1.0f32;
269 let y = f32::from_bits(x.to_bits() + 1);
270 assert_eq!(max_ulp_diff(&[x], &[y]), 1);
271 assert!(max_abs_err(&[x], &[y]) > 0.0);
272 }
273
274 #[test]
275 fn q8_0_roundtrip_within_one_step() {
276 let w: Vec<f32> = (0..32).map(|i| (i as f32 - 16.0) / 16.0).collect();
277 let mut bytes = vec![0u8; q8_0_bytes(w.len())];
278 assert!(quantize_q8_0_from_f32(&w, &mut bytes));
279 let info = crate::gguf_sharder::GgufTensorInfo {
280 dims: [32, 1, 1, 1],
281 n_dims: 1,
282 ggml_type: crate::ggml_quants::GGML_TYPE_Q8_0,
283 byte_offset: 0,
284 };
285 let mut back = vec![0f32; 32];
286 let n = crate::ggml_quants::dequant_matrix_row_into(&bytes, &info, 0, &mut back).unwrap();
287 assert_eq!(n, 32);
288 let step = (15.0f32 / 16.0) / 127.0;
290 assert!(max_abs_err(&w, &back) <= step + 1e-5);
291 }
292
293 #[test]
294 fn q4_0_roundtrip_within_one_step() {
295 let w: Vec<f32> = (0..32).map(|i| (i as f32 - 16.0) / 8.0).collect(); let mut bytes = vec![0u8; q4_0_bytes(w.len())];
297 assert!(quantize_q4_0_from_f32(&w, &mut bytes));
298 let info = crate::gguf_sharder::GgufTensorInfo {
299 dims: [32, 1, 1, 1],
300 n_dims: 1,
301 ggml_type: crate::ggml_quants::GGML_TYPE_Q4_0,
302 byte_offset: 0,
303 };
304 let mut back = vec![0f32; 32];
305 let n = crate::ggml_quants::dequant_matrix_row_into(&bytes, &info, 0, &mut back).unwrap();
306 assert_eq!(n, 32);
307 let absmax = w.iter().cloned().fold(0f32, |m, x| m.max(x.abs())); let step = absmax / 8.0; assert!(
310 max_abs_err(&w, &back) <= step * 1.1,
311 "q4_0 roundtrip err {} > step {step}",
312 max_abs_err(&w, &back)
313 );
314 }
315
316 #[test]
317 fn q4_k_roundtrip_and_beats_q4_0() {
318 let w: Vec<f32> = (0..256).map(|i| (i as f32 - 128.0) / 64.0).collect(); let info_k = crate::gguf_sharder::GgufTensorInfo {
321 dims: [256, 1, 1, 1],
322 n_dims: 1,
323 ggml_type: crate::ggml_quants::GGML_TYPE_Q4_K,
324 byte_offset: 0,
325 };
326 let mut kbytes = vec![0u8; q4_k_bytes(w.len())];
327 assert!(quantize_q4_k_from_f32(&w, &mut kbytes));
328 let mut back_k = vec![0f32; 256];
329 let nk =
330 crate::ggml_quants::dequant_matrix_row_into(&kbytes, &info_k, 0, &mut back_k).unwrap();
331 assert_eq!(nk, 256);
332 let err_k = max_abs_err(&w, &back_k);
333
334 let info_0 = crate::gguf_sharder::GgufTensorInfo {
336 dims: [256, 1, 1, 1],
337 n_dims: 1,
338 ggml_type: crate::ggml_quants::GGML_TYPE_Q4_0,
339 byte_offset: 0,
340 };
341 let mut zbytes = vec![0u8; q4_0_bytes(w.len())];
342 assert!(quantize_q4_0_from_f32(&w, &mut zbytes));
343 let mut back_0 = vec![0f32; 256];
344 crate::ggml_quants::dequant_matrix_row_into(&zbytes, &info_0, 0, &mut back_0).unwrap();
345 let err_0 = max_abs_err(&w, &back_0);
346
347 let range = 2.0 + 1.98; eprintln!("q4_k err {err_k:.4} vs q4_0 err {err_0:.4} (range {range:.2})");
349 assert!(
350 err_k.is_finite() && err_k <= range / 15.0 * 1.3,
351 "q4_k roundtrip err {err_k} too high"
352 );
353 assert!(
354 err_k <= err_0 + 1e-4,
355 "q4_k ({err_k}) should not be worse than q4_0 ({err_0})"
356 );
357 }
358}