cubecl_runtime/throughput/
roofline.rs1use core::time::Duration;
2
3use alloc::vec::Vec;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
13#[cfg_attr(autotune_persistence, derive(serde::Serialize, serde::Deserialize))]
14pub struct ResourceBound {
15 pub amount: usize,
17 pub peak_per_s: f64,
20}
21
22impl ResourceBound {
23 pub fn time_at_peak(&self) -> Option<Duration> {
29 if self.peak_per_s.is_normal() {
30 Some(Duration::from_secs_f64(
31 self.amount as f64 / self.peak_per_s,
32 ))
33 } else {
34 None
35 }
36 }
37}
38
39pub fn binding_resource(bounds: &[ResourceBound]) -> Option<&ResourceBound> {
50 bounds
51 .iter()
52 .filter(|bound| bound.time_at_peak().is_some())
53 .max_by_key(|bound| bound.time_at_peak())
54}
55
56#[derive(Debug, Clone, Copy, PartialEq)]
59pub struct AchievedThroughput {
60 pub achieved_per_s: f64,
62 pub fraction_of_peak: f64,
66}
67
68pub fn score_resources(duration: Duration, bounds: &[ResourceBound]) -> Vec<AchievedThroughput> {
77 bounds
78 .iter()
79 .map(|bound| {
80 let achieved_per_s = if duration.is_zero() {
81 f64::NAN
82 } else {
83 bound.amount as f64 / duration.as_secs_f64()
84 };
85
86 AchievedThroughput {
87 achieved_per_s,
88 fraction_of_peak: achieved_per_s / bound.peak_per_s,
89 }
90 })
91 .collect()
92}
93
94pub fn binding_achieved(scores: &[AchievedThroughput]) -> Option<&AchievedThroughput> {
103 scores
104 .iter()
105 .filter(|score| score.fraction_of_peak.is_finite())
106 .max_by(|a, b| a.fraction_of_peak.total_cmp(&b.fraction_of_peak))
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 fn bound(amount: usize, peak_per_s: f64) -> ResourceBound {
114 ResourceBound { amount, peak_per_s }
115 }
116
117 #[test]
118 fn time_at_peak_is_amount_over_peak() {
119 assert_eq!(bound(8, 4.0).time_at_peak(), Some(Duration::from_secs(2)));
120 }
121
122 #[test]
123 fn time_at_peak_is_none_for_a_non_normal_peak() {
124 assert_eq!(bound(8, 0.0).time_at_peak(), None);
125 assert_eq!(bound(8, f64::NAN).time_at_peak(), None);
126 assert_eq!(bound(8, f64::INFINITY).time_at_peak(), None);
127 }
128
129 #[test]
130 fn binding_resource_is_the_one_needing_the_most_time_at_peak() {
131 let slower = bound(8, 4.0);
135 let faster = bound(8, 8.0);
136
137 assert_eq!(binding_resource(&[slower, faster]), Some(&slower));
138 }
139
140 #[test]
141 fn binding_resource_skips_non_normal_peaks_and_is_none_if_all_are() {
142 let unusable = bound(8, 0.0);
143 let usable = bound(8, 4.0);
144
145 assert_eq!(binding_resource(&[unusable, usable]), Some(&usable));
146 assert_eq!(binding_resource(&[unusable]), None);
147 assert_eq!(binding_resource(&[]), None);
148 }
149
150 #[test]
151 fn score_resources_reports_achieved_rate_and_fraction_of_peak() {
152 let bounds = [bound(100, 200.0), bound(400, 800.0)];
153
154 let scores = score_resources(Duration::from_secs(1), &bounds);
155
156 assert_eq!(scores[0].achieved_per_s, 100.0);
157 assert_eq!(scores[0].fraction_of_peak, 0.5);
158 assert_eq!(scores[1].achieved_per_s, 400.0);
159 assert_eq!(scores[1].fraction_of_peak, 0.5);
160 }
161
162 #[test]
163 fn a_zero_duration_reports_nan_instead_of_dividing_by_zero() {
164 let scores = score_resources(Duration::ZERO, &[bound(100, 200.0)]);
165
166 assert!(scores[0].achieved_per_s.is_nan());
167 assert!(scores[0].fraction_of_peak.is_nan());
168 }
169
170 #[test]
176 fn resources_with_different_peaks_score_independently_and_pick_the_slower_one() {
177 let duration = Duration::from_secs(1);
178 let read = bound(900_000, 1_000_000.0); let write = bound(100_000, 200_000.0); assert_eq!(binding_resource(&[read, write]), Some(&read));
182
183 let scores = score_resources(duration, &[read, write]);
184
185 assert_eq!(scores[0].achieved_per_s, 900_000.0);
186 assert_eq!(scores[0].fraction_of_peak, 0.9);
187 assert_eq!(scores[1].achieved_per_s, 100_000.0);
188 assert_eq!(scores[1].fraction_of_peak, 0.5);
189
190 let binding = binding_achieved(&scores).unwrap();
191 assert_eq!(binding.fraction_of_peak, 0.9);
192 }
193
194 #[test]
195 fn binding_achieved_skips_non_finite_entries_and_is_none_if_all_are() {
196 let finite = AchievedThroughput {
197 achieved_per_s: 10.0,
198 fraction_of_peak: 0.4,
199 };
200 let non_finite = AchievedThroughput {
201 achieved_per_s: f64::NAN,
202 fraction_of_peak: f64::NAN,
203 };
204
205 assert_eq!(
206 binding_achieved(&[non_finite, finite])
207 .unwrap()
208 .fraction_of_peak,
209 0.4
210 );
211 assert!(binding_achieved(&[non_finite]).is_none());
212 assert!(binding_achieved(&[]).is_none());
213 }
214}