use std::path::{Path, PathBuf};
use std::process::Command;
use ferrox_models::embedding_model::EmbeddingModel;
use ferrox_models::pooling::PoolingType;
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("crates/<crate>/ has two ancestors")
.to_path_buf()
}
fn model_path() -> Option<PathBuf> {
let p = repo_root().join("models/bge-small-en-v1.5-q8_0.gguf");
p.exists().then_some(p)
}
const CASES: &[&str] = &[
"Hello world",
"The quick brown fox jumps over the lazy dog.",
"What is the capital of France?",
"def main():\n print(\"hi\")",
"Représentant naïve café",
"东京是日本的首都",
"a",
"Embeddings are dense vector representations of text used for retrieval.",
];
#[test]
#[ignore = "needs models/bge-small-en-v1.5-q8_0.gguf"]
fn bge_small_loads_and_embeds() {
let Some(path) = model_path() else {
eprintln!("SKIP: models/bge-small-en-v1.5-q8_0.gguf not present");
return;
};
let model = EmbeddingModel::from_gguf_path(&path).expect("load bge-small");
assert_eq!(model.architecture(), "bert");
assert_eq!(model.n_embd(), 384);
assert_eq!(model.n_ctx_train(), 512);
assert_eq!(
model.pooling_type(),
PoolingType::Cls,
"bert.pooling_type = 2 is CLS; reading it wrong is the whole point of the key"
);
for case in CASES {
let ids = model.token_ids(case);
assert_eq!(ids.first(), Some(&101), "[CLS] must lead: {case:?}");
assert_eq!(ids.last(), Some(&102), "[SEP] must trail: {case:?}");
let v = model.embed(case, true).expect("embed");
assert_eq!(v.len(), 384);
assert!(
v.iter().all(|x| x.is_finite()),
"non-finite output: {case:?}"
);
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((norm - 1.0).abs() < 1e-4, "‖v‖ = {norm} for {case:?}");
}
let cos = |a: &[f32], b: &[f32]| -> f32 { a.iter().zip(b).map(|(x, y)| x * y).sum() };
let q = model.embed("How do I bake bread?", true).unwrap();
let near = model
.embed("What is a good recipe for baking bread?", true)
.unwrap();
let far = model
.embed("The stock market fell three percent today.", true)
.unwrap();
let (s_near, s_far) = (cos(&q, &near), cos(&q, &far));
assert!(
s_near > s_far + 0.2,
"paraphrase {s_near:.3} is not clearly closer than the unrelated sentence {s_far:.3}"
);
}
#[test]
#[ignore = "needs models/…gguf and target/llama_logits"]
fn ferrox_matches_llama_cpp_embeddings() {
let Some(path) = model_path() else {
eprintln!("SKIP: models/bge-small-en-v1.5-q8_0.gguf not present");
return;
};
let tool = repo_root().join("target/llama_logits");
if !tool.exists() {
eprintln!("SKIP: target/llama_logits not built (./tools/build_llama_logits.sh)");
return;
}
let model = EmbeddingModel::from_gguf_path(&path).expect("load bge-small");
let mut worst = 0.0f32;
let mut worst_case = "";
for case in CASES {
let out = Command::new(&tool)
.arg("--embed")
.arg(&path)
.arg(case)
.output()
.expect("run llama_logits --embed");
assert!(
out.status.success(),
"llama_logits --embed failed on {case:?}: {}",
String::from_utf8_lossy(&out.stderr)
);
let reference: Vec<f32> = String::from_utf8_lossy(&out.stdout)
.split_ascii_whitespace()
.map(|t| t.parse::<f32>().expect("float"))
.collect();
assert_eq!(reference.len(), model.n_embd(), "reference width, {case:?}");
let stderr = String::from_utf8_lossy(&out.stderr);
let line = stderr
.lines()
.find(|l| l.starts_with("pooling_type "))
.expect("llama_embed prints its ids");
let reference_ids: Vec<u32> = line
.rsplit("ids:")
.next()
.unwrap()
.split_ascii_whitespace()
.map(|t| t.parse().expect("id"))
.collect();
assert_eq!(
model.token_ids(case),
reference_ids,
"token ids differ from llama.cpp for {case:?}"
);
let ours = model.embed(case, false).expect("embed");
let mut max_abs = 0.0f32;
for (a, b) in ours.iter().zip(&reference) {
max_abs = max_abs.max((a - b).abs());
}
let dot: f32 = ours.iter().zip(&reference).map(|(a, b)| a * b).sum();
let na: f32 = ours.iter().map(|x| x * x).sum::<f32>().sqrt();
let nb: f32 = reference.iter().map(|x| x * x).sum::<f32>().sqrt();
let cos = dot / (na * nb);
eprintln!("{case:?}: max|Δ| = {max_abs:.3e}, cos = {cos:.9}");
assert!(
cos > 0.999_5,
"cosine {cos} against llama.cpp for {case:?} — the graph disagrees. \
The noise floor on this checkpoint is 0.9998 and the mildest single-fault \
sabotage measured 0.9905; see this file's header"
);
if max_abs > worst {
worst = max_abs;
worst_case = case;
}
}
eprintln!("worst element-wise difference: {worst:.3e} on {worst_case:?}");
assert!(
worst < 6e-2,
"worst element-wise difference {worst:.3e} on {worst_case:?} is larger than \
quantized-kernel rounding explains"
);
}