use super::fit::{nearest_int, GROUP_MAX_EPS};
pub(crate) fn make_qp_quants(x: &[f32], l: &mut [u8], nmax: i32, quant_weights: &[f32]) -> f32 {
let n = x.len();
debug_assert_eq!(l.len(), n);
debug_assert_eq!(quant_weights.len(), n);
let mut max = 0f32;
for &v in x {
if v > max {
max = v;
}
}
if max < GROUP_MAX_EPS {
l[..n].fill(0);
return 0.0;
}
let mut iscale = nmax as f32 / max;
for i in 0..n {
l[i] = nearest_int(iscale * x[i]) as u8;
}
let scale = 1.0 / iscale;
let mut best_mse = 0f32;
for i in 0..n {
let diff = (-scale).mul_add(l[i] as f32, x[i]);
let w = quant_weights[i];
best_mse = (w * diff).mul_add(diff, best_mse);
}
for is in -4..=4i32 {
if is == 0 {
continue;
}
let iscale_is = (0.1 * is as f32 + nmax as f32) / max;
let scale_is = 1.0 / iscale_is;
let mut mse = 0f32;
for i in 0..n {
let li = nearest_int(iscale_is * x[i]).min(nmax);
let diff = (-scale_is).mul_add(li as f32, x[i]);
let w = quant_weights[i];
mse = (w * diff).mul_add(diff, mse);
}
if mse < best_mse {
best_mse = mse;
iscale = iscale_is;
}
}
let mut sumlx = 0f32;
let mut suml2 = 0f32;
for i in 0..n {
let li = nearest_int(iscale * x[i]).min(nmax);
l[i] = li as u8;
let w = quant_weights[i];
sumlx = (w * x[i]).mul_add(li as f32, sumlx);
suml2 = (w * li as f32).mul_add(li as f32, suml2);
}
for _itry in 0..5 {
let mut n_changed = 0;
for i in 0..n {
let w = quant_weights[i];
let li = l[i] as f32;
let mut slx = (-(w * x[i])).mul_add(li, sumlx);
let mut sl2 = (-(w * li)).mul_add(li, suml2);
if slx > 0.0 && sl2 > 0.0 {
let new_l = nearest_int(x[i] * sl2 / slx).min(nmax);
if new_l != l[i] as i32 {
slx = (w * x[i]).mul_add(new_l as f32, slx);
sl2 = (w * new_l as f32).mul_add(new_l as f32, sl2);
if slx * slx * suml2 > sumlx * sumlx * sl2 {
l[i] = new_l as u8;
sumlx = slx;
suml2 = sl2;
n_changed += 1;
}
}
}
}
if n_changed == 0 {
break;
}
}
if suml2 > 0.0 {
sumlx / suml2
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_input_with_no_positive_entry_yields_zero_codes_and_zero_scale() {
let mut l = [9u8; 8];
assert_eq!(make_qp_quants(&[0.0; 8], &mut l, 63, &[1.0; 8]), 0.0);
assert_eq!(l, [0u8; 8]);
let mut l = [9u8; 8];
assert_eq!(make_qp_quants(&[-0.5; 8], &mut l, 63, &[1.0; 8]), 0.0);
assert_eq!(l, [0u8; 8]);
}
#[test]
fn the_importance_weights_change_the_fit() {
let x = [
0.031f32, 0.0155, 0.0071, 0.0203, 0.0119, 0.0298, 0.0043, 0.0176,
];
let mut w_a = [1.0f32; 8];
w_a[2] = 40.0;
let mut w_b = [1.0f32; 8];
w_b[0] = 40.0;
let mut l_a = [0u8; 8];
let mut l_b = [0u8; 8];
let s_a = make_qp_quants(&x, &mut l_a, 63, &w_a);
let s_b = make_qp_quants(&x, &mut l_b, 63, &w_b);
assert!(
s_a != s_b || l_a != l_b,
"moving the weight changed nothing: {s_a} {l_a:?} vs {s_b} {l_b:?}"
);
}
#[test]
fn a_negative_entry_is_stored_as_its_uint8_wrap_not_clamped() {
let x = [0.02f32, 0.01, -0.001, 0.015, 0.012, 0.018, 0.005, 0.009];
let mut l = [0u8; 8];
make_qp_quants(&x, &mut l, 63, &[1.0; 8]);
assert!(l[2] > 63, "expected a wrapped negative code, got {}", l[2]);
assert!(l.iter().enumerate().all(|(i, &c)| i == 2 || c <= 63));
}
}