use std::time::Duration;
use cubecl::benchmark::TimingMethod;
use crate::{HostData, Progress};
#[derive(Debug, Clone)]
pub struct ItemDescriptor {
pub id: String,
pub label: String,
}
impl ItemDescriptor {
pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
Self {
id: id.into(),
label: label.into(),
}
}
}
pub struct CatalogEntry<T> {
pub id: String,
pub label: String,
pub value: T,
}
impl<T> CatalogEntry<T> {
pub fn new(id: impl Into<String>, label: impl Into<String>, value: T) -> Self {
Self {
id: id.into(),
label: label.into(),
value,
}
}
pub fn descriptor(&self) -> ItemDescriptor {
ItemDescriptor {
id: self.id.clone(),
label: self.label.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct RunSamples {
pub durations: Vec<Duration>,
pub tflops: Option<f64>,
}
impl RunSamples {
pub fn new(durations: Vec<Duration>) -> Self {
Self {
durations,
tflops: None,
}
}
pub fn with_tflops(mut self, tflops: f64) -> Self {
self.tflops = Some(tflops);
self
}
pub fn with_flops(self, flops: f64) -> Self {
if self.durations.is_empty() {
return self;
}
let mut ns: Vec<u128> = self.durations.iter().map(|d| d.as_nanos()).collect();
ns.sort_unstable();
let median_secs = ns[ns.len() / 2] as f64 / 1e9;
if median_secs <= 0.0 {
return self;
}
self.with_tflops(flops / median_secs / 1e12)
}
}
pub trait Category: Sync {
type Problem;
type Strategy;
fn id(&self) -> &'static str;
fn label(&self) -> &'static str;
fn problems(&self) -> Vec<CatalogEntry<Self::Problem>>;
fn strategies(&self) -> Vec<CatalogEntry<Self::Strategy>>;
fn bench(
&self,
strategy: &Self::Strategy,
problem: &Self::Problem,
num_samples: usize,
) -> Result<RunSamples, String>;
fn timing_method(&self) -> TimingMethod {
TimingMethod::System
}
fn correctness(
&self,
) -> Option<&dyn Correctness<Problem = Self::Problem, Strategy = Self::Strategy>> {
None
}
}
pub trait Correctness: Sync {
type Problem;
type Strategy;
fn kernel_result(
&self,
strategy: &Self::Strategy,
problem: &Self::Problem,
seeds: &[u64],
) -> Result<HostData, String>;
fn reference_result(
&self,
problem: &Self::Problem,
seeds: &[u64],
progress: Option<&Progress>,
) -> Result<HostData, String>;
}
pub trait BenchmarkCategory: Sync {
fn id(&self) -> &'static str;
fn label(&self) -> &'static str;
fn strategies(&self) -> Vec<ItemDescriptor>;
fn problems(&self) -> Vec<ItemDescriptor>;
fn timing_method(&self) -> TimingMethod {
TimingMethod::System
}
fn run(
&self,
strategy_id: &str,
problem_id: &str,
num_samples: usize,
) -> Result<RunSamples, String>;
fn kernel_result(
&self,
_strategy_id: &str,
_problem_id: &str,
_seed_lhs: u64,
_seed_rhs: u64,
) -> Option<Result<HostData, String>> {
None
}
fn reference_result(
&self,
_problem_id: &str,
_seed_lhs: u64,
_seed_rhs: u64,
_progress: Option<&Progress>,
) -> Option<Result<HostData, String>> {
None
}
}
impl<C: Category> BenchmarkCategory for C {
fn id(&self) -> &'static str {
Category::id(self)
}
fn label(&self) -> &'static str {
Category::label(self)
}
fn strategies(&self) -> Vec<ItemDescriptor> {
Category::strategies(self)
.iter()
.map(CatalogEntry::descriptor)
.collect()
}
fn problems(&self) -> Vec<ItemDescriptor> {
Category::problems(self)
.iter()
.map(CatalogEntry::descriptor)
.collect()
}
fn timing_method(&self) -> TimingMethod {
Category::timing_method(self)
}
fn run(
&self,
strategy_id: &str,
problem_id: &str,
num_samples: usize,
) -> Result<RunSamples, String> {
let problems = Category::problems(self);
let problem = problems
.iter()
.find(|e| e.id == problem_id)
.ok_or_else(|| format!("unknown problem: {problem_id}"))?;
let strategies = Category::strategies(self);
let strategy = strategies
.iter()
.find(|e| e.id == strategy_id)
.ok_or_else(|| format!("unknown strategy: {strategy_id}"))?;
Category::bench(self, &strategy.value, &problem.value, num_samples)
}
fn kernel_result(
&self,
strategy_id: &str,
problem_id: &str,
seed_lhs: u64,
seed_rhs: u64,
) -> Option<Result<HostData, String>> {
let correctness = Category::correctness(self)?;
let problems = Category::problems(self);
let problem = match problems.iter().find(|e| e.id == problem_id) {
Some(p) => p,
None => return Some(Err(format!("unknown problem: {problem_id}"))),
};
let strategies = Category::strategies(self);
let strategy = match strategies.iter().find(|e| e.id == strategy_id) {
Some(s) => s,
None => return Some(Err(format!("unknown strategy: {strategy_id}"))),
};
Some(correctness.kernel_result(&strategy.value, &problem.value, &[seed_lhs, seed_rhs]))
}
fn reference_result(
&self,
problem_id: &str,
seed_lhs: u64,
seed_rhs: u64,
progress: Option<&Progress>,
) -> Option<Result<HostData, String>> {
let correctness = Category::correctness(self)?;
let problems = Category::problems(self);
let problem = match problems.iter().find(|e| e.id == problem_id) {
Some(p) => p,
None => return Some(Err(format!("unknown problem: {problem_id}"))),
};
Some(correctness.reference_result(&problem.value, &[seed_lhs, seed_rhs], progress))
}
}