use half::f16;
use crate::{Q8_0_BLOCK_BYTES, Q8_0_BLOCK_ELEMS};
#[inline]
pub fn encode_block_q8_0(block: &[f32; Q8_0_BLOCK_ELEMS], out: &mut Vec<u8>) {
let mut amax = 0f32;
for &v in block.iter() {
let av = v.abs();
amax = if amax > av { amax } else { av };
}
let d = amax / 127.0;
let id = if d != 0.0 { 1.0 / d } else { 0.0 };
out.extend_from_slice(&f16::from_f32(d).to_le_bytes());
for &v in block.iter() {
out.push(((v * id).round() as i8) as u8);
}
}
pub fn encode_row_q8_0(src: &[f32], out: &mut Vec<u8>) -> Option<()> {
let (blocks, rest) = src.as_chunks::<Q8_0_BLOCK_ELEMS>();
if !rest.is_empty() {
return None;
}
out.reserve(blocks.len() * Q8_0_BLOCK_BYTES);
for block in blocks {
encode_block_q8_0(block, out);
}
Some(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dequant_q8_0;
fn sample_input(n: usize) -> Vec<f32> {
let mut state: u32 = 0x1234_5678;
(0..n)
.map(|_| {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
((state >> 8) as f32 / 8_388_608.0) - 1.0
})
.collect()
}
const LLAMA_CPP_Q8_0_GOLDEN: [u8; 2 * Q8_0_BLOCK_BYTES] = [
0xdc, 0x1f, 0x08, 0x93, 0xc7, 0x02, 0xf0, 0xa8, 0x0a, 0x46, 0x55, 0xb9, 0xe9, 0xcd, 0x7f,
0xb4, 0x0d, 0x79, 0x4f, 0x71, 0x6a, 0xc0, 0xac, 0x6d, 0xa8, 0x51, 0x7a, 0x77, 0x2d, 0x42,
0x6c, 0xcc, 0x8e, 0x7a, 0xe9, 0x1f, 0x42, 0x4d, 0x18, 0x19, 0x05, 0x50, 0x66, 0xfa, 0xe8,
0x59, 0xf7, 0xc4, 0xac, 0x9c, 0xb4, 0xa8, 0xe9, 0x93, 0x17, 0x3f, 0xad, 0xef, 0x06, 0x4a,
0xf8, 0x3f, 0xa3, 0xea, 0x7f, 0x30, 0x3e, 0x8d,
];
#[test]
fn q8_0_matches_llama_cpp_quantize_row_q8_0_ref() {
let x = sample_input(2 * Q8_0_BLOCK_ELEMS);
let mut got = Vec::new();
encode_row_q8_0(&x, &mut got).unwrap();
assert_eq!(
got.as_slice(),
&LLAMA_CPP_Q8_0_GOLDEN[..],
"ferrox's Q8_0 encoder disagrees with llama.cpp's"
);
}
const TIE_BLOCK_F16_BITS: [u16; Q8_0_BLOCK_ELEMS] = [
0xadf3, 0x247e, 0x28d0, 0x221a, 0xb017, 0x98ed, 0xa97d, 0x3010, 0xac34, 0x0c3e, 0x2cb8,
0x2bf9, 0xa7e5, 0xb0b8, 0x3030, 0xb00a, 0xac33, 0xac39, 0xac46, 0x2b78, 0x3007, 0xa5fe,
0x2feb, 0x30b2, 0x3033, 0xad15, 0xb046, 0x2cd7, 0xaff4, 0xaca6, 0x2c7c, 0xaf49,
];
const LLAMA_CPP_TIE_BLOCK_GOLDEN: [u8; Q8_0_BLOCK_BYTES] = [
0xc2, 0x14, 0xb0, 0x0f, 0x20, 0x0a, 0x92, 0xfe, 0xdb, 0x6d, 0xc7, 0x00, 0x3f, 0x36, 0xe5,
0x81, 0x71, 0x93, 0xc7, 0xc7, 0xc6, 0x32, 0x6c, 0xec, 0x6b, 0x7e, 0x71, 0xbc, 0x8d, 0x41,
0x95, 0xc1, 0x3c, 0x9e,
];
#[test]
fn the_scale_is_applied_as_llama_cpp_applies_it_not_as_a_division() {
let x: Vec<f32> = TIE_BLOCK_F16_BITS
.iter()
.map(|b| f16::from_bits(*b).to_f32())
.collect();
let mut got = Vec::new();
encode_row_q8_0(&x, &mut got).unwrap();
assert_eq!(got.as_slice(), &LLAMA_CPP_TIE_BLOCK_GOLDEN[..]);
let amax = x
.iter()
.fold(0f32, |a, &b| if a > b.abs() { a } else { b.abs() });
let d = amax / 127.0;
let divided: Vec<u8> = x.iter().map(|v| ((v / d).round() as i8) as u8).collect();
assert_ne!(
divided.as_slice(),
&LLAMA_CPP_TIE_BLOCK_GOLDEN[2..],
"this block no longer distinguishes the two spellings"
);
}
#[test]
fn an_all_zero_block_stores_a_zero_scale_the_way_llama_cpp_does() {
let mut out = Vec::new();
encode_row_q8_0(&[0.0; Q8_0_BLOCK_ELEMS], &mut out).unwrap();
assert_eq!(out, vec![0u8; Q8_0_BLOCK_BYTES]);
}
#[test]
fn a_row_that_is_not_a_whole_number_of_blocks_is_refused() {
let mut out = Vec::new();
assert!(encode_row_q8_0(&[0.5; Q8_0_BLOCK_ELEMS + 1], &mut out).is_none());
assert!(encode_row_q8_0(&[0.5; 1], &mut out).is_none());
assert!(encode_row_q8_0(&[], &mut out).is_some());
}
#[test]
fn dequantizing_what_this_encodes_lands_within_a_quantization_step() {
let x = sample_input(8 * Q8_0_BLOCK_ELEMS);
let mut bytes = Vec::new();
encode_row_q8_0(&x, &mut bytes).unwrap();
let back = dequant_q8_0(&bytes).unwrap();
assert_eq!(back.len(), x.len());
for (block_i, (chunk, got)) in x
.chunks(Q8_0_BLOCK_ELEMS)
.zip(back.chunks(Q8_0_BLOCK_ELEMS))
.enumerate()
{
let amax = chunk.iter().fold(0f32, |a, &b| a.max(b.abs()));
let step = amax / 127.0;
let bound = step * (0.5 + 127.0 * 2f32.powi(-11));
for (i, (&want, &have)) in chunk.iter().zip(got.iter()).enumerate() {
assert!(
(want - have).abs() <= bound,
"block {block_i} element {i}: {want} -> {have}, error {} > {bound}",
(want - have).abs()
);
}
}
}
}