use std::collections::HashMap;
use std::path::PathBuf;
use cera::gguf::GgufFile;
use cera::kv_cache::{InferenceState, KvCompression};
use cera::model::Model;
use cera::model::llama::LlamaModel;
use cera::model::transformer::oracle_dump;
use cera::tokenizer::BpeTokenizer;
fn rel_diff(a: f64, b: f64) -> f64 {
(a - b).abs() / (a.abs() + b.abs() + 1e-9)
}
fn argmax(logits: &[f32]) -> u32 {
logits
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
let a = if a.is_nan() { f32::NEG_INFINITY } else { **a };
let b = if b.is_nan() { f32::NEG_INFINITY } else { **b };
a.total_cmp(&b)
})
.map(|(i, _)| i as u32)
.unwrap_or(0)
}
const SUM_REL_TOL: f64 = 0.05;
const SUM_MAG_FLOOR: f64 = 10.0;
fn fixtures_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/oracle")
}
fn models_dir() -> PathBuf {
if let Ok(d) = std::env::var("CERA_ORACLE_MODELS_DIR") {
return PathBuf::from(d);
}
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../target/oracle/models")
}
fn encode_with_bos(tok: &BpeTokenizer, want_tokens: &[u32], prompt: &str) -> Vec<u32> {
let base = tok.encode(prompt);
match tok.bos_token() {
Some(bos)
if want_tokens.first() == Some(&bos)
&& want_tokens.len() == base.len() + 1
&& want_tokens[1..] == base[..] =>
{
let mut tokens = Vec::with_capacity(base.len() + 1);
tokens.push(bos);
tokens.extend_from_slice(&base);
tokens
}
_ => base,
}
}
fn check_model(fixture_dir: &std::path::Path) -> Option<Vec<String>> {
let index_path = fixture_dir.join("index.json");
let index: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(&index_path)
.unwrap_or_else(|e| panic!("read {}: {e}", index_path.display())),
)
.unwrap_or_else(|e| panic!("parse {}: {e}", index_path.display()));
let model_file = index["model_file"].as_str().unwrap();
let mp = models_dir().join(model_file);
if !mp.exists() {
eprintln!("skipping {model_file}: not found at {}", mp.display());
return None;
}
eprintln!("=== oracle model: {} ===", mp.display());
let gguf = GgufFile::open(&mp).expect("open gguf");
let tokenizer = BpeTokenizer::from_gguf(&gguf).expect("tokenizer");
let model = LlamaModel::from_gguf(GgufFile::open(&mp).expect("open gguf"), 8192)
.expect("load LlamaModel");
let n_layers = model.config().n_layers;
let last = n_layers - 1;
let scalars = model.config().scalars;
let expected_op = |name: &str| -> &'static str {
if name == "embd" {
if scalars.embedding != 1.0 {
"SCALE"
} else {
"GET_ROWS"
}
} else if name.starts_with("l_out-") {
"ADD"
} else if name == "result_norm" {
"MUL"
} else if name == "result_output" {
if scalars.logit != 1.0 {
"SCALE"
} else {
"MUL_MAT"
}
} else {
panic!("unmapped oracle node name {name:?}")
}
};
let last_pos_only =
|name: &str| name.starts_with("result_") || name.ends_with(&format!("-{last}"));
let informational = |name: &str| {
name == "result_norm" || name == "result_output" || name == format!("l_out-{last}")
};
let mut failures = Vec::new();
for entry in index["prompts"].as_array().unwrap() {
let fname = entry["fixture"].as_str().unwrap();
let fx: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(fixture_dir.join(fname)).unwrap())
.unwrap();
let prompt = fx["prompt"].as_str().unwrap();
let want_tokens: Vec<u32> = fx["input_tokens"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_u64().unwrap() as u32)
.collect();
let got_tokens = encode_with_bos(&tokenizer, &want_tokens, prompt);
if got_tokens != want_tokens {
failures.push(format!(
"[{fname}] tokenizer mismatch:\n cera: {got_tokens:?}\n llama:{want_tokens:?}"
));
continue; }
let mut state =
InferenceState::from_config_with_compression(model.config(), &KvCompression::None);
oracle_dump::begin();
let _ = model.forward_prefill(&got_tokens, 0, &mut state);
let occ = oracle_dump::take();
let mut cera: HashMap<String, f64> = HashMap::new();
for (name, sum) in occ {
if last_pos_only(&name) {
cera.insert(name, sum); } else {
*cera.entry(name).or_insert(0.0) += sum;
}
}
let mut want: HashMap<(&str, &str), f64> = HashMap::new();
for node in fx["nodes"].as_array().unwrap() {
want.insert(
(node["name"].as_str().unwrap(), node["op"].as_str().unwrap()),
node["sum"].as_f64().unwrap(),
);
}
let mut worst = 0.0f64;
let mut checked = 0usize;
for (name, &got) in &cera {
let op = expected_op(name);
let Some(&exp) = want.get(&(name.as_str(), op)) else {
failures.push(format!(
"[{fname}] oracle has no node {name:?} with op {op:?}"
));
continue;
};
let d = rel_diff(got, exp);
if informational(name) || exp.abs() < SUM_MAG_FLOOR {
eprintln!("[{fname}] (info) {name}/{op}: cera={got:.4} llama={exp:.4} rel={d:.4}");
continue;
}
checked += 1;
worst = worst.max(d);
if d > SUM_REL_TOL {
failures.push(format!(
"[{fname}] sum mismatch at {name}/{op}: cera={got:.4} llama={exp:.4} rel={d:.4}"
));
}
}
assert!(
checked >= n_layers / 2,
"[{fname}] only {checked} nodes checked — instrumentation/fixture drift"
);
eprintln!("[{fname}] sums OK — {checked} gated nodes, worst rel diff {worst:.5}");
let n_predict = index["n_predict"].as_u64().unwrap_or(16) as usize;
let want_text = fx["greedy_text"].as_str().unwrap().trim_end();
let mut gstate =
InferenceState::from_config_with_compression(model.config(), &KvCompression::None);
let mut logits = model.forward_prefill(&got_tokens, 0, &mut gstate);
let mut out_tokens: Vec<u32> = Vec::new();
for _ in 0..n_predict {
let next = argmax(&logits);
if tokenizer.eos_token() == Some(next) {
break;
}
out_tokens.push(next);
logits = model.forward(&[next], gstate.seq_len, &mut gstate);
}
let got_text = tokenizer.decode(&out_tokens);
let got_text = got_text.trim_end();
if got_text == want_text {
eprintln!("[{fname}] greedy MATCH — {got_text:?}");
} else {
eprintln!(
"[{fname}] greedy DIVERGES (tie-flip; not gated):\n cera: {got_text:?}\n llama:{want_text:?}"
);
}
}
Some(failures)
}
#[test]
#[ignore] fn text_models_match_llama_cpp_oracle() {
if std::env::var("CERA_ORACLE").as_deref() != Ok("1") {
eprintln!("skipping: CERA_ORACLE=1 not set");
return;
}
let mut dirs: Vec<PathBuf> = std::fs::read_dir(fixtures_root())
.expect("read fixtures dir")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.is_dir())
.collect();
dirs.sort();
assert!(!dirs.is_empty(), "no oracle fixture sets found");
let mut all_failures = Vec::new();
let mut ran = 0usize;
for dir in &dirs {
if let Some(failures) = check_model(dir) {
ran += 1;
all_failures.extend(failures);
}
}
if ran == 0 {
eprintln!(
"skipping: no oracle models under {}",
models_dir().display()
);
return;
}
assert!(
all_failures.is_empty(),
"oracle gate failures:\n{}",
all_failures.join("\n")
);
}