1use super::{AutotuneError, AutotuneKey, TuneFn, TuneInputs};
2
3use alloc::boxed::Box;
4use alloc::string::ToString;
5use alloc::{string::String, sync::Arc, vec, vec::Vec};
6use core::sync::atomic::{AtomicU32, Ordering};
7use cubecl_environment::collections::HashMap;
8
9pub struct Tunable<K, F: TuneInputs, Output> {
12 pub(crate) function: TuneFn<F, Output>,
13 groups: Vec<(TuneGroup<K>, PriorityFunc<K>)>,
14}
15
16impl<K, F: TuneInputs, Output: 'static> Tunable<K, F, Output> {
17 pub fn new<Func, Err>(name: &str, func: Func) -> Self
28 where
29 Err: Into<String> + 'static,
30 Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
31 {
32 let name: String = name.into();
33 let name_for_err = name.clone();
34 Self {
35 function: TuneFn::new(
36 name,
37 Box::new(move |inputs| {
38 func(inputs).map_err(|err| AutotuneError::Unknown {
39 name: name_for_err.to_string(),
40 err: err.into(),
41 })
42 }),
43 ),
44 groups: Vec::new(),
45 }
46 }
47
48 pub fn group(
54 mut self,
55 group: &TuneGroup<K>,
56 priority: impl Fn(&K) -> i8 + Send + Sync + 'static,
57 ) -> Self {
58 self.groups.push((group.clone(), Arc::new(priority)));
59 self
60 }
61}
62
63pub struct TuneGroup<K> {
68 id: u32,
69 name: Arc<String>,
70 pub(crate) priority: PriorityFunc<K>,
71}
72
73impl<K> core::fmt::Debug for TuneGroup<K> {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 f.debug_struct("TuneGroup").field("id", &self.id).finish()
76 }
77}
78
79impl<K> Clone for TuneGroup<K> {
80 fn clone(&self) -> Self {
81 Self {
82 id: self.id,
83 name: self.name.clone(),
84 priority: self.priority.clone(),
85 }
86 }
87}
88
89impl<K> TuneGroup<K> {
90 pub fn new(name: &str, f: impl Fn(&K) -> i8 + Send + Sync + 'static) -> Self {
92 let id = GROUP_COUNTER.fetch_add(1, Ordering::Relaxed);
93
94 Self {
95 id,
96 name: Arc::new(name.into()),
97 priority: Arc::new(f),
98 }
99 }
100}
101
102#[derive(Debug)]
103pub(crate) struct TunePlan {
105 priorities: Vec<i8>,
106 no_groups: Vec<usize>,
107 groups: HashMap<i8, GroupPlan>,
108 returned: Vec<usize>,
109}
110
111#[derive(Default, Debug)]
112struct GroupPlan {
113 priorities: Vec<i8>,
114 indices: HashMap<i8, Vec<(usize, Arc<String>)>>,
115}
116
117#[derive(Debug)]
118struct Cleanup {
119 groups: Vec<i8>,
120 tunables: Vec<(i8, i8)>,
121 skipped: bool,
123}
124
125impl TunePlan {
126 pub fn new<K: AutotuneKey, F: TuneInputs, Out>(
127 key: &K,
128 tunables: &[Tunable<K, F, Out>],
129 ) -> Self {
130 let mut priorities = Vec::<i8>::new();
131 let mut no_groups = Vec::new();
132 let mut groups = HashMap::<i8, GroupPlan>::new();
133
134 for (index, tunable) in tunables.iter().enumerate() {
135 if tunable.groups.is_empty() {
136 no_groups.push(index);
137 } else {
138 for (group, within_group_priority_fn) in tunable.groups.iter() {
139 let priority_fn = &group.priority;
140 let priority = priority_fn(key);
141 if !priorities.contains(&priority) {
142 priorities.push(priority);
143 }
144
145 let group_priorities = match groups.get_mut(&priority) {
146 Some(val) => val,
147 None => {
148 groups.insert(priority, GroupPlan::default());
149 groups.get_mut(&priority).unwrap()
150 }
151 };
152 let priority = within_group_priority_fn(key);
153
154 if group_priorities.priorities.contains(&priority) {
155 group_priorities
156 .indices
157 .get_mut(&priority)
158 .unwrap()
159 .push((index, group.name.clone()));
160 } else {
161 group_priorities.priorities.push(priority);
162 group_priorities
163 .indices
164 .insert(priority, vec![(index, group.name.clone())]);
165 }
166 }
167 }
168 }
169
170 priorities.sort();
171
172 for group in groups.iter_mut() {
173 group.1.priorities.sort();
174 }
175
176 Self {
177 priorities,
178 no_groups,
179 groups,
180 returned: Vec::new(),
181 }
182 }
183
184 pub(crate) fn next(&mut self) -> Vec<usize> {
188 let mut indices = core::mem::take(&mut self.no_groups);
189 let priority = self.priorities.last();
190
191 let priority = match priority {
192 Some(val) => *val,
193 None => return indices,
194 };
195
196 let (group_indices, cleanup) = self.group_plan_next(priority);
197 let skipped = cleanup.skipped || priority < 0;
199 let mut all_skip = true;
200
201 self.cleanup(cleanup);
202
203 if priority >= 0 {
204 for (index, _name) in group_indices {
205 if !self.returned.contains(&index) && !indices.contains(&index) {
206 all_skip = false;
207 indices.push(index);
208 }
209 }
210 }
211
212 if indices.is_empty() && (skipped || all_skip) {
216 self.next()
217 } else {
218 for i in indices.iter() {
219 self.returned.push(*i);
220 }
221 indices
222 }
223 }
224
225 fn cleanup(&mut self, cleanup: Cleanup) {
226 for group_p in cleanup.groups {
227 let index = self
228 .priorities
229 .iter()
230 .enumerate()
231 .find(|p| *p.1 == group_p)
232 .unwrap();
233
234 self.priorities.remove(index.0);
235 self.groups.remove(&group_p);
236 }
237
238 for (group_p, tunable_p) in cleanup.tunables {
239 if let Some(group) = self.groups.get_mut(&group_p) {
240 let index = group
241 .priorities
242 .iter()
243 .enumerate()
244 .find(|p| *p.1 == tunable_p)
245 .unwrap();
246 group.priorities.remove(index.0);
247 group.indices.remove(&tunable_p);
248 }
249 }
250 }
251
252 fn group_plan_next(&mut self, priority: i8) -> (Vec<(usize, Arc<String>)>, Cleanup) {
253 let group_plan = self.groups.get_mut(&priority).expect("To be filled");
254 let within_group_prio = group_plan.priorities.pop().unwrap();
255 let mut next_indices = group_plan.indices.remove(&within_group_prio).unwrap();
256
257 let mut cleanup_groups = Vec::new();
258 let mut cleanup_tunables = Vec::new();
259
260 for (pg, group) in self.groups.iter_mut() {
261 let mut num_empty_tunables = 0;
262 let num_tunables = group.priorities.len();
263
264 for (pt, indices) in group.indices.iter_mut() {
265 for n in &next_indices {
266 let entry = indices.iter().enumerate().find(|p| *p.1 == *n);
267 if let Some(entry) = entry {
268 indices.remove(entry.0);
269 }
270 }
271
272 if indices.is_empty() {
273 num_empty_tunables += 1;
274 cleanup_tunables.push((*pg, *pt));
275 }
276 }
277
278 if num_empty_tunables == num_tunables {
279 cleanup_groups.push(*pg);
280 }
281 }
282
283 if within_group_prio < 0 {
284 next_indices.clear();
286 }
287
288 (
289 next_indices,
290 Cleanup {
291 groups: cleanup_groups,
292 tunables: cleanup_tunables,
293 skipped: within_group_prio < 0,
294 },
295 )
296 }
297}
298
299type PriorityFunc<K> = Arc<dyn Fn(&K) -> i8 + Send + Sync>;
300
301static GROUP_COUNTER: AtomicU32 = AtomicU32::new(0);
302
303#[cfg(test)]
304mod tests {
305 use core::fmt::Display;
306
307 use serde::{Deserialize, Serialize};
308
309 use super::*;
310
311 #[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
312 struct FakeAutotuneKey;
313
314 impl Display for FakeAutotuneKey {
315 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
316 f.write_str("FakeAutotuneKey")
317 }
318 }
319
320 impl AutotuneKey for FakeAutotuneKey {}
321
322 #[test_log::test]
323 fn test_plan_order() {
324 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
325 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
326
327 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
328 let tunable1 =
329 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
330 let tunable2 =
331 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
332 let tunable3 =
333 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
334
335 let key = FakeAutotuneKey;
336 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
337
338 assert_eq!(plan.next(), vec![0, 2]);
339 assert_eq!(plan.next(), vec![1]);
340 assert_eq!(plan.next(), vec![3]);
341 assert!(plan.next().is_empty());
342 }
343
344 #[test_log::test]
345 fn test_plan_order_multi_groups_same_priority() {
346 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
347 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
348 let group2 = TuneGroup::<FakeAutotuneKey>::new("group2", |_| 1);
349
350 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
351 let tunable1 =
352 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
353 let tunable2 =
354 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
355 let tunable3 =
356 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
357 let tunable4 =
358 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group2, |_| 2);
359
360 let key = FakeAutotuneKey;
361 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
362
363 assert_eq!(plan.next(), vec![0, 2]);
364 assert_eq!(plan.next(), vec![1]);
365 assert_eq!(plan.next(), vec![3, 4]);
366 assert!(plan.next().is_empty());
367 }
368
369 #[test_log::test]
370 fn test_plan_order_tunable_multiple_groups() {
371 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
372 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
373
374 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
375 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
376 .group(&group0, |_| 1)
377 .group(&group1, |_| 2);
378 let tunable2 =
379 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
380 let tunable3 =
381 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 3);
382
383 let key = FakeAutotuneKey;
384 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
385
386 assert_eq!(plan.next(), vec![0, 3]);
387 assert_eq!(plan.next(), vec![1]);
388 assert_eq!(plan.next(), vec![2]);
389 assert!(plan.next().is_empty());
390 }
391
392 #[test_log::test]
393 fn test_plan_negative_priority() {
394 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
395 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
396
397 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
398 let tunable1 =
399 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
400 let tunable2 =
401 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
402 let tunable3 =
403 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
404
405 let key = FakeAutotuneKey;
406 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
407
408 assert_eq!(plan.next(), vec![0, 2]);
409 assert_eq!(plan.next(), vec![3]);
410 assert!(plan.next().is_empty());
411 }
412
413 #[test_log::test]
414 fn test_plan_no_group() {
415 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
416 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
417
418 let key = FakeAutotuneKey;
419 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
420
421 assert_eq!(plan.next(), vec![0, 1]);
422 assert!(plan.next().is_empty());
423 }
424
425 #[test_log::test]
426 fn test_plan_falls_through_when_all_group_tunables_fail() {
427 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
431 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
432
433 let tunable0 =
434 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
435 let tunable1 =
436 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
437 let tunable2 =
438 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
439 let tunable3 =
440 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
441
442 let key = FakeAutotuneKey;
443 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
444
445 let mut all_returned: Vec<usize> = Vec::new();
446 loop {
447 let batch = plan.next();
448 if batch.is_empty() {
449 break;
450 }
451 all_returned.extend(batch);
452 }
453
454 assert_eq!(all_returned, vec![1, 0, 3, 2]);
456 }
457
458 #[test_log::test]
459 fn test_plan_single_group_exhausts_all_intra_priorities() {
460 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 0);
463
464 let tunable0 =
465 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
466 let tunable1 =
467 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
468 let tunable2 =
469 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 3);
470
471 let key = FakeAutotuneKey;
472 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
473
474 assert_eq!(plan.next(), vec![2]);
475 assert_eq!(plan.next(), vec![1]);
476 assert_eq!(plan.next(), vec![0]);
477 assert!(plan.next().is_empty());
478 }
479
480 #[test_log::test]
481 fn test_plan_all_negative_group_advances_to_next_group() {
482 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
485 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
486
487 let tunable0 =
488 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
489 let tunable1 =
490 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -2);
491 let tunable2 =
492 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
493
494 let key = FakeAutotuneKey;
495 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
496
497 assert_eq!(plan.next(), vec![2]);
498 assert!(plan.next().is_empty());
499 }
500
501 #[test_log::test]
502 fn test_plan_no_group_tunables_only_emitted_once_even_on_failures() {
503 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
507 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
508
509 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
510 let tunable1 =
511 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
512 let tunable2 =
513 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
514
515 let key = FakeAutotuneKey;
516 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
517
518 assert_eq!(plan.next(), vec![0, 1]);
519 assert_eq!(plan.next(), vec![2]);
520 assert!(plan.next().is_empty());
521 }
522
523 #[test_log::test]
524 fn test_plan_multi_group_tunable_not_duplicated_across_failed_groups() {
525 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
528 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
529
530 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
531 .group(&group0, |_| 1)
532 .group(&group1, |_| 1);
533 let tunable1 =
534 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
535
536 let key = FakeAutotuneKey;
537 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
538
539 let mut all_returned: Vec<usize> = Vec::new();
540 loop {
541 let batch = plan.next();
542 if batch.is_empty() {
543 break;
544 }
545 all_returned.extend(batch);
546 }
547
548 assert_eq!(all_returned, vec![0, 1]);
551 }
552
553 #[test_log::test]
554 fn test_plan_recurses_when_batch_is_fully_already_returned() {
555 let group_hi = TuneGroup::<FakeAutotuneKey>::new("hi", |_| 2);
565 let group_lo = TuneGroup::<FakeAutotuneKey>::new("lo", |_| 1);
566
567 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
569 .group(&group_hi, |_| 1)
570 .group(&group_lo, |_| 2);
571 let tunable1 =
572 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group_lo, |_| 1);
573
574 let key = FakeAutotuneKey;
575 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
576
577 assert_eq!(plan.next(), vec![0]);
579 assert_eq!(plan.next(), vec![1]);
583 assert!(plan.next().is_empty());
584 }
585
586 fn fake_kernel(_: ()) -> Result<(), String> {
587 Ok(())
588 }
589}