versorc_math/quantization/
turboquant.rs1use super::clifford_3bit::{
16 bf16_to_f32, f32_to_bf16, LLOYD_MAX_3BIT_BOUNDS, LLOYD_MAX_3BIT_CENTROIDS,
17};
18use crate::versor::DyadicWalshVersor;
19
20#[repr(C)]
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct TurboQuantBlock32 {
29 pub base_codes: [u8; 12],
31 pub residual_signs: u32,
33 pub base_scale: u16,
35 pub residual_scale: u16,
37}
38
39impl Default for TurboQuantBlock32 {
40 fn default() -> Self {
41 Self {
42 base_codes: [0u8; 12],
43 residual_signs: 0,
44 base_scale: 0,
45 residual_scale: 0,
46 }
47 }
48}
49
50impl TurboQuantBlock32 {
51 pub fn pack(weights: &[f32; 32]) -> Self {
53 let sum_sq: f32 = weights.iter().map(|&w| w * w).sum();
55 let std_dev = (sum_sq / 32.0).sqrt().max(1e-8);
56 let inv_scale = 1.0 / std_dev;
57
58 let mut base_codes = [0u8; 12];
59 let mut residual = [0.0f32; 32];
60
61 for g in 0..4 {
64 let mut g_bits: u32 = 0;
65 for i in 0..8 {
66 let idx = g * 8 + i;
67 let w = weights[idx];
68 let norm = w * inv_scale;
69 let sign_bit = if norm < 0.0 { 1u32 } else { 0u32 };
70 let abs_val = norm.abs();
71
72 let mag_code = if abs_val < LLOYD_MAX_3BIT_BOUNDS[0] {
73 0u32
74 } else if abs_val < LLOYD_MAX_3BIT_BOUNDS[1] {
75 1u32
76 } else if abs_val < LLOYD_MAX_3BIT_BOUNDS[2] {
77 2u32
78 } else {
79 3u32
80 };
81
82 let code_3bit = (sign_bit << 2) | mag_code;
83 g_bits |= code_3bit << (i * 3);
84
85 let recon_sign = if sign_bit == 1 { -1.0f32 } else { 1.0f32 };
87 let recon_base = recon_sign * LLOYD_MAX_3BIT_CENTROIDS[mag_code as usize] * std_dev;
88 residual[idx] = w - recon_base;
89 }
90
91 base_codes[g * 3] = (g_bits & 0xFF) as u8;
92 base_codes[g * 3 + 1] = ((g_bits >> 8) & 0xFF) as u8;
93 base_codes[g * 3 + 2] = ((g_bits >> 16) & 0xFF) as u8;
94 }
95
96 let mut residual_signs: u32 = 0;
98 for (i, &r) in residual.iter().enumerate() {
99 if r >= 0.0 {
100 residual_signs |= 1u32 << i;
101 }
102 }
103
104 let mut u_norm = [0.0f32; 32];
107 let mut v_sign = [0.0f32; 32];
108 for g in 0..4 {
109 let b0 = base_codes[g * 3] as u32;
110 let b1 = base_codes[g * 3 + 1] as u32;
111 let b2 = base_codes[g * 3 + 2] as u32;
112 let g_bits = b0 | (b1 << 8) | (b2 << 16);
113 for i in 0..8 {
114 let idx = g * 8 + i;
115 let code = ((g_bits >> (i * 3)) & 0x7) as usize;
116 let sign = if (code & 0x4) != 0 { -1.0f32 } else { 1.0f32 };
117 let mag_idx = code & 0x3;
118 u_norm[idx] = sign * LLOYD_MAX_3BIT_CENTROIDS[mag_idx];
119
120 let res_bit = (residual_signs >> idx) & 1;
121 v_sign[idx] = if res_bit == 1 { 1.0f32 } else { -1.0f32 };
122 }
123 }
124
125 let g11: f32 = u_norm.iter().map(|&u| u * u).sum();
126 let g12: f32 = u_norm.iter().zip(v_sign.iter()).map(|(&u, &v)| u * v).sum();
127 let g22: f32 = 32.0f32; let rhs1: f32 = weights.iter().zip(u_norm.iter()).map(|(&w, &u)| w * u).sum();
130 let rhs2: f32 = weights.iter().zip(v_sign.iter()).map(|(&w, &v)| w * v).sum();
131
132 let det = g11 * g22 - g12 * g12;
133 let (alpha1, alpha2) = if det.abs() > 1e-12 {
134 let a1 = (rhs1 * g22 - rhs2 * g12) / det;
135 let a2 = (rhs2 * g11 - rhs1 * g12) / det;
136 (a1, a2)
137 } else {
138 (std_dev, std_dev * 0.2)
139 };
140
141 Self {
142 base_codes,
143 residual_signs,
144 base_scale: f32_to_bf16(alpha1),
145 residual_scale: f32_to_bf16(alpha2),
146 }
147 }
148
149 pub fn unpack(&self, out: &mut [f32; 32]) {
151 let b_scale = bf16_to_f32(self.base_scale);
152 let r_scale = bf16_to_f32(self.residual_scale);
153
154 for g in 0..4 {
155 let b0 = self.base_codes[g * 3] as u32;
156 let b1 = self.base_codes[g * 3 + 1] as u32;
157 let b2 = self.base_codes[g * 3 + 2] as u32;
158 let g_bits = b0 | (b1 << 8) | (b2 << 16);
159
160 for i in 0..8 {
161 let idx = g * 8 + i;
162 let code = ((g_bits >> (i * 3)) & 0x7) as usize;
163 let sign = if (code & 0x4) != 0 { -1.0f32 } else { 1.0f32 };
164 let mag_idx = code & 0x3;
165 let base_val = sign * LLOYD_MAX_3BIT_CENTROIDS[mag_idx] * b_scale;
166
167 let res_bit = (self.residual_signs >> idx) & 1;
168 let res_sign = if res_bit == 1 { 1.0f32 } else { -1.0f32 };
169 let res_val = res_sign * r_scale;
170
171 out[idx] = base_val + res_val;
172 }
173 }
174 }
175
176 #[inline(always)]
178 pub fn dot_with_slice(&self, x: &[f32; 32]) -> f32 {
179 let b_scale = bf16_to_f32(self.base_scale);
180 let r_scale = bf16_to_f32(self.residual_scale);
181 let mut total = 0.0f32;
182
183 for g in 0..4 {
184 let b0 = self.base_codes[g * 3] as u32;
185 let b1 = self.base_codes[g * 3 + 1] as u32;
186 let b2 = self.base_codes[g * 3 + 2] as u32;
187 let g_bits = b0 | (b1 << 8) | (b2 << 16);
188
189 for i in 0..8 {
190 let idx = g * 8 + i;
191 let code = ((g_bits >> (i * 3)) & 0x7) as usize;
192 let sign = if (code & 0x4) != 0 { -1.0f32 } else { 1.0f32 };
193 let mag_idx = code & 0x3;
194 let base_val = sign * LLOYD_MAX_3BIT_CENTROIDS[mag_idx] * b_scale;
195
196 let res_bit = (self.residual_signs >> idx) & 1;
197 let res_sign = if res_bit == 1 { 1.0f32 } else { -1.0f32 };
198 let res_val = res_sign * r_scale;
199
200 total += (base_val + res_val) * x[idx];
201 }
202 }
203
204 total
205 }
206}
207
208#[repr(C, align(64))]
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub struct TurboQuantCacheLine64 {
213 pub blocks: [TurboQuantBlock32; 2],
214 pub _padding: [u8; 24], }
216
217impl Default for TurboQuantCacheLine64 {
218 fn default() -> Self {
219 Self {
220 blocks: [TurboQuantBlock32::default(); 2],
221 _padding: [0u8; 24],
222 }
223 }
224}
225
226impl TurboQuantCacheLine64 {
227 pub const CAPACITY: usize = 64;
228
229 pub fn pack_64(weights: &[f32; 64]) -> Self {
231 let b0: &[f32; 32] = weights[0..32].try_into().unwrap();
232 let b1: &[f32; 32] = weights[32..64].try_into().unwrap();
233 Self {
234 blocks: [TurboQuantBlock32::pack(b0), TurboQuantBlock32::pack(b1)],
235 _padding: [0u8; 24],
236 }
237 }
238
239 #[inline(always)]
241 pub fn dot_64(&self, x: &[f32; 64]) -> f32 {
242 let x0: &[f32; 32] = x[0..32].try_into().unwrap();
243 let x1: &[f32; 32] = x[32..64].try_into().unwrap();
244 self.blocks[0].dot_with_slice(x0) + self.blocks[1].dot_with_slice(x1)
245 }
246}
247
248#[derive(Debug, Clone)]
250pub struct TurboQuantTensor {
251 pub rows: usize,
252 pub cols: usize,
253 pub cache_lines: Vec<TurboQuantCacheLine64>,
254}
255
256impl TurboQuantTensor {
257 pub fn compile_aot(weights: &[f32], rows: usize, cols: usize, versor: &DyadicWalshVersor) -> Self {
259 assert_eq!(weights.len(), rows * cols);
260 assert_eq!(cols % 64, 0, "Columns must be a multiple of 64 for cache-line alignment");
261 assert_eq!(versor.dimension(), cols);
262
263 let lines_per_row = cols / 64;
264 let mut cache_lines = Vec::with_capacity(rows * lines_per_row);
265 let mut row_buffer = vec![0.0f32; cols];
266
267 for r in 0..rows {
268 let row_slice = &weights[r * cols..(r + 1) * cols];
269 row_buffer.copy_from_slice(row_slice);
270
271 versor.apply_in_place(&mut row_buffer);
273
274 for l in 0..lines_per_row {
275 let chunk: &[f32; 64] = row_buffer[l * 64..(l + 1) * 64].try_into().unwrap();
276 cache_lines.push(TurboQuantCacheLine64::pack_64(chunk));
277 }
278 }
279
280 Self {
281 rows,
282 cols,
283 cache_lines,
284 }
285 }
286
287 pub fn matvec(&self, x: &[f32], out: &mut [f32], versor: &DyadicWalshVersor) {
289 assert_eq!(x.len(), self.cols);
290 assert_eq!(out.len(), self.rows);
291
292 let mut x_rot = x.to_vec();
294 versor.apply_in_place(&mut x_rot);
295
296 let lines_per_row = self.cols / 64;
298 for r in 0..self.rows {
299 let mut row_dot = 0.0f32;
300 let line_offset = r * lines_per_row;
301 for l in 0..lines_per_row {
302 let cache_line = &self.cache_lines[line_offset + l];
303 let chunk_x: &[f32; 64] = x_rot[l * 64..(l + 1) * 64].try_into().unwrap();
304 row_dot += cache_line.dot_64(chunk_x);
305 }
306 out[r] = row_dot;
307 }
308 }
309}