1use crate::k_quants::{BlockQ8_0, BlockQ8K, GgmlType, QK_K};
29use fuel_ir::quantized::GgmlDType;
30use half::f16;
31
32pub const QK4_NL: usize = 32;
33
34pub const KVALUES_IQ4NL: [i8; 16] = [
37 -127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113,
38];
39
40#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
41#[repr(C)]
42pub struct BlockIq4Nl {
43 pub(crate) d: f16,
44 pub(crate) qs: [u8; QK4_NL / 2],
45}
46const _: () = assert!(std::mem::size_of::<BlockIq4Nl>() == 2 + QK4_NL / 2);
47
48#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
49#[repr(C)]
50pub struct BlockIq4Xs {
51 pub(crate) d: f16,
52 pub(crate) scales_h: u16,
53 pub(crate) scales_l: [u8; QK_K / 64],
54 pub(crate) qs: [u8; QK_K / 2],
55}
56const _: () = assert!(std::mem::size_of::<BlockIq4Xs>() == 2 + 2 + QK_K / 64 + QK_K / 2);
57
58fn nearest_code(v: f32) -> u8 {
60 let mut best = 0u8;
61 let mut best_err = f32::INFINITY;
62 for (i, &k) in KVALUES_IQ4NL.iter().enumerate() {
63 let err = (v - f32::from(k)).abs();
64 if err < best_err {
65 best_err = err;
66 best = i as u8;
67 }
68 }
69 best
70}
71
72fn block_scale(xs: &[f32]) -> f32 {
75 let mut amax = 0f32;
76 let mut max = 0f32;
77 for &x in xs {
78 if amax < x.abs() {
79 amax = x.abs();
80 max = x;
81 }
82 }
83 max / f32::from(KVALUES_IQ4NL[0])
84}
85
86fn pack32(xs: &[f32], d: f32, qs: &mut [u8]) {
89 let id = if d != 0.0 { 1.0 / d } else { 0.0 };
90 for j in 0..16 {
91 qs[j] = nearest_code(xs[j] * id) | (nearest_code(xs[j + 16] * id) << 4);
92 }
93}
94
95fn dot_via_to_float<X: GgmlType, Y: GgmlType>(n: usize, xs: &[X], ys: &[Y]) -> f32 {
98 let mut a = vec![0f32; n];
99 let mut b = vec![0f32; n];
100 X::to_float(&xs[..n / X::BLCK_SIZE], &mut a);
101 Y::to_float(&ys[..n / Y::BLCK_SIZE], &mut b);
102 a.iter().zip(&b).map(|(x, y)| x * y).sum()
103}
104
105impl GgmlType for BlockIq4Nl {
106 const DTYPE: GgmlDType = GgmlDType::Iq4Nl;
107 const BLCK_SIZE: usize = QK4_NL;
108 type VecDotType = BlockQ8_0;
109
110 fn to_float(xs: &[Self], ys: &mut [f32]) {
112 for (b, y) in xs.iter().zip(ys.as_chunks_mut::<QK4_NL>().0.iter_mut()) {
113 let d = b.d.to_f32();
114 for j in 0..QK4_NL / 2 {
115 y[j] = d * f32::from(KVALUES_IQ4NL[(b.qs[j] & 0xf) as usize]);
116 y[j + QK4_NL / 2] = d * f32::from(KVALUES_IQ4NL[(b.qs[j] >> 4) as usize]);
117 }
118 }
119 }
120
121 fn from_float(xs: &[f32], ys: &mut [Self]) {
122 for (x, y) in xs.as_chunks::<QK4_NL>().0.iter().zip(ys.iter_mut()) {
123 let d = block_scale(x);
124 y.d = f16::from_f32(d);
125 pack32(x, y.d.to_f32(), &mut y.qs);
128 }
129 }
130
131 fn vec_dot(n: usize, xs: &[Self], ys: &[Self::VecDotType]) -> f32 {
132 Self::vec_dot_unopt(n, xs, ys)
133 }
134
135 fn vec_dot_unopt(n: usize, xs: &[Self], ys: &[Self::VecDotType]) -> f32 {
136 dot_via_to_float(n, xs, ys)
137 }
138}
139
140impl GgmlType for BlockIq4Xs {
141 const DTYPE: GgmlDType = GgmlDType::Iq4Xs;
142 const BLCK_SIZE: usize = QK_K;
143 type VecDotType = BlockQ8K;
144
145 fn to_float(xs: &[Self], ys: &mut [f32]) {
147 for (b, y) in xs.iter().zip(ys.as_chunks_mut::<QK_K>().0.iter_mut()) {
148 let d = b.d.to_f32();
149 for ib in 0..QK_K / 32 {
150 let ls = ((b.scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf) as i32
151 | ((((b.scales_h >> (2 * ib)) & 3) as i32) << 4);
152 let dl = d * (ls - 32) as f32;
153 let qs = &b.qs[ib * 16..ib * 16 + 16];
154 let out = &mut y[ib * 32..ib * 32 + 32];
155 for j in 0..16 {
156 out[j] = dl * f32::from(KVALUES_IQ4NL[(qs[j] & 0xf) as usize]);
157 out[j + 16] = dl * f32::from(KVALUES_IQ4NL[(qs[j] >> 4) as usize]);
158 }
159 }
160 }
161 }
162
163 fn from_float(xs: &[f32], ys: &mut [Self]) {
164 for (x, y) in xs.as_chunks::<QK_K>().0.iter().zip(ys.iter_mut()) {
165 let mut sub = [0f32; QK_K / 32];
168 let mut dmax = 0f32;
169 for (ib, s) in sub.iter_mut().enumerate() {
170 *s = block_scale(&x[ib * 32..ib * 32 + 32]);
171 dmax = dmax.max(s.abs());
172 }
173 let d = f16::from_f32(dmax / 31.0);
174 let df = d.to_f32();
175 y.d = d;
176 y.scales_h = 0;
177 y.scales_l = [0; QK_K / 64];
178 for (ib, &s) in sub.iter().enumerate() {
179 let ls = if df != 0.0 {
180 ((s / df).round() as i32).clamp(-32, 31) + 32
181 } else {
182 32
183 } as u16;
184 y.scales_l[ib / 2] |= ((ls & 0xf) as u8) << (4 * (ib % 2));
185 y.scales_h |= (ls >> 4) << (2 * ib);
186 let dl = df * (i32::from(ls) - 32) as f32;
187 pack32(
188 &x[ib * 32..ib * 32 + 32],
189 dl,
190 &mut y.qs[ib * 16..ib * 16 + 16],
191 );
192 }
193 }
194 }
195
196 fn vec_dot(n: usize, xs: &[Self], ys: &[Self::VecDotType]) -> f32 {
197 Self::vec_dot_unopt(n, xs, ys)
198 }
199
200 fn vec_dot_unopt(n: usize, xs: &[Self], ys: &[Self::VecDotType]) -> f32 {
201 dot_via_to_float(n, xs, ys)
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
212 fn codebook_matches_ggml_common_h() {
213 let printed: [i8; 16] = [
214 -127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113,
215 ];
216 assert_eq!(KVALUES_IQ4NL, printed);
217 }
218
219 #[test]
224 fn iq4_nl_dequantizes_as_the_ggml_formula() {
225 let mut qs = [0u8; 16];
226 for (j, q) in qs.iter_mut().enumerate() {
227 *q = j as u8 | ((15 - j as u8) << 4);
228 }
229 let blk = BlockIq4Nl {
230 d: f16::from_f32(0.5),
231 qs,
232 };
233 let mut y = [0f32; 32];
234 BlockIq4Nl::to_float(&[blk], &mut y);
235 assert_eq!(y[0], -63.5);
236 assert_eq!(y[15], 56.5);
237 assert_eq!(y[16], 56.5); assert_eq!(y[31], -63.5); assert_eq!(y[8], 0.5); }
241
242 #[test]
247 fn iq4_xs_dequantizes_as_the_ggml_formula() {
248 let mut blk = BlockIq4Xs::zeros();
249 blk.d = f16::from_f32(2.0);
250 blk.scales_l[0] = 0xE1;
251 blk.scales_h = 0b0000_0110; blk.qs[0] = 0x80; blk.qs[16] = 0x0F; let mut y = vec![0f32; QK_K];
255 BlockIq4Xs::to_float(&[blk], &mut y);
256 assert_eq!(y[0], 2.0 * -127.0);
257 assert_eq!(y[16], 2.0 * 1.0);
258 assert_eq!(y[32], -4.0 * 113.0);
259 assert_eq!(y[48], -4.0 * -127.0);
260 }
261
262 fn wave(n: usize) -> Vec<f32> {
263 (0..n)
264 .map(|i| ((i as f32) * 0.37).sin() * 3.0 + ((i as f32) * 0.11).cos())
265 .collect()
266 }
267
268 #[test]
272 fn quantize_then_dequantize_stays_within_codebook_resolution() {
273 for sign in [1.0f32, -1.0] {
274 let x: Vec<f32> = wave(QK_K).into_iter().map(|v| v * sign).collect();
275 let amax = x.iter().fold(0f32, |m, v| m.max(v.abs()));
276
277 let mut nl = vec![BlockIq4Nl::zeros(); QK_K / QK4_NL];
278 BlockIq4Nl::from_float(&x, &mut nl);
279 let mut y = vec![0f32; QK_K];
280 BlockIq4Nl::to_float(&nl, &mut y);
281 let nl_err = x
282 .iter()
283 .zip(&y)
284 .fold(0f32, |m, (a, b)| m.max((a - b).abs()));
285
286 let mut xs = vec![BlockIq4Xs::zeros()];
287 BlockIq4Xs::from_float(&x, &mut xs);
288 BlockIq4Xs::to_float(&xs, &mut y);
289 let xs_err = x
290 .iter()
291 .zip(&y)
292 .fold(0f32, |m, (a, b)| m.max((a - b).abs()));
293
294 assert!(nl_err <= 0.14 * amax, "iq4_nl err {nl_err} vs amax {amax}");
295 assert!(xs_err <= 0.20 * amax, "iq4_xs err {xs_err} vs amax {amax}");
296 assert!(amax > 1.0);
298 }
299 }
300
301 #[test]
303 fn all_zero_input_quantizes_to_zero() {
304 let x = vec![0f32; QK_K];
305 let mut xs = vec![BlockIq4Xs::zeros()];
306 BlockIq4Xs::from_float(&x, &mut xs);
307 let mut y = vec![1f32; QK_K];
308 BlockIq4Xs::to_float(&xs, &mut y);
309 assert!(y.iter().all(|v| *v == 0.0));
310 }
311
312 #[test]
315 fn vec_dot_matches_dequantized_dot() {
316 let x = wave(QK_K);
317 let a: Vec<f32> = wave(QK_K).iter().map(|v| v * 0.5 + 0.1).collect();
318 let mut w = vec![BlockIq4Nl::zeros(); QK_K / QK4_NL];
319 BlockIq4Nl::from_float(&x, &mut w);
320 let mut q = vec![BlockQ8_0::zeros(); QK_K / 32];
321 BlockQ8_0::from_float(&a, &mut q);
322 let (mut wf, mut qf) = (vec![0f32; QK_K], vec![0f32; QK_K]);
323 BlockIq4Nl::to_float(&w, &mut wf);
324 BlockQ8_0::to_float(&q, &mut qf);
325 let want: f32 = wf.iter().zip(&qf).map(|(p, r)| p * r).sum();
326 let got = BlockIq4Nl::vec_dot(QK_K, &w, &q);
327 assert!(
328 (got - want).abs() <= 1e-3 * want.abs().max(1.0),
329 "{got} vs {want}"
330 );
331 }
332}