cubecl_runtime/throughput/
base.rs1use crate::throughput::{CmmaDims, ComputeCmmaConfig};
2use alloc::{format, string::String};
3use core::time::Duration;
4use cubecl_ir::{ElemType, FloatKind};
5
6#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)]
8#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
9pub enum ThroughputMode {
10 ComputeDirect {
12 dtype: ElemType,
14 },
15 ComputeCmma {
17 dtype: ElemType,
19 config: ComputeCmmaConfig,
21 },
22 Memory,
24 Launch,
26}
27
28#[derive(Eq, PartialEq, Clone, Hash, Debug, Copy)]
30#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(std_io, serde(deny_unknown_fields))]
33pub struct ThroughputKey {
34 pub mode: ThroughputMode,
36}
37
38impl ThroughputKey {
39 pub fn dtype(&self) -> ElemType {
41 match self.mode {
42 ThroughputMode::ComputeDirect { dtype } => dtype,
43 ThroughputMode::ComputeCmma { dtype, .. } => dtype,
44 ThroughputMode::Memory | ThroughputMode::Launch => ElemType::Float(FloatKind::F32),
46 }
47 }
48}
49
50#[derive(Eq, PartialEq, Clone, Copy, Debug)]
52#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
53pub struct ThroughputValue {
54 pub ops_count: usize,
56 pub duration: Duration,
58}
59
60impl ThroughputValue {
61 pub const ZERO: Self = Self {
63 ops_count: 0,
64 duration: Duration::ZERO,
65 };
66
67 pub fn ops_per_s(&self) -> f64 {
69 if self.duration.is_zero() {
70 return f64::NAN;
71 }
72 self.ops_count as f64 / self.duration.as_secs_f64()
73 }
74
75 pub fn bytes_per_s(&self, key: &ThroughputKey) -> f64 {
77 if self.duration.is_zero() {
78 return f64::NAN;
79 }
80 (self.ops_count * key.dtype().size()) as f64 / self.duration.as_secs_f64()
81 }
82
83 pub fn duration_per_op(&self) -> Duration {
85 if self.ops_count == 0 {
86 Duration::ZERO
87 } else {
88 Duration::from_secs_f64(self.duration.as_secs_f64() / self.ops_count as f64)
89 }
90 }
91
92 pub fn format(&self, key: &ThroughputKey) -> String {
94 let (mut val_per_s, unit) = match key.mode {
95 ThroughputMode::ComputeDirect { .. } | ThroughputMode::ComputeCmma { .. } => {
96 (self.ops_per_s(), "OPS")
97 }
98 ThroughputMode::Memory => (self.bytes_per_s(key), "bytes"),
99 ThroughputMode::Launch => {
100 let dur = self.duration_per_op();
101 if dur.is_zero() {
102 return String::from("N/A");
103 }
104 return format!("{dur:?}/launch");
105 }
106 };
107
108 if val_per_s.is_nan() {
109 return String::from("N/A");
110 }
111
112 let suffixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
113 let mut suffix_idx = 0;
114
115 for _ in 0..suffixes.len() - 1 {
116 if val_per_s < 1000.0 {
117 break;
118 }
119 val_per_s /= 1000.0;
120 suffix_idx += 1;
121 }
122
123 format!("{val_per_s:.4} {}{unit}/s", suffixes[suffix_idx])
124 }
125}
126
127pub fn compute_throughput_key(
129 cmma_tile: Option<(u32, u32, u32)>,
130 input_elem_type: ElemType,
131 acc_elem_type: ElemType,
132) -> ThroughputKey {
133 let mode = match cmma_tile {
134 Some((tile_m, tile_n, tile_k)) => ThroughputMode::ComputeCmma {
135 dtype: input_elem_type,
136 config: ComputeCmmaConfig {
137 accumulator_type: acc_elem_type,
138 cmma_dims: CmmaDims {
139 m: tile_m as usize,
140 n: tile_n as usize,
141 k: tile_k as usize,
142 },
143 },
144 },
145 None => ThroughputMode::ComputeDirect {
146 dtype: acc_elem_type,
147 },
148 };
149
150 ThroughputKey { mode }
151}