Skip to main content

fuel_quantized/
utils.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Derived from candle (https://github.com/huggingface/candle, MIT OR Apache-2.0): `candle-core/src/quantized/utils.rs`
3// at commit 549eacb011f25b2c6fc765d6c3dbd26e42f72ee6, modified here. candle's code is itself a port of ggml / llama.cpp (MIT), which its own comments cite by commit.
4// Contributors, how this was verified and the licence texts: THIRD-PARTY-NOTICES.md (section "candle").
5pub(super) fn nearest_int(v: f32) -> i32 {
6    v.round() as i32
7}
8
9/// Validates that the input and output are the right size and returns an iterator which maps each
10/// input region `xs` to its corresponding output block in `ys`. Each output region is guaranteed
11/// to be `T::BLCK_SIZE` long.
12pub(super) fn group_for_quantization<'a, 'b, T: super::k_quants::GgmlType>(
13    xs: &'b [f32],
14    ys: &'a mut [T],
15) -> Vec<(&'a mut T, &'b [f32])> {
16    let block_size = T::BLCK_SIZE;
17    let dtype = T::DTYPE;
18
19    let expected_blocks = xs.len() / block_size;
20    let actual_blocks = ys.len();
21
22    // Validate that the input is the right size
23    debug_assert_eq!(
24        expected_blocks, actual_blocks,
25        "quantize {dtype:?}: expected {expected_blocks} blocks but only {actual_blocks} were provided!"
26    );
27
28    ys.iter_mut().zip(xs.chunks_exact(block_size)).collect()
29}
30
31/// Validates that the input and output are the right size and returns an iterator which maps each
32/// input block `xs` to its corresponding output region in `ys`. Each output region is guaranteed
33/// to be `T::BLCK_SIZE` long.
34pub(super) fn group_for_dequantization<'a, 'b, T: super::k_quants::GgmlType>(
35    xs: &'a [T],
36    ys: &'b mut [f32],
37) -> Vec<(&'a T, &'b mut [f32])> {
38    let block_size = T::BLCK_SIZE;
39    let dtype = T::DTYPE;
40
41    let actual_output_len = ys.len();
42    let expected_output_len = xs.len() * block_size;
43    // Validate that the output is the right size
44    debug_assert_eq!(
45        expected_output_len, actual_output_len,
46        "dequantize {dtype:?}: ys (len = {actual_output_len}) does not match the expected length of {expected_output_len}!"
47    );
48
49    // Zip the blocks and outputs together
50    xs.iter().zip(ys.chunks_exact_mut(block_size)).collect()
51}
52
53pub(super) fn get_scale_min_k4(j: usize, q: &[u8]) -> (u8, u8) {
54    if j < 4 {
55        let d = q[j] & 63;
56        let m = q[j + 4] & 63;
57        (d, m)
58    } else {
59        let d = (q[j + 4] & 0xF) | ((q[j - 4] >> 6) << 4);
60        let m = (q[j + 4] >> 4) | ((q[j] >> 6) << 4);
61        (d, m)
62    }
63}
64
65pub(super) unsafe fn make_qx_quants(
66    n: usize,
67    nmax: i32,
68    x: *const f32,
69    ls: *mut i8,
70    rmse_type: i32,
71    qw: *const f32,
72) -> f32 {
73    unsafe {
74        let mut max = 0f32;
75        let mut amax = 0f32;
76        for i in 0..n {
77            let x = *x.add(i);
78            let ax = x.abs();
79            if ax > amax {
80                amax = ax;
81                max = x;
82            }
83        }
84        if amax == 0. {
85            // all zero
86            for i in 0..n {
87                *ls.add(i) = 0;
88            }
89            return 0.;
90        }
91        let mut iscale = -(nmax as f32) / max;
92        if rmse_type == 0 {
93            for i in 0..n {
94                let x = *x.add(i);
95                let l = nearest_int(iscale * x);
96                *ls.add(i) = (nmax + l.clamp(-nmax, nmax - 1)) as i8;
97            }
98            return 1.0 / iscale;
99        }
100        let weight_type = rmse_type % 2;
101        let mut sumlx = 0f32;
102        let mut suml2 = 0f32;
103        for i in 0..n {
104            let x = *x.add(i);
105            let l = nearest_int(iscale * x);
106            let l = l.clamp(-nmax, nmax - 1);
107            *ls.add(i) = (l + nmax) as i8;
108            let w = if !qw.is_null() {
109                *qw.add(i)
110            } else if weight_type == 1 {
111                x * x
112            } else {
113                1.0
114            };
115            let l = l as f32;
116            sumlx += w * x * l;
117            suml2 += w * l * l;
118        }
119        let mut scale = sumlx / suml2;
120        let mut best = scale * sumlx;
121        for _itry in 0..3 {
122            let iscale = 1.0 / scale;
123            let mut slx = 0f32;
124            let mut sl2 = 0f32;
125            let mut changed = false;
126            for i in 0..n {
127                let x = *x.add(i);
128                let l = nearest_int(iscale * x);
129                let l = l.clamp(-nmax, nmax - 1);
130                if l + nmax != *ls.add(i) as i32 {
131                    changed = true;
132                }
133                let w = if !qw.is_null() {
134                    *qw.add(i)
135                } else if weight_type == 1 {
136                    x * x
137                } else {
138                    1.0
139                };
140                let l = l as f32;
141                slx += w * x * l;
142                sl2 += w * l * l;
143            }
144            if !changed || sl2 == 0.0 || slx * slx <= best * sl2 {
145                break;
146            }
147            for i in 0..n {
148                let x = *x.add(i);
149                let l = nearest_int(iscale * x);
150                *ls.add(i) = (nmax + l.clamp(-nmax, nmax - 1)) as i8;
151            }
152            sumlx = slx;
153            suml2 = sl2;
154            scale = sumlx / suml2;
155            best = scale * sumlx;
156        }
157        for _itry in 0..5 {
158            let mut n_changed = 0;
159            for i in 0..n {
160                let x = *x.add(i);
161                let w = if !qw.is_null() {
162                    *qw.add(i)
163                } else if weight_type == 1 {
164                    x * x
165                } else {
166                    1.0
167                };
168                let l = *ls.add(i) as i32 - nmax;
169                let mut slx = sumlx - w * x * l as f32;
170                if slx > 0. {
171                    let mut sl2 = suml2 - w * l as f32 * l as f32;
172                    let new_l = nearest_int(x * sl2 / slx);
173                    let new_l = new_l.clamp(-nmax, nmax - 1);
174                    if new_l != l {
175                        slx += w * x * new_l as f32;
176                        sl2 += w * new_l as f32 * new_l as f32;
177                        if sl2 > 0. && slx * slx * suml2 > sumlx * sumlx * sl2 {
178                            *ls.add(i) = (nmax + new_l) as i8;
179                            sumlx = slx;
180                            suml2 = sl2;
181                            scale = sumlx / suml2;
182                            best = scale * sumlx;
183                            n_changed += 1;
184                        }
185                    }
186                }
187            }
188            if n_changed == 0 {
189                break;
190            }
191        }
192        if rmse_type < 3 {
193            return scale;
194        }
195        for is in -4..4 {
196            if is == 0 {
197                continue;
198            }
199            iscale = -(nmax as f32 + 0.1f32 * is as f32) / max;
200            let mut sumlx = 0.;
201            let mut suml2 = 0.;
202            for i in 0..n {
203                let x = *x.add(i);
204                let l = nearest_int(iscale * x);
205                let l = l.clamp(-nmax, nmax - 1);
206                let w = if !qw.is_null() {
207                    *qw.add(i)
208                } else if weight_type == 1 {
209                    x * x
210                } else {
211                    1.0
212                };
213                let l = l as f32;
214                sumlx += w * x * l;
215                suml2 += w * l * l;
216            }
217            if suml2 > 0. && sumlx * sumlx > best * suml2 {
218                for i in 0..n {
219                    let x = *x.add(i);
220                    let l = nearest_int(iscale * x);
221                    *ls.add(i) = (nmax + l.clamp(-nmax, nmax - 1)) as i8;
222                }
223                scale = sumlx / suml2;
224                best = scale * sumlx;
225            }
226        }
227        scale
228    }
229}
230
231// https://github.com/ggerganov/llama.cpp/blob/8183159cf3def112f6d1fe94815fce70e1bffa12/k_quants.c#L224
232pub(super) fn make_qkx1_quants(nmax: i32, ntry: usize, x: &[f32]) -> (f32, f32) {
233    let n = x.len();
234    let mut l = vec![0; n];
235    // Get min/max
236    let min = *x
237        .iter()
238        .take(n)
239        .min_by(|a, b| a.total_cmp(b))
240        .unwrap_or(&x[0]);
241    let max = *x.iter().max_by(|a, b| a.total_cmp(b)).unwrap_or(&x[0]);
242
243    // If min == max, all values are the same => nothing to do here
244    if max == min {
245        return (0.0, 0.0);
246    }
247
248    // Ensure min <= 0.0
249    let mut min = min.min(0.);
250
251    // Compute scale and inverse scale
252    let mut iscale = nmax as f32 / (max - min);
253    let mut scale = 1.0 / iscale;
254
255    for _ in 0..ntry {
256        let mut sumlx = 0.0;
257        let mut suml2 = 0;
258        let mut did_change = false;
259
260        for (i, value) in x.iter().enumerate().take(n) {
261            let li = nearest_int(iscale * (value - min)).clamp(0, nmax);
262            let clamped_li = li as u8;
263            if clamped_li != l[i] {
264                l[i] = clamped_li;
265                did_change = true;
266            }
267            sumlx += (value - min) * li as f32;
268            suml2 += li * li;
269        }
270        scale = sumlx / suml2 as f32;
271
272        let sum: f32 = x
273            .iter()
274            .take(n)
275            .zip(l.iter().take(n))
276            .map(|(xi, &li)| xi - scale * li as f32)
277            .sum();
278
279        min = sum / n as f32;
280        if min > 0.0 {
281            min = 0.0;
282        }
283        iscale = 1.0 / scale;
284        if !did_change {
285            break;
286        }
287    }
288    (scale, -min)
289}
290
291// https://github.com/ggerganov/llama.cpp/blob/8183159cf3def112f6d1fe94815fce70e1bffa12/k_quants.c#L165
292pub(super) fn make_q3_quants(x: &[f32], nmax: i32, do_rmse: bool) -> f32 {
293    let n = x.len();
294    let mut l = vec![0i8; n];
295
296    let mut max = 0.0;
297    let mut amax = 0.0;
298    for &xi in x.iter().take(n) {
299        let ax = xi.abs();
300        if ax > amax {
301            amax = ax;
302            max = xi;
303        }
304    }
305
306    if amax == 0.0 {
307        return 0.0;
308    }
309
310    let iscale = -(nmax as f32) / max;
311    if do_rmse {
312        let mut sumlx = 0.0;
313        let mut suml2 = 0.0;
314        for i in 0..n {
315            let li = (iscale * x[i]).round() as i32;
316            let li = li.clamp(-nmax, nmax - 1);
317            l[i] = li as i8;
318            let w = x[i] * x[i];
319            sumlx += w * x[i] * li as f32;
320            suml2 += w * (li * li) as f32;
321        }
322        for _ in 0..5 {
323            let mut n_changed = 0;
324            for i in 0..n {
325                let w = x[i] * x[i];
326                let mut slx = sumlx - w * x[i] * l[i] as f32;
327                if slx > 0.0 {
328                    let mut sl2 = suml2 - w * (l[i] as i32 * l[i] as i32) as f32;
329                    let mut new_l = (x[i] * sl2 / slx).round() as i32;
330                    new_l = new_l.clamp(-nmax, nmax - 1);
331                    if new_l != l[i] as i32 {
332                        slx += w * x[i] * new_l as f32;
333                        sl2 += w * (new_l * new_l) as f32;
334                        if sl2 > 0.0 && slx * slx * suml2 > sumlx * sumlx * sl2 {
335                            l[i] = new_l as i8;
336                            sumlx = slx;
337                            suml2 = sl2;
338                            n_changed += 1;
339                        }
340                    }
341                }
342            }
343            if n_changed == 0 {
344                break;
345            }
346        }
347        for li in l.iter_mut() {
348            *li += nmax as i8;
349        }
350        return sumlx / suml2;
351    }
352    for i in 0..n {
353        let li = (iscale * x[i]).round() as i32;
354        l[i] = (li.clamp(-nmax, nmax - 1) + nmax) as i8;
355    }
356    1.0 / iscale
357}
358
359// https://github.com/ggerganov/llama.cpp/blob/678d7994f4da0af3d29046be99950ac999ee9762/ggml/src/ggml-quants.c#L744
360/// (scale, min)
361pub(super) fn make_qkx3_quants(
362    nmax: i32,
363    x: &[f32],
364    weights: Option<&[f32]>,
365    rmin: f32,
366    rdelta: f32,
367    nstep: usize,
368    use_mad: bool,
369) -> (f32, f32) {
370    let n = x.len();
371    let mut l: [u8; 32] = [0; 32];
372    let mut l_aux: [u8; 32] = [0; 32];
373
374    let mut min_val = x[0];
375    let mut max_val = x[0];
376    let mut sum_w = match weights {
377        Some(w) => w[0],
378        None => x[0] * x[0],
379    };
380    let mut sum_x = sum_w * x[0];
381
382    for i in 1..n {
383        if x[i] < min_val {
384            min_val = x[i];
385        }
386        if x[i] > max_val {
387            max_val = x[i];
388        }
389        let w = match weights {
390            Some(w) => w[i],
391            None => x[i] * x[i],
392        };
393        sum_w += w;
394        sum_x += w * x[i];
395    }
396
397    if min_val > 0.0 {
398        min_val = 0.0;
399    }
400
401    if max_val <= min_val {
402        return (0.0, -min_val);
403    }
404
405    let mut iscale = nmax as f32 / (max_val - min_val);
406    let mut scale = 1.0 / iscale;
407    let mut best_mad = 0.0;
408
409    for i in 0..n {
410        let l_val = nearest_int(iscale * (x[i] - min_val)).clamp(0, nmax) as u8;
411        l[i] = l_val;
412        let diff = scale * (l_val as f32) + min_val - x[i];
413        let diff = if use_mad { diff.abs() } else { diff * diff };
414        let w = match weights {
415            Some(w) => w[i],
416            None => x[i] * x[i],
417        };
418        best_mad += w * diff;
419    }
420
421    if nstep < 1 {
422        return (scale, -min_val);
423    }
424
425    for is in 0..=nstep {
426        iscale = (rmin + rdelta * is as f32 + nmax as f32) / (max_val - min_val);
427        let (mut sum_l, mut sum_l2, mut sum_xl) = (0.0, 0.0, 0.0);
428
429        for i in 0..n {
430            let l_val = nearest_int(iscale * (x[i] - min_val)).clamp(0, nmax) as u8;
431            l_aux[i] = l_val;
432            let w = match weights {
433                Some(w) => w[i],
434                None => x[i] * x[i],
435            };
436            sum_l += w * l_val as f32;
437            sum_l2 += w * (l_val as f32).powi(2);
438            sum_xl += w * l_val as f32 * x[i];
439        }
440
441        let d = sum_w * sum_l2 - sum_l * sum_l;
442        if d > 0.0 {
443            let mut this_scale = (sum_w * sum_xl - sum_x * sum_l) / d;
444            let mut this_min = (sum_l2 * sum_x - sum_l * sum_xl) / d;
445
446            if this_min > 0.0 {
447                this_min = 0.0;
448                this_scale = sum_xl / sum_l2;
449            }
450
451            let mut mad = 0.0;
452            for i in 0..n {
453                let diff = this_scale * (l_aux[i] as f32) + this_min - x[i];
454                let diff = if use_mad { diff.abs() } else { diff * diff };
455                let w = match weights {
456                    Some(w) => w[i],
457                    None => x[i] * x[i],
458                };
459                mad += w * diff;
460            }
461
462            if mad < best_mad {
463                l.copy_from_slice(&l_aux);
464                best_mad = mad;
465                scale = this_scale;
466                min_val = this_min;
467            }
468        }
469    }
470
471    (scale, -min_val)
472}
473
474// https://github.com/ggerganov/llama.cpp/blob/678d7994f4da0af3d29046be99950ac999ee9762/ggml/src/ggml-quants.c#L827
475pub(super) fn make_qp_quants(
476    n: usize,
477    nmax: u8,
478    x: &[f32],
479    l: &mut [u8],
480    quant_weights: &[f32],
481) -> f32 {
482    assert_eq!(x.len(), n);
483    assert_eq!(l.len(), n);
484    assert_eq!(quant_weights.len(), n);
485
486    let max = x.iter().copied().fold(0.0, f32::max);
487    if max == 0.0 {
488        l.iter_mut().for_each(|li| *li = 0);
489        return 0.0;
490    }
491
492    let mut iscale = nmax as f32 / max;
493    for (xi, li) in x.iter().zip(l.iter_mut()) {
494        *li = nearest_int(iscale * xi) as u8;
495    }
496
497    let scale = 1.0 / iscale;
498    let mut best_mse = x
499        .iter()
500        .zip(l.iter())
501        .zip(quant_weights.iter())
502        .map(|((&xi, &li), &w)| {
503            let diff = xi - scale * li as f32;
504            w * diff * diff
505        })
506        .sum::<f32>();
507
508    for is in -4..=4 {
509        if is == 0 {
510            continue;
511        }
512        let iscale_is = (0.1 * is as f32 + nmax as f32) / max;
513        let scale_is = 1.0 / iscale_is;
514
515        let mse = x
516            .iter()
517            .zip(quant_weights.iter())
518            .map(|(&xi, &w)| {
519                let mut li = nearest_int(iscale_is * xi) as u8;
520                li = li.min(nmax);
521                let diff = xi - scale_is * li as f32;
522                w * diff * diff
523            })
524            .sum::<f32>();
525
526        if mse < best_mse {
527            best_mse = mse;
528            iscale = iscale_is;
529        }
530    }
531
532    let mut sumlx = 0.0;
533    let mut suml2 = 0.0;
534    for ((xi, li), &w) in x.iter().zip(l.iter_mut()).zip(quant_weights.iter()) {
535        let mut li_new = (iscale * xi).round() as u8;
536        li_new = li_new.min(nmax);
537        *li = li_new;
538        sumlx += w * xi * li_new as f32;
539        suml2 += w * (li_new as f32).powi(2);
540    }
541
542    for _ in 0..5 {
543        let mut n_changed = 0;
544        for ((xi, li), &w) in x.iter().zip(l.iter_mut()).zip(quant_weights.iter()) {
545            let mut slx = sumlx - w * xi * *li as f32;
546            let mut sl2 = suml2 - w * (*li as f32).powi(2);
547            if slx > 0.0 && sl2 > 0.0 {
548                let new_li = (nearest_int(xi * sl2 / slx) as u8).min(nmax);
549                if new_li != *li {
550                    slx += w * xi * new_li as f32;
551                    sl2 += w * (new_li as f32).powi(2);
552                    if slx.powi(2) * suml2 > sumlx.powi(2) * sl2 {
553                        *li = new_li;
554                        sumlx = slx;
555                        suml2 = sl2;
556                        n_changed += 1;
557                    }
558                }
559            }
560        }
561        if n_changed == 0 {
562            break;
563        }
564    }
565
566    sumlx / suml2
567}