Skip to main content

ruda/runtime/tune/
tune_benchmark.rs

1use 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
7/// The trait to be implemented by an autotune output.
8pub trait AutotuneOutput: Send + 'static {
9    /// Fallible correctness gate for stack autotuning. `Ok(false)` explicitly means that this
10    /// output has no validator; a successful launch alone is NOT a correctness check.
11    /// Existing implementers need not add a method. Strict stack policy rejects unsupported output.
12    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    /// Checks if the output of an autotune operation is the same as another one on the same
17    /// problem.
18    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        //
25    }
26}
27
28/// Benchmark how long this operation takes for a number of samples.
29///
30/// Returns at least one duration, otherwise an error is returned.
31pub 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    // `scoped` holds exclusive device access for the whole benchmark loop and
37    // accepts non-`'static` closures.
38    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                    // It is important to return the output since otherwise deadcode elimination
67                    // might optimize away code that needs to be profiled.
68                    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    // We make sure the server is in a correct state.
111    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}