ruda_runtime/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::with_capacity(num_samples);
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 num_errors = 0;
110 let mut last_error = None;
111 let _errs = client.flush();
113
114 for _ in 0..num_warmup {
115 let inputs = inputs.clone();
116 let profiled = client.profile(move || operation.execute(inputs), &operation.name);
117
118 match profiled {
119 Ok((Ok(_), _)) => {}
120 Ok((Err(err), _)) => return Err(err),
121 Err(err) => {
122 num_errors += 1;
123 last_error = Some(err);
124 }
125 }
126 }
127
128 if num_errors < num_warmup {
129 Ok(())
130 } else {
131 let msg = alloc::format!("{:?}", last_error.expect("all warmup profiles failed"));
132 Err(AutotuneError::Unknown {
133 name: operation.name.to_string(),
134 err: msg,
135 })
136 }
137}