#![allow(clippy::needless_range_loop)]
use serde::{Deserialize, Serialize};
pub const SCHEMA_VERSION: u32 = 1;
pub const DEFAULT_CV_MAX: f64 = 5.0;
pub const MIN_USL_SAMPLES: usize = 2;
pub const MIN_R2: f64 = 0.90;
const LINEAR_DET_EPS: f64 = 1e-18;
const GN_DET_EPS: f64 = 1e-20;
const GN_ITERS: usize = 12;
const GN_BACKTRACK: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Sample {
pub n: u32,
pub throughput: f64,
#[serde(default)]
pub cv_pct: f64,
}
impl Sample {
pub fn new(n: u32, throughput: f64) -> Self {
Self {
n,
throughput,
cv_pct: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sweep {
#[serde(default = "default_unknown")]
pub arch: String,
#[serde(default = "default_unknown")]
pub op_class: String,
#[serde(default)]
pub num_cpus: u32,
#[serde(default)]
pub physical_cores: u32,
pub samples: Vec<Sample>,
}
fn default_unknown() -> String {
"unknown".to_string()
}
impl Sweep {
pub fn new(
arch: impl Into<String>,
op_class: impl Into<String>,
num_cpus: u32,
physical_cores: u32,
samples: Vec<Sample>,
) -> Self {
let mut s = Sweep {
arch: arch.into(),
op_class: op_class.into(),
num_cpus,
physical_cores,
samples,
};
s.fill_defaults();
s
}
pub fn from_samples_defaults(samples: Vec<Sample>) -> Self {
Self::new("unknown", "unknown", 0, 0, samples)
}
pub fn parse_json(json: &str) -> Result<Self, UslError> {
let mut sweep: Sweep =
serde_json::from_str(json).map_err(|e| UslError::BadInput(e.to_string()))?;
if sweep.samples.is_empty() {
return Err(UslError::BadInput("'samples' is empty".to_string()));
}
sweep.fill_defaults();
Ok(sweep)
}
fn fill_defaults(&mut self) {
if self.num_cpus == 0 {
self.num_cpus = self.samples.iter().map(|s| s.n).max().unwrap_or(1);
}
if self.physical_cores == 0 {
self.physical_cores = self.num_cpus;
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UslError {
BadInput(String),
TooFewSamples,
SingularSystem,
NonPositiveBase,
}
impl std::fmt::Display for UslError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UslError::BadInput(m) => write!(f, "bad input: {m}"),
UslError::TooFewSamples => {
write!(f, "need at least two N>1 samples to fit USL")
}
UslError::SingularSystem => {
write!(
f,
"degenerate normal-equation system (collinear regressors)"
)
}
UslError::NonPositiveBase => write!(f, "base-N throughput must be positive"),
}
}
}
impl std::error::Error for UslError {}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct LossMatrix {
pub oversubscribe_loss: f64,
pub underutilize_loss: f64,
}
pub fn usl_speedup(n: f64, alpha: f64, beta: f64) -> f64 {
let denom = 1.0 + alpha * (n - 1.0) + beta * n * (n - 1.0);
if denom <= 0.0 {
return 0.0;
}
n / denom
}
pub fn usl_peak_real(alpha: f64, beta: f64) -> f64 {
if beta <= 0.0 {
return f64::INFINITY;
}
let num = 1.0 - alpha;
if num <= 0.0 {
return 1.0;
}
(num / beta).sqrt()
}
fn normalize_to_speedup(samples: &[Sample]) -> Result<Vec<(f64, f64)>, UslError> {
if samples.is_empty() {
return Ok(Vec::new());
}
let base = samples
.iter()
.enumerate()
.min_by(|(ai, a), (bi, b)| a.n.cmp(&b.n).then(ai.cmp(bi)))
.map(|(_, s)| s)
.expect("non-empty");
if base.throughput <= 0.0 {
return Err(UslError::NonPositiveBase);
}
let scale = base.n as f64 / base.throughput;
Ok(samples
.iter()
.map(|s| (s.n as f64, s.throughput * scale))
.collect())
}
fn fit_usl_linear(points: &[(f64, f64)]) -> Result<(f64, f64), UslError> {
let (mut s11, mut s12, mut s22, mut s1y, mut s2y) = (0.0, 0.0, 0.0, 0.0, 0.0);
let mut used = 0usize;
for &(n, c) in points {
if n <= 1.0 {
continue;
}
if c <= 0.0 {
continue;
}
let x1 = n - 1.0;
let x2 = n * (n - 1.0);
let y = n / c - 1.0;
s11 += x1 * x1;
s12 += x1 * x2;
s22 += x2 * x2;
s1y += x1 * y;
s2y += x2 * y;
used += 1;
}
if used < MIN_USL_SAMPLES {
return Err(UslError::TooFewSamples);
}
let det = s11 * s22 - s12 * s12;
if det.abs() < LINEAR_DET_EPS {
return Err(UslError::SingularSystem);
}
let alpha = (s1y * s22 - s2y * s12) / det;
let beta = (s11 * s2y - s12 * s1y) / det;
Ok((alpha, beta))
}
fn nonlinear_sse(points: &[(f64, f64)], alpha: f64, beta: f64) -> f64 {
let mut sse = 0.0;
for &(n, c) in points {
let r = c - usl_speedup(n, alpha, beta);
sse += r * r;
}
sse
}
fn refine_usl_gauss_newton(
points: &[(f64, f64)],
alpha0: f64,
beta0: f64,
iters: usize,
) -> (f64, f64) {
let (mut alpha, mut beta) = (alpha0, beta0);
let mut best_sse = nonlinear_sse(points, alpha, beta);
for _ in 0..iters {
let (mut jtj00, mut jtj01, mut jtj11) = (0.0, 0.0, 0.0);
let (mut jtr0, mut jtr1) = (0.0, 0.0);
for &(n, c) in points {
let denom = 1.0 + alpha * (n - 1.0) + beta * n * (n - 1.0);
if denom <= 0.0 {
continue;
}
let chat = n / denom;
let r = c - chat;
let d_alpha = -n * (n - 1.0) / (denom * denom);
let d_beta = -n * n * (n - 1.0) / (denom * denom);
jtj00 += d_alpha * d_alpha;
jtj01 += d_alpha * d_beta;
jtj11 += d_beta * d_beta;
jtr0 += d_alpha * r;
jtr1 += d_beta * r;
}
let det = jtj00 * jtj11 - jtj01 * jtj01;
if det.abs() < GN_DET_EPS {
break;
}
let d_a = (jtr0 * jtj11 - jtr1 * jtj01) / det;
let d_b = (jtj00 * jtr1 - jtj01 * jtr0) / det;
let mut step = 1.0;
let mut improved = false;
for _ in 0..GN_BACKTRACK {
let na = alpha + step * d_a;
let nb = beta + step * d_b;
let sse = nonlinear_sse(points, na, nb);
if sse < best_sse && sse.is_finite() {
alpha = na;
beta = nb;
best_sse = sse;
improved = true;
break;
}
step *= 0.5;
}
if !improved {
break;
}
}
(alpha, beta)
}
fn r2_rmse(points: &[(f64, f64)], alpha: f64, beta: f64) -> (f64, f64) {
let ys: Vec<f64> = points.iter().map(|&(_, c)| c).collect();
let mean = ys.iter().sum::<f64>() / ys.len() as f64;
let ss_tot: f64 = ys.iter().map(|c| (c - mean) * (c - mean)).sum();
let ss_res = nonlinear_sse(points, alpha, beta);
let r2 = if ss_tot > 0.0 {
1.0 - ss_res / ss_tot
} else {
1.0
};
let rmse = (ss_res / points.len() as f64).sqrt();
(r2, rmse)
}
fn choose_integer_peak(alpha: f64, beta: f64, n_real: f64, num_cpus: u32) -> u32 {
if !n_real.is_finite() {
return num_cpus.max(1);
}
let lo = (n_real.floor() as i64).max(1);
let hi = (n_real.ceil() as i64).max(1);
let cap = num_cpus as i64;
let raw = [lo, hi, hi.min(cap), lo.min(cap)];
let mut best_n: u32 = 1;
let mut best_c = f64::NEG_INFINITY;
let mut seen: Vec<i64> = Vec::with_capacity(4);
for &r in &raw {
let cand = r.clamp(1, cap.max(1));
if seen.contains(&cand) {
continue;
}
seen.push(cand);
let c = usl_speedup(cand as f64, alpha, beta);
if c > best_c {
best_c = c;
best_n = cand as u32;
}
}
best_n
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UslFit {
pub alpha: f64,
pub beta: f64,
pub peak_n_real: f64,
pub peak_n: u32,
pub r2: f64,
pub rmse: f64,
pub speedup_at_peak: f64,
pub speedup_at_num_cpus: f64,
pub noisy: bool,
pub degenerate: bool,
}
impl UslFit {
fn unfittable(physical_cores: u32, noisy: bool) -> Self {
UslFit {
alpha: f64::NAN,
beta: f64::NAN,
peak_n_real: f64::NAN,
peak_n: physical_cores.max(1),
r2: 0.0,
rmse: f64::INFINITY,
speedup_at_peak: f64::NAN,
speedup_at_num_cpus: f64::NAN,
noisy,
degenerate: true,
}
}
}
pub fn fit_sweep(sweep: &Sweep, cv_max: f64, refine: bool) -> UslFit {
let noisy = sweep.samples.iter().any(|s| s.cv_pct > cv_max);
let points = match normalize_to_speedup(&sweep.samples) {
Ok(p) => p,
Err(_) => return UslFit::unfittable(sweep.physical_cores, noisy),
};
let (alpha, beta) = match fit_usl_linear(&points) {
Ok((a, b)) => {
if refine {
refine_usl_gauss_newton(&points, a, b, GN_ITERS)
} else {
(a, b)
}
}
Err(_) => return UslFit::unfittable(sweep.physical_cores, noisy),
};
let degenerate = !beta.is_finite() || beta <= 0.0 || alpha >= 1.0;
let n_real = usl_peak_real(alpha, beta);
let peak_n = if degenerate {
sweep.physical_cores.max(1)
} else {
choose_integer_peak(alpha, beta, n_real, sweep.num_cpus)
};
let (r2, rmse) = r2_rmse(&points, alpha, beta);
let speedup_at_peak = usl_speedup(peak_n as f64, alpha, beta);
let speedup_at_num_cpus = usl_speedup(sweep.num_cpus as f64, alpha, beta);
UslFit {
alpha,
beta,
peak_n_real: n_real,
peak_n,
r2,
rmse,
speedup_at_peak,
speedup_at_num_cpus,
noisy,
degenerate,
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PoolDecision {
pub schema_version: u32,
pub arch: String,
pub op_class: String,
pub alpha: Option<f64>,
pub beta: Option<f64>,
pub peak_n_real: Option<f64>,
pub peak_n: u32,
pub r2: Option<f64>,
pub rmse: Option<f64>,
pub speedup_at_peak: Option<f64>,
pub speedup_at_num_cpus: Option<f64>,
pub num_cpus: u32,
pub physical_cores: u32,
pub cap_is_win: bool,
pub predicted_gain_pct: Option<f64>,
pub noisy: bool,
pub degenerate: bool,
pub fallback_used: bool,
pub chosen_pool_n: u32,
pub decision: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub loss_matrix: Option<LossMatrix>,
}
fn round_opt(x: f64, ndigits: i32) -> Option<f64> {
if !x.is_finite() {
return None;
}
let f = 10f64.powi(ndigits);
Some((x * f).round() / f)
}
pub fn decide(sweep: &Sweep, fit: &UslFit) -> PoolDecision {
let poor_fit = !fit.degenerate && (!fit.r2.is_finite() || fit.r2 < MIN_R2);
let fallback_used = fit.degenerate || poor_fit;
let (chosen, decision, cap_is_win, predicted_gain_pct, loss_matrix);
if fallback_used {
chosen = sweep.physical_cores.max(1);
decision = "fallback-physical-cores".to_string();
cap_is_win = false;
predicted_gain_pct = 0.0;
loss_matrix = None;
} else {
chosen = fit.peak_n;
cap_is_win = fit.speedup_at_peak >= fit.speedup_at_num_cpus;
let base = fit.speedup_at_num_cpus;
predicted_gain_pct = if base > 0.0 {
100.0 * (fit.speedup_at_peak - base) / base
} else {
0.0
};
decision = if chosen >= sweep.num_cpus {
"no-cap-needed".to_string()
} else {
"cap-at-usl-peak".to_string()
};
let over = (fit.speedup_at_peak - fit.speedup_at_num_cpus).max(0.0);
let half = (fit.peak_n as f64 / 2.0).floor().max(1.0);
let under = (fit.speedup_at_peak - usl_speedup(half, fit.alpha, fit.beta)).max(0.0);
loss_matrix = Some(LossMatrix {
oversubscribe_loss: round_opt(over, 4).unwrap_or(0.0),
underutilize_loss: round_opt(under, 4).unwrap_or(0.0),
});
}
PoolDecision {
schema_version: SCHEMA_VERSION,
arch: sweep.arch.clone(),
op_class: sweep.op_class.clone(),
alpha: round_opt(fit.alpha, 4),
beta: round_opt(fit.beta, 6),
peak_n_real: round_opt(fit.peak_n_real, 4),
peak_n: fit.peak_n,
r2: round_opt(fit.r2, 4),
rmse: round_opt(fit.rmse, 4),
speedup_at_peak: round_opt(fit.speedup_at_peak, 4),
speedup_at_num_cpus: round_opt(fit.speedup_at_num_cpus, 4),
num_cpus: sweep.num_cpus,
physical_cores: sweep.physical_cores,
cap_is_win,
predicted_gain_pct: round_opt(predicted_gain_pct, 1),
noisy: fit.noisy,
degenerate: fit.degenerate,
fallback_used,
chosen_pool_n: chosen,
decision,
loss_matrix,
}
}
pub fn fit_and_decide(sweep: &Sweep) -> PoolDecision {
let fit = fit_sweep(sweep, DEFAULT_CV_MAX, true);
decide(sweep, &fit)
}
#[cfg(test)]
mod tests {
use super::*;
fn selfcheck_sweep() -> (Sweep, f64, f64) {
let true_alpha = 0.040;
let true_beta = 0.0020;
let ns = [1u32, 2, 4, 8, 12, 16, 24, 32, 48, 64];
let samples = ns
.iter()
.map(|&n| Sample {
n,
throughput: usl_speedup(n as f64, true_alpha, true_beta),
cv_pct: 0.5,
})
.collect();
let sweep = Sweep::new("selfcheck-threadripper", "decode_gemv", 64, 64, samples);
(sweep, true_alpha, true_beta)
}
#[test]
fn selfcheck_recovers_truth_and_caps() {
let (sweep, ta, tb) = selfcheck_sweep();
let fit = fit_sweep(&sweep, DEFAULT_CV_MAX, true);
let expected_peak = usl_peak_real(ta, tb);
assert!(
(fit.alpha - ta).abs() < 5e-3,
"alpha={} not within 5e-3 of {ta}",
fit.alpha
);
assert!(
(fit.beta - tb).abs() < 5e-4,
"beta={} not within 5e-4 of {tb}",
fit.beta
);
assert!(
(fit.peak_n_real - expected_peak).abs() < 1.5,
"peak_n_real={} not within 1.5 of {expected_peak}",
fit.peak_n_real
);
assert!(
fit.peak_n < sweep.num_cpus,
"peak_n={} should cap below num_cpus",
fit.peak_n
);
assert!(fit.r2 > 0.999, "r2={} should exceed 0.999", fit.r2);
let row = decide(&sweep, &fit);
assert!(
row.cap_is_win,
"cap_is_win must be true on decode-shaped data"
);
assert!(!row.fallback_used, "clean decode fit must not fall back");
assert_eq!(row.decision, "cap-at-usl-peak");
assert_eq!(row.chosen_pool_n, fit.peak_n);
}
#[test]
fn peak_matches_sampled_argmax() {
let (sweep, _, _) = selfcheck_sweep();
let fit = fit_sweep(&sweep, DEFAULT_CV_MAX, true);
let mut argmax_n = 1u32;
let mut argmax_c = f64::NEG_INFINITY;
for n in 1..=sweep.num_cpus {
let c = usl_speedup(n as f64, fit.alpha, fit.beta);
if c > argmax_c {
argmax_c = c;
argmax_n = n;
}
}
assert!(
(fit.peak_n as i64 - argmax_n as i64).abs() <= 1,
"chosen peak_n={} vs sampled argmax={argmax_n}",
fit.peak_n
);
assert!(fit.speedup_at_peak >= fit.speedup_at_num_cpus - 1e-9);
}
#[test]
fn selfcheck_row_fields_match_python() {
let (sweep, _, _) = selfcheck_sweep();
let row = fit_and_decide(&sweep);
assert_eq!(row.schema_version, SCHEMA_VERSION);
assert_eq!(row.arch, "selfcheck-threadripper");
assert_eq!(row.op_class, "decode_gemv");
assert_eq!(row.num_cpus, 64);
assert_eq!(row.physical_cores, 64);
assert!(!row.noisy, "all cv_pct=0.5 < 5 -> not noisy");
assert!(!row.degenerate);
assert!(!row.fallback_used);
assert!(row.peak_n < 64);
assert!(row.chosen_pool_n < 64);
assert!(row.alpha.is_some() && row.beta.is_some());
assert!(row.r2.unwrap() > 0.999);
}
#[test]
fn fallback_on_flat_curve() {
let samples: Vec<Sample> = [1u32, 2, 4, 8, 16, 32, 64]
.iter()
.map(|&n| Sample::new(n, 1.0))
.collect();
let sweep = Sweep::new("flat-arch", "prefill_gemm", 64, 64, samples);
let fit = fit_sweep(&sweep, DEFAULT_CV_MAX, true);
let row = decide(&sweep, &fit);
assert!(row.fallback_used, "flat curve must trigger the fallback");
assert_eq!(row.decision, "fallback-physical-cores");
assert_eq!(row.chosen_pool_n, 64, "fallback pool = physical cores");
assert!(!row.cap_is_win);
assert_eq!(row.predicted_gain_pct, Some(0.0));
}
#[test]
fn fallback_on_compute_bound_prefill() {
let true_alpha = 0.01;
let true_beta = 0.0;
let ns = [1u32, 2, 4, 8, 16, 32, 64];
let samples = ns
.iter()
.map(|&n| Sample::new(n, usl_speedup(n as f64, true_alpha, true_beta)))
.collect();
let sweep = Sweep::new("tr-prefill", "prefill_gemm", 64, 64, samples);
let fit = fit_sweep(&sweep, DEFAULT_CV_MAX, true);
let row = decide(&sweep, &fit);
assert!(fit.degenerate, "beta~0 prefill must be degenerate (Amdahl)");
assert!(row.fallback_used);
assert_eq!(
row.chosen_pool_n, 64,
"compute-bound prefill -> all physical cores"
);
}
#[test]
fn fallback_on_too_few_samples() {
let samples = vec![Sample::new(1, 1.0), Sample::new(2, 1.9)];
let sweep = Sweep::new("sparse", "decode_gemv", 16, 8, samples);
let fit = fit_sweep(&sweep, DEFAULT_CV_MAX, true);
assert!(fit.degenerate);
let row = decide(&sweep, &fit);
assert!(row.fallback_used);
assert_eq!(row.chosen_pool_n, 8, "fallback = physical_cores=8");
}
#[test]
fn noisy_flag_when_cv_exceeds_max() {
let mut samples: Vec<Sample> = [1u32, 2, 4, 8, 16, 32, 64]
.iter()
.map(|&n| Sample {
n,
throughput: usl_speedup(n as f64, 0.04, 0.002),
cv_pct: 1.0,
})
.collect();
samples[5].cv_pct = 6.2; let sweep = Sweep::new("noisy-tr", "decode_gemv", 64, 64, samples);
let row = fit_and_decide(&sweep);
assert!(row.noisy, "a cv_pct > cv_max must flag noisy");
assert!(!row.fallback_used);
}
#[test]
fn fallback_is_deterministic() {
let samples: Vec<Sample> = [1u32, 2, 4, 8, 16]
.iter()
.map(|&n| Sample::new(n, 1.0))
.collect();
let sweep = Sweep::new("d", "decode_gemv", 32, 16, samples);
let a = fit_and_decide(&sweep);
let b = fit_and_decide(&sweep);
assert_eq!(a, b);
assert!(a.fallback_used);
assert_eq!(a.chosen_pool_n, 16);
}
#[test]
fn decision_json_roundtrip() {
let (sweep, _, _) = selfcheck_sweep();
let row = fit_and_decide(&sweep);
let json = serde_json::to_string(&row).expect("serialize");
let back: PoolDecision = serde_json::from_str(&json).expect("deserialize");
assert_eq!(row, back);
assert!(json.contains("\"schema_version\":1"));
assert!(json.contains("\"decision\":\"cap-at-usl-peak\""));
}
#[test]
fn parse_input_json_and_fit() {
let json = r#"{
"arch": "threadripper-7980x", "op_class": "decode_gemv",
"num_cpus": 64, "physical_cores": 64,
"samples": [
{"n": 1, "throughput": 1.00, "cv_pct": 0.7},
{"n": 2, "throughput": 1.92, "cv_pct": 1.0},
{"n": 4, "throughput": 3.55, "cv_pct": 1.4},
{"n": 8, "throughput": 5.80, "cv_pct": 2.1},
{"n": 16, "throughput": 7.10, "cv_pct": 3.3},
{"n": 32, "throughput": 6.40, "cv_pct": 4.8},
{"n": 64, "throughput": 5.10, "cv_pct": 6.2}
]
}"#;
let sweep = Sweep::parse_json(json).expect("parse");
assert_eq!(sweep.num_cpus, 64);
assert_eq!(sweep.physical_cores, 64);
assert_eq!(sweep.samples.len(), 7);
let row = fit_and_decide(&sweep);
assert!(!row.fallback_used, "retrograde curve is a real USL fit");
assert!(row.peak_n < 64, "must cap below num_cpus");
assert!(row.noisy, "cv_pct 6.2 > 5 -> noisy/advisory");
assert!(row.cap_is_win, "speedup(peak) >= speedup(64)");
assert!(
row.loss_matrix.is_some(),
"non-degenerate fit emits a loss matrix"
);
assert!(row.loss_matrix.unwrap().oversubscribe_loss > 0.0);
}
#[test]
fn parse_rejects_empty_samples() {
let json = r#"{"arch":"x","op_class":"y","num_cpus":8,"physical_cores":4,"samples":[]}"#;
let err = Sweep::parse_json(json).unwrap_err();
assert!(matches!(err, UslError::BadInput(_)));
}
#[test]
fn math_helpers_edge_cases() {
assert!((usl_speedup(1.0, 0.04, 0.002) - 1.0).abs() < 1e-12);
assert!(usl_peak_real(0.04, 0.0).is_infinite());
assert_eq!(usl_peak_real(1.0, 0.002), 1.0);
assert_eq!(usl_speedup(10.0, -2.0, 0.0), 0.0);
}
#[test]
fn fallback_on_poor_fit() {
let samples = vec![
Sample::new(1, 1.0),
Sample::new(2, 5.0),
Sample::new(4, 1.2),
Sample::new(8, 6.0),
Sample::new(16, 1.1),
Sample::new(32, 4.0),
];
let sweep = Sweep::new("jagged", "decode_gemv", 32, 16, samples);
let fit = fit_sweep(&sweep, DEFAULT_CV_MAX, true);
let row = decide(&sweep, &fit);
if row.r2.is_some() && fit.r2 < MIN_R2 {
assert!(row.fallback_used, "low-R^2 non-USL curve must fall back");
assert_eq!(row.decision, "fallback-physical-cores");
assert_eq!(row.chosen_pool_n, 16);
}
}
}