use std::sync::Mutex;
use metrale_speculative::prompt_lookup::PromptLookupConfig;
use super::model::ModelCfg;
use super::runner::{EOS, ReqSpec, RunOptions, Scenario, run_scenario};
static SERIAL: Mutex<()> = Mutex::new(());
const PL: PromptLookupConfig = PromptLookupConfig {
ngram: 2,
max_drafts: 3,
max_seqs: 8,
min_match: 2,
miss_backoff: 0,
};
fn traced(sc: &Scenario) -> Vec<String> {
let _g = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
run_scenario(sc)
}
fn period_from(from: usize, n: usize) -> Vec<u32> {
(from..from + n).map(|i| 2 + (i as u32 % 7)).collect()
}
fn scenario(reqs: Vec<ReqSpec>, num_drafts: usize, pl: Option<PromptLookupConfig>) -> Scenario {
Scenario {
name: "prompt_lookup",
cfg: ModelCfg {
has_proposer: true,
..ModelCfg::default()
},
opts: RunOptions {
use_speculative: true,
num_drafts,
prompt_lookup: pl,
..RunOptions::default()
},
reqs,
}
}
fn outputs(lines: &[String]) -> Vec<String> {
lines
.iter()
.filter(|l| l.starts_with("out s"))
.map(|l| {
l.split(", ")
.filter(|f| !f.starts_with("acc="))
.collect::<Vec<_>>()
.join(", ")
})
.collect()
}
fn verifies(lines: &[String]) -> usize {
lines
.iter()
.filter(|l| l.starts_with("decode_verify"))
.count()
}
fn repeating_with(id: u64, wrong_every: usize) -> ReqSpec {
let prompt_len = 15;
let mut toks = period_from(prompt_len, 16);
toks.extend([40, 41, 42, 43, 44]);
toks.extend(period_from(prompt_len + 2, 9));
toks.push(EOS);
let mut r = ReqSpec::new(id, prompt_len, toks);
r.draft_wrong_every = Some(wrong_every);
r
}
fn repeating(id: u64) -> ReqSpec {
repeating_with(id, 4)
}
#[test]
fn copies_change_the_verify_count_not_the_output() {
let off = traced(&scenario(vec![repeating_with(1, 2)], 3, None));
let on = traced(&scenario(vec![repeating_with(1, 2)], 3, Some(PL)));
assert_eq!(outputs(&on), outputs(&off));
assert!(
verifies(&on) < verifies(&off),
"prompt lookup verified {} times, MTP alone {}",
verifies(&on),
verifies(&off)
);
}
#[test]
fn batched_copies_change_the_verify_count_not_the_output() {
let reqs = || (1..=3).map(|i| repeating_with(i, 2)).collect::<Vec<_>>();
let off = traced(&scenario(reqs(), 3, None));
let on = traced(&scenario(reqs(), 3, Some(PL)));
assert_eq!(outputs(&on), outputs(&off));
assert!(
on.iter().any(|l| l.starts_with("decode_verify_batched")),
"the copies must be verified inside the batch"
);
assert!(verifies(&on) < verifies(&off));
}
#[test]
fn copies_that_are_always_wrong_never_change_the_output() {
let prompt_len = 15;
let mut toks = Vec::new();
for k in 0..6u32 {
toks.extend(period_from(prompt_len, 2));
toks.extend([50 + k, 57 + k]);
}
toks.push(EOS);
let mut r = ReqSpec::new(1, prompt_len, toks);
r.draft_wrong_every = Some(5);
let mk = |pl| scenario(vec![r.clone()], 3, pl);
let off = traced(&mk(None));
let on = traced(&mk(Some(PL)));
assert_eq!(outputs(&on), outputs(&off));
assert!(
on.iter().any(|l| l.contains("na=0")),
"a refused copy must trim the drafter as a full reject"
);
}
#[test]
fn with_nothing_to_match_the_trace_is_unchanged() {
let toks: Vec<u32> = (40..60).chain([EOS]).collect();
let mut r = ReqSpec::new(1, 9, toks);
r.draft_wrong_every = Some(3);
let pl = PromptLookupConfig { ngram: 3, ..PL };
let strip = |v: Vec<String>| -> Vec<String> {
v.into_iter()
.filter(|l| !l.starts_with("mtp_slot_draft_capacity("))
.collect()
};
let off = strip(traced(&scenario(vec![r.clone()], 3, None)));
let on = strip(traced(&scenario(vec![r], 3, Some(pl))));
assert_eq!(on, off);
}
#[test]
fn a_one_draft_drafter_gets_three_token_copies() {
let off = traced(&scenario(vec![repeating(1)], 1, None));
let on = traced(&scenario(vec![repeating(1)], 1, Some(PL)));
assert_eq!(outputs(&on), outputs(&off));
assert!(
on.iter()
.any(|l| l.starts_with("decode_verify_graphed_k4(")),
"a 3-token copy takes the K4 verify"
);
assert!(
!off.iter()
.any(|l| l.starts_with("decode_verify_graphed_k4("))
);
assert!(verifies(&on) < verifies(&off));
}
#[test]
fn a_one_draft_slot_caps_the_copy() {
let mut sc = scenario(vec![repeating(1)], 1, Some(PL));
sc.cfg.slot_draft_capacity = 1;
let on = traced(&sc);
assert!(
!on.iter().any(|l| l.starts_with("decode_verify_graphed_k4(")
|| l.starts_with("decode_verify_graphed_k3(")),
"no verify wider than the slot"
);
}
fn k4_line(l: &str) -> Option<(Vec<u32>, Vec<u32>)> {
let nums = |s: &str| -> Vec<u32> { s.split(", ").filter_map(|t| t.parse().ok()).collect() };
let rest = l.strip_prefix("decode_verify_graphed_k4(tokens=[")?;
let (toks, rest) = rest.split_once(']')?;
let picks = rest.split_once("-> [")?.1.trim_end_matches(']');
Some((nums(toks)[1..].to_vec(), nums(picks)))
}
#[test]
fn a_fully_accepted_copy_still_trims_the_drafter_as_a_full_reject() {
let on = traced(&scenario(vec![repeating_with(1, 2)], 3, Some(PL)));
let off = traced(&scenario(vec![repeating(1)], 3, None));
let trims_after_full_accepts = |lines: &[String]| -> Vec<String> {
lines
.iter()
.enumerate()
.filter(|(_, l)| k4_line(l).is_some_and(|(d, v)| d[..] == v[..3]))
.filter_map(|(i, _)| {
lines[i..]
.iter()
.find(|l| l.starts_with("trim_proposer_state("))
.map(|l| {
l.rsplit_once("na=")
.map_or(String::new(), |t| t.1.to_string())
})
})
.collect()
};
let on_trims = trims_after_full_accepts(&on);
let off_trims = trims_after_full_accepts(&off);
assert!(!off_trims.is_empty() && off_trims.iter().all(|t| t.starts_with("3,")));
assert!(
on_trims.iter().any(|t| t.starts_with("0,")),
"a fully accepted copy must trim the drafter by na=0: {on_trims:?}"
);
}
#[test]
fn a_lone_sequence_verifies_a_long_copy_in_one_pass() {
let pl = PromptLookupConfig {
max_drafts: 8,
..PL
};
let off = traced(&scenario(vec![repeating_with(1, 2)], 1, None));
let on = traced(&scenario(vec![repeating_with(1, 2)], 1, Some(pl)));
assert_eq!(outputs(&on), outputs(&off));
assert!(
on.iter()
.any(|l| l.starts_with("decode_verify_graphed_kgamma(")),
"a copy of 4+ drafts takes the K-row MTP verify"
);
assert!(
!on.iter().any(|l| l.starts_with("decode_verify_dflash(")),
"never the DFlash verify on an MTP serve"
);
assert!(verifies(&on) < verifies(&off));
}
#[test]
fn inside_a_batch_a_long_copy_is_cut_to_the_four_row_verify() {
let pl = PromptLookupConfig {
max_drafts: 8,
..PL
};
let reqs = || (1..=3).map(|i| repeating_with(i, 2)).collect::<Vec<_>>();
let off = traced(&scenario(reqs(), 3, None));
let on = traced(&scenario(reqs(), 3, Some(pl)));
assert_eq!(outputs(&on), outputs(&off));
let first_kgamma = on
.iter()
.position(|l| l.starts_with("decode_verify_graphed_kgamma("));
let last_batched = on
.iter()
.rposition(|l| l.starts_with("decode_verify_batched("));
if let (Some(k), Some(b)) = (first_kgamma, last_batched) {
assert!(k > b, "a long copy only after the batch has drained");
}
for l in on
.iter()
.filter(|l| l.starts_with("decode_verify_batched("))
{
let ks = l
.split("ks=[")
.nth(1)
.and_then(|r| r.split(']').next())
.unwrap_or("");
assert!(
ks.split(", ")
.all(|k| k.parse::<usize>().is_ok_and(|k| (2..=4).contains(&k))),
"batched rows out of 2..=4: {l}"
);
}
}