#[test]
#[ignore = "needs APR_IQ_AB_MODEL + APR_GGUFPY_REF_DIR + APR_GGUFPY_QTYPE"]
fn every_tensor_decodes_like_gguf_py() {
use sha2::{Digest, Sha256};
let (Ok(model), Ok(refdir), Ok(qtype)) = (
std::env::var("APR_IQ_AB_MODEL"),
std::env::var("APR_GGUFPY_REF_DIR"),
std::env::var("APR_GGUFPY_QTYPE"),
) else {
eprintln!("SKIP: set APR_IQ_AB_MODEL, APR_GGUFPY_REF_DIR and APR_GGUFPY_QTYPE");
return;
};
let qtype: u32 = qtype
.parse()
.expect("APR_GGUFPY_QTYPE is a ggml type number");
let block_bytes = super::iq_dispatch::iq_block_bytes(qtype)
.unwrap_or_else(|| panic!("ggml type {qtype} is not an IQ type this crate decodes"));
let block_elems = super::iq_dispatch::iq_block_elems(qtype).expect("elems for an IQ type");
let manifest = std::fs::read_to_string(format!("{refdir}/MANIFEST.tsv"))
.expect("the reference MANIFEST.tsv (run scripts/iq_gguf_py_reference.py first)");
let rows: Vec<Vec<&str>> = manifest.lines().map(|l| l.split('\t').collect()).collect();
assert!(
!rows.is_empty(),
"the MANIFEST names no tensor: nothing would be compared"
);
let mapped = crate::gguf::MappedGGUFModel::from_path(&model).expect("map the GGUF");
let base = mapped.model.tensor_data_start;
let (mut exact_tensors, mut elems_total, mut elems_exact) = (0usize, 0usize, 0usize);
let mut worst = (0.0f64, String::new());
for r in &rows {
let (name, ne1, ne0, sha) = (
r[0],
r[1].parse::<usize>().unwrap(),
r[2].parse::<usize>().unwrap(),
r[3],
);
let t = mapped
.model
.tensors
.iter()
.find(|t| t.name == name)
.unwrap_or_else(|| panic!("MANIFEST names {name}, which the model does not hold"));
assert_eq!(
t.qtype, qtype,
"{name}: model says type {}, reference was built for {qtype}",
t.qtype
);
let nb = ne0.div_ceil(block_elems);
let start = base + usize::try_from(t.offset).unwrap();
let raw = &mapped.mmap[start..start + ne1 * nb * block_bytes];
let got_sha = format!("{:x}", Sha256::digest(raw));
assert_eq!(
got_sha, sha,
"{name}: the reference was computed from different bytes"
);
let reference: Vec<f32> = std::fs::read(format!("{refdir}/{name}.f32"))
.unwrap_or_else(|e| panic!("{name}.f32: {e}"))
.chunks_exact(4)
.map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.collect();
assert_eq!(reference.len(), ne1 * ne0, "{name}: reference size");
let mut row_buf = vec![0.0f32; nb * block_elems];
let mut tensor_exact = true;
for row in 0..ne1 {
for b in 0..nb {
let off = (row * nb + b) * block_bytes;
super::iq_dispatch::dequantize_iq_block(
qtype,
&raw[off..off + block_bytes],
&mut row_buf[b * block_elems..(b + 1) * block_elems],
)
.expect("decode one block");
}
for c in 0..ne0 {
let (a, e) = (row_buf[c], reference[row * ne0 + c]);
elems_total += 1;
if a.to_bits() == e.to_bits() {
elems_exact += 1;
} else {
tensor_exact = false;
let rel = f64::from((a - e).abs()) / f64::from(e.abs()).max(1e-30);
if !matches!(
rel.partial_cmp(&worst.0),
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
) {
worst = (
rel,
format!("{name}[{row},{c}]: ours {a:e} vs gguf-py {e:e}"),
);
}
}
}
}
if tensor_exact {
exact_tensors += 1;
}
}
eprintln!(
"CELL ({model}, q={qtype}, CPU decoder vs gguf-py) -> {} tensors, {exact_tensors} \
bit-exact; {elems_exact}/{elems_total} elements bit-exact; worst relative {:.3e} {}",
rows.len(),
worst.0,
worst.1
);
assert!(
worst.0 <= 1e-6,
"our CPU decoder disagrees with gguf-py beyond rounding: {}",
worst.1
);
}