1pub const CENTROIDS_4BIT: [f32; 16] = [
11 -1.996_112, -1.512_225, -1.172_563, -0.887_568, -0.632_509, -0.395_530, -0.169_891, 0.049_892,
12 0.269_673, 0.495_312, 0.732_291, 0.987_350, 1.272_345, 1.612_007, 2.095_894, 2.724_265,
13];
14
15pub const BOUNDARIES_4BIT: [f32; 15] = [
17 -1.751_289, -1.340_438, -1.026_996, -0.756_128, -0.509_062, -0.276_322, -0.056_279, 0.164_289,
18 0.391_029, 0.626_095, 0.868_960, 1.125_026, 1.413_956, 1.764_827, 2.238_297,
19];
20
21#[inline]
24pub fn quantize_4bit(x: f32) -> u8 {
25 let mut lo = 0usize;
26 let mut hi = BOUNDARIES_4BIT.len(); while lo < hi {
28 let mid = (lo + hi) / 2;
29 if x > BOUNDARIES_4BIT[mid] {
30 lo = mid + 1;
31 } else {
32 hi = mid;
33 }
34 }
35 lo as u8
36}
37
38#[inline]
40pub fn dequantize_4bit(i: u8) -> f32 {
41 CENTROIDS_4BIT[i as usize]
42}
43
44pub const CENTROIDS_5BIT: [f32; 32] = [
46 -3.2641168,
47 -2.6949832,
48 -2.321901,
49 -2.0330853,
50 -1.7916883,
51 -1.5806495,
52 -1.3905898,
53 -1.2158026,
54 -1.0524858,
55 -0.89793956,
56 -0.7501299,
57 -0.6074978,
58 -0.4687885,
59 -0.33291993,
60 -0.19900484,
61 -0.06620281,
62 0.06625264,
63 0.19905461,
64 0.33296943,
65 0.46883777,
66 0.60757166,
67 0.75022924,
68 0.8980622,
69 1.052632,
70 1.2159506,
71 1.3907394,
72 1.5808226,
73 1.7919079,
74 2.0333521,
75 2.3222392,
76 2.6954327,
77 3.2648566,
78];
79
80pub const BOUNDARIES_5BIT: [f32; 31] = [
82 -2.9795978,
83 -2.5084915,
84 -2.177543,
85 -1.9124366,
86 -1.6862187,
87 -1.4856696,
88 -1.3032461,
89 -1.1341941,
90 -0.97524965,
91 -0.8240596,
92 -0.67883897,
93 -0.5381553,
94 -0.40085423,
95 -0.2659624,
96 -0.13260382,
97 0.000024914742,
98 0.13265362,
99 0.266012,
100 0.40090358,
101 0.5382047,
102 0.6789125,
103 0.82418275,
104 0.975397,
105 1.1343412,
106 1.3033949,
107 1.4858308,
108 1.6864152,
109 1.9126797,
110 2.177845,
111 2.508878,
112 2.980162,
113];
114
115pub const CENTROIDS_6BIT: [f32; 64] = [
117 -3.8273482,
118 -3.333197,
119 -3.0166795,
120 -2.7765143,
121 -2.5793982,
122 -2.41001,
123 -2.260009,
124 -2.124298,
125 -1.9995066,
126 -1.8833042,
127 -1.7739713,
128 -1.6702707,
129 -1.571215,
130 -1.4760238,
131 -1.3840784,
132 -1.2948383,
133 -1.207906,
134 -1.1230142,
135 -1.0398592,
136 -0.9581757,
137 -0.87773234,
138 -0.7983605,
139 -0.7199343,
140 -0.6422799,
141 -0.5652983,
142 -0.48888877,
143 -0.41292742,
144 -0.33736518,
145 -0.26210162,
146 -0.1870626,
147 -0.11217268,
148 -0.037357662,
149 0.037407417,
150 0.11222233,
151 0.1871122,
152 0.26215115,
153 0.33741492,
154 0.412977,
155 0.48893812,
156 0.5653473,
157 0.64232975,
158 0.71998423,
159 0.7984106,
160 0.8777821,
161 0.9582232,
162 1.0399102,
163 1.1230648,
164 1.2079563,
165 1.2948877,
166 1.384128,
167 1.4760972,
168 1.5713139,
169 1.6703697,
170 1.7740716,
171 1.8834057,
172 1.9996046,
173 2.1243992,
174 2.2601078,
175 2.4101071,
176 2.5794992,
177 2.7766082,
178 3.016757,
179 3.3332598,
180 3.827421,
181];
182
183pub const BOUNDARIES_6BIT: [f32; 63] = [
185 -3.580496,
186 -3.1751587,
187 -2.8968368,
188 -2.6782057,
189 -2.4949536,
190 -2.335259,
191 -2.192403,
192 -2.0621655,
193 -1.9416802,
194 -1.8289125,
195 -1.7223959,
196 -1.6210177,
197 -1.5238943,
198 -1.4303128,
199 -1.3397081,
200 -1.2516222,
201 -1.1656973,
202 -1.0816617,
203 -0.9992423,
204 -0.91816604,
205 -0.8382337,
206 -0.75930965,
207 -0.6812571,
208 -0.6039391,
209 -0.5272308,
210 -0.4510203,
211 -0.37523368,
212 -0.29980838,
213 -0.22464456,
214 -0.14965503,
215 -0.07479018,
216 0.000024879351,
217 0.07483988,
218 0.14970465,
219 0.2246941,
220 0.29985797,
221 0.37528333,
222 0.4510699,
223 0.52728003,
224 0.6039884,
225 0.68130684,
226 0.75935954,
227 0.8382834,
228 0.9182147,
229 0.9992916,
230 1.0817122,
231 1.1657475,
232 1.2516719,
233 1.3397577,
234 1.4303625,
235 1.5239555,
236 1.6211035,
237 1.7224953,
238 1.8290132,
239 1.9417915,
240 2.06229,
241 2.1925168,
242 2.3353572,
243 2.4950526,
244 2.678303,
245 2.8969312,
246 3.1752489,
247 3.580552,
248];
249
250#[inline]
252pub fn levels(bits: u8) -> usize {
253 match bits {
254 4 => 16,
255 5 => 32,
256 6 => 64,
257 w => panic!("unsupported bit width {w} (supported: 4, 5, 6)"),
258 }
259}
260
261#[inline]
263pub fn centroids(bits: u8) -> &'static [f32] {
264 match bits {
265 4 => &CENTROIDS_4BIT,
266 5 => &CENTROIDS_5BIT,
267 6 => &CENTROIDS_6BIT,
268 w => panic!("unsupported bit width {w} (supported: 4, 5, 6)"),
269 }
270}
271
272#[inline]
274pub fn boundaries(bits: u8) -> &'static [f32] {
275 match bits {
276 4 => &BOUNDARIES_4BIT,
277 5 => &BOUNDARIES_5BIT,
278 6 => &BOUNDARIES_6BIT,
279 w => panic!("unsupported bit width {w} (supported: 4, 5, 6)"),
280 }
281}
282
283#[inline]
285pub fn quantize(x: f32, bits: u8) -> u8 {
286 let bnd = boundaries(bits);
287 let mut lo = 0usize;
288 let mut hi = bnd.len();
289 while lo < hi {
290 let mid = (lo + hi) / 2;
291 if x > bnd[mid] {
292 lo = mid + 1;
293 } else {
294 hi = mid;
295 }
296 }
297 lo as u8
298}
299
300#[inline]
302pub fn dequantize(code: u8, bits: u8) -> f32 {
303 centroids(bits)[code as usize]
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn centroids_are_sorted() {
312 for w in CENTROIDS_4BIT.windows(2) {
313 assert!(w[0] < w[1], "centroids must be strictly increasing");
314 }
315 }
316
317 #[test]
318 fn boundaries_are_sorted_and_interleaved() {
319 for w in BOUNDARIES_4BIT.windows(2) {
320 assert!(w[0] < w[1]);
321 }
322 for i in 0..15 {
324 assert!(CENTROIDS_4BIT[i] < BOUNDARIES_4BIT[i]);
325 assert!(BOUNDARIES_4BIT[i] < CENTROIDS_4BIT[i + 1]);
326 }
327 }
328
329 #[test]
330 fn extreme_values_map_to_endpoints() {
331 assert_eq!(quantize_4bit(-10.0), 0);
332 assert_eq!(quantize_4bit(10.0), 15);
333 }
334
335 #[test]
336 fn zero_maps_near_center() {
337 let i = quantize_4bit(0.0);
338 assert!(
339 (7..=8).contains(&i),
340 "0.0 should map to centroid 7 or 8, got {i}"
341 );
342 }
343
344 #[test]
345 fn round_trip_error_is_bounded() {
346 let mut max_gap = 0f32;
349 for i in 0..15 {
350 max_gap = max_gap.max(CENTROIDS_4BIT[i + 1] - CENTROIDS_4BIT[i]);
351 }
352 assert!(max_gap < 0.75, "max centroid gap {max_gap} too large");
355 }
356}