ferrox_core/
turboquant.rs1pub fn fwht_inplace(x: &mut [f32]) {
14 let n = x.len();
15 assert!(
16 n.is_power_of_two() && n > 0,
17 "fwht length must be 2^k, got {n}"
18 );
19 let mut h = 1usize;
20 while h < n {
21 let step = h * 2;
22 for i in (0..n).step_by(step) {
23 for j in i..i + h {
24 let a = x[j];
25 let b = x[j + h];
26 x[j] = a + b;
27 x[j + h] = a - b;
28 }
29 }
30 h = step;
31 }
32}
33
34pub fn fwht_orthonormal_inplace(x: &mut [f32]) {
36 let n = x.len();
37 fwht_inplace(x);
38 let inv = 1.0 / (n as f32).sqrt();
39 for v in x.iter_mut() {
40 *v *= inv;
41 }
42}
43
44pub fn quantize_turbo4_groups(x: &[f32], group: usize) -> (Vec<u8>, Vec<f32>) {
49 assert!(group >= 2 && group.is_power_of_two());
50 assert_eq!(x.len() % group, 0);
51 let n_groups = x.len() / group;
52 let mut packed = Vec::with_capacity(n_groups * (group / 2));
53 let mut scales = Vec::with_capacity(n_groups);
54 for g in 0..n_groups {
55 let chunk = &x[g * group..(g + 1) * group];
56 let mut amax = 0.0f32;
57 for &v in chunk {
58 amax = amax.max(v.abs());
59 }
60 let scale = if amax > 0.0 { amax / 7.0 } else { 1.0 };
61 scales.push(scale);
62 let inv = 1.0 / scale;
63 for pair in chunk.as_chunks::<2>().0 {
64 let q0 = (pair[0] * inv).round().clamp(-8.0, 7.0) as i8;
65 let q1 = (pair[1] * inv).round().clamp(-8.0, 7.0) as i8;
66 let n0 = (q0 as u8) & 0x0f;
67 let n1 = (q1 as u8) & 0x0f;
68 packed.push(n0 | (n1 << 4));
69 }
70 }
71 (packed, scales)
72}
73
74pub fn dequantize_turbo4_groups(packed: &[u8], scales: &[f32], group: usize) -> Vec<f32> {
76 assert!(group >= 2 && group.is_power_of_two());
77 let n_groups = scales.len();
78 assert_eq!(packed.len(), n_groups * (group / 2));
79 let mut out = Vec::with_capacity(n_groups * group);
80 for (g, &scale) in scales.iter().enumerate() {
81 let bytes = &packed[g * (group / 2)..(g + 1) * (group / 2)];
82 for &b in bytes {
83 let q0 = ((b & 0x0f) as i8) << 4 >> 4; let q1 = ((b >> 4) as i8) << 4 >> 4;
85 out.push(q0 as f32 * scale);
86 out.push(q1 as f32 * scale);
87 }
88 }
89 out
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn fwht_involutory_up_to_scale() {
98 let mut x = vec![1.0, 2.0, 3.0, 4.0, -1.0, 0.5, 0.25, -0.5];
99 let orig = x.clone();
100 fwht_inplace(&mut x);
101 fwht_inplace(&mut x);
102 let n = orig.len() as f32;
104 for (a, b) in orig.iter().zip(x.iter()) {
105 assert!((a * n - b).abs() < 1e-4, "{a} vs {b}");
106 }
107 }
108
109 #[test]
110 fn orthonormal_fwht_is_involution() {
111 let mut x = vec![0.1, -0.2, 0.3, -0.4];
112 let orig = x.clone();
113 fwht_orthonormal_inplace(&mut x);
114 fwht_orthonormal_inplace(&mut x);
115 for (a, b) in orig.iter().zip(x.iter()) {
116 assert!((a - b).abs() < 1e-5, "{a} vs {b}");
117 }
118 }
119
120 #[test]
121 fn turbo4_roundtrip_reasonable() {
122 let mut x: Vec<f32> = (0..16).map(|i| (i as f32 * 0.3).sin()).collect();
123 fwht_orthonormal_inplace(&mut x);
124 let (packed, scales) = quantize_turbo4_groups(&x, 8);
125 let mut y = dequantize_turbo4_groups(&packed, &scales, 8);
126 fwht_orthonormal_inplace(&mut y);
127 let orig: Vec<f32> = (0..16).map(|i| (i as f32 * 0.3).sin()).collect();
130 let mut err = 0.0f32;
131 for (a, b) in orig.iter().zip(y.iter()) {
132 err += (a - b).abs();
133 }
134 err /= orig.len() as f32;
135 assert!(err < 0.15, "mean abs err {err}");
136 }
137}