Skip to main content

cubecl_runtime/tune/
operation.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5use core::fmt::{Debug, Display};
6use core::hash::Hash;
7use cubecl_common::hash::{StableHash, StableHasher};
8
9use alloc::format;
10
11use crate::tune::{Bounds, BoundsGenerator, Eviction, Evictor};
12
13use super::{
14    AutotuneError, input_generator::InputGenerator, key_generator::KeyGenerator,
15    tune_inputs::TuneInputs,
16};
17use super::{Tunable, TunePlan};
18
19/// A type-erased delegate for a tunable function.
20///
21/// The lifetime `'inp` is the lifetime of the input data, the function must be defined such that
22/// it can be called for any lifetime `inp` and produce a `Result<Out, AutotuneError>`.
23type TuneDelegate<I, Out> =
24    dyn for<'inp> Fn(<I as TuneInputs>::At<'inp>) -> Result<Out, AutotuneError> + Send + Sync;
25
26/// A named, type-erased tunable function stored in a [`TunableSet`]. Constructed via
27/// [`Tunable::new`](super::Tunable::new); callers don't name this type directly.
28#[derive(new)]
29pub struct TuneFn<I: TuneInputs, Out> {
30    pub(crate) name: String,
31    func: Box<TuneDelegate<I, Out>>,
32}
33
34impl<I: TuneInputs, Out: 'static> TuneFn<I, Out> {
35    /// Run the wrapped function on the given inputs.
36    pub fn execute<'a>(&self, inputs: <I as TuneInputs>::At<'a>) -> Result<Out, AutotuneError> {
37        (self.func)(inputs)
38    }
39}
40
41/// A set of candidate tunable functions for autotune, sharing a key generator and an
42/// input generator. See [`TuneInputs`] for the `F` parameter.
43///
44/// `Id` is what its tunables are [identified](Tunable::identified) by, inferred from the
45/// tunables it is given: `()` for tunables built by [`Tunable::new`], named and nothing more.
46pub struct TunableSet<K: AutotuneKey, F: TuneInputs, Output: 'static, Id = ()> {
47    tunables: Vec<Tunable<K, F, Output, Id>>,
48    /// The low half of the [checksum](Self::compute_checksum), kept in step with `tunables`: every
49    /// launch looks its tuner up by it.
50    checksum: u64,
51    key_gen: Arc<dyn KeyGenerator<K, F> + Send + Sync>,
52    input_gen: Arc<dyn InputGenerator<K, F> + Send + Sync>,
53    bounds_gen: Option<Arc<dyn BoundsGenerator<K, F> + Send + Sync>>,
54    eviction: Option<Arc<dyn Eviction<K, F> + Send + Sync>>,
55    short_circuit: bool,
56}
57
58impl<K: AutotuneKey, F: TuneInputs, Output: 'static, Id> TunableSet<K, F, Output, Id> {
59    /// Create a tunable set from a key generator and an input generator.
60    pub fn new(key_gen: impl KeyGenerator<K, F>, input_gen: impl InputGenerator<K, F>) -> Self {
61        let mut set = Self {
62            tunables: Default::default(),
63            checksum: 0,
64            input_gen: Arc::new(input_gen),
65            key_gen: Arc::new(key_gen),
66            bounds_gen: None,
67            eviction: None,
68            short_circuit: true,
69        };
70        set.checksum = set.names_hash() as u64;
71        set
72    }
73
74    /// Shorthand for [`new`](Self::new) with a [`CloneInputGenerator`](super::CloneInputGenerator): benchmarks run
75    /// on clones of the real call inputs.
76    pub fn new_cloning_inputs(key_gen: impl KeyGenerator<K, F>) -> Self {
77        Self::new(key_gen, super::CloneInputGenerator)
78    }
79
80    /// The number of tunables in the set.
81    pub fn len(&self) -> usize {
82        self.tunables.len()
83    }
84
85    /// Whether this set contains no tunables.
86    pub fn is_empty(&self) -> bool {
87        self.tunables.is_empty()
88    }
89
90    /// Register a tunable with this tunable set.
91    pub fn with(mut self, tunable: Tunable<K, F, Output, Id>) -> Self {
92        self.tunables.push(tunable);
93        self.checksum = self.names_hash() as u64;
94        self
95    }
96
97    /// Sets the autotune bounds for this set.
98    pub fn with_bounds(mut self, bounds: Arc<dyn BoundsGenerator<K, F> + Send + Sync>) -> Self {
99        self.bounds_gen = Some(bounds);
100        self
101    }
102
103    /// Sets what runs before every measured sample, so the candidates are timed reading
104    /// memory rather than the cache the previous sample left warm. See [`Eviction`].
105    pub fn with_eviction(mut self, eviction: Arc<dyn Eviction<K, F> + Send + Sync>) -> Self {
106        self.eviction = Some(eviction);
107        self
108    }
109
110    /// Set whether bounds-based short-circuiting is enabled.
111    pub fn with_short_circuit(mut self, enabled: bool) -> Self {
112        self.short_circuit = enabled;
113        self
114    }
115
116    /// Whether short-circuiting is enabled for this set.
117    pub fn is_short_circuit_enabled(&self) -> bool {
118        self.short_circuit
119    }
120
121    /// All candidate operations in this set, in registration order.
122    pub fn autotunables(&self) -> impl Iterator<Item = &TuneFn<F, Output>> {
123        self.tunables.iter().map(|tunable| &tunable.function)
124    }
125
126    /// Returns the [autotune plan](TunePlan) for the given set.
127    pub(crate) fn plan(&self, key: &K) -> TunePlan {
128        TunePlan::new(key, &self.tunables)
129    }
130
131    /// Returns the operation for the given index, matching the order returned by
132    /// `autotunables`. Tunables are tried in order, so index 0 should be a good default.
133    pub fn fastest(&self, fastest_index: usize) -> &TuneFn<F, Output> {
134        &self.tunables[fastest_index].function
135    }
136
137    /// What the tunable at `index` is [identified](Tunable::identified) by. The index is one
138    /// the tuner stored for this set, so it panics out of range as [`fastest`](Self::fastest)
139    /// does.
140    pub(crate) fn identity(&self, index: usize) -> &Id {
141        self.tunables[index].identity()
142    }
143
144    /// Compute a checksum that invalidates outdated cached auto-tune results when the
145    /// set of tunable names changes.
146    pub fn compute_checksum(&self) -> String {
147        format!("{:x}", self.names_hash())
148    }
149
150    /// The low half of the [checksum](Self::compute_checksum), without hashing the names again.
151    pub(crate) fn checksum(&self) -> u64 {
152        self.checksum
153    }
154
155    fn names_hash(&self) -> StableHash {
156        // Each name is hashed with its own terminator, so `["a", "bc"]` and `["abc"]` differ.
157        let mut hasher = StableHasher::new();
158        for tune in &self.tunables {
159            tune.function.name.hash(&mut hasher);
160        }
161        hasher.finalize()
162    }
163
164    /// Generate a key from a set of inputs
165    pub fn generate_key<'a>(&self, inputs: &F::At<'a>) -> K {
166        self.key_gen.generate(inputs)
167    }
168
169    /// Generate a set of test inputs from a key and reference inputs.
170    pub fn generate_inputs<'a>(&self, key: &K, inputs: &F::At<'a>) -> F::At<'a> {
171        self.input_gen.generate(key, inputs)
172    }
173
174    /// The eviction registered on this set, bound to `key` and the reference `inputs`, if
175    /// any: what a benchmark loop runs before each of its samples.
176    pub(crate) fn evictor<'i>(&self, key: &K, inputs: &F::At<'i>) -> Option<Box<Evictor<'i>>> {
177        let eviction = self.eviction.clone()?;
178        let key = key.clone();
179        let inputs = inputs.clone();
180        Some(Box::new(move || eviction.evict(&key, &inputs)))
181    }
182
183    /// The throughput bounds registered on this set, if any.
184    pub fn bounds(&self, key: &K, inputs: &F::At<'_>) -> Option<Bounds> {
185        self.bounds_gen.as_ref().map(|f| f.generate(key, inputs))
186    }
187}
188
189#[cfg(serializable)]
190/// Trait alias, serializable for the persistent cache and the autotune log
191pub trait AutotuneKey:
192    Clone
193    + Debug
194    + PartialEq
195    + Eq
196    + Hash
197    + Display
198    + serde::Serialize
199    + serde::de::DeserializeOwned
200    + Send
201    + Sync
202    + 'static
203{
204}
205#[cfg(not(serializable))]
206/// Trait alias
207pub trait AutotuneKey:
208    Clone + Debug + PartialEq + Eq + Hash + Display + Send + Sync + 'static
209{
210}
211
212impl AutotuneKey for String {}