use half::f16;
use rayon::prelude::*;
use super::common::{best_index_int8, nearest_int, GROUP_MAX_EPS};
pub const QK_K: usize = 256;
pub const SUB_BLOCK: usize = 32;
pub const BLOCK_BYTES: usize = 2 + 2 + QK_K / 64 + QK_K / 2;
const KVALUES_IQ4NL: [i8; 16] = [
-127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113,
];
fn quantize_block_iq4_xs(
x: &[f32], out: &mut [u8], values: &[i8], quant_weights: Option<&[f32]>, ntry: i32,
) {
debug_assert_eq!(x.len(), QK_K);
debug_assert_eq!(out.len(), BLOCK_BYTES);
debug_assert_eq!(values.len(), 16);
let super_block_size = QK_K;
let block_size = SUB_BLOCK; let n_sub = super_block_size / block_size;
let mut sigma2 = 0.0f32;
for &v in x.iter() {
sigma2 = v.mul_add(v, sigma2);
}
sigma2 *= 2.0 / (super_block_size as f32);
let mut l_buf = [0u8; QK_K];
let mut scales = [0.0f32; 8];
let mut weight = [0.0f32; SUB_BLOCK];
let mut max_scale = 0.0f32;
let mut amax_scale = 0.0f32;
for ib in 0..n_sub {
let xb_start = ib * block_size;
let xb = &x[xb_start..xb_start + block_size];
if let Some(qw) = quant_weights {
let qwb = &qw[xb_start..xb_start + block_size];
for j in 0..block_size {
weight[j] = qwb[j] * (sigma2 + xb[j] * xb[j]).sqrt();
}
} else {
for j in 0..block_size {
weight[j] = xb[j] * xb[j];
}
}
let mut amax = 0.0f32;
let mut max = 0.0f32;
for j in 0..block_size {
let ax = xb[j].abs();
if ax > amax {
amax = ax;
max = xb[j];
}
}
if amax < GROUP_MAX_EPS {
scales[ib] = 0.0;
continue;
}
let mut d = if ntry > 0 {
-max / (values[0] as f32)
} else {
max / (values[0] as f32)
};
let mut id = 1.0 / d;
let lb = &mut l_buf[xb_start..xb_start + block_size];
let mut sumqx = 0.0f32;
let mut sumq2 = 0.0f32;
for j in 0..block_size {
let al = id * xb[j];
let l = best_index_int8(values, al);
lb[j] = l as u8;
let q = values[l] as f32;
let w = weight[j];
sumqx = (w * q).mul_add(xb[j], sumqx);
sumq2 = (w * q).mul_add(q, sumq2);
}
d = if sumq2 > 0.0 { sumqx / sumq2 } else { 0.0 };
let mut best = d * sumqx;
let lo = -ntry;
let hi = ntry;
for itry in lo..=hi {
id = ((itry as f32) + (values[0] as f32)) / max;
let mut sumqx_t = 0.0f32;
let mut sumq2_t = 0.0f32;
for j in 0..block_size {
let al = id * xb[j];
let l = best_index_int8(values, al);
let q = values[l] as f32;
let w = weight[j];
sumqx_t = (w * q).mul_add(xb[j], sumqx_t);
sumq2_t = (w * q).mul_add(q, sumq2_t);
}
if sumq2_t > 0.0 && sumqx_t * sumqx_t > best * sumq2_t {
d = sumqx_t / sumq2_t;
best = d * sumqx_t;
}
}
scales[ib] = d;
let abs_d = d.abs();
if abs_d > amax_scale {
amax_scale = abs_d;
max_scale = d;
}
}
let mut scales_h_acc: u16 = 0;
let mut scales_l_buf = [0u8; QK_K / 64];
let d = -max_scale / 32.0;
let dh = f16::from_f32(d);
let id_super = if d != 0.0 { 1.0 / d } else { 0.0 };
for ib in 0..n_sub {
let mut l_signed = nearest_int(id_super * scales[ib]);
if l_signed < -32 {
l_signed = -32;
} else if l_signed > 31 {
l_signed = 31;
}
let dl = d * (l_signed as f32);
let idl = if dl != 0.0 { 1.0 / dl } else { 0.0 };
let xb_start = ib * block_size;
let xb = &x[xb_start..xb_start + block_size];
let lb = &mut l_buf[xb_start..xb_start + block_size];
for j in 0..block_size {
lb[j] = best_index_int8(values, idl * xb[j]) as u8;
}
let l_unsigned: u8 = (l_signed + 32) as u8;
let l_l = l_unsigned & 0xf;
let l_h = l_unsigned >> 4; if ib % 2 == 0 {
scales_l_buf[ib / 2] = l_l;
} else {
scales_l_buf[ib / 2] |= l_l << 4;
}
scales_h_acc |= (l_h as u16) << (2 * ib);
}
let mut qs = [0u8; QK_K / 2]; for i in 0..n_sub {
let base = i * SUB_BLOCK;
for j in 0..16 {
qs[16 * i + j] = l_buf[base + j] | (l_buf[base + 16 + j] << 4);
}
}
out[0..2].copy_from_slice(&dh.to_le_bytes());
out[2..4].copy_from_slice(&scales_h_acc.to_le_bytes());
out[4..8].copy_from_slice(&scales_l_buf);
out[8..].copy_from_slice(&qs);
}
pub fn quantize(src: &[f32], n_per_row: usize, imatrix: Option<&[f32]>) -> Vec<u8> {
assert!(
n_per_row % QK_K == 0,
"n_per_row {} not multiple of QK_K {}",
n_per_row,
QK_K
);
assert!(
src.len() % n_per_row == 0,
"src len {} not multiple of n_per_row {}",
src.len(),
n_per_row
);
if let Some(im) = imatrix {
assert_eq!(
im.len(),
n_per_row,
"imatrix len {} must equal n_per_row {} (per-row weights)",
im.len(),
n_per_row,
);
}
let ntry: i32 = 7;
let nblock_per_row = n_per_row / QK_K;
let nrows = src.len() / n_per_row;
let row_bytes = nblock_per_row * BLOCK_BYTES;
let mut out = vec![0u8; nrows * row_bytes];
out.par_chunks_exact_mut(row_bytes)
.enumerate()
.for_each(|(row, row_dst)| {
let row_src = &src[row * n_per_row..(row + 1) * n_per_row];
for ibl in 0..nblock_per_row {
let xb = &row_src[ibl * QK_K..(ibl + 1) * QK_K];
let qw_block = imatrix.map(|im| &im[ibl * QK_K..(ibl + 1) * QK_K]);
let blk_out = &mut row_dst[ibl * BLOCK_BYTES..(ibl + 1) * BLOCK_BYTES];
quantize_block_iq4_xs(xb, blk_out, &KVALUES_IQ4NL, qw_block, ntry);
}
});
out
}
#[cfg(test)]
pub fn dequantize(bytes: &[u8], n_per_row: usize) -> Vec<f32> {
assert_eq!(bytes.len() % BLOCK_BYTES, 0);
assert_eq!(n_per_row % QK_K, 0);
let nblock_per_row = n_per_row / QK_K;
let row_bytes = nblock_per_row * BLOCK_BYTES;
assert_eq!(bytes.len() % row_bytes, 0);
let nrows = bytes.len() / row_bytes;
let mut out = vec![0.0f32; nrows * n_per_row];
for row in 0..nrows {
let row_src = &bytes[row * row_bytes..(row + 1) * row_bytes];
let row_dst = &mut out[row * n_per_row..(row + 1) * n_per_row];
for ibl in 0..nblock_per_row {
let blk = &row_src[ibl * BLOCK_BYTES..(ibl + 1) * BLOCK_BYTES];
let d = f16::from_le_bytes([blk[0], blk[1]]).to_f32();
let scales_h = u16::from_le_bytes([blk[2], blk[3]]);
let scales_l = &blk[4..8];
let qs = &blk[8..];
let dst_blk = &mut row_dst[ibl * QK_K..(ibl + 1) * QK_K];
for ib in 0..(QK_K / 32) {
let lo = (scales_l[ib / 2] >> (4 * (ib % 2))) & 0xf;
let hi = ((scales_h >> (2 * ib)) & 0x3) as u8;
let ls = (lo | (hi << 4)) as i32;
let dl = d * ((ls - 32) as f32);
let qs_sub = &qs[16 * ib..16 * (ib + 1)];
let dst_sub = &mut dst_blk[32 * ib..32 * (ib + 1)];
for j in 0..16 {
dst_sub[j] = dl * (KVALUES_IQ4NL[(qs_sub[j] & 0xf) as usize] as f32);
dst_sub[j + 16] = dl * (KVALUES_IQ4NL[(qs_sub[j] >> 4) as usize] as f32);
}
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;
fn fixture_path(name: &str) -> PathBuf {
let manifest =
std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set by cargo test");
PathBuf::from(manifest)
.join("tests/fixtures/ggml_quants")
.join(name)
}
fn read_f32s(name: &str) -> Vec<f32> {
let bytes = fs::read(fixture_path(name)).expect("read fixture");
assert!(bytes.len() % 4 == 0);
bytes
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
}
fn read_bytes(name: &str) -> Vec<u8> {
fs::read(fixture_path(name)).expect("read fixture")
}
fn make_imatrix(n: usize, seed: u32) -> Vec<f32> {
let mut state = seed;
(0..n)
.map(|_| {
state = state.wrapping_add(0x6D2B79F5);
let mut t = state;
t = (t ^ (t >> 15)).wrapping_mul(t | 1);
t ^= t.wrapping_add((t ^ (t >> 7)).wrapping_mul(t | 61));
let u = t ^ (t >> 14);
let v = (u as f32 / u32::MAX as f32) * 2.0 - 1.0;
v.abs() + 1e-3
})
.collect()
}
#[test]
fn byte_cmp_noim() {
let input = read_f32s("iq4_xs_512_noim_input.bin");
let expected = read_bytes("iq4_xs_512_noim_expected.bin");
let got = quantize(&input, 512, None);
assert_eq!(got.len(), expected.len(), "IQ4_XS noim length mismatch");
assert_eq!(got, expected, "IQ4_XS noim byte-cmp failed");
}
#[test]
fn byte_cmp_im() {
let input = read_f32s("iq4_xs_512_im_input.bin");
let expected = read_bytes("iq4_xs_512_im_input.bin"); let _ = expected; let expected = read_bytes("iq4_xs_512_im_expected.bin");
let imatrix = make_imatrix(512, 2);
let got = quantize(&input, 512, Some(&imatrix));
assert_eq!(got.len(), expected.len(), "IQ4_XS im length mismatch");
assert_eq!(got, expected, "IQ4_XS im byte-cmp failed");
}
#[test]
fn round_trip_sanity() {
let input: Vec<f32> = (0..QK_K)
.map(|i| {
let t = i as f32 / QK_K as f32;
0.5 * (t * std::f32::consts::TAU * 4.0).sin()
})
.collect();
let bytes = quantize(&input, QK_K, None);
assert_eq!(bytes.len(), BLOCK_BYTES);
let recovered = dequantize(&bytes, QK_K);
assert_eq!(recovered.len(), QK_K);
let mut sse = 0.0f64;
for j in 0..QK_K {
let e = (recovered[j] - input[j]) as f64;
sse += e * e;
}
let rmse = (sse / QK_K as f64).sqrt();
assert!(rmse < 0.05, "IQ4_XS round-trip RMSE too high: {rmse}");
}
#[test]
fn all_zero_block() {
let input = vec![0.0f32; QK_K];
let bytes = quantize(&input, QK_K, None);
let recovered = dequantize(&bytes, QK_K);
for v in recovered.iter() {
assert_eq!(*v, 0.0);
}
}
#[test]
fn multi_row_multi_block() {
let n_per_row = QK_K * 3;
let nrows = 2;
let input: Vec<f32> = (0..nrows * n_per_row)
.map(|i| ((i as f32) * 0.001).sin() * 0.3)
.collect();
let bytes = quantize(&input, n_per_row, None);
let expected_bytes = nrows * (n_per_row / QK_K) * BLOCK_BYTES;
assert_eq!(bytes.len(), expected_bytes);
let recovered = dequantize(&bytes, n_per_row);
assert_eq!(recovered.len(), nrows * n_per_row);
let max_err = input
.iter()
.zip(recovered.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max);
assert!(max_err < 0.1, "max abs error too high: {max_err}");
}
}