#![allow(dead_code)]
use ferrox_core::cache::KvCache;
use ferrox_models::{Decoder, ModelConfig};
use std::path::{Path, PathBuf};
pub fn assert_close(got: &[f32], want: &[f32], tol: f32, what: &str) {
assert_eq!(got.len(), want.len(), "{what}: logit count");
let worst = got
.iter()
.zip(want.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max);
assert!(
worst <= tol,
"{what}: max |ferrox - llama.cpp| = {worst} > {tol}\n ferrox: {:?}\n llama: {:?}",
&got[..8.min(got.len())],
&want[..8.min(want.len())]
);
}
pub fn collect_gguf(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
collect_gguf(&p, out);
} else if p.extension().and_then(|x| x.to_str()) == Some("gguf") {
out.push(p);
}
}
}
pub const GRAPH_PROMPT: [usize; 6] = [3, 7, 11, 19, 23, 5];
pub const GRAPH_TOL: f32 = 1e-5;
pub fn graph_fixture_path(name: &str) -> String {
format!(
"{}/tests/fixtures/{name}_tiny.gguf",
env!("CARGO_MANIFEST_DIR")
)
}
pub fn load_graph_fixture(name: &str) -> Decoder {
let path = graph_fixture_path(name);
let file = ferrox_gguf::GgufFile::open(&path).expect("fixture opens");
let config = ModelConfig::from_gguf(&file).expect("fixture config parses");
Decoder::from_gguf(&path, config).expect("fixture loads")
}
pub fn graph_caches(decoder: &Decoder) -> Vec<KvCache> {
decoder
.layers
.iter()
.map(|_| KvCache::new(decoder.config.n_kv_heads, decoder.config.head_dim))
.collect()
}
pub fn assert_all_three_paths_match(name: &str, golden: &[f32]) {
let decoder = load_graph_fixture(name);
let mut kv = graph_caches(&decoder);
assert_close(
&decoder.forward_batch_last(&GRAPH_PROMPT, 0, &mut kv),
golden,
GRAPH_TOL,
&format!("{name}: prefill (forward_batch_last)"),
);
let mut kv = graph_caches(&decoder);
let mut out = Vec::new();
for (pos, &tok) in GRAPH_PROMPT.iter().enumerate() {
out = decoder.forward_token(tok, pos, &mut kv);
}
assert_close(&out, golden, GRAPH_TOL, &format!("{name}: decode"));
let mut kv = vec![graph_caches(&decoder)];
let mut out = Vec::new();
for (pos, &tok) in GRAPH_PROMPT.iter().enumerate() {
let batch = decoder.forward_multi_seq(&[tok], &[pos], &mut kv);
out = batch.into_iter().next().unwrap();
}
assert_close(&out, golden, GRAPH_TOL, &format!("{name}: multi-seq"));
}
pub fn worst_vs(got: &[f32], golden: &[f32]) -> f32 {
got.iter()
.zip(golden.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max)
}