use crate::throughput::{CmmaDims, ComputeCmmaConfig};
use alloc::{format, string::String};
use core::time::Duration;
use cubecl_ir::{ElemType, FloatKind};
#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)]
#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
pub enum ThroughputMode {
ComputeDirect {
dtype: ElemType,
},
ComputeCmma {
dtype: ElemType,
config: ComputeCmmaConfig,
},
Memory,
Launch,
}
#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)]
#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(std_io, serde(deny_unknown_fields))]
pub struct ThroughputKey {
pub mode: ThroughputMode,
}
impl ThroughputKey {
pub fn dtype(&self) -> ElemType {
match self.mode {
ThroughputMode::ComputeDirect { dtype } => dtype,
ThroughputMode::ComputeCmma { dtype, .. } => dtype,
ThroughputMode::Memory | ThroughputMode::Launch => ElemType::Float(FloatKind::F32),
}
}
}
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
pub struct ThroughputValue {
pub ops_count: usize,
pub duration: Duration,
}
impl ThroughputValue {
pub const ZERO: Self = Self {
ops_count: 0,
duration: Duration::ZERO,
};
pub fn ops_per_s(&self) -> f64 {
if self.duration.is_zero() {
return f64::NAN;
}
self.ops_count as f64 / self.duration.as_secs_f64()
}
pub fn bytes_per_s(&self, key: &ThroughputKey) -> f64 {
if self.duration.is_zero() {
return f64::NAN;
}
(self.ops_count * key.dtype().size()) as f64 / self.duration.as_secs_f64()
}
pub fn duration_per_op(&self) -> Duration {
if self.ops_count == 0 {
Duration::ZERO
} else {
Duration::from_secs_f64(self.duration.as_secs_f64() / self.ops_count as f64)
}
}
pub fn format(&self, key: &ThroughputKey) -> String {
let (mut val_per_s, unit) = match key.mode {
ThroughputMode::ComputeDirect { .. } | ThroughputMode::ComputeCmma { .. } => {
(self.ops_per_s(), "OPS")
}
ThroughputMode::Memory => (self.bytes_per_s(key), "bytes"),
ThroughputMode::Launch => {
let dur = self.duration_per_op();
if dur.is_zero() {
return String::from("N/A");
}
return format!("{dur:?}/launch");
}
};
if val_per_s.is_nan() {
return String::from("N/A");
}
let suffixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
let mut suffix_idx = 0;
for _ in 0..suffixes.len() - 1 {
if val_per_s < 1000.0 {
break;
}
val_per_s /= 1000.0;
suffix_idx += 1;
}
format!("{val_per_s:.4} {}{unit}/s", suffixes[suffix_idx])
}
}
pub fn compute_throughput_key(
cmma_tile: Option<(u32, u32, u32)>,
input_elem_type: ElemType,
acc_elem_type: ElemType,
) -> ThroughputKey {
let mode = match cmma_tile {
Some((tile_m, tile_n, tile_k)) => ThroughputMode::ComputeCmma {
dtype: input_elem_type,
config: ComputeCmmaConfig {
accumulator_type: acc_elem_type,
cmma_dims: CmmaDims {
m: tile_m as usize,
n: tile_n as usize,
k: tile_k as usize,
},
},
},
None => ThroughputMode::ComputeDirect {
dtype: acc_elem_type,
},
};
ThroughputKey { mode }
}