cubecl_runtime/tune/
local.rs1use super::{AutotuneKey, AutotuneOutput, TunableSet, TuneInputs, Tuner};
2#[cfg(feature = "autotune-checks")]
3use crate::tune::AutotuneLoggerExt;
4use crate::{client::ComputeClient, runtime::Runtime, 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
15pub struct LocalTuner<AK: AutotuneKey, ID> {
18 state: Mutex<Option<HashMap<ID, Arc<Tuner<AK>>>>>,
19 name: &'static str,
20 sets: RwLock<Option<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>>,
21}
22
23#[macro_export]
25macro_rules! local_tuner {
26 ($name:expr) => {
27 LocalTuner::new(concat!(module_path!(), "-", $name));
28 };
29 () => {
30 LocalTuner::new(module_path!());
31 };
32}
33
34pub use local_tuner;
35
36impl<AK, ID> LocalTuner<AK, ID>
37where
38 AK: AutotuneKey + 'static,
39 ID: Hash + PartialEq + Eq + Clone + Display,
40{
41 pub const fn new(name: &'static str) -> Self {
43 Self {
44 state: Mutex::new(None),
45 name,
46 sets: RwLock::new(None),
47 }
48 }
49
50 pub fn init<I, Out, F>(&self, init_set: F) -> Arc<TunableSet<AK, I, Out>>
55 where
56 F: Fn() -> TunableSet<AK, I, Out> + 'static + Send + Sync,
57 I: TuneInputs,
58 Out: AutotuneOutput,
59 {
60 let sets = self.sets.read();
61 let type_id = TypeId::of::<F>();
62
63 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.";
64
65 if let Some(sets) = sets.as_ref()
66 && let Some(set) = sets.get(&type_id)
67 {
68 return set.clone().downcast().expect(DOWNCAST_ERROR);
69 };
70
71 core::mem::drop(sets);
72
73 let mut sets = self.sets.write();
74
75 if let Some(sets) = sets.as_ref()
76 && let Some(set) = sets.get(&type_id)
77 {
78 return set.clone().downcast().expect(DOWNCAST_ERROR);
79 };
80
81 let content = Arc::new(init_set());
82
83 if let Some(sets) = sets.as_mut() {
84 sets.insert(type_id, content.clone());
85 } else {
86 let mut map = HashMap::<TypeId, Arc<dyn Any + Send + Sync>>::new();
87 map.insert(type_id, content.clone());
88 *sets = Some(map);
89 };
90
91 content
92 }
93
94 pub fn clear(&self) {
96 if let Some(s) = self.state.lock().as_mut() {
97 s.clear()
98 }
99 }
100
101 #[cfg(feature = "autotune-checks")]
102 fn checks<'a, I: TuneInputs, Out: AutotuneOutput>(
103 &self,
104 operations: &TunableSet<AK, I, Out>,
105 inputs: &<I as TuneInputs>::At<'a>,
106 ) -> alloc::vec::Vec<crate::tune::log::CheckResult>
107 where
108 <I as TuneInputs>::At<'a>: Clone + Send,
109 {
110 use alloc::vec::Vec;
111
112 let mut checks_outputs = Vec::new();
113 for i in 0..operations.len() {
114 let op = operations.fastest(i);
115 let result = op.execute(inputs.clone());
116 checks_outputs.push((op.name.to_string(), result));
117 }
118 super::check_autotune_outputs(checks_outputs)
119 }
120
121 pub fn execute<'a, R: Runtime, I: TuneInputs, Out>(
124 &self,
125 id: &ID,
126 client: &ComputeClient<R>,
127 operations: Arc<TunableSet<AK, I, Out>>,
128 inputs: <I as TuneInputs>::At<'a>,
129 ) -> Out
130 where
131 <I as TuneInputs>::At<'a>: Clone + Send,
132 Out: AutotuneOutput,
133 {
134 let key = operations.generate_key(&inputs);
135
136 let tuner = {
137 let mut state_lock = self.state.lock();
138 let state_map = state_lock.get_or_insert_with(|| HashMap::new());
139 state_map
140 .entry(id.clone())
141 .or_insert_with(move || {
142 let name = self.name.replace("::", "-");
143 Arc::new(Tuner::new(&name, &id.to_string()))
144 })
145 .clone()
146 };
147
148 #[allow(unused_mut)]
149 let mut log_context = crate::tune::AutotuneLogContext::new(&mut tuner.logger().lock());
150
151 #[cfg(feature = "autotune-checks")]
152 log_context.set_checks(|| self.checks::<I, Out>(&operations, &inputs));
153
154 if let TuneCacheResult::Hit { fastest_index } = tuner.fastest(&key) {
158 return operations
159 .fastest(fastest_index)
160 .execute(inputs)
161 .expect("Should run when selected by autotune.");
162 }
163
164 let fastest = tuner.check_tune::<R, I, Out>(
165 &key,
166 &inputs,
167 &operations,
168 || operations.compute_checksum(),
169 client,
170 log_context,
171 );
172
173 match fastest {
175 TuneCacheResult::Hit { fastest_index } => operations
176 .fastest(fastest_index)
177 .execute(inputs)
178 .expect("Should run when selected by autotune."),
179 TuneCacheResult::Unchecked | TuneCacheResult::Miss => {
180 panic!(
181 "Somehow we STILL didn't check a tuning checksum or start tuning, something has gone wrong."
182 )
183 }
184 TuneCacheResult::Pending => {
185 for i in 0..operations.len() {
187 if let Ok(output) = operations.fastest(i).execute(inputs.clone()) {
188 return output;
189 }
190 }
191 panic!("All autotune operations failed, no viable operation found.");
192 }
193 }
194 }
195}