use kime_engine::{Device, Error, Kime, Precision};
use serde_json::Value;
fn model() -> String {
std::env::var("KIME_MODELS").map_or_else(|_| "laya".into(), |m| format!("{m}/laya"))
}
fn open(precision: Precision) -> Option<Kime> {
let b = Kime::builder().model(model()).device(Device::Cpu { threads: 0 }).precision(precision);
match b.build() {
Ok(k) => Some(k),
Err(e) => {
assert!(std::env::var_os("KIME_REQUIRE_WEIGHTS").is_none(), "{e}");
eprintln!("skipping: {e}");
None
}
}
}
fn cosine(a: &[f32], b: &[f64]) -> f64 {
let dot: f64 = a.iter().zip(b).map(|(x, y)| f64::from(*x) * y).sum();
let na: f64 = a.iter().map(|x| f64::from(*x).powi(2)).sum();
let nb: f64 = b.iter().map(|y| y * y).sum();
dot / (na * nb).sqrt()
}
#[test]
fn matches_laya() {
let Some(kime) = open(Precision::F32) else { return };
let path = format!("{}/../kime-eval/fixtures/parity/embed.json", env!("CARGO_MANIFEST_DIR"));
let fixture: Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
let texts: Vec<&str> =
fixture["texts"].as_array().unwrap().iter().map(|t| t.as_str().unwrap()).collect();
for (max_length, rows) in fixture["emb"].as_object().unwrap() {
let got = kime.embed(&texts, max_length.parse().unwrap()).unwrap();
assert_eq!(got.len(), texts.len());
let (mut worst_abs, mut worst_cos) = (0f64, 1f64);
for (i, (g, w)) in got.iter().zip(rows.as_array().unwrap()).enumerate() {
let w: Vec<f64> = w.as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect();
assert_eq!(g.len(), w.len());
let abs = g.iter().zip(&w).map(|(a, b)| (f64::from(*a) - b).abs()).fold(0.0, f64::max);
let cos = cosine(g, &w);
assert!(
abs < 5e-3 && cos > 0.99999,
"text {i} at {max_length}: abs {abs:.2e}, cosine {cos}"
);
(worst_abs, worst_cos) = (worst_abs.max(abs), worst_cos.min(cos));
}
eprintln!(
"max_length {max_length}: worst abs {worst_abs:.2e}, worst cosine {worst_cos:.7}"
);
for (i, t) in texts.iter().enumerate() {
let alone = kime.embed(&[t], max_length.parse().unwrap()).unwrap();
assert!(
alone[0].iter().zip(&got[i]).all(|(a, b)| a.to_bits() == b.to_bits()),
"text {i}"
);
}
}
assert!(kime.embed(&[], 512).unwrap().is_empty());
}
#[test]
fn int8_is_refused() {
let Some(kime) = open(Precision::Int8) else { return };
assert!(matches!(kime.embed(&["card lost"], 512), Err(Error::Unsupported(_))));
}