ruda/runtime/tune/
tune_benchmark.rs1use super::{AutotuneError, TuneFn, TuneInputs};
2use crate::runtime::{client::ComputeClient, backend::Runtime};
3use alloc::string::ToString;
4use alloc::vec::Vec;
5use ruda_core::profile::ProfileDuration;
6
7pub trait AutotuneOutput: Send + 'static {
9 fn validate_for_tuning(&self, _other: &Self, _absolute: f64, _relative: f64, _max_bytes: u64)
13 -> Result<bool, alloc::string::String> { Ok(false) }
14
15 #[cfg(feature = "runtime-autotune-checks")]
16 fn check_equivalence(&self, other: Self);
19}
20
21impl AutotuneOutput for () {
22 #[cfg(feature = "runtime-autotune-checks")]
23 fn check_equivalence(&self, _other: Self) {
24 }
26}
27
28pub fn tune_benchmark<'a, R: Runtime, F: TuneInputs, Out: AutotuneOutput>(
32 operation: &TuneFn<F, Out>,
33 inputs: <F as TuneInputs>::At<'a>,
34 client: ComputeClient<R>,
35) -> Result<Vec<ProfileDuration>, AutotuneError> {
36 client
39 .clone()
40 .exclusive(move || profile_exclusive(operation, inputs, client))
41 .map_err(|err| AutotuneError::Unknown {
42 name: operation.name.to_string(),
43 err: err.to_string(),
44 })?
45}
46
47fn profile_exclusive<'a, R: Runtime, F: TuneInputs, Out: AutotuneOutput>(
48 operation: &TuneFn<F, Out>,
49 inputs: <F as TuneInputs>::At<'a>,
50 client: ComputeClient<R>,
51) -> Result<Vec<ProfileDuration>, AutotuneError> {
52 warmup(operation, inputs.clone(), client.clone())?;
53
54 let num_samples = 10;
55 let mut durations = Vec::new();
56
57 for _ in 0..num_samples {
58 let result: Result<
59 (Result<Out, AutotuneError>, ProfileDuration),
60 crate::runtime::server::ProfileError,
61 > = {
62 let inputs = inputs.clone();
63
64 client.profile(
65 move || {
66 operation.execute(inputs)
69 },
70 &operation.name,
71 )
72 };
73
74 let result = match result {
75 Ok((out, duration)) => match out {
76 Ok(_) => Some(duration),
77 Err(err) => {
78 log::trace!("Error while autotuning {err:?}");
79 None
80 }
81 },
82 Err(err) => {
83 log::trace!("Error while autotuning {err:?}");
84 None
85 }
86 };
87
88 if let Some(item) = result {
89 durations.push(item);
90 }
91 }
92
93 if durations.is_empty() {
94 Err(AutotuneError::InvalidSamples {
95 name: operation.name.to_string(),
96 })
97 } else {
98 Ok(durations)
99 }
100}
101
102fn warmup<'a, R: Runtime, F: TuneInputs, Out: AutotuneOutput>(
103 operation: &TuneFn<F, Out>,
104 inputs: <F as TuneInputs>::At<'a>,
105 client: ComputeClient<R>,
106) -> Result<(), AutotuneError> {
107 let num_warmup = 3;
108
109 let mut errors = Vec::with_capacity(num_warmup);
110 let _errs = client.flush();
112
113 for _ in 0..num_warmup {
114 let inputs = inputs.clone();
115 let profiled = client.profile(move || operation.execute(inputs), &operation.name);
116
117 match profiled {
118 Ok((Ok(_), _)) => {}
119 Ok((Err(err), _)) => return Err(err),
120 Err(err) => errors.push(err),
121 }
122 }
123
124 if errors.len() < num_warmup {
125 Ok(())
126 } else {
127 let msg = alloc::format!("{:?}", errors.remove(num_warmup - 1));
128 Err(AutotuneError::Unknown {
129 name: operation.name.to_string(),
130 err: msg,
131 })
132 }
133}