1use super::{AutotuneKey, AutotuneOutput, TunableSet, TuneInputs, Tuner};
2#[cfg(feature = "autotune-checks")]
3use crate::tune::AutotuneLoggerExt;
4use crate::{client::Client, device::HardwareIdentity, tune::TuneCacheResult};
5#[cfg(feature = "autotune-checks")]
6use alloc::string::ToString;
7use alloc::sync::Arc;
8use alloc::vec::Vec;
9use core::{
10 any::{Any, TypeId},
11 hash::Hash,
12};
13use cubecl_environment::collections::HashMap;
14use cubecl_environment::sync::{Mutex, RwLock};
15
16type Sets<ID> = RwLock<Option<HashMap<(TypeId, ID), Arc<dyn Any + Send + Sync>>>>;
21
22pub struct LocalTuner<AK: AutotuneKey, ID> {
25 state: Mutex<Option<Tuners<AK, ID>>>,
26 name: &'static str,
27 sets: Sets<ID>,
28}
29
30struct Tuners<AK: AutotuneKey, ID> {
34 by_id: HashMap<ID, HashMap<u64, Arc<Tuner<AK>>>>,
36 opened: HashMap<TunerIdentity, Arc<Tuner<AK>>>,
37}
38
39#[derive(Clone, Debug, PartialEq, Eq, Hash)]
41struct TunerIdentity {
42 hardware: HardwareIdentity,
43 checksum: u64,
46}
47
48impl<AK: AutotuneKey, ID: Hash + Eq + Clone> Tuners<AK, ID> {
49 fn new() -> Self {
50 Self {
51 by_id: HashMap::new(),
52 opened: HashMap::new(),
53 }
54 }
55
56 fn get_or_open<I: TuneInputs, Out, Id>(
59 &mut self,
60 id: &ID,
61 name: &str,
62 client: &Client,
63 operations: &TunableSet<AK, I, Out, Id>,
64 ) -> Arc<Tuner<AK>> {
65 let checksum = operations.checksum();
66 if let Some(tuner) = self.by_id.get(id).and_then(|lists| lists.get(&checksum)) {
67 return tuner.clone();
68 }
69 let identity = TunerIdentity {
70 hardware: client.hardware_identity(),
71 checksum,
72 };
73 let tuner = self
74 .opened
75 .entry(identity)
76 .or_insert_with_key(|identity| {
77 Arc::new(Tuner::new(&name.replace("::", "-"), &identity.hardware))
78 })
79 .clone();
80 self.by_id
81 .entry(id.clone())
82 .or_default()
83 .insert(checksum, tuner.clone());
84 tuner
85 }
86}
87
88#[macro_export]
90macro_rules! local_tuner {
91 ($name:expr) => {
92 LocalTuner::new(concat!(module_path!(), "-", $name))
93 };
94 () => {
95 LocalTuner::new(module_path!())
96 };
97}
98
99pub use local_tuner;
100
101impl<AK, ID> LocalTuner<AK, ID>
102where
103 AK: AutotuneKey + 'static,
104 ID: Hash + PartialEq + Eq + Clone,
105{
106 pub const fn new(name: &'static str) -> Self {
108 Self {
109 state: Mutex::new(None),
110 name,
111 sets: RwLock::new(None),
112 }
113 }
114
115 pub fn init<I, Out, Id, F>(&self, id: &ID, init_set: F) -> Arc<TunableSet<AK, I, Out, Id>>
130 where
131 F: Fn() -> TunableSet<AK, I, Out, Id> + 'static + Send + Sync,
132 Id: Send + Sync + 'static,
133 I: TuneInputs,
134 Out: AutotuneOutput,
135 {
136 let key = (TypeId::of::<F>(), id.clone());
137 let sets = self.sets.read();
138
139 static DOWNCAST_ERROR: &str = "Local tuner only support one set of tunable that must work on the same input and output declared with the init function.";
140
141 if let Some(sets) = sets.as_ref()
142 && let Some(set) = sets.get(&key)
143 {
144 return set.clone().downcast().expect(DOWNCAST_ERROR);
145 };
146
147 core::mem::drop(sets);
148
149 let mut sets = self.sets.write();
150
151 if let Some(sets) = sets.as_ref()
152 && let Some(set) = sets.get(&key)
153 {
154 return set.clone().downcast().expect(DOWNCAST_ERROR);
155 };
156
157 let content = Arc::new(init_set());
158
159 if let Some(sets) = sets.as_mut() {
160 sets.insert(key, content.clone());
161 } else {
162 let mut map = HashMap::<(TypeId, ID), Arc<dyn Any + Send + Sync>>::new();
163 map.insert(key, content.clone());
164 *sets = Some(map);
165 };
166
167 content
168 }
169
170 pub fn fastest_identity<I, Out, Id>(
183 &self,
184 id: &ID,
185 operations: &TunableSet<AK, I, Out, Id>,
186 key: &AK,
187 ) -> Option<Id>
188 where
189 I: TuneInputs,
190 Id: Clone,
191 {
192 let tuner = self
193 .state
194 .lock()
195 .as_ref()?
196 .by_id
197 .get(id)?
198 .get(&operations.checksum())?
199 .clone();
200 let fastest_index = tuner.settled(key)?;
201 Some(operations.identity(fastest_index).clone())
202 }
203
204 pub fn clear(&self) {
206 self.state.lock().take();
207 }
208
209 #[cfg(feature = "autotune-checks")]
210 fn checks<'a, I: TuneInputs, Out: AutotuneOutput, Id>(
211 &self,
212 operations: &TunableSet<AK, I, Out, Id>,
213 inputs: &<I as TuneInputs>::At<'a>,
214 ) -> alloc::vec::Vec<crate::tune::log::CheckResult>
215 where
216 <I as TuneInputs>::At<'a>: Clone + Send,
217 {
218 let mut checks_outputs = Vec::new();
219 for i in 0..operations.len() {
220 let op = operations.fastest(i);
221 let result = op.execute(inputs.clone());
222 checks_outputs.push((op.name.to_string(), result));
223 }
224 super::check_autotune_outputs(checks_outputs)
225 }
226
227 pub fn execute<'a, I: TuneInputs, Out, Id>(
230 &self,
231 id: &ID,
232 client: &Client,
233 operations: Arc<TunableSet<AK, I, Out, Id>>,
234 inputs: <I as TuneInputs>::At<'a>,
235 ) -> Out
236 where
237 <I as TuneInputs>::At<'a>: Clone + Send,
238 Out: AutotuneOutput,
239 Id: Send + Sync,
240 {
241 let key = operations.generate_key(&inputs);
242
243 let tuner = self
244 .state
245 .lock()
246 .get_or_insert_with(Tuners::new)
247 .get_or_open(id, self.name, client, &operations);
248
249 if let TuneCacheResult::Hit { fastest_index } = tuner.fastest(&key) {
253 #[cfg(feature = "autotune-checks")]
256 {
257 let mut log_context =
258 crate::tune::AutotuneLogContext::new(&mut tuner.logger().lock());
259 log_context.set_checks(|| self.checks::<I, Out, Id>(&operations, &inputs));
260 }
261 return operations
262 .fastest(fastest_index)
263 .execute(inputs)
264 .expect("Should run when selected by autotune.");
265 }
266
267 #[allow(unused_mut)]
269 let mut log_context = crate::tune::AutotuneLogContext::new(&mut tuner.logger().lock());
270
271 #[cfg(feature = "autotune-checks")]
272 log_context.set_checks(|| self.checks::<I, Out, Id>(&operations, &inputs));
273
274 let fastest =
275 tuner.check_tune::<I, Out, Id>(&key, &inputs, &operations, client, log_context);
276
277 match fastest {
279 TuneCacheResult::Hit { fastest_index } => operations
280 .fastest(fastest_index)
281 .execute(inputs)
282 .expect("Should run when selected by autotune."),
283 TuneCacheResult::Miss => {
284 panic!(
285 "Somehow we STILL didn't check a tuning checksum or start tuning, something has gone wrong."
286 )
287 }
288 TuneCacheResult::Pending | TuneCacheResult::Compiled => {
292 let mut failures = Vec::new();
293 for i in 0..operations.len() {
294 match operations.fastest(i).execute(inputs.clone()) {
295 Ok(output) => return output,
296 Err(err) => failures.push(err),
297 }
298 }
299 panic!("All autotune operations failed, no viable operation found.\n{failures:?}");
300 }
301 }
302 }
303}