use anyhow::Context;
use cortiq_core::CmfModel;
use cortiq_engine::{Pipeline, SamplerConfig};
use std::sync::Arc;
pub struct RecallArgs<'a> {
pub models: &'a [String],
pub pairs: usize,
pub distances: Vec<usize>,
pub trials: usize,
pub seed: u64,
pub filler: String,
pub json: bool,
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, n: u64) -> u64 {
self.next() % n.max(1)
}
}
struct DistanceResult {
distance: usize,
trials: usize,
acc1: f64,
acc5: f64,
any_value: f64,
mean_logp: f64,
ms_per_trial: f64,
}
struct ModelResult {
model: String,
pairs: usize,
filler: String,
seed: u64,
backend: String,
results: Vec<DistanceResult>,
}
fn log_softmax_at(logits: &[f32], id: usize) -> f64 {
let max = logits.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v)) as f64;
let sum: f64 = logits.iter().map(|&v| (v as f64 - max).exp()).sum();
logits[id] as f64 - max - sum.ln()
}
fn rank_of(logits: &[f32], id: usize) -> usize {
let v = logits[id];
logits.iter().filter(|&&x| x > v).count()
}
pub fn cmd_probe_recall(a: RecallArgs<'_>) -> anyhow::Result<()> {
if a.pairs == 0 || a.trials == 0 || a.distances.is_empty() {
anyhow::bail!("probe-recall: pairs, trials and distances must be non-empty");
}
let longest = a.distances.iter().copied().max().unwrap_or(0) + 2 * a.pairs + 8;
if std::env::var("CMF_MAX_SEQ").is_err() {
unsafe { std::env::set_var("CMF_MAX_SEQ", longest.to_string()) };
}
let backend = format!(
"CMF_GPU={} resident={}",
std::env::var("CMF_GPU").unwrap_or_else(|_| "unset".into()),
std::env::var("CMF_EMBRYO_RESIDENT").unwrap_or_else(|_| "unset".into())
);
let mut all = Vec::new();
for path in a.models {
let model = Arc::new(CmfModel::open_sharded(path).with_context(|| path.clone())?);
let mut pipeline = Pipeline::from_model(
&model,
SamplerConfig {
temperature: 0.0,
repetition_penalty: 1.0,
seed: Some(0),
..Default::default()
},
)?;
let vocab = model.arch().vocab_size;
let lo = 256usize.min(vocab / 8);
let hi = vocab.saturating_sub(64).max(lo + 16);
let span = (hi - lo) as u64;
let bos = pipeline.tokenizer.with_bos(Vec::new());
if !a.json {
println!(
"probe-recall: {path} | pairs={} trials={} filler={} pool=[{lo},{hi}) bos={} | {backend}",
a.pairs,
a.trials,
a.filler,
bos.len()
);
println!("{:>9} {:>7} {:>7} {:>9} {:>10} {:>9}", "D", "acc@1", "acc@5", "any_val", "mean_logp", "ms/trial");
}
let mut results = Vec::new();
for &d in &a.distances {
let mut rng = Rng(a.seed ^ (d as u64).wrapping_mul(0x5851_F42D_4C95_7F2D));
let (mut hit1, mut hit5, mut anyv) = (0usize, 0usize, 0usize);
let mut logp_sum = 0f64;
let t0 = std::time::Instant::now();
for _ in 0..a.trials {
let mut used: Vec<u32> = Vec::with_capacity(2 * a.pairs);
let draw = |rng: &mut Rng, used: &mut Vec<u32>| -> u32 {
loop {
let id = (lo as u64 + rng.below(span)) as u32;
if !used.contains(&id) {
used.push(id);
return id;
}
}
};
let keys: Vec<u32> = (0..a.pairs).map(|_| draw(&mut rng, &mut used)).collect();
let vals: Vec<u32> = (0..a.pairs).map(|_| draw(&mut rng, &mut used)).collect();
let q = rng.below(a.pairs as u64) as usize;
let mut ids = bos.clone();
for i in 0..a.pairs {
ids.push(keys[i]);
ids.push(vals[i]);
}
let repeat_id = draw(&mut rng, &mut used);
for _ in 0..d {
let f = if a.filler == "repeat" {
repeat_id
} else {
loop {
let id = (lo as u64 + rng.below(span)) as u32;
if !used.contains(&id) {
break id;
}
}
};
ids.push(f);
}
ids.push(keys[q]);
let logits = pipeline
.forward_ids(&ids, None)
.map_err(|e| anyhow::anyhow!("{path}: forward_ids: {e}"))?;
let target = vals[q] as usize;
let r = rank_of(&logits, target);
if r == 0 {
hit1 += 1;
}
if r < 5 {
hit5 += 1;
}
let argmax = logits
.iter()
.enumerate()
.fold((0usize, f32::NEG_INFINITY), |m, (i, &v)| if v > m.1 { (i, v) } else { m })
.0 as u32;
if vals.contains(&argmax) {
anyv += 1;
}
logp_sum += log_softmax_at(&logits, target);
}
let n = a.trials as f64;
let res = DistanceResult {
distance: d,
trials: a.trials,
acc1: hit1 as f64 / n,
acc5: hit5 as f64 / n,
any_value: anyv as f64 / n,
mean_logp: logp_sum / n,
ms_per_trial: t0.elapsed().as_secs_f64() * 1e3 / n,
};
if !a.json {
println!(
"{:>9} {:>7.3} {:>7.3} {:>9.3} {:>10.3} {:>9.1}",
res.distance, res.acc1, res.acc5, res.any_value, res.mean_logp, res.ms_per_trial
);
}
results.push(res);
}
all.push(ModelResult {
model: path.clone(),
pairs: a.pairs,
filler: a.filler.clone(),
seed: a.seed,
backend: backend.clone(),
results,
});
}
if a.json {
let arr: Vec<serde_json::Value> = all
.iter()
.map(|m| {
serde_json::json!({
"model": m.model,
"pairs": m.pairs,
"filler": m.filler,
"seed": m.seed,
"backend": m.backend,
"results": m.results.iter().map(|r| serde_json::json!({
"distance": r.distance,
"trials": r.trials,
"acc1": r.acc1,
"acc5": r.acc5,
"any_value": r.any_value,
"mean_logp": r.mean_logp,
"ms_per_trial": r.ms_per_trial,
})).collect::<Vec<_>>(),
})
})
.collect();
println!("{}", serde_json::to_string_pretty(&arr)?);
}
Ok(())
}