use crate::hadamard::fwht_normalized;
use crate::math;
use alloc::vec;
use alloc::vec::Vec;
pub const TERNARY_RECORD_BITS: u8 = 5;
pub const TERNARY_CENTROID: f32 = 1.2240064;
pub const CODEBOOK_WIDTHS: [u8; 3] = [2, 3, 4];
pub const CODEBOOK_LEN: usize = 4 + 8 + 16;
pub const MAX_GROUP: usize = 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CqError {
UnsupportedBits(u8),
BadGroup(usize),
BadCodebookLen(usize),
ShortBlob { need: usize, got: usize },
EmptyShape,
}
pub fn codebook_slice(codebook: &[f32], bits: u8) -> Result<&[f32], CqError> {
if codebook.len() != CODEBOOK_LEN {
return Err(CqError::BadCodebookLen(codebook.len()));
}
match bits {
2 => Ok(&codebook[0..4]),
3 => Ok(&codebook[4..12]),
4 => Ok(&codebook[12..28]),
other => Err(CqError::UnsupportedBits(other)),
}
}
#[inline]
pub fn packed_row_bytes(in_padded: usize, bits: u8) -> usize {
let effective = if bits == TERNARY_RECORD_BITS {
2
} else {
bits as usize
};
in_padded * effective / 8
}
pub struct CqWeight {
packed: Vec<u8>,
norms: Vec<f32>,
lut: Vec<f32>,
per_byte: usize,
row_bytes: usize,
num_groups: usize,
pub out_feat: usize,
pub in_feat: usize,
pub in_padded: usize,
pub group: usize,
pub bits: u8,
levels: Vec<f32>,
}
impl CqWeight {
pub fn from_blob(
blob: &[u8],
out_feat: usize,
in_feat: usize,
group: usize,
bits: u8,
codebook: &[f32],
) -> Result<Self, CqError> {
if out_feat == 0 || in_feat == 0 {
return Err(CqError::EmptyShape);
}
if group == 0 || !group.is_multiple_of(8) || !group.is_power_of_two() {
return Err(CqError::BadGroup(group));
}
let levels: Vec<f32> = if bits == TERNARY_RECORD_BITS {
let s = TERNARY_CENTROID / math::sqrt(group as f32);
vec![-s, 0.0, s]
} else {
codebook_slice(codebook, bits)?.to_vec()
};
let in_padded = in_feat.div_ceil(group) * group;
let num_groups = in_padded / group;
let row_bytes = packed_row_bytes(in_padded, bits);
let n_packed = out_feat * row_bytes;
let n_norm_bytes = out_feat * num_groups * 2;
if blob.len() < n_packed + n_norm_bytes {
return Err(CqError::ShortBlob {
need: n_packed + n_norm_bytes,
got: blob.len(),
});
}
let packed = blob[..n_packed].to_vec();
let mut norms = Vec::with_capacity(out_feat * num_groups);
for c in blob[n_packed..n_packed + n_norm_bytes].chunks_exact(2) {
norms.push(math::f16_to_f32(u16::from_le_bytes([c[0], c[1]])));
}
let (per_byte, lut) = build_lut(bits, &levels);
Ok(Self {
packed,
norms,
lut,
per_byte,
row_bytes,
num_groups,
out_feat,
in_feat,
in_padded,
group,
bits,
levels,
})
}
pub fn packed_bytes(&self) -> usize {
self.packed.len()
}
pub fn effective_bits(&self) -> f32 {
let total_bits = (self.packed.len() + self.norms.len() * 2) * 8;
total_bits as f32 / (self.out_feat * self.in_padded) as f32
}
#[inline]
pub fn prepared_len(&self) -> usize {
self.in_padded
}
pub fn prepare_input(&self, x: &[f32], xh: &mut [f32]) {
debug_assert_eq!(x.len(), self.in_feat);
debug_assert_eq!(xh.len(), self.in_padded);
xh[..self.in_feat].copy_from_slice(x);
xh[self.in_feat..].fill(0.0);
for g in xh.chunks_exact_mut(self.group) {
fwht_normalized(g);
}
}
pub fn matvec(&self, x: &[f32], y: &mut [f32]) {
let mut xh = vec![0.0f32; self.in_padded];
self.prepare_input(x, &mut xh);
self.matvec_prepared(&xh, y);
}
pub fn matvec_prepared(&self, xh: &[f32], y: &mut [f32]) {
self.matvec_rows_prepared(xh, 0, y)
}
pub fn matvec_rows_prepared(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
#[cfg(feature = "parallel")]
if self.should_parallelise(y.len()) {
return self.matvec_rows_par(xh, row_start, y);
}
self.matvec_rows_serial(xh, row_start, y)
}
#[cfg(feature = "parallel")]
fn matvec_rows_par(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
use rayon::prelude::*;
let rows = y.len();
let chunk = rows
.div_ceil(rayon::current_num_threads())
.max(MIN_PAR_ROWS);
y.par_chunks_mut(chunk).enumerate().for_each(|(ci, part)| {
self.matvec_rows_serial(xh, row_start + ci * chunk, part);
});
}
fn matvec_rows_serial(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
debug_assert_eq!(xh.len(), self.in_padded);
debug_assert!(row_start + y.len() <= self.out_feat);
match self.per_byte {
4 => self.matvec_lut::<4>(xh, row_start, y),
2 => self.matvec_lut::<2>(xh, row_start, y),
_ => self.matvec_generic(xh, row_start, y),
}
}
#[cfg(feature = "bench-internals")]
pub fn matvec_prepared_single_acc(&self, xh: &[f32], y: &mut [f32]) {
debug_assert_eq!(xh.len(), self.in_padded);
debug_assert_eq!(y.len(), self.out_feat);
match self.per_byte {
4 => self.single_acc::<4>(xh, y),
2 => self.single_acc::<2>(xh, y),
_ => self.matvec_prepared_reference(xh, y),
}
}
#[cfg(feature = "bench-internals")]
fn single_acc<const P: usize>(&self, xh: &[f32], y: &mut [f32]) {
let bytes_per_group = self.group / P;
let lut = &self.lut[..256 * P];
for o in 0..self.out_feat {
let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
let mut total = 0.0f32;
for (g, &norm) in row_norms.iter().enumerate() {
let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
let gx = &xh[g * self.group..(g + 1) * self.group];
let mut acc = 0.0f32;
for (bi, &byte) in gbytes.iter().enumerate() {
let base = byte as usize * P;
let vals: &[f32; P] = lut[base..base + P].try_into().unwrap();
let xs: &[f32; P] = gx[bi * P..bi * P + P].try_into().unwrap();
for k in 0..P {
acc += vals[k] * xs[k];
}
}
total += norm * acc;
}
y[o] = total;
}
}
pub fn matvec_prepared_reference(&self, xh: &[f32], y: &mut [f32]) {
debug_assert_eq!(xh.len(), self.in_padded);
debug_assert_eq!(y.len(), self.out_feat);
let bits = if self.bits == TERNARY_RECORD_BITS {
2
} else {
self.bits as usize
};
let mask = (1u32 << bits) - 1;
for (o, slot) in y.iter_mut().enumerate() {
let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
let mut total = 0.0f32;
for g in 0..self.num_groups {
let norm = self.norms[o * self.num_groups + g];
let mut acc = 0.0f32;
for k in 0..self.group {
let code = read_bits(row, (g * self.group + k) * bits, bits, mask);
let idx = if self.bits == TERNARY_RECORD_BITS {
match code {
3 => 0,
0 => 1,
1 => 2,
_ => 1,
}
} else {
code as usize
};
acc += self.levels[idx] * xh[g * self.group + k];
}
total += norm * acc;
}
*slot = total;
}
}
pub fn matmul_rows_prepared(
&self,
xh: &[f32],
batch: usize,
row_start: usize,
rows: usize,
y: &mut [f32],
acc: &mut [f32],
) {
#[cfg(feature = "parallel")]
if self.matmul_work(rows, batch) >= PAR_MIN_MACS {
if rows >= 2 * MIN_PAR_ROWS {
return self.matmul_rows_par(xh, batch, row_start, rows, y);
}
if batch >= 2 * MIN_PAR_BATCH {
return self.matmul_batch_par(xh, batch, row_start, rows, y);
}
}
self.matmul_rows_serial(xh, batch, row_start, rows, y, acc)
}
#[cfg(feature = "parallel")]
#[inline]
fn should_parallelise(&self, rows: usize) -> bool {
rows >= 2 * MIN_PAR_ROWS && rows.saturating_mul(self.in_padded) >= PAR_MIN_MACS
}
#[cfg(feature = "parallel")]
fn matmul_rows_par(
&self,
xh: &[f32],
batch: usize,
row_start: usize,
rows: usize,
y: &mut [f32],
) {
use rayon::prelude::*;
struct Out(*mut f32);
unsafe impl Send for Out {}
unsafe impl Sync for Out {}
impl Out {
#[inline]
fn ptr(&self) -> *mut f32 {
self.0
}
}
let out = Out(y.as_mut_ptr());
let chunk = rows
.div_ceil(rayon::current_num_threads())
.max(MIN_PAR_ROWS);
let bands: Vec<usize> = (0..rows).step_by(chunk).collect();
bands.into_par_iter().for_each(|r0| {
let band = chunk.min(rows - r0);
let mut local = vec![0.0f32; batch * band];
let mut acc = vec![0.0f32; batch];
self.matmul_rows_serial(xh, batch, row_start + r0, band, &mut local, &mut acc);
for b in 0..batch {
unsafe {
core::ptr::copy_nonoverlapping(
local.as_ptr().add(b * band),
out.ptr().add(b * rows + r0),
band,
);
}
}
});
}
#[cfg(feature = "parallel")]
fn matmul_batch_par(
&self,
xh: &[f32],
batch: usize,
row_start: usize,
rows: usize,
y: &mut [f32],
) {
use rayon::prelude::*;
let stride = self.in_padded;
let band = batch
.div_ceil(rayon::current_num_threads())
.max(MIN_PAR_BATCH);
y[..batch * rows]
.par_chunks_mut(band * rows)
.enumerate()
.for_each(|(ci, part)| {
let b0 = ci * band;
let n = part.len() / rows;
let mut acc = vec![0.0f32; n];
self.matmul_rows_serial(
&xh[b0 * stride..(b0 + n) * stride],
n,
row_start,
rows,
part,
&mut acc,
);
});
}
#[cfg(feature = "parallel")]
#[inline]
fn matmul_work(&self, rows: usize, batch: usize) -> usize {
rows.saturating_mul(batch).saturating_mul(self.in_padded)
}
#[allow(clippy::too_many_arguments)]
fn matmul_rows_serial(
&self,
xh: &[f32],
batch: usize,
row_start: usize,
rows: usize,
y: &mut [f32],
acc: &mut [f32],
) {
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
if crate::quant::has_avx2() {
return unsafe { self.matmul_rows_prepared_avx2(xh, batch, row_start, rows, y, acc) };
}
self.matmul_rows_prepared_impl(xh, batch, row_start, rows, y, acc)
}
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
#[target_feature(enable = "avx2,fma")]
unsafe fn matmul_rows_prepared_avx2(
&self,
xh: &[f32],
batch: usize,
row_start: usize,
rows: usize,
y: &mut [f32],
acc: &mut [f32],
) {
self.matmul_rows_prepared_impl(xh, batch, row_start, rows, y, acc)
}
#[inline(always)]
fn matmul_rows_prepared_impl(
&self,
xh: &[f32],
batch: usize,
row_start: usize,
rows: usize,
y: &mut [f32],
acc: &mut [f32],
) {
debug_assert_eq!(xh.len(), batch * self.in_padded);
debug_assert_eq!(y.len(), batch * rows);
debug_assert!(acc.len() >= batch);
debug_assert!(row_start + rows <= self.out_feat);
debug_assert!(self.group <= MAX_GROUP);
let stride = self.in_padded;
let mut buf = [0.0f32; MAX_GROUP];
let ug = &mut buf[..self.group];
let lanewise = self.per_byte != 0;
for r in 0..rows {
let o = row_start + r;
let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
acc[..batch].fill(0.0);
for (g, &norm) in row_norms.iter().enumerate() {
self.decode_group(row, g, 1.0, ug);
let base = g * self.group;
for b in 0..batch {
let gx = &xh[b * stride + base..b * stride + base + self.group];
let s = if lanewise {
group_dot_lanes(ug, gx)
} else {
group_dot_serial(ug, gx)
};
acc[b] += norm * s;
}
}
for b in 0..batch {
y[b * rows + r] = acc[b];
}
}
}
fn matvec_lut<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
if crate::quant::has_avx2() {
return unsafe { self.matvec_lut_avx2::<P>(xh, row_start, y) };
}
self.matvec_lut_impl::<P>(xh, row_start, y)
}
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
#[target_feature(enable = "avx2,fma")]
unsafe fn matvec_lut_avx2<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
self.matvec_lut_impl::<P>(xh, row_start, y)
}
#[inline(always)]
fn matvec_lut_impl<const P: usize>(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
let bytes_per_group = self.group / P;
let lut = &self.lut[..256 * P];
for (yi, o) in (row_start..row_start + y.len()).enumerate() {
let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
let mut total = 0.0f32;
for (g, &norm) in row_norms.iter().enumerate() {
let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
let gx = &xh[g * self.group..(g + 1) * self.group];
total += norm * dot_group::<P>(lut, gbytes, gx);
}
y[yi] = total;
}
}
fn matvec_generic(&self, xh: &[f32], row_start: usize, y: &mut [f32]) {
let bits = self.bits as usize;
let mask = (1u32 << bits) - 1;
for (yi, o) in (row_start..row_start + y.len()).enumerate() {
let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
let mut total = 0.0f32;
for (g, &norm) in row_norms.iter().enumerate() {
let gx = &xh[g * self.group..(g + 1) * self.group];
let mut acc = 0.0f32;
for (k, &x) in gx.iter().enumerate() {
let bit = (g * self.group + k) * bits;
let idx = read_bits(row, bit, bits, mask);
acc += self.levels[idx as usize] * x;
}
total += norm * acc;
}
y[yi] = total;
}
}
pub fn dequantize_row(&self, o: usize, out: &mut [f32]) {
debug_assert!(o < self.out_feat);
debug_assert_eq!(out.len(), self.in_feat);
let row = &self.packed[o * self.row_bytes..(o + 1) * self.row_bytes];
let row_norms = &self.norms[o * self.num_groups..(o + 1) * self.num_groups];
let mut buf = vec![0.0f32; self.group];
for (g, &norm) in row_norms.iter().enumerate() {
self.decode_group(row, g, norm, &mut buf);
fwht_normalized(&mut buf);
let base = g * self.group;
for k in 0..self.group {
if base + k < self.in_feat {
out[base + k] = buf[k];
}
}
}
}
pub fn dequantize_to(&self, out: &mut [f32]) {
debug_assert_eq!(out.len(), self.out_feat * self.in_feat);
for o in 0..self.out_feat {
self.dequantize_row(o, &mut out[o * self.in_feat..(o + 1) * self.in_feat]);
}
}
fn decode_group(&self, row: &[u8], g: usize, norm: f32, buf: &mut [f32]) {
if self.per_byte != 0 {
let p = self.per_byte;
let bytes_per_group = self.group / p;
let gbytes = &row[g * bytes_per_group..(g + 1) * bytes_per_group];
for (bi, &byte) in gbytes.iter().enumerate() {
for k in 0..p {
buf[bi * p + k] = self.lut[byte as usize * p + k] * norm;
}
}
} else {
let bits = self.bits as usize;
let mask = (1u32 << bits) - 1;
for (k, slot) in buf.iter_mut().enumerate() {
let idx = read_bits(row, (g * self.group + k) * bits, bits, mask);
*slot = self.levels[idx as usize] * norm;
}
}
}
}
#[cfg(feature = "parallel")]
const MIN_PAR_ROWS: usize = 64;
#[cfg(feature = "parallel")]
const MIN_PAR_BATCH: usize = 8;
#[cfg(feature = "parallel")]
const PAR_MIN_MACS: usize = 1 << 20;
pub const ACC_LANES: usize = 8;
#[inline(always)]
fn dot_group<const P: usize>(lut: &[f32], gbytes: &[u8], gx: &[f32]) -> f32 {
debug_assert_eq!(ACC_LANES % P, 0);
let per_iter = ACC_LANES / P;
let mut lanes = [0.0f32; ACC_LANES];
let full = gbytes.len() - gbytes.len() % per_iter;
let mut bi = 0;
while bi < full {
let mut vals = [0.0f32; ACC_LANES];
for t in 0..per_iter {
let base = gbytes[bi + t] as usize * P;
let entry: &[f32; P] = lut[base..base + P].try_into().unwrap();
vals[t * P..(t + 1) * P].copy_from_slice(entry);
}
let xs: &[f32; ACC_LANES] = gx[bi * P..bi * P + ACC_LANES].try_into().unwrap();
for k in 0..ACC_LANES {
lanes[k] += vals[k] * xs[k];
}
bi += per_iter;
}
let mut acc: f32 = lanes.iter().sum();
for b in bi..gbytes.len() {
let base = gbytes[b] as usize * P;
for k in 0..P {
acc += lut[base + k] * gx[b * P + k];
}
}
acc
}
#[inline(always)]
fn group_dot_lanes(u: &[f32], x: &[f32]) -> f32 {
debug_assert_eq!(u.len(), x.len());
let chunks = u.len() / ACC_LANES;
let mut lanes = [0.0f32; ACC_LANES];
for c in 0..chunks {
let uu: &[f32; ACC_LANES] = u[c * ACC_LANES..(c + 1) * ACC_LANES].try_into().unwrap();
let xx: &[f32; ACC_LANES] = x[c * ACC_LANES..(c + 1) * ACC_LANES].try_into().unwrap();
for k in 0..ACC_LANES {
lanes[k] += uu[k] * xx[k];
}
}
let mut s: f32 = lanes.iter().sum();
for k in chunks * ACC_LANES..u.len() {
s += u[k] * x[k];
}
s
}
#[inline(always)]
fn group_dot_serial(u: &[f32], x: &[f32]) -> f32 {
let mut s = 0.0f32;
for (a, b) in u.iter().zip(x.iter()) {
s += a * b;
}
s
}
#[inline]
fn read_bits(row: &[u8], bit: usize, bits: usize, mask: u32) -> u32 {
let byte = bit / 8;
let shift = bit % 8;
let lo = row[byte] as u32;
let hi = if shift + bits > 8 {
row[byte + 1] as u32
} else {
0
};
((lo | (hi << 8)) >> shift) & mask
}
fn build_lut(bits: u8, levels: &[f32]) -> (usize, Vec<f32>) {
let ternary = bits == TERNARY_RECORD_BITS;
let width = if ternary { 2 } else { bits as usize };
let per_byte = match width {
2 => 4,
4 => 2,
_ => return (0, Vec::new()), };
let mask = (1u32 << width) - 1;
let mut lut = vec![0.0f32; 256 * per_byte];
for byte in 0..256usize {
for k in 0..per_byte {
let code = ((byte as u32) >> (k * width)) & mask;
let idx = if ternary {
match code {
3 => 0,
0 => 1,
1 => 2,
_ => 1,
}
} else {
code as usize
};
lut[byte * per_byte + k] = levels[idx];
}
}
(per_byte, lut)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hadamard::fwht_normalized;
fn fake_codebook(group: usize) -> Vec<f32> {
let s = 1.0 / math::sqrt(group as f32);
let mut cb = Vec::with_capacity(CODEBOOK_LEN);
for &bits in &CODEBOOK_WIDTHS {
let levels = 1usize << bits;
for i in 0..levels {
let v = (i as f32 - (levels as f32 - 1.0) / 2.0) / (levels as f32 / 2.0);
cb.push(v * s);
}
}
assert_eq!(cb.len(), CODEBOOK_LEN);
cb
}
fn pack_lsb(idx: &[u8], out: usize, in_pad: usize, bits: usize) -> Vec<u8> {
let row_bytes = in_pad * bits / 8;
let mut packed = vec![0u8; out * row_bytes];
for o in 0..out {
for k in 0..in_pad {
let v = idx[o * in_pad + k] as u32;
let bit = k * bits;
for b in 0..bits {
if (v >> b) & 1 == 1 {
let abs = bit + b;
packed[o * row_bytes + abs / 8] |= 1 << (abs % 8);
}
}
}
}
packed
}
fn f32_to_f16(v: f32) -> u16 {
let x = v.to_bits();
let sign = ((x >> 31) as u16) << 15;
let exp = ((x >> 23) & 0xFF) as i32 - 127;
let mant = x & 0x7F_FFFF;
if exp < -14 {
return sign;
}
if exp > 15 {
return sign | 0x7C00;
}
sign | (((exp + 15) as u16) << 10) | ((mant >> 13) as u16)
}
struct Case {
w: CqWeight,
dense: Vec<f32>,
}
fn build_case(out: usize, in_feat: usize, group: usize, bits: u8) -> Case {
let cb = fake_codebook(group);
let levels: Vec<f32> = if bits == TERNARY_RECORD_BITS {
let s = TERNARY_CENTROID / math::sqrt(group as f32);
vec![-s, 0.0, s]
} else {
codebook_slice(&cb, bits).unwrap().to_vec()
};
let n_levels = levels.len();
let in_pad = in_feat.div_ceil(group) * group;
let num_groups = in_pad / group;
let idx: Vec<u8> = (0..out * in_pad)
.map(|i| ((i * 7 + i / 13) % n_levels) as u8)
.collect();
let norms: Vec<f32> = (0..out * num_groups)
.map(|i| 0.25 + (i % 11) as f32 * 0.125)
.collect();
let packed = if bits == TERNARY_RECORD_BITS {
let crumbs: Vec<u8> = idx
.iter()
.map(|&t| if t == 0 { 3 } else { t - 1 })
.collect();
pack_lsb(&crumbs, out, in_pad, 2)
} else {
pack_lsb(&idx, out, in_pad, bits as usize)
};
let mut blob = packed;
for &n in &norms {
blob.extend_from_slice(&f32_to_f16(n).to_le_bytes());
}
let mut dense = vec![0.0f32; out * in_feat];
for o in 0..out {
for g in 0..num_groups {
let mut u: Vec<f32> = (0..group)
.map(|k| levels[idx[o * in_pad + g * group + k] as usize])
.collect();
let norm = math::f16_to_f32(f32_to_f16(norms[o * num_groups + g]));
for v in u.iter_mut() {
*v *= norm;
}
fwht_normalized(&mut u);
for (k, &val) in u.iter().enumerate() {
let col = g * group + k;
if col < in_feat {
dense[o * in_feat + col] = val;
}
}
}
}
let w = CqWeight::from_blob(&blob, out, in_feat, group, bits, &cb).unwrap();
Case { w, dense }
}
const WIDTHS: [u8; 4] = [2, 3, 4, TERNARY_RECORD_BITS];
#[test]
fn dequantize_matches_reference_reconstruction() {
for &bits in &WIDTHS {
for &(out, in_feat, group) in &[(5usize, 16usize, 16usize), (3, 40, 8), (7, 128, 128)] {
let c = build_case(out, in_feat, group, bits);
let mut got = vec![0.0f32; out * in_feat];
c.w.dequantize_to(&mut got);
for (i, (&g_i, &want)) in got.iter().zip(c.dense.iter()).enumerate() {
assert!(
(g_i - want).abs() < 1e-5,
"bits={bits} shape={out}x{in_feat} g={group} i={i}: {g_i} vs {want}"
);
}
}
}
}
#[test]
fn matvec_matches_dense_multiply() {
for &bits in &WIDTHS {
for &(out, in_feat, group) in &[
(5usize, 16usize, 16usize),
(3, 40, 8),
(9, 128, 128),
(4, 200, 64),
] {
let c = build_case(out, in_feat, group, bits);
let x: Vec<f32> = (0..in_feat)
.map(|i| (i as f32 * 0.31).sin() * 1.7 + 0.05 * i as f32)
.collect();
let mut got = vec![0.0f32; out];
c.w.matvec(&x, &mut got);
for (o, &g_o) in got.iter().enumerate() {
let want: f32 = (0..in_feat).map(|j| c.dense[o * in_feat + j] * x[j]).sum();
let tol = 1e-4 * want.abs().max(1.0);
assert!(
(g_o - want).abs() < tol,
"bits={bits} shape={out}x{in_feat} g={group} o={o}: {g_o} vs {want}"
);
}
}
}
}
#[test]
fn padding_does_not_leak_into_matvec() {
let c = build_case(6, 40, 32, 2);
let x: Vec<f32> = (0..40).map(|i| 1.0 + i as f32).collect();
let mut got = vec![0.0f32; 6];
c.w.matvec(&x, &mut got);
for (o, &g_o) in got.iter().enumerate() {
let want: f32 = (0..40).map(|j| c.dense[o * 40 + j] * x[j]).sum();
assert!((g_o - want).abs() < 1e-3 * want.abs().max(1.0));
}
}
#[test]
fn row_range_matches_full_matvec() {
for &bits in &WIDTHS {
let c = build_case(24, 128, 128, bits);
let x: Vec<f32> = (0..128).map(|i| (i as f32 * 0.19).sin()).collect();
let mut full = vec![0.0f32; 24];
c.w.matvec(&x, &mut full);
let mut xh = vec![0.0f32; c.w.prepared_len()];
c.w.prepare_input(&x, &mut xh);
for &(start, len) in &[(0usize, 4usize), (4, 4), (8, 16), (20, 4), (23, 1)] {
let mut band = vec![0.0f32; len];
c.w.matvec_rows_prepared(&xh, start, &mut band);
assert_eq!(
&band[..],
&full[start..start + len],
"bits={bits} band {start}+{len}"
);
}
}
}
#[test]
fn matvec_matches_simple_reference() {
for &bits in &WIDTHS {
for &(out, in_feat, group) in &[
(16usize, 128usize, 128usize),
(5, 40, 8),
(7, 200, 64),
(9, 512, 128),
] {
let c = build_case(out, in_feat, group, bits);
let x: Vec<f32> = (0..in_feat)
.map(|i| (i as f32 * 0.29).sin() * 2.0)
.collect();
let mut xh = vec![0.0f32; c.w.prepared_len()];
c.w.prepare_input(&x, &mut xh);
let mut fast = vec![0.0f32; out];
let mut slow = vec![0.0f32; out];
c.w.matvec_prepared(&xh, &mut fast);
c.w.matvec_prepared_reference(&xh, &mut slow);
let l1: f32 = xh.iter().map(|v| v.abs()).sum();
for o in 0..out {
assert!(
(fast[o] - slow[o]).abs() <= 1e-5 * l1.max(1.0),
"bits={bits} {out}x{in_feat} g={group} o={o}: {} vs {}",
fast[o],
slow[o]
);
}
}
}
}
#[test]
fn matmul_matches_repeated_matvec() {
for &bits in &WIDTHS {
for &(out, in_feat, group) in &[
(24usize, 128usize, 128usize),
(9, 40, 8),
(5, 200, 64),
(16, 512, 128),
] {
let c = build_case(out, in_feat, group, bits);
for &batch in &[1usize, 2, 7, 16] {
let mut xh = vec![0.0f32; batch * c.w.prepared_len()];
let mut xs = Vec::new();
for b in 0..batch {
let x: Vec<f32> = (0..in_feat)
.map(|i| ((i + b * 13) as f32 * 0.23).sin() * 1.3)
.collect();
c.w.prepare_input(
&x,
&mut xh[b * c.w.prepared_len()..(b + 1) * c.w.prepared_len()],
);
xs.push(x);
}
for &(row_start, rows) in &[(0usize, out), (0, 1), (1, 3.min(out - 1))] {
if row_start + rows > out || rows == 0 {
continue;
}
let mut got = vec![0.0f32; batch * rows];
let mut acc = vec![0.0f32; batch];
c.w.matmul_rows_prepared(&xh, batch, row_start, rows, &mut got, &mut acc);
for b in 0..batch {
let mut want = vec![0.0f32; rows];
c.w.matvec_rows_prepared(
&xh[b * c.w.prepared_len()..(b + 1) * c.w.prepared_len()],
row_start,
&mut want,
);
for r in 0..rows {
assert_eq!(
got[b * rows + r],
want[r],
"bits={bits} {out}x{in_feat} g={group} batch={batch} \
rows={row_start}+{rows} b={b} r={r}"
);
}
}
}
}
}
}
}
#[test]
fn prepared_input_is_reusable_across_weights() {
let a = build_case(4, 128, 128, 2);
let b = build_case(6, 128, 128, 4);
let x: Vec<f32> = (0..128).map(|i| (i as f32 * 0.07).cos()).collect();
let mut xh = vec![0.0f32; a.w.prepared_len()];
a.w.prepare_input(&x, &mut xh);
let mut ya = vec![0.0f32; 4];
let mut yb = vec![0.0f32; 6];
a.w.matvec_prepared(&xh, &mut ya);
b.w.matvec_prepared(&xh, &mut yb);
let mut ya_ref = vec![0.0f32; 4];
let mut yb_ref = vec![0.0f32; 6];
a.w.matvec(&x, &mut ya_ref);
b.w.matvec(&x, &mut yb_ref);
assert_eq!(ya, ya_ref);
assert_eq!(yb, yb_ref);
}
#[test]
fn row_bytes_match_shipped_tensor_sizes() {
for &(out, in_f, bits, group, want) in &[
(8192usize, 512usize, 4u8, 128usize, 2_162_688usize), (512, 512, 2, 128, 69_632), (256, 512, 2, 128, 34_816), (108, 2048, 4, 128, 114_048), (432, 2048, 4, 128, 456_192), (32768, 128, 2, 128, 1_114_112), ] {
let in_pad = in_f.div_ceil(group) * group;
let got = out * packed_row_bytes(in_pad, bits) + out * (in_pad / group) * 2;
assert_eq!(got, want, "{out}x{in_f} @ {bits}b/{group}");
}
}
#[test]
fn rejects_bad_geometry() {
let cb = fake_codebook(128);
let blob = vec![0u8; 1 << 16];
let err = |b: &[u8], out, in_f, group, bits, cb: &[f32]| {
CqWeight::from_blob(b, out, in_f, group, bits, cb).err()
};
assert_eq!(
err(&blob, 4, 128, 128, 7, &cb),
Some(CqError::UnsupportedBits(7))
);
assert_eq!(err(&blob, 4, 128, 96, 2, &cb), Some(CqError::BadGroup(96)));
assert_eq!(
err(&blob, 4, 128, 100, 2, &cb),
Some(CqError::BadGroup(100))
);
assert_eq!(err(&blob, 0, 128, 128, 2, &cb), Some(CqError::EmptyShape));
assert_eq!(err(&blob, 4, 0, 128, 2, &cb), Some(CqError::EmptyShape));
assert!(matches!(
err(&[0u8; 4], 512, 512, 128, 2, &cb),
Some(CqError::ShortBlob { .. })
));
assert_eq!(
err(&blob, 4, 128, 128, 2, &cb[..10]),
Some(CqError::BadCodebookLen(10))
);
}
}
#[cfg(test)]
mod dispatch_tests {
extern crate std;
#[test]
#[cfg(all(target_arch = "x86_64", feature = "simd"))]
fn avx2_path_is_actually_selected_when_supported() {
let cpuid_says = self::std::is_x86_feature_detected!("avx2");
let we_say = crate::quant::has_avx2();
assert_eq!(
we_say, cpuid_says,
"has_avx2() disagrees with std's detection; the dispatch would pick the wrong kernel"
);
if cpuid_says {
assert!(
we_say,
"AVX2 is present but the wide kernel would not be selected"
);
self::std::eprintln!("AVX2 dispatch active: the wide packed-dot kernel is in use");
} else {
self::std::eprintln!("no AVX2 on this host; the portable kernel is in use");
}
}
}