#![allow(clippy::cast_possible_truncation)]
use crate::error::InferenceError;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Q3Block {
pub scale: u16,
pub bias: u16,
pub packed: [u8; 12],
}
const _: () = assert!(std::mem::size_of::<Q3Block>() == 16);
pub const Q3_BLOCK_BYTES: usize = std::mem::size_of::<Q3Block>();
#[derive(Debug, Clone)]
pub struct Q3Tensor {
pub blocks: Vec<Q3Block>,
pub shape: Vec<usize>,
pub original_len: usize,
}
#[inline]
pub(crate) fn q3_f32_to_f16(x: f32) -> u16 {
crate::weights::half_bits::f32_to_f16_bits(x)
}
#[inline]
pub(crate) fn q3_f16_to_f32(bits: u16) -> f32 {
crate::weights::half_bits::f16_bits_to_f32(bits)
}
#[inline]
fn bf16_to_f32(v: u16) -> f32 {
crate::weights::half_bits::bf16_bits_to_f32(v)
}
#[inline]
fn pack_plane_split(q: &[u8; 32]) -> [u8; 12] {
let mut packed = [0u8; 12];
for b in 0..8 {
packed[b] = (q[4 * b] & 0x3)
| ((q[4 * b + 1] & 0x3) << 2)
| ((q[4 * b + 2] & 0x3) << 4)
| ((q[4 * b + 3] & 0x3) << 6);
}
for b in 0..4 {
let mut byte = 0u8;
for k in 0..8 {
byte |= ((q[8 * b + k] >> 2) & 0x1) << k;
}
packed[8 + b] = byte;
}
packed
}
#[inline]
fn unpack_plane_split(packed: &[u8], i: usize) -> u8 {
let low2 = (packed[i / 4] >> ((i % 4) * 2)) & 0x3;
let hi = (packed[8 + i / 8] >> (i % 8)) & 0x1;
low2 | (hi << 2)
}
#[inline]
fn quantize_block_with_mode_len(
vals: &[f32; 32],
valid_len: usize,
symmetric: bool,
) -> Result<Q3Block, InferenceError> {
if !(1..=32).contains(&valid_len) {
return Err(InferenceError::InvalidInput(format!(
"Q3 weight block valid_len {valid_len} must be in 1..=32"
)));
}
for (i, &v) in vals.iter().enumerate() {
if !v.is_finite() {
return Err(InferenceError::InvalidInput(format!(
"Q3 weight block element {i} contains a non-finite value ({v}); \
source weights must be finite"
)));
}
}
if symmetric {
let abs_max = vals.iter().map(|x| x.abs()).fold(0.0f32, f32::max);
let scale = if abs_max == 0.0 {
1.0f32
} else {
abs_max / 3.5
};
let inv_scale = 1.0 / scale;
let bias = -4.0 * scale;
let mut q = [0u8; 32];
for (i, slot) in q.iter_mut().enumerate() {
*slot = ((vals[i] * inv_scale).round() + 4.0).clamp(0.0, 7.0) as u8;
}
Ok(Q3Block {
scale: q3_f32_to_f16(scale),
bias: q3_f32_to_f16(bias),
packed: pack_plane_split(&q),
})
} else {
let real = &vals[..valid_len];
let min_val = real.iter().copied().fold(f32::INFINITY, f32::min);
let max_val = real.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let range = max_val - min_val;
let scale = if range == 0.0 { 1.0f32 } else { range / 7.0 };
let inv_scale = 1.0 / scale;
let mut q = [0u8; 32];
for (i, slot) in q.iter_mut().enumerate() {
*slot = (((vals[i] - min_val) * inv_scale).round()).clamp(0.0, 7.0) as u8;
}
Ok(Q3Block {
scale: q3_f32_to_f16(scale),
bias: q3_f32_to_f16(min_val),
packed: pack_plane_split(&q),
})
}
}
pub fn quantize_row_q3_0(src: &[f32]) -> Result<Vec<u8>, InferenceError> {
let n_blocks = src.len().div_ceil(32);
let mut out = Vec::with_capacity(n_blocks * Q3_BLOCK_BYTES);
for chunk in src.chunks(32) {
let mut vals = [0.0f32; 32];
vals[..chunk.len()].copy_from_slice(chunk);
let block = quantize_block_with_mode_len(&vals, chunk.len(), false)?;
let bytes: &[u8; Q3_BLOCK_BYTES] = unsafe { &*std::ptr::from_ref(&block).cast() };
out.extend_from_slice(bytes);
}
Ok(out)
}
pub fn dequantize_row_q3_0(data: &[u8], n_weights: usize) -> Vec<f32> {
let mut out = Vec::with_capacity(n_weights);
for chunk in data.chunks_exact(Q3_BLOCK_BYTES) {
let scale = q3_f16_to_f32(u16::from_ne_bytes([chunk[0], chunk[1]]));
let bias = q3_f16_to_f32(u16::from_ne_bytes([chunk[2], chunk[3]]));
let packed = &chunk[4..Q3_BLOCK_BYTES];
for i in 0..32 {
out.push(unpack_plane_split(packed, i) as f32 * scale + bias);
}
}
out.truncate(n_weights);
out
}
pub fn quantize_tensor_q3_0(
src: &[f32],
rows: usize,
cols: usize,
) -> Result<Vec<u8>, InferenceError> {
assert_eq!(
src.len(),
rows * cols,
"src length does not match rows * cols"
);
let blocks_per_row = cols.div_ceil(32);
let mut out = Vec::with_capacity(rows * blocks_per_row * Q3_BLOCK_BYTES);
for row_idx in 0..rows {
let row = &src[row_idx * cols..(row_idx + 1) * cols];
out.extend_from_slice(&quantize_row_q3_0(row)?);
}
Ok(out)
}
#[track_caller]
fn assert_shape_matches_data_len(shape: &[usize], data_len: usize) {
let numel = shape
.iter()
.try_fold(1_usize, |acc, &d| acc.checked_mul(d))
.unwrap_or_else(|| {
panic!("shape product overflowed usize: shape={shape:?}");
});
assert_eq!(
numel, data_len,
"shape product {numel} (shape={shape:?}) must equal data length {data_len}"
);
}
pub fn quantize_bf16_to_q3(data: &[u16], shape: &[usize]) -> Result<Q3Tensor, InferenceError> {
assert_shape_matches_data_len(shape, data.len());
let original_len = data.len();
let n_blocks = original_len.div_ceil(32);
let mut blocks = Vec::with_capacity(n_blocks);
for chunk in data.chunks(32) {
let mut vals = [0.0f32; 32];
for (i, &v) in chunk.iter().enumerate() {
vals[i] = bf16_to_f32(v);
}
blocks.push(quantize_block_with_mode_len(&vals, chunk.len(), false)?);
}
Ok(Q3Tensor {
blocks,
shape: shape.to_vec(),
original_len,
})
}
pub fn quantize_f32_to_q3(data: &[f32], shape: &[usize]) -> Result<Q3Tensor, InferenceError> {
assert_shape_matches_data_len(shape, data.len());
let original_len = data.len();
let n_blocks = original_len.div_ceil(32);
let mut blocks = Vec::with_capacity(n_blocks);
for chunk in data.chunks(32) {
let mut vals = [0.0f32; 32];
vals[..chunk.len()].copy_from_slice(chunk);
blocks.push(quantize_block_with_mode_len(&vals, chunk.len(), false)?);
}
Ok(Q3Tensor {
blocks,
shape: shape.to_vec(),
original_len,
})
}
pub fn quantize_f32_to_q3_mode(
data: &[f32],
shape: &[usize],
symmetric: bool,
) -> Result<Q3Tensor, InferenceError> {
assert_shape_matches_data_len(shape, data.len());
let original_len = data.len();
let n_blocks = original_len.div_ceil(32);
let mut blocks = Vec::with_capacity(n_blocks);
for chunk in data.chunks(32) {
let mut vals = [0.0f32; 32];
vals[..chunk.len()].copy_from_slice(chunk);
blocks.push(quantize_block_with_mode_len(&vals, chunk.len(), symmetric)?);
}
Ok(Q3Tensor {
blocks,
shape: shape.to_vec(),
original_len,
})
}
pub fn dequantize_q3_to_f32(tensor: &Q3Tensor) -> Vec<f32> {
let mut out = Vec::with_capacity(tensor.original_len);
for block in &tensor.blocks {
let scale = q3_f16_to_f32(block.scale);
let bias = q3_f16_to_f32(block.bias);
for i in 0..32 {
out.push(unpack_plane_split(&block.packed, i) as f32 * scale + bias);
}
}
out.truncate(tensor.original_len);
out
}
pub fn save_q3_file(path: &std::path::Path, tensor: &Q3Tensor) -> std::io::Result<()> {
use std::io::Write;
let mut f = std::fs::File::create(path)?;
f.write_all(b"KHQ3")?;
f.write_all(&1u32.to_le_bytes())?;
f.write_all(&(tensor.shape.len() as u32).to_le_bytes())?;
for &dim in &tensor.shape {
f.write_all(&(dim as u64).to_le_bytes())?;
}
f.write_all(&(tensor.original_len as u64).to_le_bytes())?;
let block_bytes: &[u8] = unsafe {
std::slice::from_raw_parts(
tensor.blocks.as_ptr().cast::<u8>(),
tensor.blocks.len() * Q3_BLOCK_BYTES,
)
};
f.write_all(block_bytes)
}
pub fn validate_q3_mlp_role(tensor_name: &str) -> Result<(), InferenceError> {
const MLP_PROJ_SUFFIXES: [&str; 4] = [
".mlp.gate_proj",
".mlp.up_proj",
".mlp.down_proj",
".mlp.gate_up_proj",
];
let is_mlp_proj = tensor_name.strip_suffix(".weight").is_some_and(|stem| {
MLP_PROJ_SUFFIXES
.iter()
.any(|suffix| stem.ends_with(suffix))
});
if is_mlp_proj {
Ok(())
} else {
Err(InferenceError::InvalidInput(format!(
"Q3 weight format is restricted to MLP gate/up/down projections \
(ADR-072 P1); tensor '{tensor_name}' is outside that set and must \
not be loaded as Q3"
)))
}
}
pub struct Q3FileHeader {
pub shape: Vec<usize>,
pub original_len: usize,
pub payload_offset: u64,
}
fn checked_alloc_bytes(
count: usize,
elem_size: usize,
file_len: u64,
what: &str,
) -> Result<usize, Box<dyn std::error::Error>> {
let bytes = count
.checked_mul(elem_size)
.ok_or_else(|| format!("{what}: element count {count} × {elem_size} overflows usize"))?;
if bytes as u64 > file_len {
return Err(format!(
"{what}: header claims {bytes} bytes but file is only {file_len} bytes"
)
.into());
}
Ok(bytes)
}
pub fn read_q3_header(
file: &mut std::fs::File,
) -> Result<Q3FileHeader, Box<dyn std::error::Error>> {
use std::io::{Read, Seek, SeekFrom};
let file_len = file.metadata()?.len();
let (shape, original_len, payload_offset) = {
let mut f = std::io::BufReader::new(&mut *file);
let mut magic = [0u8; 4];
f.read_exact(&mut magic)?;
if &magic != b"KHQ3" {
return Err("invalid magic: not a .q3 file".into());
}
let mut b4 = [0u8; 4];
f.read_exact(&mut b4)?;
let ver = u32::from_le_bytes(b4);
if ver != 1 {
return Err(format!("unsupported .q3 file version: {ver}").into());
}
f.read_exact(&mut b4)?;
let ndim = u32::from_le_bytes(b4) as usize;
checked_alloc_bytes(ndim, 8, file_len, "shape dims")?;
let mut shape = Vec::with_capacity(ndim);
let mut b8 = [0u8; 8];
for _ in 0..ndim {
f.read_exact(&mut b8)?;
shape.push(u64::from_le_bytes(b8) as usize);
}
f.read_exact(&mut b8)?;
let original_len = u64::from_le_bytes(b8) as usize;
let payload_offset = (20 + ndim * 8) as u64;
(shape, original_len, payload_offset)
};
file.seek(SeekFrom::Start(payload_offset))?;
Ok(Q3FileHeader {
shape,
original_len,
payload_offset,
})
}
#[allow(dead_code)]
pub(crate) fn validate_q3_header_payload_bounds(
header: &Q3FileHeader,
file_len: u64,
path: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
let shape_product = header
.shape
.iter()
.try_fold(1_usize, |acc, &d| acc.checked_mul(d))
.ok_or("shape dims overflow usize")?;
if shape_product != header.original_len {
return Err(format!(
"{}: shape product {shape_product} (shape={:?}) != original_len {}",
path.display(),
header.shape,
header.original_len
)
.into());
}
let payload_bytes = header
.original_len
.div_ceil(32)
.checked_mul(Q3_BLOCK_BYTES)
.ok_or("Q3 block payload byte count overflows usize")? as u64;
let required_len = header
.payload_offset
.checked_add(payload_bytes)
.ok_or("Q3 payload end offset overflows u64")?;
if file_len < required_len {
return Err(format!(
"{}: file truncated below Q3 block payload ({file_len} bytes < required {required_len})",
path.display()
)
.into());
}
Ok(())
}
pub fn load_q3_file(path: &std::path::Path) -> Result<Q3Tensor, Box<dyn std::error::Error>> {
use std::io::Read;
let mut f = std::fs::File::open(path)?;
let file_len = f.metadata()?.len();
let mut magic = [0u8; 4];
f.read_exact(&mut magic)?;
if &magic != b"KHQ3" {
return Err("invalid magic: not a .q3 file".into());
}
let mut b4 = [0u8; 4];
f.read_exact(&mut b4)?;
let ver = u32::from_le_bytes(b4);
if ver != 1 {
return Err(format!("unsupported .q3 file version: {ver}").into());
}
f.read_exact(&mut b4)?;
let ndim = u32::from_le_bytes(b4) as usize;
checked_alloc_bytes(ndim, 8, file_len, "shape dims")?;
let mut shape = Vec::with_capacity(ndim);
let mut b8 = [0u8; 8];
for _ in 0..ndim {
f.read_exact(&mut b8)?;
shape.push(u64::from_le_bytes(b8) as usize);
}
f.read_exact(&mut b8)?;
let original_len = u64::from_le_bytes(b8) as usize;
let shape_product = shape
.iter()
.try_fold(1_usize, |acc, &d| acc.checked_mul(d))
.ok_or("shape dims overflow usize")?;
if shape_product != original_len {
return Err(format!(
"shape product {shape_product} (shape={shape:?}) != original_len {original_len}"
)
.into());
}
let n_blocks = original_len.div_ceil(32);
let raw_len = checked_alloc_bytes(n_blocks, Q3_BLOCK_BYTES, file_len, "block payload")?;
let mut raw = vec![0u8; raw_len];
f.read_exact(&mut raw)?;
let blocks: Vec<Q3Block> = raw
.chunks_exact(Q3_BLOCK_BYTES)
.map(|c| Q3Block {
scale: u16::from_ne_bytes([c[0], c[1]]),
bias: u16::from_ne_bytes([c[2], c[3]]),
packed: c[4..Q3_BLOCK_BYTES]
.try_into()
.expect("slice is exactly 12 bytes"),
})
.collect();
Ok(Q3Tensor {
blocks,
shape,
original_len,
})
}
pub fn gemv_q3_reference(x: &[f32], qweight: &[u8], n: usize, k: usize) -> Vec<f32> {
assert_eq!(k % 32, 0, "K must be divisible by 32 for Q3 GEMV");
let row_bytes = (k / 32) * Q3_BLOCK_BYTES;
assert_eq!(
qweight.len(),
n * row_bytes,
"qweight length does not match N * (K/32) * Q3_BLOCK_BYTES"
);
let mut y = vec![0.0f32; n];
for (row_idx, row_bytes_slice) in qweight.chunks_exact(row_bytes).enumerate() {
let w = dequantize_row_q3_0(row_bytes_slice, k);
y[row_idx] = x.iter().zip(w.iter()).map(|(&xv, &wv)| xv * wv).sum();
}
y
}
pub fn gemm_q3_reference(x: &[f32], qweight: &[u8], m: usize, n: usize, k: usize) -> Vec<f32> {
assert_eq!(k % 32, 0, "K must be divisible by 32 for Q3 GEMM");
assert_eq!(x.len(), m * k, "x length does not match M * K");
let row_bytes = (k / 32) * Q3_BLOCK_BYTES;
assert_eq!(
qweight.len(),
n * row_bytes,
"qweight length does not match N * (K/32) * Q3_BLOCK_BYTES"
);
let w_deq: Vec<Vec<f32>> = qweight
.chunks_exact(row_bytes)
.map(|row| dequantize_row_q3_0(row, k))
.collect();
let mut y = vec![0.0f32; m * n];
for mi in 0..m {
let xrow = &x[mi * k..(mi + 1) * k];
for ni in 0..n {
y[mi * n + ni] = xrow
.iter()
.zip(w_deq[ni].iter())
.map(|(&xv, &wv)| xv * wv)
.sum();
}
}
y
}
#[cfg(test)]
mod tests {
use super::*;
fn asym_err_bound(min: f32, max: f32) -> f32 {
let scale = (max - min) / 7.0;
scale / 2.0 + scale.abs() * 1e-3 + 1e-6
}
#[test]
fn q3_block_is_sixteen_bytes() {
assert_eq!(Q3_BLOCK_BYTES, 16);
assert_eq!(std::mem::size_of::<Q3Block>(), 16);
}
#[test]
fn plane_split_pack_unpack_roundtrips_all_values() {
for base in 0u8..8 {
let mut q = [0u8; 32];
for (i, slot) in q.iter_mut().enumerate() {
*slot = ((base as usize + i) % 8) as u8;
}
let packed = pack_plane_split(&q);
for (i, &expected) in q.iter().enumerate() {
assert_eq!(
unpack_plane_split(&packed, i),
expected,
"value {i} (={expected}) did not survive plane-split pack/unpack"
);
}
}
}
#[test]
fn plane_split_mutation_sensitive_high_bit() {
let mut q = [0u8; 32];
q[5] = 6; q[17] = 7; let packed = pack_plane_split(&q);
assert_eq!(unpack_plane_split(&packed, 5), 6);
assert_eq!(unpack_plane_split(&packed, 17), 7);
assert_ne!(packed[8] & (1 << 5), 0, "high bit for value 5 must be set");
assert_ne!(
packed[10] & (1 << 1),
0,
"high bit for value 17 must be set"
);
}
#[test]
fn asymmetric_roundtrip_within_scale_half() {
let src: Vec<f32> = (0..32).map(|i| (i as f32 - 12.0) * 0.37).collect();
let bytes = quantize_row_q3_0(&src).unwrap();
assert_eq!(bytes.len(), Q3_BLOCK_BYTES);
let back = dequantize_row_q3_0(&bytes, src.len());
let min = src.iter().copied().fold(f32::INFINITY, f32::min);
let max = src.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let bound = asym_err_bound(min, max);
for (i, (&a, &b)) in src.iter().zip(back.iter()).enumerate() {
assert!(
(a - b).abs() <= bound,
"elem {i}: |{a} - {b}| = {} exceeds bound {bound}",
(a - b).abs()
);
}
}
#[test]
fn symmetric_roundtrip_zero_mean() {
let src: Vec<f32> = (0..32).map(|i| (i as f32 - 15.5) * 0.1).collect();
let t = quantize_f32_to_q3_mode(&src, &[32], true).unwrap();
let back = dequantize_q3_to_f32(&t);
let abs_max = src.iter().map(|x| x.abs()).fold(0.0f32, f32::max);
let bound = (abs_max / 3.5) / 2.0 + abs_max * 1e-3 + 1e-6;
for (i, (&a, &b)) in src.iter().zip(back.iter()).enumerate() {
assert!(
(a - b).abs() <= bound,
"elem {i}: |{a} - {b}| = {} exceeds bound {bound}",
(a - b).abs()
);
}
}
#[test]
fn partial_block_uses_real_tail_min_max() {
let src = vec![10.0f32, 10.5, 11.0];
let t = quantize_f32_to_q3(&src, &[3]).unwrap();
assert_eq!(t.blocks.len(), 1);
assert_eq!(t.original_len, 3);
let back = dequantize_q3_to_f32(&t);
assert_eq!(back.len(), 3);
let bound = asym_err_bound(10.0, 11.0);
for (i, (&a, &b)) in src.iter().zip(back.iter()).enumerate() {
assert!(
(a - b).abs() <= bound,
"elem {i}: |{a} - {b}| = {} exceeds bound {bound}",
(a - b).abs()
);
}
}
#[test]
fn constant_block_is_exact() {
let src = vec![3.5f32; 32];
let t = quantize_f32_to_q3(&src, &[32]).unwrap();
let back = dequantize_q3_to_f32(&t);
for &b in &back {
assert_eq!(b, 3.5);
}
}
#[test]
fn rejects_non_finite() {
let mut src = vec![0.1f32; 32];
src[7] = f32::NAN;
assert!(quantize_f32_to_q3(&src, &[32]).is_err());
src[7] = f32::INFINITY;
assert!(quantize_f32_to_q3(&src, &[32]).is_err());
}
#[test]
#[should_panic(expected = "must equal data length")]
fn rejects_shape_data_mismatch() {
let src = vec![0.1f32; 32];
let _ = quantize_f32_to_q3(&src, &[64]);
}
#[test]
fn bf16_path_matches_f32_path_on_bf16_castable_input() {
let bf16_bits: Vec<u16> = (0..64).map(|i| ((i as u16) << 7) | 0x3C00).collect();
let f32_vals: Vec<f32> = bf16_bits.iter().map(|&b| bf16_to_f32(b)).collect();
let via_bf16 = quantize_bf16_to_q3(&bf16_bits, &[64]).unwrap();
let via_f32 = quantize_f32_to_q3(&f32_vals, &[64]).unwrap();
assert_eq!(via_bf16.blocks, via_f32.blocks);
}
#[test]
fn save_load_roundtrip_preserves_blocks_and_shape() {
let src: Vec<f32> = (0..96).map(|i| (i as f32 - 40.0) * 0.05).collect();
let t = quantize_f32_to_q3(&src, &[3, 32]).unwrap();
let dir = std::env::temp_dir();
let path = dir.join(format!("lattice_q3_roundtrip_{}.q3", std::process::id()));
save_q3_file(&path, &t).unwrap();
let mut file = std::fs::File::open(&path).unwrap();
let header = read_q3_header(&mut file).unwrap();
assert_eq!(header.shape, vec![3, 32]);
assert_eq!(header.original_len, 96);
assert_eq!(header.payload_offset, 20 + 2 * 8);
let loaded = load_q3_file(&path).unwrap();
assert_eq!(loaded.shape, t.shape);
assert_eq!(loaded.original_len, t.original_len);
assert_eq!(loaded.blocks, t.blocks);
let _ = std::fs::remove_file(&path);
}
#[test]
fn read_q3_header_leaves_cursor_at_payload_offset() {
let src: Vec<f32> = (0..96).map(|i| (i as f32 - 40.0) * 0.05).collect();
let t = quantize_f32_to_q3(&src, &[3, 32]).unwrap();
let dir = std::env::temp_dir();
let path = dir.join(format!("lattice_q3_cursor_{}.q3", std::process::id()));
save_q3_file(&path, &t).unwrap();
let mut file = std::fs::File::open(&path).unwrap();
let header = read_q3_header(&mut file).unwrap();
assert_eq!(
std::io::Seek::stream_position(&mut file).unwrap(),
header.payload_offset,
"cursor must sit at payload_offset after read_q3_header returns"
);
let mut first_block_bytes = [0u8; Q3_BLOCK_BYTES];
std::io::Read::read_exact(&mut file, &mut first_block_bytes).unwrap();
let expected: &[u8; Q3_BLOCK_BYTES] = unsafe { &*std::ptr::from_ref(&t.blocks[0]).cast() };
assert_eq!(
&first_block_bytes, expected,
"reading off the same handle post-header must yield the first block, not header tail"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn load_rejects_bad_magic() {
let dir = std::env::temp_dir();
let path = dir.join(format!("lattice_q3_badmagic_{}.q3", std::process::id()));
std::fs::write(&path, b"KHQ4\x01\x00\x00\x00").unwrap();
assert!(load_q3_file(&path).is_err());
let _ = std::fs::remove_file(&path);
}
#[test]
fn four_bpw_storage() {
let bits_per_weight = (Q3_BLOCK_BYTES * 8) as f32 / 32.0;
assert_eq!(bits_per_weight, 4.0);
}
#[test]
fn mlp_role_accepts_gate_up_down_and_fused() {
assert!(validate_q3_mlp_role("model.layers.3.mlp.gate_proj.weight").is_ok());
assert!(validate_q3_mlp_role("model.layers.3.mlp.up_proj.weight").is_ok());
assert!(validate_q3_mlp_role("model.layers.3.mlp.down_proj.weight").is_ok());
assert!(validate_q3_mlp_role("model.layers.3.mlp.gate_up_proj.weight").is_ok());
}
#[test]
fn mlp_role_rejects_attention_and_gdn_tensors() {
for name in [
"model.layers.0.self_attn.q_proj.weight",
"model.layers.0.self_attn.k_proj.weight",
"model.layers.0.self_attn.v_proj.weight",
"model.layers.0.self_attn.o_proj.weight",
"model.layers.0.linear_attn.in_proj_qkv.weight",
"model.layers.0.mlp.experts.gate_up_proj", "lm_head.weight",
"model.embed_tokens.weight",
] {
assert!(
validate_q3_mlp_role(name).is_err(),
"expected '{name}' to be rejected as outside the Q3 MLP role set"
);
}
}
#[test]
fn mlp_role_rejects_near_miss_suffixes() {
for name in [
"model.layers.3.mlp.gate_proj.lora_A.weight",
"model.layers.3.mlp.up_proj_backup.weight",
"model.layers.3.mlp.up_proj_backup",
"model.layers.3.mlp.down_proj.weight.extra",
] {
assert!(
validate_q3_mlp_role(name).is_err(),
"expected near-miss '{name}' to be rejected by the exact-suffix gate"
);
}
}
#[test]
fn mlp_role_requires_terminal_weight_suffix() {
for name in [
"model.layers.3.mlp.gate_proj",
"model.layers.3.mlp.up_proj",
"model.layers.3.mlp.down_proj",
"model.layers.3.mlp.gate_up_proj",
] {
assert!(
validate_q3_mlp_role(name).is_err(),
"expected '{name}' (missing '.weight') to be rejected"
);
}
}
#[test]
fn header_bounds_rejects_truncated_payload() {
let header = Q3FileHeader {
shape: vec![64],
original_len: 64, payload_offset: 20,
};
let err = validate_q3_header_payload_bounds(&header, 51, std::path::Path::new("t.q3"));
assert!(err.is_err());
}
#[test]
fn header_bounds_accepts_exact_payload() {
let header = Q3FileHeader {
shape: vec![64],
original_len: 64,
payload_offset: 20,
};
assert!(
validate_q3_header_payload_bounds(&header, 52, std::path::Path::new("t.q3")).is_ok()
);
}
#[test]
fn header_bounds_rejects_shape_mismatch() {
let header = Q3FileHeader {
shape: vec![63], original_len: 64,
payload_offset: 20,
};
assert!(
validate_q3_header_payload_bounds(&header, 1_000, std::path::Path::new("t.q3"))
.is_err()
);
}
fn synth_q3_weight_matrix(seed: u64, n: usize, k: usize) -> (Vec<u8>, Vec<f32>) {
let mut rng = seed;
let mut next = || -> f32 {
rng = rng
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((rng >> 11) as u32 as f32 / u32::MAX as f32) * 2.0 - 1.0
};
let weights_f32: Vec<f32> = (0..n * k).map(|_| next()).collect();
let mut packed = Vec::with_capacity(n * (k / 32) * Q3_BLOCK_BYTES);
let mut deq = Vec::with_capacity(n * k);
for row in weights_f32.chunks_exact(k) {
let row_packed = quantize_row_q3_0(row).unwrap();
deq.extend_from_slice(&dequantize_row_q3_0(&row_packed, k));
packed.extend_from_slice(&row_packed);
}
(packed, deq)
}
#[test]
fn gemv_reference_matches_naive_dequant_dot_product() {
let (n, k) = (17usize, 64usize);
let (packed, deq) = synth_q3_weight_matrix(0x1234_5678, n, k);
let x: Vec<f32> = (0..k).map(|i| (i as f32) * 0.01 - 0.32).collect();
let y = gemv_q3_reference(&x, &packed, n, k);
for (ni, &yv) in y.iter().enumerate() {
let expect: f32 = x
.iter()
.zip(&deq[ni * k..(ni + 1) * k])
.map(|(&xv, &wv)| xv * wv)
.sum();
assert!((yv - expect).abs() < 1e-3, "row {ni}: {yv} vs {expect}");
}
}
#[test]
fn gemm_reference_matches_gemv_reference_per_row() {
let (n, k) = (9usize, 96usize);
let (packed, _deq) = synth_q3_weight_matrix(0xC0FF_EE11, n, k);
let x: Vec<f32> = (0..k).map(|i| ((i * 7) % 13) as f32 * 0.05 - 0.3).collect();
let y_gemv = gemv_q3_reference(&x, &packed, n, k);
let y_gemm = gemm_q3_reference(&x, &packed, 1, n, k);
assert_eq!(y_gemv, y_gemm);
}
#[test]
fn gemv_reference_mutation_sensitive_high_plane_bit() {
let (n, k) = (4usize, 32usize);
let (mut packed, _deq) = synth_q3_weight_matrix(0xFEED_FACE, n, k);
let x: Vec<f32> = (0..k).map(|i| (i as f32) * 0.02 - 0.3).collect();
let y_before = gemv_q3_reference(&x, &packed, n, k);
packed[12] ^= 0x01;
let y_after = gemv_q3_reference(&x, &packed, n, k);
assert_ne!(
y_before[0], y_after[0],
"flipping a high-plane bit must change the dequantized GEMV output \
for the row whose block it belongs to"
);
}
}