use std::collections::BTreeMap;
use std::path::Path;
use anyhow::{bail, Result};
use frink_gguf::GgufValue;
use super::Planned;
use crate::imatrix::file::ImatrixWeights;
pub(crate) fn load_imatrix(path: &Path) -> Result<ImatrixWeights> {
let im = crate::imatrix::file::read(path)?;
for (name, vals) in &im.entries {
if let Some(bad) = vals.iter().find(|v| !v.is_finite()) {
bail!("imatrix contains non-finite value {bad} in entry {name}");
}
}
println!(
"quantize: have weights data with {} entries from {} (computed on {} chunks)",
im.entries.len(),
path.display(),
im.chunk_count
);
Ok(im)
}
fn truncate_127(s: &str) -> String {
let mut end = s.len().min(127);
while !s.is_char_boundary(end) {
end -= 1;
}
s[..end].to_string()
}
pub(crate) fn imatrix_metadata(
metadata: &mut BTreeMap<String, GgufValue>,
im: &ImatrixWeights,
path: &Path,
) {
metadata.insert(
"quantize.imatrix.file".to_string(),
GgufValue::String(truncate_127(&path.to_string_lossy())),
);
if let Some(d) = im.datasets.first() {
metadata.insert(
"quantize.imatrix.dataset".to_string(),
GgufValue::String(truncate_127(d)),
);
}
metadata.insert(
"quantize.imatrix.entries_count".to_string(),
GgufValue::U32(im.entries.len() as u32),
);
if im.chunk_count > 0 {
metadata.insert(
"quantize.imatrix.chunks_count".to_string(),
GgufValue::U32(im.chunk_count),
);
}
}
pub(crate) fn imatrix_for_tensor<'a>(
im: &'a ImatrixWeights,
p: &Planned,
) -> Result<Option<&'a [f32]>> {
let Some(vals) = im.entries.get(&p.name) else {
println!("quantize: did not find weights for {}", p.name);
return Ok(None);
};
let ne0 = p.shape[0] as usize;
let ne2 = p.shape.get(2).copied().unwrap_or(1) as usize;
if vals.len() == ne0 * ne2 {
return Ok(Some(vals));
}
if p.name == "token_embd.weight" {
println!(
"quantize: imatrix size {} is different from tensor size {} for {}; quantizing it \
unweighted, as llama.cpp does",
vals.len(),
ne0 * ne2,
p.name
);
return Ok(None);
}
bail!(
"imatrix size {} is different from tensor size {} for {}",
vals.len(),
ne0 * ne2,
p.name
)
}
#[cfg(test)]
mod tests {
use super::*;
use frink_gguf::GgmlType;
fn planned(name: &str, shape: Vec<u64>) -> Planned {
Planned {
name: name.into(),
shape,
source_dtype: GgmlType::F16,
out_dtype: GgmlType::Q4K,
source_bytes: 0,
out_bytes: 0,
copy_reason: None,
}
}
fn weights(entries: &[(&str, usize)]) -> ImatrixWeights {
ImatrixWeights {
entries: entries
.iter()
.map(|(n, len)| (n.to_string(), vec![1.0; *len]))
.collect(),
datasets: vec!["calib.txt".into()],
chunk_count: 3,
}
}
#[test]
fn a_tensor_gets_its_slice_when_the_width_matches() {
let im = weights(&[
("blk.0.attn_q.weight", 256),
("blk.0.ffn_gate_exps.weight", 256 * 4),
]);
let p = planned("blk.0.attn_q.weight", vec![256, 8]);
assert_eq!(imatrix_for_tensor(&im, &p).unwrap().unwrap().len(), 256);
let p = planned("blk.0.ffn_gate_exps.weight", vec![256, 8, 4]);
assert_eq!(imatrix_for_tensor(&im, &p).unwrap().unwrap().len(), 1024);
}
#[test]
fn a_missing_entry_is_unweighted_and_a_wrong_width_is_refused_except_for_the_embedding() {
let im = weights(&[("blk.0.attn_q.weight", 128), ("token_embd.weight", 128)]);
let p = planned("output.weight", vec![256, 8]);
assert!(imatrix_for_tensor(&im, &p).unwrap().is_none());
let p = planned("blk.0.attn_q.weight", vec![256, 8]);
let err = imatrix_for_tensor(&im, &p).unwrap_err().to_string();
assert!(
err.contains("imatrix size 128 is different from tensor size 256"),
"{err}"
);
let p = planned("token_embd.weight", vec![256, 8]);
assert!(imatrix_for_tensor(&im, &p).unwrap().is_none());
let im = weights(&[("blk.0.attn_q.weight", 1024)]);
let p = planned("blk.0.attn_q.weight", vec![256, 8]);
assert!(imatrix_for_tensor(&im, &p).is_err());
}
#[test]
fn the_metadata_keys_match_llama_quantizes_types_and_truncation() {
let im = weights(&[("blk.0.attn_q.weight", 4)]);
let long = format!("/{}/imatrix.gguf", "x".repeat(200));
let mut md = BTreeMap::new();
imatrix_metadata(&mut md, &im, Path::new(&long));
assert_eq!(md["quantize.imatrix.file"].as_str(), Some(&long[..127]));
assert_eq!(md["quantize.imatrix.dataset"].as_str(), Some("calib.txt"));
assert!(matches!(
md["quantize.imatrix.entries_count"],
GgufValue::U32(1)
));
assert!(matches!(
md["quantize.imatrix.chunks_count"],
GgufValue::U32(3)
));
let mut md = BTreeMap::new();
let mut im0 = im.clone();
im0.chunk_count = 0;
imatrix_metadata(&mut md, &im0, Path::new("im.dat"));
assert!(!md.contains_key("quantize.imatrix.chunks_count"));
}
#[test]
fn the_truncation_respects_char_boundaries() {
let s = format!("{}\u{e9}", "a".repeat(126));
assert_eq!(truncate_127(&s).len(), 126);
}
}