1use crate::k_quants::{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
95impl GgmlType for BlockIq4Nl {
96 const DTYPE: GgmlDType = GgmlDType::Iq4Nl;
97 const BLCK_SIZE: usize = QK4_NL;
98
99 fn to_float(xs: &[Self], ys: &mut [f32]) {
101 for (b, y) in xs.iter().zip(ys.as_chunks_mut::<QK4_NL>().0.iter_mut()) {
102 let d = b.d.to_f32();
103 for j in 0..QK4_NL / 2 {
104 y[j] = d * f32::from(KVALUES_IQ4NL[(b.qs[j] & 0xf) as usize]);
105 y[j + QK4_NL / 2] = d * f32::from(KVALUES_IQ4NL[(b.qs[j] >> 4) as usize]);
106 }
107 }
108 }
109
110 fn from_float(xs: &[f32], ys: &mut [Self]) {
111 for (x, y) in xs.as_chunks::<QK4_NL>().0.iter().zip(ys.iter_mut()) {
112 let d = block_scale(x);
113 y.d = f16::from_f32(d);
114 pack32(x, y.d.to_f32(), &mut y.qs);
117 }
118 }
119}
120
121impl GgmlType for BlockIq4Xs {
122 const DTYPE: GgmlDType = GgmlDType::Iq4Xs;
123 const BLCK_SIZE: usize = QK_K;
124
125 fn to_float(xs: &[Self], ys: &mut [f32]) {
127 for (b, y) in xs.iter().zip(ys.as_chunks_mut::<QK_K>().0.iter_mut()) {
128 let d = b.d.to_f32();
129 for ib in 0..QK_K / 32 {
130 let ls = ((b.scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf) as i32
131 | ((((b.scales_h >> (2 * ib)) & 3) as i32) << 4);
132 let dl = d * (ls - 32) as f32;
133 let qs = &b.qs[ib * 16..ib * 16 + 16];
134 let out = &mut y[ib * 32..ib * 32 + 32];
135 for j in 0..16 {
136 out[j] = dl * f32::from(KVALUES_IQ4NL[(qs[j] & 0xf) as usize]);
137 out[j + 16] = dl * f32::from(KVALUES_IQ4NL[(qs[j] >> 4) as usize]);
138 }
139 }
140 }
141 }
142
143 fn from_float(xs: &[f32], ys: &mut [Self]) {
144 for (x, y) in xs.as_chunks::<QK_K>().0.iter().zip(ys.iter_mut()) {
145 let mut sub = [0f32; QK_K / 32];
148 let mut dmax = 0f32;
149 for (ib, s) in sub.iter_mut().enumerate() {
150 *s = block_scale(&x[ib * 32..ib * 32 + 32]);
151 dmax = dmax.max(s.abs());
152 }
153 let d = f16::from_f32(dmax / 31.0);
154 let df = d.to_f32();
155 y.d = d;
156 y.scales_h = 0;
157 y.scales_l = [0; QK_K / 64];
158 for (ib, &s) in sub.iter().enumerate() {
159 let ls = if df != 0.0 {
160 ((s / df).round() as i32).clamp(-32, 31) + 32
161 } else {
162 32
163 } as u16;
164 y.scales_l[ib / 2] |= ((ls & 0xf) as u8) << (4 * (ib % 2));
165 y.scales_h |= (ls >> 4) << (2 * ib);
166 let dl = df * (i32::from(ls) - 32) as f32;
167 pack32(
168 &x[ib * 32..ib * 32 + 32],
169 dl,
170 &mut y.qs[ib * 16..ib * 16 + 16],
171 );
172 }
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
184 fn codebook_matches_ggml_common_h() {
185 let printed: [i8; 16] = [
186 -127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113,
187 ];
188 assert_eq!(KVALUES_IQ4NL, printed);
189 }
190
191 #[test]
196 fn iq4_nl_dequantizes_as_the_ggml_formula() {
197 let mut qs = [0u8; 16];
198 for (j, q) in qs.iter_mut().enumerate() {
199 *q = j as u8 | ((15 - j as u8) << 4);
200 }
201 let blk = BlockIq4Nl {
202 d: f16::from_f32(0.5),
203 qs,
204 };
205 let mut y = [0f32; 32];
206 BlockIq4Nl::to_float(&[blk], &mut y);
207 assert_eq!(y[0], -63.5);
208 assert_eq!(y[15], 56.5);
209 assert_eq!(y[16], 56.5); assert_eq!(y[31], -63.5); assert_eq!(y[8], 0.5); }
213
214 #[test]
219 fn iq4_xs_dequantizes_as_the_ggml_formula() {
220 let mut blk = BlockIq4Xs::zeros();
221 blk.d = f16::from_f32(2.0);
222 blk.scales_l[0] = 0xE1;
223 blk.scales_h = 0b0000_0110; blk.qs[0] = 0x80; blk.qs[16] = 0x0F; let mut y = vec![0f32; QK_K];
227 BlockIq4Xs::to_float(&[blk], &mut y);
228 assert_eq!(y[0], 2.0 * -127.0);
229 assert_eq!(y[16], 2.0 * 1.0);
230 assert_eq!(y[32], -4.0 * 113.0);
231 assert_eq!(y[48], -4.0 * -127.0);
232 }
233
234 fn wave(n: usize) -> Vec<f32> {
235 (0..n)
236 .map(|i| ((i as f32) * 0.37).sin() * 3.0 + ((i as f32) * 0.11).cos())
237 .collect()
238 }
239
240 #[test]
244 fn quantize_then_dequantize_stays_within_codebook_resolution() {
245 for sign in [1.0f32, -1.0] {
246 let x: Vec<f32> = wave(QK_K).into_iter().map(|v| v * sign).collect();
247 let amax = x.iter().fold(0f32, |m, v| m.max(v.abs()));
248
249 let mut nl = vec![BlockIq4Nl::zeros(); QK_K / QK4_NL];
250 BlockIq4Nl::from_float(&x, &mut nl);
251 let mut y = vec![0f32; QK_K];
252 BlockIq4Nl::to_float(&nl, &mut y);
253 let nl_err = x
254 .iter()
255 .zip(&y)
256 .fold(0f32, |m, (a, b)| m.max((a - b).abs()));
257
258 let mut xs = vec![BlockIq4Xs::zeros()];
259 BlockIq4Xs::from_float(&x, &mut xs);
260 BlockIq4Xs::to_float(&xs, &mut y);
261 let xs_err = x
262 .iter()
263 .zip(&y)
264 .fold(0f32, |m, (a, b)| m.max((a - b).abs()));
265
266 assert!(nl_err <= 0.14 * amax, "iq4_nl err {nl_err} vs amax {amax}");
267 assert!(xs_err <= 0.20 * amax, "iq4_xs err {xs_err} vs amax {amax}");
268 assert!(amax > 1.0);
270 }
271 }
272
273 #[test]
275 fn all_zero_input_quantizes_to_zero() {
276 let x = vec![0f32; QK_K];
277 let mut xs = vec![BlockIq4Xs::zeros()];
278 BlockIq4Xs::from_float(&x, &mut xs);
279 let mut y = vec![1f32; QK_K];
280 BlockIq4Xs::to_float(&xs, &mut y);
281 assert!(y.iter().all(|v| *v == 0.0));
282 }
283
284 #[test]
287 fn matmul_matches_the_dequantized_dot() {
288 let x = wave(QK_K);
289 let a: Vec<f32> = wave(QK_K).iter().map(|v| v * 0.5 + 0.1).collect();
290 let mut w = vec![BlockIq4Nl::zeros(); QK_K / QK4_NL];
291 BlockIq4Nl::from_float(&x, &mut w);
292 let mut wf = vec![0f32; QK_K];
293 BlockIq4Nl::to_float(&w, &mut wf);
294 let want: f32 = wf.iter().zip(&a).map(|(p, r)| p * r).sum();
295 let mut got = [0f32; 1];
296 crate::k_quants::matmul((1, QK_K, 1), &a, &w, &mut got).expect("matmul");
297 assert!(
298 (got[0] - want).abs() <= 1e-3 * want.abs().max(1.0),
299 "{} vs {want}",
300 got[0]
301 );
302 }
303}