use import::quant::canonical as enc;
use run::backend::cpu::quant::canonical as dec;
fn assert_within(actual: &[f32], expected: &[f32], tol: f32, label: &str) {
assert_eq!(actual.len(), expected.len(), "{label}: length mismatch");
let mut max_err = 0.0f32;
for (i, (&a, &e)) in actual.iter().zip(expected.iter()).enumerate() {
let err = (a - e).abs();
if err > tol {
panic!(
"{label}: index {i} actual={a} expected={e} err={err} > tol={tol}"
);
}
if err > max_err {
max_err = err;
}
}
eprintln!("{label}: max_err = {max_err:.6} (tol {tol})");
}
#[test]
fn u32_roundtrip_typical_weights() {
let input = vec![0.0, 1.0, -1.0, 0.5, -0.5, 1e-3, -1e-3, 32767.0, -32768.0];
let bytes = enc::f32_to_u32(&input);
let back = dec::u32_to_f32(&bytes);
assert_within(&back, &input, 2.0 / 65536.0, "u32 roundtrip");
}
#[test]
fn u32_zero_is_exact() {
let input = vec![0.0; 16];
let bytes = enc::f32_to_u32(&input);
let back = dec::u32_to_f32(&bytes);
for v in back {
assert_eq!(v, 0.0);
}
}
#[test]
fn u16_roundtrip_typical_weights() {
let input = vec![0.0, 1.0, -1.0, 0.5, -0.5, 0.1, -0.1, 127.0, -127.0];
let bytes = enc::f32_to_u16(&input);
let back = dec::u16_to_f32(&bytes);
assert_within(&back, &input, 2.0 / 256.0, "u16 roundtrip");
}
#[test]
fn u16_saturates_silently_above_128() {
let input = vec![1000.0, -1000.0];
let bytes = enc::f32_to_u16(&input);
let back = dec::u16_to_f32(&bytes);
assert!(back[0] > 127.0 && back[0] < 128.0, "saturated +max: {}", back[0]);
assert!(back[1] >= -128.0 && back[1] < -127.0, "saturated -min: {}", back[1]);
}
#[test]
fn q8_roundtrip_random_block() {
let input: Vec<f32> = (0..32).map(|i| (i as f32 - 16.0) * 0.05).collect();
let bytes = enc::f32_to_q8(&input);
assert_eq!(bytes.len(), 2 + 32);
let back = dec::q8_to_f32(&bytes);
let block_max = input.iter().fold(0.0f32, |a, &v| a.max(v.abs()));
let q_step = block_max / 127.0;
assert_within(&back, &input, q_step + 1.0 / 256.0, "q8 roundtrip");
}
#[test]
fn q8_zero_block_is_zeros() {
let input = vec![0.0f32; 32];
let bytes = enc::f32_to_q8(&input);
let back = dec::q8_to_f32(&bytes);
for v in back {
assert_eq!(v, 0.0);
}
}
#[test]
fn q4_roundtrip_block() {
let input: Vec<f32> = (0..32).map(|i| (i as f32 - 16.0) * 0.05).collect();
let bytes = enc::f32_to_q4(&input);
assert_eq!(bytes.len(), 2 + 16);
let back = dec::q4_to_f32(&bytes);
let block_max = input.iter().fold(0.0f32, |a, &v| a.max(v.abs()));
let q_step = block_max / 8.0;
assert_within(&back, &input, q_step + 1.0 / 256.0, "q4 roundtrip");
}
#[test]
fn q4_zero_block_is_zeros() {
let input = vec![0.0f32; 32];
let bytes = enc::f32_to_q4(&input);
let back = dec::q4_to_f32(&bytes);
for v in back {
assert_eq!(v, 0.0);
}
}
#[test]
fn q4_block_size_strictness() {
let input = vec![0.1f32; 30];
let result = std::panic::catch_unwind(|| enc::f32_to_q4(&input));
assert!(result.is_err(), "non-32-multiple should panic");
}
#[test]
fn ternary_roundtrip_exact_for_canonical_values() {
let input: Vec<f32> = (0..32)
.map(|i| match i % 3 {
0 => 0.0,
1 => 1.0,
_ => -1.0,
})
.collect();
let bytes = enc::f32_to_ternary(&input);
assert_eq!(bytes.len(), 8);
let back = dec::ternary_to_f32(&bytes);
assert_eq!(back, input);
}
#[test]
fn ternary_quantizes_by_threshold() {
let input: Vec<f32> = vec![
0.0, 0.49, -0.49, 0.5, -0.5, 0.7, -0.7, 0.1, -0.1, 0.6, -0.6, 1.5, -1.5, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];
let bytes = enc::f32_to_ternary(&input);
let back = dec::ternary_to_f32(&bytes);
assert_eq!(back[0], 0.0);
assert_eq!(back[1], 0.0); assert_eq!(back[2], 0.0); assert_eq!(back[3], 1.0); assert_eq!(back[4], -1.0); assert_eq!(back[5], 1.0);
assert_eq!(back[6], -1.0);
assert_eq!(back[7], 0.0);
assert_eq!(back[8], 0.0);
assert_eq!(back[9], 1.0);
assert_eq!(back[10], -1.0);
assert_eq!(back[11], 1.0); assert_eq!(back[12], -1.0); }
#[test]
fn q4_first_byte_is_u16_scale_lo_byte() {
let mut input = vec![0.0f32; 32];
input[0] = 1.0;
input[16] = -1.0;
let bytes = enc::f32_to_q4(&input);
assert_eq!(bytes[0], 0x00, "u16 scale lo byte");
assert_eq!(bytes[1], 0x01, "u16 scale hi byte");
assert_eq!(bytes[2], 0x0F, "first nibble byte (split layout)");
}
#[test]
fn q8_first_two_bytes_are_u16_scale() {
let mut input = vec![0.0f32; 32];
input[0] = 1.0;
let bytes = enc::f32_to_q8(&input);
let scale_u16 = i16::from_le_bytes([bytes[0], bytes[1]]);
assert_eq!(scale_u16, 256, "scale u16 = round(1.0 * 256) = 256");
assert_eq!(bytes[2] as i8, 127);
}