cubecl_runtime/tune/
tune_benchmark.rs1use super::{AutotuneError, TuneFn, TuneInputs};
2use crate::{client::ComputeClient, runtime::Runtime};
3use alloc::string::ToString;
4use alloc::vec::Vec;
5use cubecl_common::profile::ProfileDuration;
6use cubecl_environment::config::RuntimeConfig;
7
8pub trait AutotuneOutput: Send + 'static {
10 #[cfg(feature = "autotune-checks")]
11 fn check_equivalence(&self, other: Self);
14}
15
16impl AutotuneOutput for () {
17 #[cfg(feature = "autotune-checks")]
18 fn check_equivalence(&self, _other: Self) {
19 }
21}
22
23pub fn tune_benchmark<'a, R: Runtime, F: TuneInputs, Out: AutotuneOutput>(
27 operation: &TuneFn<F, Out>,
28 inputs: <F as TuneInputs>::At<'a>,
29 client: ComputeClient<R>,
30) -> Result<Vec<ProfileDuration>, AutotuneError> {
31 client
34 .clone()
35 .exclusive(move || profile_exclusive(operation, inputs, client))
36 .map_err(|err| AutotuneError::Unknown {
37 name: operation.name.to_string(),
38 err: err.to_string(),
39 })?
40}
41
42impl<F: TuneInputs, Out: AutotuneOutput> TuneFn<F, Out> {
43 pub(crate) fn warmup_once<'a, R: Runtime>(
48 &self,
49 inputs: <F as TuneInputs>::At<'a>,
50 client: &ComputeClient<R>,
51 ) -> Result<(), AutotuneError> {
52 let _errs = client.flush();
54
55 self.sample_once(inputs, client).map(|_| ())
58 }
59
60 pub(crate) fn sample_once<'a, R: Runtime>(
62 &self,
63 inputs: <F as TuneInputs>::At<'a>,
64 client: &ComputeClient<R>,
65 ) -> Result<ProfileDuration, AutotuneError> {
66 let profiled = client.profile(move || self.execute(inputs), &self.name);
68
69 match profiled {
70 Ok((Ok(_), duration)) => Ok(duration),
71 Ok((Err(err), _)) => Err(err),
72 Err(err) => Err(AutotuneError::Unknown {
73 name: self.name.to_string(),
74 err: err.to_string(),
75 }),
76 }
77 }
78}
79
80fn profile_exclusive<'a, R: Runtime, F: TuneInputs, Out: AutotuneOutput>(
81 operation: &TuneFn<F, Out>,
82 inputs: <F as TuneInputs>::At<'a>,
83 client: ComputeClient<R>,
84) -> Result<Vec<ProfileDuration>, AutotuneError> {
85 let _real_run = crate::dry_run::RealRun::new();
95
96 warmup(operation, inputs.clone(), client.clone())?;
97
98 let (_, num_samples) = crate::config::CubeClRuntimeConfig::get()
102 .autotune
103 .bench
104 .samples();
105 let mut durations = Vec::new();
106
107 for _ in 0..num_samples {
108 durations.push(operation.sample_once(inputs.clone(), &client)?);
113 }
114
115 if durations.is_empty() {
116 Err(AutotuneError::InvalidSamples {
117 name: operation.name.to_string(),
118 })
119 } else {
120 Ok(durations)
121 }
122}
123
124fn warmup<'a, R: Runtime, F: TuneInputs, Out: AutotuneOutput>(
125 operation: &TuneFn<F, Out>,
126 inputs: <F as TuneInputs>::At<'a>,
127 client: ComputeClient<R>,
128) -> Result<(), AutotuneError> {
129 let num_warmup = 3;
130
131 let mut errors = Vec::with_capacity(num_warmup);
132 let _errs = client.flush();
134
135 for _ in 0..num_warmup {
136 let inputs = inputs.clone();
137 let profiled = client.profile(move || operation.execute(inputs), &operation.name);
138
139 match profiled {
140 Ok((Err(err), _)) => return Err(err),
144 Ok(_) => {}
145 Err(err) => errors.push(err),
146 }
147 }
148
149 if errors.len() < num_warmup {
150 Ok(())
151 } else {
152 let msg = alloc::format!("{:?}", errors.remove(num_warmup - 1));
153 Err(AutotuneError::Unknown {
154 name: operation.name.to_string(),
155 err: msg,
156 })
157 }
158}