use half::f16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TritLayout {
pub qs_len: usize,
pub qh_len: usize,
}
impl TritLayout {
pub const fn block_elems(self) -> usize {
self.qs_len * 5 + self.qh_len * 4
}
pub const fn block_bytes(self) -> usize {
self.qs_len + self.qh_len + 2
}
}
pub const TQ1_0: TritLayout = TritLayout {
qs_len: 48,
qh_len: 4,
};
pub const PTQ1_0: TritLayout = TritLayout {
qs_len: 24,
qh_len: 2,
};
pub const TQ1_0_BLOCK_BYTES: usize = TQ1_0.block_bytes();
pub const TQ1_0_BLOCK_ELEMS: usize = TQ1_0.block_elems();
pub const PTQ1_0_BLOCK_BYTES: usize = PTQ1_0.block_bytes();
pub const PTQ1_0_BLOCK_ELEMS: usize = PTQ1_0.block_elems();
const _: () = assert!(TQ1_0_BLOCK_BYTES == 54 && TQ1_0_BLOCK_ELEMS == 256);
const _: () = assert!(PTQ1_0_BLOCK_BYTES == 28 && PTQ1_0_BLOCK_ELEMS == 128);
const STAGES: [usize; 3] = [32, 16, 8];
const POW3: [u8; 5] = [1, 3, 9, 27, 81];
#[inline]
pub fn unpack_trits(block: &[u8], layout: TritLayout, out: &mut [i8]) {
debug_assert!(block.len() >= layout.block_bytes());
debug_assert!(out.len() >= layout.block_elems());
let qs = &block[..layout.qs_len];
let qh = &block[layout.qs_len..layout.qs_len + layout.qh_len];
let mut o = 0usize;
let mut j = 0usize;
for &c in &STAGES {
while j + c <= layout.qs_len {
for &p in &POW3 {
for &b in &qs[j..j + c] {
let q = b.wrapping_mul(p);
let xi = ((q as u16) * 3) >> 8;
out[o] = xi as i8 - 1;
o += 1;
}
}
j += c;
}
}
for &p in &POW3[..4] {
for &b in qh {
let q = b.wrapping_mul(p);
let xi = ((q as u16) * 3) >> 8;
out[o] = xi as i8 - 1;
o += 1;
}
}
debug_assert_eq!(o, layout.block_elems());
}
#[inline]
pub fn block_scale(block: &[u8], layout: TritLayout) -> f32 {
let at = layout.qs_len + layout.qh_len;
f16::from_le_bytes([block[at], block[at + 1]]).to_f32()
}
pub fn dequant_trits(row_bytes: &[u8], layout: TritLayout) -> Result<Vec<f32>, crate::QuantError> {
let bb = layout.block_bytes();
if !row_bytes.len().is_multiple_of(bb) {
return Err(crate::QuantError::Misaligned(row_bytes.len(), bb));
}
let n = row_bytes.len() / bb;
let mut out = Vec::with_capacity(n * layout.block_elems());
let mut trits = [0i8; 256];
for block in row_bytes.chunks_exact(bb) {
let d = block_scale(block, layout);
unpack_trits(block, layout, &mut trits);
out.extend(trits[..layout.block_elems()].iter().map(|&t| t as f32 * d));
}
Ok(out)
}
pub fn dot_trits_f32(row_bytes: &[u8], layout: TritLayout, x: &[f32]) -> f32 {
let bb = layout.block_bytes();
let be = layout.block_elems();
debug_assert_eq!(row_bytes.len() / bb * be, x.len());
let mut trits = [0i8; 256];
let mut acc = 0f32;
for (block, xs) in row_bytes.chunks_exact(bb).zip(x.chunks_exact(be)) {
unpack_trits(block, layout, &mut trits);
let mut plus = 0f32;
let mut minus = 0f32;
for (&t, &v) in trits[..be].iter().zip(xs) {
if t > 0 {
plus += v;
} else if t < 0 {
minus += v;
}
}
acc += block_scale(block, layout) * (plus - minus);
}
acc
}
pub fn dot_trits_q8(row_bytes: &[u8], layout: TritLayout, act: &crate::Q8Activations) -> f32 {
let bb = layout.block_bytes();
let be = layout.block_elems();
let sub = be / crate::Q8_0_BLOCK_ELEMS;
debug_assert_eq!(row_bytes.len() / bb * sub, act.n_blocks());
let mut trits = [0i8; 256];
let mut acc = 0f32;
for (bi, block) in row_bytes.chunks_exact(bb).enumerate() {
unpack_trits(block, layout, &mut trits);
let mut block_sum = 0f32;
for s in 0..sub {
let ab = bi * sub + s;
let q = &act.q[ab * 32..(ab + 1) * 32];
let t = &trits[s * 32..(s + 1) * 32];
let isum: i32 = t.iter().zip(q).map(|(&t, &q)| t as i32 * q as i32).sum();
block_sum += isum as f32 * act.d[ab];
}
acc += block_scale(block, layout) * block_sum;
}
acc
}
pub fn quantize_trits(x: &[f32], layout: TritLayout) -> Vec<u8> {
let be = layout.block_elems();
assert!(x.len().is_multiple_of(be));
let mut out = Vec::with_capacity(x.len() / be * layout.block_bytes());
for xs in x.chunks_exact(be) {
let amax = xs.iter().fold(0f32, |m, v| m.max(v.abs()));
let id = if amax > 0.0 { 1.0 / amax } else { 0.0 };
let trit = |v: f32| -> u8 { ((v * id).round() as i32 + 1) as u8 };
let mut block = Vec::with_capacity(layout.block_bytes());
let mut e = 0usize;
let mut j = 0usize;
for &c in &STAGES {
while j + c <= layout.qs_len {
for m in 0..c {
let mut q: u8 = 0;
for n in 0..5 {
q = q.wrapping_mul(3).wrapping_add(trit(xs[e + m + n * c]));
}
block.push(((q as u16) * 256).div_ceil(243) as u8);
}
e += 5 * c;
j += c;
}
}
for h in 0..layout.qh_len {
let mut q: u8 = 0;
for m in 0..4 {
q = q
.wrapping_mul(3)
.wrapping_add(trit(xs[e + h + m * layout.qh_len]));
}
q = q.wrapping_mul(3);
block.push(((q as u16) * 256).div_ceil(243) as u8);
}
block.extend_from_slice(&f16::from_f32(amax).to_le_bytes());
debug_assert_eq!(block.len(), layout.block_bytes());
out.extend(block);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn ternary_row(n: usize, seed: u32) -> Vec<f32> {
let mut s = seed;
(0..n)
.map(|_| {
s = s.wrapping_mul(1664525).wrapping_add(1013904223);
match (s >> 24) % 3 {
0 => -0.75,
1 => 0.0,
_ => 0.75,
}
})
.collect()
}
#[test]
fn the_two_layouts_are_the_structs_in_ggml_common_h() {
assert_eq!((TQ1_0.block_bytes(), TQ1_0.block_elems()), (54, 256));
assert_eq!((PTQ1_0.block_bytes(), PTQ1_0.block_elems()), (28, 128));
assert_eq!(POW3, [1, 3, 9, 27, 81]);
}
#[test]
fn a_ternary_row_round_trips_exactly_in_both_layouts() {
for (layout, n) in [(PTQ1_0, 128 * 3), (TQ1_0, 256 * 2)] {
let x = ternary_row(n, 7);
let packed = quantize_trits(&x, layout);
assert_eq!(
packed.len(),
n / layout.block_elems() * layout.block_bytes()
);
let back = dequant_trits(&packed, layout).unwrap();
assert_eq!(back, x, "{layout:?}");
}
}
#[test]
fn a_hand_packed_ptq1_0_block_decodes_in_stage_order() {
let x: Vec<f32> = (0..128).map(|e| (e % 3) as f32 - 1.0).collect();
let packed = quantize_trits(&x, PTQ1_0);
let mut trits = [0i8; 256];
unpack_trits(&packed, PTQ1_0, &mut trits);
for (e, &t) in trits[..128].iter().enumerate() {
assert_eq!(t as i32, (e % 3) as i32 - 1, "element {e}");
}
assert_eq!(block_scale(&packed, PTQ1_0), 1.0);
let digits = [x[0], x[16], x[32], x[48], x[64]]
.iter()
.fold(0u32, |q, v| q * 3 + (*v as i32 + 1) as u32);
assert_eq!(packed[0] as u32, (digits * 256).div_ceil(243));
}
#[test]
fn the_dots_agree_with_a_dequantized_f32_dot() {
for (layout, n) in [(PTQ1_0, 128 * 4), (TQ1_0, 256 * 2)] {
let w = ternary_row(n, 3);
let packed = quantize_trits(&w, layout);
let x: Vec<f32> = (0..n).map(|i| ((i as f32) * 0.37).sin()).collect();
let want: f32 = dequant_trits(&packed, layout)
.unwrap()
.iter()
.zip(&x)
.map(|(a, b)| a * b)
.sum();
let got = dot_trits_f32(&packed, layout, &x);
assert!((got - want).abs() < 1e-4, "{layout:?}: {got} vs {want}");
let act = crate::quantize_activations_q8(&x);
let got_q8 = dot_trits_q8(&packed, layout, &act);
assert!(
(got_q8 - want).abs() < 2e-2 * want.abs().max(1.0),
"{layout:?}: q8 {got_q8} vs {want}"
);
}
}
}