use std::collections::BTreeMap;
use anyhow::{Context, Result, bail, ensure};
use metrale_bench::gate;
#[derive(Debug, thiserror::Error)]
pub(super) enum HardwareRefusal {
#[error(
"{hardware:?} is not a box class Metrale Engine knows, so nothing can be scored against it. \
Registered classes are [{registered}]; {benchmark_id} has baselines for [{measured}]."
)]
Unknown {
benchmark_id: String,
hardware: String,
registered: String,
measured: String,
},
#[error(
"{benchmark_id} has no record yet for {hardware:?}. It is a registered box class with \
nothing measured on it — run the gate on that box and commit one \
(`met benchmark run {benchmark_id} --hardware {hardware} --pull-request-gate`), \
which needs a `[[benchmark]]` entry in kernels/{hardware}/<model>/BENCH.toml. \
Today it has baselines for [{measured}]."
)]
NoRecordYet {
benchmark_id: String,
hardware: String,
measured: String,
},
}
impl HardwareRefusal {
fn of(benchmark_id: &str, hardware: &str, baseline: &gate::GateBaseline) -> Self {
let measured = baseline
.hardware
.keys()
.cloned()
.collect::<Vec<_>>()
.join(", ");
if metrale_bench::hardware::ids::is_known_hardware_id(hardware) {
Self::NoRecordYet {
benchmark_id: benchmark_id.to_string(),
hardware: hardware.to_string(),
measured,
}
} else {
Self::Unknown {
benchmark_id: benchmark_id.to_string(),
hardware: hardware.to_string(),
registered: metrale_bench::hardware::ids::KNOWN_HARDWARE_IDS.join(", "),
measured,
}
}
}
}
#[derive(Debug)]
pub(super) struct Resolved {
pub model: String,
pub recipe_id: String,
pub entry: gate::ModelBaseline,
pub hardware: String,
}
pub(super) fn resolve(
baseline: &gate::GateBaseline,
benchmark_id: &str,
hardware: Option<&str>,
checkpoint: Option<&str>,
) -> Result<Resolved> {
let hw_key = match hardware {
Some(h) => h.to_string(),
None => {
let mut keys = baseline.hardware.keys();
match (keys.next(), keys.next()) {
(Some(only), None) => only.clone(),
(Some(_), Some(_)) => bail!(
"{benchmark_id} has baselines for several box classes ([{}]); pass \
--hardware to say which one this run is for rather than guessing",
baseline
.hardware
.keys()
.cloned()
.collect::<Vec<_>>()
.join(", ")
),
(None, _) => bail!("{benchmark_id} has no hardware entries in its baseline"),
}
}
};
if !baseline.hardware.contains_key(&hw_key) {
return Err(HardwareRefusal::of(benchmark_id, &hw_key, baseline).into());
}
let (model, entry) = baseline.resolve(&hw_key, checkpoint)?;
let recipe_id = entry.recipe.clone().ok_or_else(|| {
anyhow::anyhow!(
"no recipe is bound to {model:?} on {hw_key:?} for {benchmark_id}. Self-start needs \
one; either add `recipe` to the baseline entry or drive an existing server with \
--url/--model and no --pull-request-gate."
)
})?;
Ok(Resolved {
model,
recipe_id,
entry: entry.clone(),
hardware: hw_key,
})
}
pub(super) fn apply_threshold_params(
descriptor: &metrale_bench::BenchmarkDescriptor,
specs: &[metrale_bench::ParamSpec],
values: &mut metrale_bench::ParamValues,
entry: &gate::ModelBaseline,
explicit: &[(String, String)],
) -> Result<Vec<(String, f64)>> {
let mut applied = Vec::new();
for (param, metric) in descriptor.threshold_params {
if explicit.iter().any(|(k, _)| k == param) {
continue;
}
let Some(bound) = entry.metrics.get(*metric) else {
continue;
};
let noise = bound.noise.unwrap_or(0.0);
let derived = match (bound.min, bound.max) {
(Some(min), Some(max)) => bail!(
"{} couples param {param:?} to metric {metric:?}, whose baseline bound \
declares BOTH min ({min}) and max ({max}) — ambiguous: the driver cannot \
tell which one to self-verdict against. Split the metric or drop a bound.",
descriptor.id
),
(None, Some(max)) => max + noise,
(Some(min), None) => min - noise,
(None, None) => continue,
};
let spec = specs.iter().find(|s| s.key == *param).ok_or_else(|| {
anyhow::anyhow!(
"{} declares threshold param {param:?} but its schema has no such parameter — \
the declaration and the schema have drifted",
descriptor.id
)
})?;
let value = spec.kind.parse(&format!("{derived}")).with_context(|| {
format!("deriving --param {param} from the baseline's {metric} bound {derived}")
})?;
values.set(param.to_string(), value);
applied.push((param.to_string(), derived));
}
Ok(applied)
}
pub(super) fn apply_param_overrides(
descriptor: &metrale_bench::BenchmarkDescriptor,
specs: &[metrale_bench::ParamSpec],
values: &mut metrale_bench::ParamValues,
entry: &gate::ModelBaseline,
explicit: &[(String, String)],
) -> Result<Vec<(String, String)>> {
let mut applied = Vec::new();
for (key, raw) in &entry.param_overrides {
if descriptor.threshold_params.iter().any(|(p, _)| p == key) {
bail!(
"{}: baseline param override {key:?} names a threshold-coupled parameter — \
its value is derived from the paired metric's bound, and a second source \
here would fight it. Move the number into the metric's bound instead.",
descriptor.id
);
}
if explicit.iter().any(|(k, _)| k == key) {
continue;
}
let spec = specs
.iter()
.find(|s| s.key == key.as_str())
.ok_or_else(|| {
anyhow::anyhow!(
"{}: baseline param override {key:?} names no parameter in the schema — \
the BENCH.toml pin and the driver have drifted, and running without it \
would measure a different instrument than the thresholds describe",
descriptor.id
)
})?;
let value = spec
.kind
.parse(raw)
.with_context(|| format!("applying the baseline's param override {key}={raw}"))?;
values.set(key.clone(), value);
applied.push((key.clone(), raw.clone()));
}
Ok(applied)
}
pub(super) fn parse_serve_overrides(pairs: &[String]) -> Result<BTreeMap<String, String>> {
let mut out = BTreeMap::new();
for pair in pairs {
let (key, value) = pair.split_once('=').with_context(|| {
format!("--serve-override {pair:?} is not KEY=VALUE (e.g. kv_cache_dtype=fp8)")
})?;
let key = key.trim();
ensure!(
!key.is_empty(),
"--serve-override {pair:?} has an empty key"
);
ensure!(
key != "port",
"--serve-override cannot set `port`: the gate binds a free port itself and serves \
on it, so an override here would name a port nothing is listening on."
);
out.insert(key.to_string(), value.to_string());
}
Ok(out)
}
#[cfg(test)]
#[path = "bench_resolve_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "bench_resolve_hardware_tests.rs"]
mod hardware_tests;
#[cfg(test)]
#[path = "bench_resolve_params_tests.rs"]
mod params_tests;