1use super::{AutotuneKey, AutotuneOutput, TunableSet, TuneInputs, Tuner};
2#[cfg(feature = "autotune-checks")]
3use crate::tune::AutotuneLoggerExt;
4use crate::{client::Client, tune::TuneCacheResult};
5use alloc::string::ToString;
6use alloc::sync::Arc;
7use core::{
8 any::{Any, TypeId},
9 fmt::Display,
10 hash::Hash,
11};
12use cubecl_environment::collections::HashMap;
13use cubecl_environment::sync::{Mutex, RwLock};
14
15type Sets<ID> = RwLock<Option<HashMap<(TypeId, ID), Arc<dyn Any + Send + Sync>>>>;
20
21pub struct LocalTuner<AK: AutotuneKey, ID> {
24 state: Mutex<Option<HashMap<ID, Arc<Tuner<AK>>>>>,
25 name: &'static str,
26 sets: Sets<ID>,
27}
28
29#[macro_export]
31macro_rules! local_tuner {
32 ($name:expr) => {
33 LocalTuner::new(concat!(module_path!(), "-", $name))
34 };
35 () => {
36 LocalTuner::new(module_path!());
37 };
38}
39
40pub use local_tuner;
41
42impl<AK, ID> LocalTuner<AK, ID>
43where
44 AK: AutotuneKey + 'static,
45 ID: Hash + PartialEq + Eq + Clone + Display,
46{
47 pub const fn new(name: &'static str) -> Self {
49 Self {
50 state: Mutex::new(None),
51 name,
52 sets: RwLock::new(None),
53 }
54 }
55
56 pub fn init<I, Out, Id, F>(&self, id: &ID, init_set: F) -> Arc<TunableSet<AK, I, Out, Id>>
71 where
72 F: Fn() -> TunableSet<AK, I, Out, Id> + 'static + Send + Sync,
73 Id: Send + Sync + 'static,
74 I: TuneInputs,
75 Out: AutotuneOutput,
76 {
77 let key = (TypeId::of::<F>(), id.clone());
78 let sets = self.sets.read();
79
80 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.";
81
82 if let Some(sets) = sets.as_ref()
83 && let Some(set) = sets.get(&key)
84 {
85 return set.clone().downcast().expect(DOWNCAST_ERROR);
86 };
87
88 core::mem::drop(sets);
89
90 let mut sets = self.sets.write();
91
92 if let Some(sets) = sets.as_ref()
93 && let Some(set) = sets.get(&key)
94 {
95 return set.clone().downcast().expect(DOWNCAST_ERROR);
96 };
97
98 let content = Arc::new(init_set());
99
100 if let Some(sets) = sets.as_mut() {
101 sets.insert(key, content.clone());
102 } else {
103 let mut map = HashMap::<(TypeId, ID), Arc<dyn Any + Send + Sync>>::new();
104 map.insert(key, content.clone());
105 *sets = Some(map);
106 };
107
108 content
109 }
110
111 pub fn fastest_identity<I, Out, Id>(
125 &self,
126 id: &ID,
127 operations: &TunableSet<AK, I, Out, Id>,
128 key: &AK,
129 ) -> Option<Id>
130 where
131 I: TuneInputs,
132 Id: Clone,
133 {
134 let tuner = self.state.lock().as_ref()?.get(id)?.clone();
135 let fastest_index = tuner.settled(key)?;
136 Some(operations.identity(fastest_index).clone())
137 }
138
139 pub fn clear(&self) {
141 if let Some(s) = self.state.lock().as_mut() {
142 s.clear()
143 }
144 }
145
146 #[cfg(feature = "autotune-checks")]
147 fn checks<'a, I: TuneInputs, Out: AutotuneOutput, Id>(
148 &self,
149 operations: &TunableSet<AK, I, Out, Id>,
150 inputs: &<I as TuneInputs>::At<'a>,
151 ) -> alloc::vec::Vec<crate::tune::log::CheckResult>
152 where
153 <I as TuneInputs>::At<'a>: Clone + Send,
154 {
155 use alloc::vec::Vec;
156
157 let mut checks_outputs = Vec::new();
158 for i in 0..operations.len() {
159 let op = operations.fastest(i);
160 let result = op.execute(inputs.clone());
161 checks_outputs.push((op.name.to_string(), result));
162 }
163 super::check_autotune_outputs(checks_outputs)
164 }
165
166 pub fn execute<'a, I: TuneInputs, Out, Id>(
169 &self,
170 id: &ID,
171 client: &Client,
172 operations: Arc<TunableSet<AK, I, Out, Id>>,
173 inputs: <I as TuneInputs>::At<'a>,
174 ) -> Out
175 where
176 <I as TuneInputs>::At<'a>: Clone + Send,
177 Out: AutotuneOutput,
178 Id: Send + Sync,
179 {
180 let key = operations.generate_key(&inputs);
181
182 let tuner = {
183 let mut state_lock = self.state.lock();
184 let state_map = state_lock.get_or_insert_with(|| HashMap::new());
185 state_map
186 .entry(id.clone())
187 .or_insert_with(move || {
188 let name = self.name.replace("::", "-");
189 Arc::new(Tuner::new(&name, &id.to_string()))
190 })
191 .clone()
192 };
193
194 #[allow(unused_mut)]
195 let mut log_context = crate::tune::AutotuneLogContext::new(&mut tuner.logger().lock());
196
197 #[cfg(feature = "autotune-checks")]
198 log_context.set_checks(|| self.checks::<I, Out, Id>(&operations, &inputs));
199
200 if let TuneCacheResult::Hit { fastest_index } = tuner.fastest(&key) {
204 return operations
205 .fastest(fastest_index)
206 .execute(inputs)
207 .expect("Should run when selected by autotune.");
208 }
209
210 let fastest = tuner.check_tune::<I, Out, Id>(
211 &key,
212 &inputs,
213 &operations,
214 || operations.compute_checksum(),
215 client,
216 log_context,
217 );
218
219 match fastest {
221 TuneCacheResult::Hit { fastest_index } => operations
222 .fastest(fastest_index)
223 .execute(inputs)
224 .expect("Should run when selected by autotune."),
225 TuneCacheResult::Unchecked | TuneCacheResult::Miss => {
226 panic!(
227 "Somehow we STILL didn't check a tuning checksum or start tuning, something has gone wrong."
228 )
229 }
230 TuneCacheResult::Pending => {
231 for i in 0..operations.len() {
233 if let Ok(output) = operations.fastest(i).execute(inputs.clone()) {
234 return output;
235 }
236 }
237 panic!("All autotune operations failed, no viable operation found.");
238 }
239 }
240 }
241}