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::fmt::Display;
7use core::sync::atomic::{AtomicU32, Ordering};
8use cubecl_environment::collections::HashMap;
9
10pub struct Tunable<K, F: TuneInputs, Output, Id = ()> {
16 pub(crate) function: TuneFn<F, Output>,
17 groups: Vec<(TuneGroup<K>, PriorityFunc<K>)>,
18 identity: Id,
19}
20
21impl<K, F: TuneInputs, Output: 'static> Tunable<K, F, Output> {
22 pub fn new<Func, Err>(name: &str, func: Func) -> Self
36 where
37 Err: Into<String> + 'static,
38 Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
39 {
40 Self::build(name, (), func)
41 }
42}
43
44impl<K, F: TuneInputs, Output: 'static, Id> Tunable<K, F, Output, Id> {
45 pub fn identified<Func, Err>(identity: Id, func: Func) -> Self
53 where
54 Id: Display,
55 Err: Into<String> + 'static,
56 Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
57 {
58 Self::build(&identity.to_string(), identity, func)
59 }
60
61 fn build<Func, Err>(name: &str, identity: Id, func: Func) -> Self
62 where
63 Err: Into<String> + 'static,
64 Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
65 {
66 let name: String = name.into();
67 let name_for_err = name.clone();
68 Self {
69 function: TuneFn::new(
70 name,
71 Box::new(move |inputs| {
72 func(inputs).map_err(|err| AutotuneError::Unknown {
73 name: name_for_err.to_string(),
74 err: err.into(),
75 })
76 }),
77 ),
78 groups: Vec::new(),
79 identity,
80 }
81 }
82
83 pub(crate) fn identity(&self) -> &Id {
85 &self.identity
86 }
87
88 pub fn group(
95 mut self,
96 group: &TuneGroup<K>,
97 priority: impl Fn(&K) -> i8 + Send + Sync + 'static,
98 ) -> Self {
99 self.groups.push((group.clone(), Arc::new(priority)));
100 self
101 }
102}
103
104pub struct TuneGroup<K> {
109 id: GroupId,
110 name: Arc<String>,
111 pub(crate) priority: PriorityFunc<K>,
112 order: GroupOrder,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub(crate) struct GroupId(u32);
118
119impl GroupId {
120 #[cfg(test)]
121 pub(crate) fn new(id: u32) -> Self {
122 Self(id)
123 }
124}
125
126#[derive(Debug, Clone, Copy)]
128enum GroupOrder {
129 Cutoff,
131 Ordered(OrderedSettings),
133}
134
135#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
137pub struct OrderedSettings {
138 pub patience: Option<Patience>,
140}
141
142impl OrderedSettings {
143 pub fn with_patience(mut self, patience: Patience) -> Self {
145 self.patience = Some(patience);
146 self
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct Patience {
170 pub min_measured: usize,
172 pub max_misses: usize,
175}
176
177impl<K> core::fmt::Debug for TuneGroup<K> {
178 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179 f.debug_struct("TuneGroup")
180 .field("id", &self.id)
181 .field("name", &self.name)
182 .finish()
183 }
184}
185
186impl<K> Clone for TuneGroup<K> {
187 fn clone(&self) -> Self {
188 Self {
189 id: self.id,
190 name: self.name.clone(),
191 priority: self.priority.clone(),
192 order: self.order,
193 }
194 }
195}
196
197impl<K> TuneGroup<K> {
198 pub fn new(name: &str, f: impl Fn(&K) -> i8 + Send + Sync + 'static) -> Self {
203 Self::build(name, f, GroupOrder::Cutoff)
204 }
205
206 pub fn ordered(
221 name: &str,
222 f: impl Fn(&K) -> i8 + Send + Sync + 'static,
223 settings: OrderedSettings,
224 ) -> Self {
225 Self::build(name, f, GroupOrder::Ordered(settings))
226 }
227
228 fn build(name: &str, f: impl Fn(&K) -> i8 + Send + Sync + 'static, order: GroupOrder) -> Self {
229 let id = GroupId(GROUP_COUNTER.fetch_add(1, Ordering::Relaxed));
230
231 Self {
232 id,
233 name: Arc::new(name.into()),
234 priority: Arc::new(f),
235 order,
236 }
237 }
238
239 fn is_ordered(&self) -> bool {
240 matches!(self.order, GroupOrder::Ordered(_))
241 }
242
243 fn patience(&self) -> Option<Patience> {
244 match self.order {
245 GroupOrder::Ordered(settings) => settings.patience,
246 GroupOrder::Cutoff => None,
247 }
248 }
249}
250
251#[derive(Debug, Default, PartialEq, Eq)]
254pub(crate) struct Batch {
255 pub(crate) entries: Vec<BatchEntry>,
256 pub(crate) patience: HashMap<GroupId, Patience>,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
262pub(crate) struct BatchEntry {
263 pub(crate) index: usize,
264 pub(crate) groups: Vec<GroupId>,
265}
266
267impl Batch {
268 pub(crate) fn unconditional(indices: impl IntoIterator<Item = usize>) -> Self {
270 let mut batch = Self::default();
271 for index in indices {
272 batch.push_unconditional(index);
273 }
274 batch
275 }
276
277 pub(crate) fn is_empty(&self) -> bool {
278 self.entries.is_empty()
279 }
280
281 pub(crate) fn indices(&self) -> Vec<usize> {
283 self.entries.iter().map(|entry| entry.index).collect()
284 }
285
286 fn push(&mut self, planned: &Planned) {
289 match self.entries.iter_mut().find(|e| e.index == planned.index) {
290 Some(entry) if entry.groups.contains(&planned.group) => {}
291 Some(entry) => entry.groups.push(planned.group),
292 None => self.entries.push(BatchEntry {
293 index: planned.index,
294 groups: vec![planned.group],
295 }),
296 }
297 }
298
299 fn push_unconditional(&mut self, index: usize) {
300 self.entries.push(BatchEntry {
301 index,
302 groups: Vec::new(),
303 });
304 }
305}
306
307#[derive(Debug, Clone)]
308pub(crate) struct TunePlan {
310 priorities: Vec<i8>,
311 no_groups: Vec<usize>,
312 groups: HashMap<i8, GroupPlan>,
313 returned: Vec<usize>,
314 patience: HashMap<GroupId, Patience>,
316 #[cfg(persistence)]
319 placements: Vec<Vec<Placement>>,
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
324pub struct Placement {
325 pub group: String,
327 pub ordered: bool,
330 pub group_priority: i8,
332 pub priority: i8,
334}
335
336#[derive(Default, Debug, Clone)]
337struct GroupPlan {
338 priorities: Vec<i8>,
339 indices: HashMap<i8, Vec<Planned>>,
340}
341
342#[derive(Debug, Clone)]
349struct Planned {
350 index: usize,
351 group: GroupId,
352 priority: i8,
353}
354
355#[derive(Debug)]
356struct Cleanup {
357 groups: Vec<i8>,
358 tunables: Vec<(i8, i8)>,
359 skipped: bool,
361}
362
363impl TunePlan {
364 pub fn new<K: AutotuneKey, F: TuneInputs, Out, Id>(
365 key: &K,
366 tunables: &[Tunable<K, F, Out, Id>],
367 ) -> Self {
368 let mut priorities = Vec::<i8>::new();
369 let mut no_groups = Vec::new();
370 let mut groups = HashMap::<i8, GroupPlan>::new();
371
372 let mut priced = Vec::new();
376 let mut ordered_levels = HashMap::<GroupId, i8>::new();
377 let mut patience = HashMap::<GroupId, Patience>::new();
378 #[cfg(persistence)]
379 let mut placements = vec![Vec::new(); tunables.len()];
380
381 for (index, tunable) in tunables.iter().enumerate() {
382 if tunable.groups.is_empty() {
383 no_groups.push(index);
384 continue;
385 }
386
387 for (group, within_group_priority_fn) in tunable.groups.iter() {
388 if let Some(group_patience) = group.patience() {
389 patience.insert(group.id, group_patience);
390 }
391 let group_priority = (group.priority)(key);
392 let priority = within_group_priority_fn(key);
393 #[cfg(persistence)]
394 placements[index].push(Placement {
395 group: group.name.to_string(),
396 ordered: group.is_ordered(),
397 group_priority,
398 priority,
399 });
400
401 if group.is_ordered() && priority >= 0 {
402 let level = ordered_levels.entry(group.id).or_insert(priority);
403 *level = (*level).max(priority);
404 }
405
406 priced.push((index, group, group_priority, priority));
407 }
408 }
409
410 for (index, group, group_priority, priority) in priced {
411 if !priorities.contains(&group_priority) {
412 priorities.push(group_priority);
413 }
414
415 let group_plan = match groups.get_mut(&group_priority) {
416 Some(val) => val,
417 None => {
418 groups.insert(group_priority, GroupPlan::default());
419 groups.get_mut(&group_priority).unwrap()
420 }
421 };
422
423 let level = match group.is_ordered() && priority >= 0 {
426 true => ordered_levels[&group.id],
427 false => priority,
428 };
429 let planned = Planned {
430 index,
431 group: group.id,
432 priority,
433 };
434
435 if group_plan.priorities.contains(&level) {
436 group_plan.indices.get_mut(&level).unwrap().push(planned);
437 } else {
438 group_plan.priorities.push(level);
439 group_plan.indices.insert(level, vec![planned]);
440 }
441 }
442
443 priorities.sort();
444
445 for group in groups.iter_mut() {
446 group.1.priorities.sort();
447 }
448
449 Self {
450 priorities,
451 no_groups,
452 groups,
453 returned: Vec::new(),
454 patience,
455 #[cfg(persistence)]
456 placements,
457 }
458 }
459
460 #[cfg(persistence)]
465 pub(crate) fn account(&self, names: &[&str]) -> Vec<super::record::PlannedCandidate> {
466 let mut plan = self.clone();
467 let batches = core::iter::from_fn(|| {
468 Some(plan.next())
469 .filter(|batch| !batch.is_empty())
470 .map(|batch| batch.indices())
471 });
472 let mut account: Vec<_> = batches
473 .enumerate()
474 .flat_map(|(batch, indices)| indices.into_iter().map(move |index| (index, Some(batch))))
475 .collect();
476 let excluded =
477 (0..names.len()).filter(|index| !account.iter().any(|(seen, _)| seen == index));
478 account.extend(excluded.map(|index| (index, None)).collect::<Vec<_>>());
479 account
480 .into_iter()
481 .map(|(index, batch)| super::record::PlannedCandidate {
482 name: names[index].to_string(),
483 index,
484 batch,
485 groups: self.placements[index].clone(),
486 })
487 .collect()
488 }
489
490 pub(crate) fn next(&mut self) -> Batch {
492 let ungrouped = core::mem::take(&mut self.no_groups);
495 let priority = self.priorities.last();
496
497 let priority = match priority {
498 Some(val) => *val,
499 None => return Batch::unconditional(ungrouped),
500 };
501 let mut batch = Batch {
502 entries: Vec::new(),
503 patience: self.patience.clone(),
504 };
505
506 let (group_indices, cleanup) = self.group_plan_next(priority);
507 let skipped = cleanup.skipped || priority < 0;
509 let mut all_skip = true;
510
511 self.cleanup(cleanup);
512
513 if priority >= 0 {
514 for planned in group_indices {
515 if self.returned.contains(&planned.index) {
516 continue;
517 }
518 all_skip = false;
519 batch.push(&planned);
520 }
521 }
522
523 for index in ungrouped {
524 batch.push_unconditional(index);
525 }
526
527 if batch.is_empty() && (skipped || all_skip) {
531 self.next()
532 } else {
533 for entry in batch.entries.iter() {
534 self.returned.push(entry.index);
535 }
536 batch
537 }
538 }
539
540 fn cleanup(&mut self, cleanup: Cleanup) {
541 for group_p in cleanup.groups {
542 let index = self
543 .priorities
544 .iter()
545 .enumerate()
546 .find(|p| *p.1 == group_p)
547 .unwrap();
548
549 self.priorities.remove(index.0);
550 self.groups.remove(&group_p);
551 }
552
553 for (group_p, tunable_p) in cleanup.tunables {
554 if let Some(group) = self.groups.get_mut(&group_p) {
555 let index = group
556 .priorities
557 .iter()
558 .enumerate()
559 .find(|p| *p.1 == tunable_p)
560 .unwrap();
561 group.priorities.remove(index.0);
562 group.indices.remove(&tunable_p);
563 }
564 }
565 }
566
567 fn group_plan_next(&mut self, priority: i8) -> (Vec<Planned>, Cleanup) {
568 let group_plan = self.groups.get_mut(&priority).expect("To be filled");
569 let within_group_prio = group_plan.priorities.pop().unwrap();
570 let mut next_indices = group_plan.indices.remove(&within_group_prio).unwrap();
571 next_indices.sort_by_key(|planned| (core::cmp::Reverse(planned.priority), planned.index));
573
574 let mut cleanup_groups = Vec::new();
575 let mut cleanup_tunables = Vec::new();
576
577 for (pg, group) in self.groups.iter_mut() {
578 let mut num_empty_tunables = 0;
579 let num_tunables = group.priorities.len();
580
581 for (pt, indices) in group.indices.iter_mut() {
582 for n in &next_indices {
583 let entry = indices
584 .iter()
585 .position(|p| p.index == n.index && p.group == n.group);
586 if let Some(entry) = entry {
587 indices.remove(entry);
588 }
589 }
590
591 if indices.is_empty() {
592 num_empty_tunables += 1;
593 cleanup_tunables.push((*pg, *pt));
594 }
595 }
596
597 if num_empty_tunables == num_tunables {
598 cleanup_groups.push(*pg);
599 }
600 }
601
602 if within_group_prio < 0 {
603 next_indices.clear();
605 }
606
607 (
608 next_indices,
609 Cleanup {
610 groups: cleanup_groups,
611 tunables: cleanup_tunables,
612 skipped: within_group_prio < 0,
613 },
614 )
615 }
616}
617
618type PriorityFunc<K> = Arc<dyn Fn(&K) -> i8 + Send + Sync>;
619
620static GROUP_COUNTER: AtomicU32 = AtomicU32::new(0);
621
622#[cfg(test)]
623mod tests {
624 use core::fmt::Display;
625
626 use serde::{Deserialize, Serialize};
627
628 use super::*;
629
630 #[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
631 struct FakeAutotuneKey;
632
633 impl Display for FakeAutotuneKey {
634 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
635 f.write_str("FakeAutotuneKey")
636 }
637 }
638
639 impl AutotuneKey for FakeAutotuneKey {}
640
641 #[test_log::test]
642 fn test_plan_order() {
643 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
644 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
645
646 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
647 let tunable1 =
648 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
649 let tunable2 =
650 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
651 let tunable3 =
652 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
653
654 let key = FakeAutotuneKey;
655 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
656
657 assert_eq!(plan.next().indices(), vec![2, 0]);
659 assert_eq!(plan.next().indices(), vec![1]);
660 assert_eq!(plan.next().indices(), vec![3]);
661 assert!(plan.next().is_empty());
662 }
663
664 #[test_log::test]
665 fn test_plan_order_multi_groups_same_priority() {
666 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
667 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
668 let group2 = TuneGroup::<FakeAutotuneKey>::new("group2", |_| 1);
669
670 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
671 let tunable1 =
672 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
673 let tunable2 =
674 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
675 let tunable3 =
676 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
677 let tunable4 =
678 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group2, |_| 2);
679
680 let key = FakeAutotuneKey;
681 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
682
683 assert_eq!(plan.next().indices(), vec![2, 0]);
684 assert_eq!(plan.next().indices(), vec![1]);
685 assert_eq!(plan.next().indices(), vec![3, 4]);
686 assert!(plan.next().is_empty());
687 }
688
689 #[test_log::test]
690 fn test_plan_order_tunable_multiple_groups() {
691 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
692 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
693
694 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
695 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
696 .group(&group0, |_| 1)
697 .group(&group1, |_| 2);
698 let tunable2 =
699 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
700 let tunable3 =
701 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 3);
702
703 let key = FakeAutotuneKey;
704 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
705
706 assert_eq!(plan.next().indices(), vec![3, 0]);
707 assert_eq!(plan.next().indices(), vec![1]);
708 assert_eq!(plan.next().indices(), vec![2]);
709 assert!(plan.next().is_empty());
710 }
711
712 #[test_log::test]
713 fn test_plan_negative_priority() {
714 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
715 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
716
717 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
718 let tunable1 =
719 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
720 let tunable2 =
721 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
722 let tunable3 =
723 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
724
725 let key = FakeAutotuneKey;
726 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
727
728 assert_eq!(plan.next().indices(), vec![2, 0]);
729 assert_eq!(plan.next().indices(), vec![3]);
730 assert!(plan.next().is_empty());
731 }
732
733 #[cfg(persistence)]
734 #[test_log::test]
735 fn test_plan_account_lists_every_tunable_in_release_order() {
736 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
737 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
738
739 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
740 let tunable1 =
741 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
742 let tunable2 =
743 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
744 let tunable3 =
745 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
746
747 let key = FakeAutotuneKey;
748 let plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
749 let account = plan.account(&["t0", "t1", "t2", "t3"]);
750
751 let order: Vec<_> = account
752 .iter()
753 .map(|planned| (planned.name.as_str(), planned.batch))
754 .collect();
755 assert_eq!(
756 order,
757 vec![
758 ("t2", Some(0)),
759 ("t0", Some(0)),
760 ("t3", Some(1)),
761 ("t1", None)
762 ]
763 );
764 assert_eq!(
765 account[0].groups,
766 vec![Placement {
767 group: "group0".into(),
768 ordered: false,
769 group_priority: 2,
770 priority: 2,
771 }]
772 );
773 assert!(account[1].groups.is_empty());
774 let mut plan = plan;
776 assert_eq!(plan.next().indices(), vec![2, 0]);
777 }
778
779 #[test_log::test]
780 fn test_plan_no_group() {
781 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
782 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
783
784 let key = FakeAutotuneKey;
785 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
786
787 assert_eq!(plan.next().indices(), vec![0, 1]);
788 assert!(plan.next().is_empty());
789 }
790
791 #[test_log::test]
792 fn test_plan_falls_through_when_all_group_tunables_fail() {
793 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
797 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
798
799 let tunable0 =
800 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
801 let tunable1 =
802 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
803 let tunable2 =
804 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
805 let tunable3 =
806 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
807
808 let key = FakeAutotuneKey;
809 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
810
811 let mut all_returned: Vec<usize> = Vec::new();
812 loop {
813 let batch = plan.next();
814 if batch.is_empty() {
815 break;
816 }
817 all_returned.extend(batch.indices());
818 }
819
820 assert_eq!(all_returned, vec![1, 0, 3, 2]);
822 }
823
824 #[test_log::test]
825 fn test_plan_single_group_exhausts_all_intra_priorities() {
826 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 0);
829
830 let tunable0 =
831 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
832 let tunable1 =
833 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
834 let tunable2 =
835 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 3);
836
837 let key = FakeAutotuneKey;
838 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
839
840 assert_eq!(plan.next().indices(), vec![2]);
841 assert_eq!(plan.next().indices(), vec![1]);
842 assert_eq!(plan.next().indices(), vec![0]);
843 assert!(plan.next().is_empty());
844 }
845
846 #[test_log::test]
847 fn test_plan_all_negative_group_advances_to_next_group() {
848 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
851 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
852
853 let tunable0 =
854 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
855 let tunable1 =
856 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -2);
857 let tunable2 =
858 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
859
860 let key = FakeAutotuneKey;
861 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
862
863 assert_eq!(plan.next().indices(), vec![2]);
864 assert!(plan.next().is_empty());
865 }
866
867 #[test_log::test]
868 fn test_plan_no_group_tunables_only_emitted_once_even_on_failures() {
869 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
873 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
874
875 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
876 let tunable1 =
877 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
878 let tunable2 =
879 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
880
881 let key = FakeAutotuneKey;
882 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
883
884 assert_eq!(plan.next().indices(), vec![1, 0]);
885 assert_eq!(plan.next().indices(), vec![2]);
886 assert!(plan.next().is_empty());
887 }
888
889 #[test_log::test]
890 fn test_plan_multi_group_tunable_not_duplicated_across_failed_groups() {
891 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
894 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
895
896 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
897 .group(&group0, |_| 1)
898 .group(&group1, |_| 1);
899 let tunable1 =
900 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
901
902 let key = FakeAutotuneKey;
903 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
904
905 let mut all_returned: Vec<usize> = Vec::new();
906 loop {
907 let batch = plan.next();
908 if batch.is_empty() {
909 break;
910 }
911 all_returned.extend(batch.indices());
912 }
913
914 assert_eq!(all_returned, vec![0, 1]);
917 }
918
919 #[test_log::test]
920 fn test_plan_recurses_when_batch_is_fully_already_returned() {
921 let group_hi = TuneGroup::<FakeAutotuneKey>::new("hi", |_| 2);
931 let group_lo = TuneGroup::<FakeAutotuneKey>::new("lo", |_| 1);
932
933 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
935 .group(&group_hi, |_| 1)
936 .group(&group_lo, |_| 2);
937 let tunable1 =
938 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group_lo, |_| 1);
939
940 let key = FakeAutotuneKey;
941 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
942
943 assert_eq!(plan.next().indices(), vec![0]);
945 assert_eq!(plan.next().indices(), vec![1]);
949 assert!(plan.next().is_empty());
950 }
951
952 #[test_log::test]
953 fn test_plan_ordered_group_is_one_batch_best_first() {
954 let group =
956 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
957
958 let tunable0 =
959 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 1);
960 let tunable1 =
961 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 3);
962 let tunable2 =
963 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| -1);
964 let tunable3 =
965 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 3);
966 let tunable4 =
967 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 2);
968
969 let key = FakeAutotuneKey;
970 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
971
972 assert_eq!(plan.next().indices(), vec![1, 3, 4, 0]);
973 assert!(plan.next().is_empty());
974 }
975
976 #[test_log::test]
977 fn test_plan_batch_carries_every_group_of_an_entry() {
978 let patience = Patience {
981 min_measured: 10,
982 max_misses: 5,
983 };
984 let ordered = TuneGroup::<FakeAutotuneKey>::ordered(
985 "ordered",
986 |_| 1,
987 OrderedSettings::default().with_patience(patience),
988 );
989 let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
990
991 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
992 let tunable1 =
993 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 2);
994 let tunable2 =
995 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
996 let tunable3 =
997 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
998 let tunable4 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
999 .group(&cutoff, |_| 2)
1000 .group(&ordered, |_| 1);
1001
1002 let key = FakeAutotuneKey;
1003 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
1004 let batch = plan.next();
1005 let entry = |index, groups: &[GroupId]| BatchEntry {
1006 index,
1007 groups: groups.to_vec(),
1008 };
1009
1010 assert_eq!(
1011 batch.entries,
1012 vec![
1013 entry(1, &[ordered.id]),
1014 entry(2, &[cutoff.id]),
1015 entry(4, &[cutoff.id, ordered.id]),
1016 entry(3, &[ordered.id]),
1017 entry(0, &[]),
1018 ]
1019 );
1020 assert_eq!(batch.patience, HashMap::from_iter([(ordered.id, patience)]));
1021 }
1022
1023 #[test_log::test]
1024 fn test_plan_entry_names_a_group_listed_twice_once() {
1025 let ordered =
1028 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1029
1030 let tunable = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
1031 .group(&ordered, |_| 2)
1032 .group(&ordered, |_| 1);
1033
1034 let key = FakeAutotuneKey;
1035 let mut plan = TunePlan::new(&key, &[tunable]);
1036
1037 assert_eq!(
1038 plan.next().entries,
1039 vec![BatchEntry {
1040 index: 0,
1041 groups: vec![ordered.id],
1042 }]
1043 );
1044 }
1045
1046 #[test_log::test]
1047 fn test_plan_ordered_group_keeps_the_group_cutoff() {
1048 let first =
1051 TuneGroup::<FakeAutotuneKey>::ordered("first", |_| 2, OrderedSettings::default());
1052 let fallback = TuneGroup::<FakeAutotuneKey>::new("fallback", |_| 1);
1053
1054 let tunable0 =
1055 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&first, |_| 1);
1056 let tunable1 =
1057 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&first, |_| 2);
1058 let tunable2 =
1059 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&fallback, |_| 1);
1060
1061 let key = FakeAutotuneKey;
1062 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
1063
1064 assert_eq!(plan.next().indices(), vec![1, 0]);
1065 assert_eq!(plan.next().indices(), vec![2]);
1066 assert!(plan.next().is_empty());
1067 }
1068
1069 #[test_log::test]
1070 fn test_plan_ordered_batch_leads_the_ungrouped_tunables() {
1071 let group =
1075 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1076
1077 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
1078 let tunable1 =
1079 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 1);
1080 let tunable2 =
1081 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 5);
1082
1083 let key = FakeAutotuneKey;
1084 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
1085
1086 assert_eq!(plan.next().indices(), vec![2, 1, 0]);
1087 assert!(plan.next().is_empty());
1088 }
1089
1090 #[test_log::test]
1091 fn test_plan_ordered_batch_is_not_jumped_by_a_cutoff_group_beside_it() {
1092 let ordered =
1097 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1098 let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
1099
1100 let tunable0 =
1101 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 3);
1102 let tunable1 =
1103 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
1104 let tunable2 =
1105 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
1106
1107 let key = FakeAutotuneKey;
1108 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
1109
1110 assert_eq!(plan.next().indices(), vec![0, 1]);
1111 assert_eq!(plan.next().indices(), vec![2]);
1112 assert!(plan.next().is_empty());
1113 }
1114
1115 #[test_log::test]
1116 fn test_plan_cutoff_member_above_the_ordered_batch_still_leads() {
1117 let ordered =
1121 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1122 let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
1123
1124 let tunable0 =
1125 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 2);
1126 let tunable1 =
1127 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
1128 let tunable2 =
1129 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 3);
1130 let tunable3 =
1131 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
1132
1133 let key = FakeAutotuneKey;
1134 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
1135
1136 assert_eq!(plan.next().indices(), vec![2]);
1137 assert_eq!(plan.next().indices(), vec![0, 3, 1]);
1138 assert!(plan.next().is_empty());
1139 }
1140
1141 #[test_log::test]
1142 fn test_plan_same_named_groups_do_not_strike_each_other_out() {
1143 let hi = TuneGroup::<FakeAutotuneKey>::new("shared", |_| 2);
1147 let lo = TuneGroup::<FakeAutotuneKey>::new("shared", |_| 1);
1148
1149 let tunable0 =
1150 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&hi, |_| 1);
1151 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
1152 .group(&hi, |_| -1)
1153 .group(&lo, |_| 1);
1154
1155 let key = FakeAutotuneKey;
1156 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
1157
1158 assert_eq!(plan.next().indices(), vec![0]);
1159 assert_eq!(plan.next().indices(), vec![1]);
1160 assert!(plan.next().is_empty());
1161 }
1162
1163 fn fake_kernel(_: ()) -> Result<(), String> {
1164 Ok(())
1165 }
1166}