use kopitiam_core::{DType, Error, Result};
use crate::half::f16_to_f32;
pub(crate) fn dequantize(dtype: DType, bytes: &[u8]) -> Result<Vec<f32>> {
let block_bytes = dtype.block_bytes();
let block_size = dtype.block_size();
let num_blocks = bytes.len() / block_bytes;
let mut out = Vec::with_capacity(num_blocks * block_size);
let decode_block: fn(&[u8], &mut Vec<f32>) = match dtype {
DType::Q4_0 => dequant_q4_0,
DType::Q4_1 => dequant_q4_1,
DType::Q5_0 => dequant_q5_0,
DType::Q5_1 => dequant_q5_1,
DType::Q8_0 => dequant_q8_0,
DType::Q2_K => dequant_q2_k,
DType::Q3_K => dequant_q3_k,
DType::Q4_K => dequant_q4_k,
DType::Q5_K => dequant_q5_k,
DType::Q6_K => dequant_q6_k,
DType::Q8_K => dequant_q8_k,
_ => return Err(Error::UnsupportedDType { op: "dequantize", dtype }),
};
for block in bytes.chunks_exact(block_bytes) {
decode_block(block, &mut out);
}
Ok(out)
}
fn read_f16(bytes: &[u8], offset: usize) -> f32 {
f16_to_f32(u16::from_le_bytes([bytes[offset], bytes[offset + 1]]))
}
fn dequant_q4_0(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let qs = &block[2..18];
let mut values = [0f32; 32];
for (j, &byte) in qs.iter().enumerate() {
values[j] = (i32::from(byte & 0x0F) - 8) as f32 * d;
values[j + 16] = (i32::from(byte >> 4) - 8) as f32 * d;
}
out.extend_from_slice(&values);
}
fn dequant_q4_1(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let m = read_f16(block, 2);
let qs = &block[4..20];
let mut values = [0f32; 32];
for (j, &byte) in qs.iter().enumerate() {
values[j] = f32::from(byte & 0x0F) * d + m;
values[j + 16] = f32::from(byte >> 4) * d + m;
}
out.extend_from_slice(&values);
}
fn dequant_q5_0(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let qh = u32::from_le_bytes([block[2], block[3], block[4], block[5]]);
let qs = &block[6..22];
let mut values = [0f32; 32];
for (j, &byte) in qs.iter().enumerate() {
let high_0 = (((qh >> j) << 4) & 0x10) as u8;
let high_1 = ((qh >> (j + 12)) & 0x10) as u8;
values[j] = (i32::from((byte & 0x0F) | high_0) - 16) as f32 * d;
values[j + 16] = (i32::from((byte >> 4) | high_1) - 16) as f32 * d;
}
out.extend_from_slice(&values);
}
fn dequant_q5_1(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let m = read_f16(block, 2);
let qh = u32::from_le_bytes([block[4], block[5], block[6], block[7]]);
let qs = &block[8..24];
let mut values = [0f32; 32];
for (j, &byte) in qs.iter().enumerate() {
let high_0 = (((qh >> j) << 4) & 0x10) as u8;
let high_1 = ((qh >> (j + 12)) & 0x10) as u8;
values[j] = f32::from((byte & 0x0F) | high_0) * d + m;
values[j + 16] = f32::from((byte >> 4) | high_1) * d + m;
}
out.extend_from_slice(&values);
}
fn dequant_q8_0(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let qs = &block[2..34];
out.extend(qs.iter().map(|&b| f32::from(b as i8) * d));
}
fn read_f32(bytes: &[u8], offset: usize) -> f32 {
f32::from_le_bytes([bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]])
}
fn get_scale_min_k4(j: usize, scales: &[u8]) -> (u8, u8) {
if j < 4 {
(scales[j] & 63, scales[j + 4] & 63)
} else {
let d = (scales[j + 4] & 0x0F) | ((scales[j - 4] >> 6) << 4);
let m = (scales[j + 4] >> 4) | ((scales[j] >> 6) << 4);
(d, m)
}
}
fn dequant_q2_k(block: &[u8], out: &mut Vec<f32>) {
let scales = &block[0..16];
let qs = &block[16..80];
let d = read_f16(block, 80);
let min = read_f16(block, 82);
let mut is = 0usize;
for half in 0..2 {
let q = &qs[half * 32..half * 32 + 32];
let mut shift = 0u32;
for _j in 0..4 {
let sc = scales[is];
is += 1;
let dl = d * f32::from(sc & 0x0F);
let ml = min * f32::from(sc >> 4);
for &b in &q[0..16] {
out.push(dl * f32::from((b >> shift) & 3) - ml);
}
let sc = scales[is];
is += 1;
let dl = d * f32::from(sc & 0x0F);
let ml = min * f32::from(sc >> 4);
for &b in &q[16..32] {
out.push(dl * f32::from((b >> shift) & 3) - ml);
}
shift += 2;
}
}
}
fn dequant_q3_k(block: &[u8], out: &mut Vec<f32>) {
const KMASK1: u32 = 0x0303_0303;
const KMASK2: u32 = 0x0f0f_0f0f;
let hmask = &block[0..32];
let qs = &block[32..96];
let scale_bytes = &block[96..108];
let d_all = read_f16(block, 108);
let aux0 = u32::from_le_bytes([scale_bytes[0], scale_bytes[1], scale_bytes[2], scale_bytes[3]]);
let aux1 = u32::from_le_bytes([scale_bytes[4], scale_bytes[5], scale_bytes[6], scale_bytes[7]]);
let aux2 = u32::from_le_bytes([scale_bytes[8], scale_bytes[9], scale_bytes[10], scale_bytes[11]]);
let tmp = aux2;
let out_aux = [
(aux0 & KMASK2) | ((tmp & KMASK1) << 4),
(aux1 & KMASK2) | (((tmp >> 2) & KMASK1) << 4),
((aux0 >> 4) & KMASK2) | (((tmp >> 4) & KMASK1) << 4),
((aux1 >> 4) & KMASK2) | (((tmp >> 6) & KMASK1) << 4),
];
let mut scales = [0i8; 16];
for (w, chunk) in out_aux.iter().enumerate() {
for (b, byte) in chunk.to_le_bytes().iter().enumerate() {
scales[w * 4 + b] = *byte as i8;
}
}
let mut m = 1u8;
let mut is = 0usize;
for half in 0..2 {
let q = &qs[half * 32..half * 32 + 32];
let mut shift = 0u32;
for _j in 0..4 {
let dl = d_all * (i32::from(scales[is]) - 32) as f32;
is += 1;
for l in 0..16 {
let high = if hmask[l] & m != 0 { 0 } else { 4 };
let v = i32::from((q[l] >> shift) & 3) - high;
out.push(dl * v as f32);
}
let dl = d_all * (i32::from(scales[is]) - 32) as f32;
is += 1;
for l in 0..16 {
let high = if hmask[l + 16] & m != 0 { 0 } else { 4 };
let v = i32::from((q[l + 16] >> shift) & 3) - high;
out.push(dl * v as f32);
}
shift += 2;
m <<= 1;
}
}
}
fn dequant_q4_k(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let min = read_f16(block, 2);
let scales = &block[4..16];
let qs = &block[16..144];
let mut is = 0usize;
for g in 0..4 {
let q = &qs[g * 32..g * 32 + 32];
let (sc, m) = get_scale_min_k4(is, scales);
let d1 = d * f32::from(sc);
let m1 = min * f32::from(m);
let (sc, m) = get_scale_min_k4(is + 1, scales);
let d2 = d * f32::from(sc);
let m2 = min * f32::from(m);
for &byte in q {
out.push(d1 * f32::from(byte & 0x0F) - m1);
}
for &byte in q {
out.push(d2 * f32::from(byte >> 4) - m2);
}
is += 2;
}
}
fn dequant_q5_k(block: &[u8], out: &mut Vec<f32>) {
let d = read_f16(block, 0);
let min = read_f16(block, 2);
let scales = &block[4..16];
let qh = &block[16..48];
let ql = &block[48..176];
let mut is = 0usize;
for g in 0..4 {
let q = &ql[g * 32..g * 32 + 32];
let u1 = 1u8 << (2 * g);
let u2 = 2u8 << (2 * g);
let (sc, m) = get_scale_min_k4(is, scales);
let d1 = d * f32::from(sc);
let m1 = min * f32::from(m);
let (sc, m) = get_scale_min_k4(is + 1, scales);
let d2 = d * f32::from(sc);
let m2 = min * f32::from(m);
for l in 0..32 {
let hi = if qh[l] & u1 != 0 { 16 } else { 0 };
out.push(d1 * (i32::from(q[l] & 0x0F) + hi) as f32 - m1);
}
for l in 0..32 {
let hi = if qh[l] & u2 != 0 { 16 } else { 0 };
out.push(d2 * (i32::from(q[l] >> 4) + hi) as f32 - m2);
}
is += 2;
}
}
fn dequant_q6_k(block: &[u8], out: &mut Vec<f32>) {
let ql = &block[0..128];
let qh = &block[128..192];
let scales = &block[192..208]; let d = read_f16(block, 208);
for half in 0..2 {
let ql = &ql[half * 64..half * 64 + 64];
let qh = &qh[half * 32..half * 32 + 32];
let sc = &scales[half * 8..half * 8 + 8];
let mut vals = [0f32; 128];
for l in 0..32 {
let is = l / 16;
let q1 = (i32::from(ql[l] & 0x0F) | (i32::from(qh[l] & 3) << 4)) - 32;
let q2 = (i32::from(ql[l + 32] & 0x0F) | (i32::from((qh[l] >> 2) & 3) << 4)) - 32;
let q3 = (i32::from(ql[l] >> 4) | (i32::from((qh[l] >> 4) & 3) << 4)) - 32;
let q4 = (i32::from(ql[l + 32] >> 4) | (i32::from((qh[l] >> 6) & 3) << 4)) - 32;
vals[l] = d * f32::from(sc[is] as i8) * q1 as f32;
vals[l + 32] = d * f32::from(sc[is + 2] as i8) * q2 as f32;
vals[l + 64] = d * f32::from(sc[is + 4] as i8) * q3 as f32;
vals[l + 96] = d * f32::from(sc[is + 6] as i8) * q4 as f32;
}
out.extend_from_slice(&vals);
}
}
fn dequant_q8_k(block: &[u8], out: &mut Vec<f32>) {
let d = read_f32(block, 0);
let qs = &block[4..260];
out.extend(qs.iter().map(|&b| d * f32::from(b as i8)));
}
pub(crate) fn quantize_row_q8_0(x: &[f32]) -> (Vec<f32>, Vec<i8>) {
debug_assert!(x.len().is_multiple_of(32), "quantize_row_q8_0 requires a whole number of 32-element blocks");
let num_blocks = x.len() / 32;
let mut scales = Vec::with_capacity(num_blocks);
let mut q = vec![0i8; x.len()];
for (b, chunk) in x.chunks_exact(32).enumerate() {
let amax = chunk.iter().fold(0f32, |m, &v| m.max(v.abs()));
let d = amax / 127.0;
let id = if d != 0.0 { 1.0 / d } else { 0.0 };
for (j, &v) in chunk.iter().enumerate() {
q[b * 32 + j] = (v * id).round().clamp(-127.0, 127.0) as i8;
}
scales.push(d);
}
(scales, q)
}
pub(crate) fn q4_0_dot_q8_0(weight_block: &[u8], x_q: &[i8], x_scale: f32) -> f32 {
debug_assert_eq!(weight_block.len(), DType::Q4_0.block_bytes());
debug_assert_eq!(x_q.len(), 32);
let d_w = read_f16(weight_block, 0);
let qs = &weight_block[2..18];
let mut acc: i32 = 0;
for (j, &byte) in qs.iter().enumerate() {
let w_lo = i32::from(byte & 0x0F) - 8;
let w_hi = i32::from(byte >> 4) - 8;
acc += w_lo * i32::from(x_q[j]);
acc += w_hi * i32::from(x_q[j + 16]);
}
acc as f32 * d_w * x_scale
}
pub(crate) fn q8_0_dot_q8_0(weight_block: &[u8], x_q: &[i8], x_scale: f32) -> f32 {
debug_assert_eq!(weight_block.len(), DType::Q8_0.block_bytes());
debug_assert_eq!(x_q.len(), 32);
let d_w = read_f16(weight_block, 0);
let qs = &weight_block[2..34];
let mut acc: i32 = 0;
for (j, &byte) in qs.iter().enumerate() {
acc += i32::from(byte as i8) * i32::from(x_q[j]);
}
acc as f32 * d_w * x_scale
}
#[cfg(test)]
mod tests {
use super::*;
fn q4_0_block(d_bits: u16, values: [i8; 32]) -> Vec<u8> {
let mut block = vec![0u8; 18];
block[0..2].copy_from_slice(&d_bits.to_le_bytes());
for j in 0..16 {
let low = (values[j] + 8) as u8 & 0x0F;
let high = (values[j + 16] + 8) as u8 & 0x0F;
block[2 + j] = low | (high << 4);
}
block
}
#[test]
fn q4_0_decodes_known_nibbles_with_the_i_and_i_plus_16_byte_sharing_rule() {
let mut values = [0i8; 32];
for (j, v) in values.iter_mut().enumerate() {
*v = ((j % 16) as i8) - 8;
}
let block = q4_0_block(0x4000, values);
let mut out = Vec::new();
dequant_q4_0(&block, &mut out);
assert_eq!(out.len(), 32);
for j in 0..32 {
assert_eq!(out[j], f32::from(values[j]) * 2.0, "mismatch at index {j}");
}
let byte0 = block[2];
assert_eq!((byte0 & 0x0F) as i32 - 8, i32::from(values[0]));
assert_eq!((byte0 >> 4) as i32 - 8, i32::from(values[16]));
}
#[test]
fn q4_0_all_zero_block_decodes_to_all_zero() {
let block = q4_0_block(0x4000, [0i8; 32]); let mut out = Vec::new();
dequant_q4_0(&block, &mut out);
assert!(out.iter().all(|&v| v == 0.0));
}
fn q4_1_block(d_bits: u16, m_bits: u16, values: [u8; 32]) -> Vec<u8> {
let mut block = vec![0u8; 20];
block[0..2].copy_from_slice(&d_bits.to_le_bytes());
block[2..4].copy_from_slice(&m_bits.to_le_bytes());
for j in 0..16 {
block[4 + j] = (values[j] & 0x0F) | ((values[j + 16] & 0x0F) << 4);
}
block
}
#[test]
fn q4_1_decodes_with_scale_and_min() {
let mut values = [0u8; 32];
for (j, v) in values.iter_mut().enumerate() {
*v = (j % 16) as u8;
}
let block = q4_1_block(0x3C00, 0x4900, values);
let mut out = Vec::new();
dequant_q4_1(&block, &mut out);
for j in 0..32 {
let expected = f32::from(values[j]) * 1.0 + 10.0;
assert_eq!(out[j], expected, "mismatch at index {j}");
}
}
fn q5_0_block(d_bits: u16, values: [i8; 32]) -> Vec<u8> {
let mut block = vec![0u8; 22];
block[0..2].copy_from_slice(&d_bits.to_le_bytes());
let mut qh: u32 = 0;
for j in 0..16 {
let raw_low = (values[j] + 16) as u8; let raw_high = (values[j + 16] + 16) as u8;
if raw_low & 0x10 != 0 {
qh |= 1 << j;
}
if raw_high & 0x10 != 0 {
qh |= 1 << (j + 16);
}
}
block[2..6].copy_from_slice(&qh.to_le_bytes());
for j in 0..16 {
let raw_low = ((values[j] + 16) as u8) & 0x0F;
let raw_high = ((values[j + 16] + 16) as u8) & 0x0F;
block[6 + j] = raw_low | (raw_high << 4);
}
block
}
#[test]
fn q5_0_decodes_the_5th_bit_from_the_qh_field() {
let mut values = [0i8; 32];
for (j, v) in values.iter_mut().enumerate() {
*v = ((j % 32) as i8) - 16;
}
let block = q5_0_block(0x3C00, values);
let mut out = Vec::new();
dequant_q5_0(&block, &mut out);
for j in 0..32 {
assert_eq!(out[j], f32::from(values[j]), "mismatch at index {j}");
}
}
fn q5_1_block(d_bits: u16, m_bits: u16, values: [u8; 32]) -> Vec<u8> {
let mut block = vec![0u8; 24];
block[0..2].copy_from_slice(&d_bits.to_le_bytes());
block[2..4].copy_from_slice(&m_bits.to_le_bytes());
let mut qh: u32 = 0;
for j in 0..16 {
if values[j] & 0x10 != 0 {
qh |= 1 << j;
}
if values[j + 16] & 0x10 != 0 {
qh |= 1 << (j + 16); }
}
block[4..8].copy_from_slice(&qh.to_le_bytes());
for j in 0..16 {
block[8 + j] = (values[j] & 0x0F) | ((values[j + 16] & 0x0F) << 4);
}
block
}
#[test]
fn q5_1_decodes_with_scale_min_and_5th_bit() {
let mut values = [0u8; 32];
for (j, v) in values.iter_mut().enumerate() {
*v = (j % 32) as u8;
}
let block = q5_1_block(0x3C00, 0x3800, values); let mut out = Vec::new();
dequant_q5_1(&block, &mut out);
for j in 0..32 {
let expected = f32::from(values[j]) * 1.0 + 0.5;
assert_eq!(out[j], expected, "mismatch at index {j}");
}
}
fn q8_0_block(d_bits: u16, values: [i8; 32]) -> Vec<u8> {
let mut block = vec![0u8; 34];
block[0..2].copy_from_slice(&d_bits.to_le_bytes());
for (j, &v) in values.iter().enumerate() {
block[2 + j] = v as u8;
}
block
}
#[test]
fn q8_0_decodes_signed_bytes_scaled() {
let mut values = [0i8; 32];
for (j, v) in values.iter_mut().enumerate() {
*v = (j as i8) - 16; }
let block = q8_0_block(0x4000, values); let mut out = Vec::new();
dequant_q8_0(&block, &mut out);
for j in 0..32 {
assert_eq!(out[j], f32::from(values[j]) * 2.0, "mismatch at index {j}");
}
}
#[test]
fn q8_0_extreme_values_do_not_overflow_i8() {
let block = q8_0_block(0x3C00, [i8::MIN, i8::MAX].repeat(16).try_into().unwrap());
let mut out = Vec::new();
dequant_q8_0(&block, &mut out);
assert_eq!(out[0], f32::from(i8::MIN));
assert_eq!(out[1], f32::from(i8::MAX));
}
#[test]
fn dequantize_dispatches_by_dtype_and_handles_multiple_blocks() {
let block_a = q8_0_block(0x3C00, [1i8; 32]); let block_b = q8_0_block(0x4000, [2i8; 32]); let mut bytes = block_a;
bytes.extend(block_b);
let out = dequantize(DType::Q8_0, &bytes).unwrap();
assert_eq!(out.len(), 64);
assert!(out[0..32].iter().all(|&v| v == 1.0));
assert!(out[32..64].iter().all(|&v| v == 4.0));
}
#[test]
fn quantize_row_q8_0_round_trips_within_one_quantization_step() {
let x: Vec<f32> = (0..32).map(|j| (j as f32 - 16.0) * 0.37).collect();
let (scales, q) = quantize_row_q8_0(&x);
assert_eq!(scales.len(), 1);
let amax = x.iter().fold(0f32, |m, &v| m.max(v.abs()));
let d = amax / 127.0;
assert_eq!(scales[0], d);
for (j, &orig) in x.iter().enumerate() {
let decoded = f32::from(q[j]) * d;
assert!((decoded - orig).abs() <= d / 2.0 + 1e-6, "index {j}: {decoded} vs {orig} (d={d})");
}
}
#[test]
fn quantize_row_q8_0_all_zero_block_has_zero_scale_and_zero_values() {
let (scales, q) = quantize_row_q8_0(&[0.0f32; 32]);
assert_eq!(scales, vec![0.0]);
assert!(q.iter().all(|&v| v == 0));
}
#[test]
fn quantize_row_q8_0_handles_multiple_blocks_independently() {
let mut x = vec![1.0f32; 32];
x.extend(vec![100.0f32; 32]);
let (scales, q) = quantize_row_q8_0(&x);
assert_eq!(scales.len(), 2);
assert_ne!(scales[0], scales[1]);
assert_eq!(q[0], 127);
assert_eq!(q[32], 127);
}
#[test]
fn quantize_row_q8_0_extreme_magnitude_does_not_overflow_i8() {
let x = [f32::MAX, -f32::MAX].repeat(16);
let (_scales, q) = quantize_row_q8_0(&x);
assert!(q.iter().all(|&v| v == 127 || v == -127));
}
#[test]
fn q4_0_dot_q8_0_matches_dequantize_then_dot() {
let mut w_values = [0i8; 32];
for (j, v) in w_values.iter_mut().enumerate() {
*v = ((j % 16) as i8) - 8;
}
let w_block = q4_0_block(0x3C00, w_values);
let x: Vec<f32> = (0..32).map(|j| ((j as f32) - 16.0) * 0.5).collect();
let (x_scales, x_q) = quantize_row_q8_0(&x);
let fused = q4_0_dot_q8_0(&w_block, &x_q, x_scales[0]);
let mut w_decoded = Vec::new();
dequant_q4_0(&w_block, &mut w_decoded);
let x_decoded: Vec<f32> = x_q.iter().map(|&q| f32::from(q) * x_scales[0]).collect();
let reference: f32 = w_decoded.iter().zip(&x_decoded).map(|(a, b)| a * b).sum();
assert!((fused - reference).abs() < 1e-3, "fused={fused}, reference={reference}");
}
#[test]
fn q8_0_dot_q8_0_matches_dequantize_then_dot() {
let mut w_values = [0i8; 32];
for (j, v) in w_values.iter_mut().enumerate() {
*v = ((j as i32 * 7) % 256 - 128) as i8;
}
let w_block = q8_0_block(0x3800, w_values);
let x: Vec<f32> = (0..32).map(|j| (j as f32 - 16.0) * 0.5).collect();
let (x_scales, x_q) = quantize_row_q8_0(&x);
let fused = q8_0_dot_q8_0(&w_block, &x_q, x_scales[0]);
let mut w_decoded = Vec::new();
dequant_q8_0(&w_block, &mut w_decoded);
let x_decoded: Vec<f32> = x_q.iter().map(|&q| f32::from(q) * x_scales[0]).collect();
let reference: f32 = w_decoded.iter().zip(&x_decoded).map(|(a, b)| a * b).sum();
assert!((fused - reference).abs() < 1e-3, "fused={fused}, reference={reference}");
}
#[test]
fn q4_0_dot_q8_0_of_an_all_zero_weight_block_is_zero() {
let w_block = q4_0_block(0x3C00, [0i8; 32]); let x = vec![5.0f32; 32];
let (x_scales, x_q) = quantize_row_q8_0(&x);
assert_eq!(q4_0_dot_q8_0(&w_block, &x_q, x_scales[0]), 0.0);
}
#[test]
fn q4_0_nibble_zero_decodes_to_minus_eight_not_zero() {
let w_block = q4_0_block(0x3C00, [-8i8; 32]); let x = vec![5.0f32; 32];
let (x_scales, x_q) = quantize_row_q8_0(&x);
let dot = q4_0_dot_q8_0(&w_block, &x_q, x_scales[0]);
assert!((dot - -1280.0).abs() < 1e-2, "expected 32 * -8 * 5 = -1280, got {dot}");
}
const F16_ONE: u16 = 0x3C00; const F16_HALF: u16 = 0x3800;
fn pack_k4_scales(scales: &[u8; 8], mins: &[u8; 8]) -> [u8; 12] {
let mut q = [0u8; 12];
for j in 0..4 {
q[j] = (scales[j] & 63) | ((scales[j + 4] >> 4) << 6);
q[j + 4] = (mins[j] & 63) | ((mins[j + 4] >> 4) << 6);
q[j + 8] = (scales[j + 4] & 0x0F) | ((mins[j + 4] & 0x0F) << 4);
}
for j in 0..8 {
assert_eq!(get_scale_min_k4(j, &q), (scales[j], mins[j]), "pack/unpack disagree at {j}");
}
q
}
fn build_q4_k(d: u16, dmin: u16, scales: &[u8; 8], mins: &[u8; 8], nib: &[u8; 256]) -> Vec<u8> {
let mut block = vec![0u8; 144];
block[0..2].copy_from_slice(&d.to_le_bytes());
block[2..4].copy_from_slice(&dmin.to_le_bytes());
block[4..16].copy_from_slice(&pack_k4_scales(scales, mins));
for g in 0..4 {
for l in 0..32 {
let low = nib[2 * g * 32 + l] & 0x0F;
let high = nib[(2 * g + 1) * 32 + l] & 0x0F;
block[16 + g * 32 + l] = low | (high << 4);
}
}
block
}
#[test]
fn q4_k_is_bit_exact_across_both_get_scale_min_k4_branches() {
let scales = [1u8, 2, 3, 4, 5, 6, 7, 63];
let mins = [0u8, 1, 2, 3, 63, 10, 20, 30];
let mut nib = [0u8; 256];
for (e, n) in nib.iter_mut().enumerate() {
*n = (e % 16) as u8; }
let block = build_q4_k(F16_ONE, F16_HALF, &scales, &mins, &nib);
let mut out = Vec::new();
dequant_q4_k(&block, &mut out);
assert_eq!(out.len(), 256);
for e in 0..256 {
let s = e / 32; let expected = 1.0 * f32::from(scales[s]) * f32::from(nib[e]) - 0.5 * f32::from(mins[s]);
assert_eq!(out[e], expected, "Q4_K mismatch at element {e} (sub-block {s})");
}
}
#[test]
fn q4_k_all_zero_superblock_decodes_to_all_zero() {
let block = build_q4_k(F16_ONE, F16_HALF, &[0; 8], &[0; 8], &[0; 256]);
let mut out = Vec::new();
dequant_q4_k(&block, &mut out);
assert_eq!(out.len(), 256);
assert!(out.iter().all(|&v| v == 0.0));
}
fn build_q5_k(
d: u16,
dmin: u16,
scales: &[u8; 8],
mins: &[u8; 8],
nib: &[u8; 256],
hi: &[u8; 256],
) -> Vec<u8> {
let mut block = vec![0u8; 176];
block[0..2].copy_from_slice(&d.to_le_bytes());
block[2..4].copy_from_slice(&dmin.to_le_bytes());
block[4..16].copy_from_slice(&pack_k4_scales(scales, mins));
for l in 0..32 {
let mut byte = 0u8;
for s in 0..8 {
byte |= (hi[s * 32 + l] & 1) << s;
}
block[16 + l] = byte;
}
for g in 0..4 {
for l in 0..32 {
let low = nib[2 * g * 32 + l] & 0x0F;
let high = nib[(2 * g + 1) * 32 + l] & 0x0F;
block[48 + g * 32 + l] = low | (high << 4);
}
}
block
}
#[test]
fn q5_k_is_bit_exact_including_the_fifth_bit() {
let scales = [2u8, 4, 6, 8, 10, 12, 63, 1];
let mins = [0u8, 2, 4, 6, 8, 63, 1, 3];
let mut nib = [0u8; 256];
let mut hi = [0u8; 256];
for e in 0..256 {
nib[e] = (e % 16) as u8;
hi[e] = ((e / 16) % 2) as u8; }
let block = build_q5_k(F16_ONE, F16_HALF, &scales, &mins, &nib, &hi);
let mut out = Vec::new();
dequant_q5_k(&block, &mut out);
assert_eq!(out.len(), 256);
for e in 0..256 {
let s = e / 32;
let val = i32::from(nib[e]) + if hi[e] != 0 { 16 } else { 0 };
let expected = 1.0 * f32::from(scales[s]) * val as f32 - 0.5 * f32::from(mins[s]);
assert_eq!(out[e], expected, "Q5_K mismatch at element {e} (sub-block {s})");
}
}
fn build_q6_k(d: u16, scales: &[i8; 16], v: &[u8; 256]) -> Vec<u8> {
let mut block = vec![0u8; 210];
for h in 0..2 {
for l in 0..32 {
let vq1 = v[h * 128 + l];
let vq2 = v[h * 128 + l + 32];
let vq3 = v[h * 128 + l + 64];
let vq4 = v[h * 128 + l + 96];
block[h * 64 + l] = (vq1 & 0x0F) | ((vq3 & 0x0F) << 4);
block[h * 64 + l + 32] = (vq2 & 0x0F) | ((vq4 & 0x0F) << 4);
block[128 + h * 32 + l] =
(vq1 >> 4) | ((vq2 >> 4) << 2) | ((vq3 >> 4) << 4) | ((vq4 >> 4) << 6);
}
}
for (i, &s) in scales.iter().enumerate() {
block[192 + i] = s as u8;
}
block[208..210].copy_from_slice(&d.to_le_bytes());
block
}
#[test]
fn q6_k_is_bit_exact_with_signed_scales_and_the_minus_32_offset() {
let scales: [i8; 16] = [1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, -8];
let mut v = [0u8; 256];
for (e, x) in v.iter_mut().enumerate() {
*x = (e % 64) as u8;
}
let block = build_q6_k(F16_ONE, &scales, &v);
let mut out = Vec::new();
dequant_q6_k(&block, &mut out);
assert_eq!(out.len(), 256);
for e in 0..256 {
let h = e / 128;
let within = e % 128;
let l = within % 32;
let quadrant = within / 32;
let scale_index = h * 8 + l / 16 + 2 * quadrant;
let expected = 1.0 * f32::from(scales[scale_index]) * (i32::from(v[e]) - 32) as f32;
assert_eq!(out[e], expected, "Q6_K mismatch at element {e}");
}
}
fn build_q2_k(d: u16, dmin: u16, scales: &[u8; 16], mins: &[u8; 16], q2: &[u8; 256]) -> Vec<u8> {
let mut block = vec![0u8; 84];
for i in 0..16 {
block[i] = (scales[i] & 0x0F) | ((mins[i] & 0x0F) << 4);
}
for h in 0..2 {
for p in 0..32 {
let mut byte = 0u8;
for j in 0..4 {
byte |= (q2[h * 128 + j * 32 + p] & 3) << (2 * j);
}
block[16 + h * 32 + p] = byte;
}
}
block[80..82].copy_from_slice(&d.to_le_bytes());
block[82..84].copy_from_slice(&dmin.to_le_bytes());
block
}
#[test]
fn q2_k_is_bit_exact() {
let mut scales = [0u8; 16];
let mut mins = [0u8; 16];
for i in 0..16 {
scales[i] = (i as u8 % 15) + 1; mins[i] = (i as u8) % 16; }
let mut q2 = [0u8; 256];
for (e, x) in q2.iter_mut().enumerate() {
*x = (e % 4) as u8; }
let block = build_q2_k(F16_ONE, F16_HALF, &scales, &mins, &q2);
let mut out = Vec::new();
dequant_q2_k(&block, &mut out);
assert_eq!(out.len(), 256);
for e in 0..256 {
let h = e / 128;
let within = e % 128;
let r = within % 32;
let j = within / 32;
let s = h * 8 + 2 * j + usize::from(r >= 16);
let expected = 1.0 * f32::from(scales[s]) * f32::from(q2[e]) - 0.5 * f32::from(mins[s]);
assert_eq!(out[e], expected, "Q2_K mismatch at element {e} (sub-block {s})");
}
}
fn pack_q3_scales(sc: &[u8; 16]) -> [u8; 12] {
let mut q = [0u8; 12];
for b in 0..4 {
q[b] = (sc[b] & 0x0F) | ((sc[8 + b] & 0x0F) << 4);
q[4 + b] = (sc[4 + b] & 0x0F) | ((sc[12 + b] & 0x0F) << 4);
q[8 + b] = (sc[b] >> 4)
| ((sc[4 + b] >> 4) << 2)
| ((sc[8 + b] >> 4) << 4)
| ((sc[12 + b] >> 4) << 6);
}
q
}
fn build_q3_k(d: u16, sc: &[u8; 16], q2: &[u8; 256], hbit: &[u8; 256]) -> Vec<u8> {
let mut block = vec![0u8; 110];
for r in 0..32 {
let mut byte = 0u8;
for h in 0..2 {
for j in 0..4 {
byte |= (hbit[h * 128 + j * 32 + r] & 1) << (h * 4 + j);
}
}
block[r] = byte;
}
for h in 0..2 {
for p in 0..32 {
let mut byte = 0u8;
for j in 0..4 {
byte |= (q2[h * 128 + j * 32 + p] & 3) << (2 * j);
}
block[32 + h * 32 + p] = byte;
}
}
block[96..108].copy_from_slice(&pack_q3_scales(sc));
block[108..110].copy_from_slice(&d.to_le_bytes());
block
}
#[test]
fn q3_k_is_bit_exact_including_the_aux_scale_unpack_and_inverted_high_bit() {
let mut sc = [0u8; 16];
for (i, s) in sc.iter_mut().enumerate() {
*s = (i as u8 * 4) & 63; }
let mut q2 = [0u8; 256];
let mut hbit = [0u8; 256];
for e in 0..256 {
q2[e] = (e % 4) as u8;
hbit[e] = ((e / 32) % 2) as u8; }
let block = build_q3_k(F16_ONE, &sc, &q2, &hbit);
let mut out = Vec::new();
dequant_q3_k(&block, &mut out);
assert_eq!(out.len(), 256);
for e in 0..256 {
let h = e / 128;
let within = e % 128;
let r = within % 32;
let j = within / 32;
let s = h * 8 + 2 * j + usize::from(r >= 16);
let val = i32::from(q2[e]) - if hbit[e] != 0 { 0 } else { 4 };
let expected = 1.0 * (i32::from(sc[s]) - 32) as f32 * val as f32;
assert_eq!(out[e], expected, "Q3_K mismatch at element {e} (sub-block {s})");
}
}
#[test]
fn q8_k_is_bit_exact_with_an_f32_scale() {
let d: f32 = 0.25;
let mut block = vec![0u8; 292];
block[0..4].copy_from_slice(&d.to_le_bytes());
let mut qs = [0i8; 256];
for (j, q) in qs.iter_mut().enumerate() {
*q = (j as i32 - 128) as i8; block[4 + j] = *q as u8;
}
block[260..292].fill(0xAB);
let mut out = Vec::new();
dequant_q8_k(&block, &mut out);
assert_eq!(out.len(), 256);
for j in 0..256 {
assert_eq!(out[j], 0.25 * f32::from(qs[j]), "Q8_K mismatch at {j}");
}
}
#[test]
fn dequantize_dispatches_k_quants_and_handles_multiple_superblocks() {
let scales = [1u8, 2, 3, 4, 5, 6, 7, 8];
let mins = [0u8; 8];
let nib_a = [1u8; 256];
let nib_b = [2u8; 256];
let mut bytes = build_q4_k(F16_ONE, F16_HALF, &scales, &mins, &nib_a);
bytes.extend(build_q4_k(F16_ONE, F16_HALF, &scales, &mins, &nib_b));
let out = dequantize(DType::Q4_K, &bytes).unwrap();
assert_eq!(out.len(), 512);
assert_eq!(out[0], 1.0);
assert_eq!(out[256], 2.0);
}
}