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::Rejected {
73 name: name_for_err.to_string(),
74 reason: 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 pub(crate) fn order(&self, len: usize) -> Vec<(usize, Option<usize>)> {
464 let mut plan = self.clone();
465 let batches = core::iter::from_fn(|| {
466 Some(plan.next())
467 .filter(|batch| !batch.is_empty())
468 .map(|batch| batch.indices())
469 });
470 let mut order: Vec<_> = batches
471 .enumerate()
472 .flat_map(|(batch, indices)| indices.into_iter().map(move |index| (index, Some(batch))))
473 .collect();
474 let excluded = (0..len).filter(|index| !order.iter().any(|(seen, _)| seen == index));
475 order.extend(excluded.map(|index| (index, None)).collect::<Vec<_>>());
476 order
477 }
478
479 #[cfg(persistence)]
483 pub(crate) fn account(&self, names: &[&str]) -> Vec<super::record::PlannedCandidate> {
484 self.order(names.len())
485 .into_iter()
486 .map(|(index, batch)| super::record::PlannedCandidate {
487 name: names[index].to_string(),
488 index,
489 batch,
490 groups: self.placements[index].clone(),
491 })
492 .collect()
493 }
494
495 pub(crate) fn next(&mut self) -> Batch {
497 let ungrouped = core::mem::take(&mut self.no_groups);
500 let priority = self.priorities.last();
501
502 let priority = match priority {
503 Some(val) => *val,
504 None => return Batch::unconditional(ungrouped),
505 };
506 let mut batch = Batch {
507 entries: Vec::new(),
508 patience: self.patience.clone(),
509 };
510
511 let (group_indices, cleanup) = self.group_plan_next(priority);
512 let skipped = cleanup.skipped || priority < 0;
514 let mut all_skip = true;
515
516 self.cleanup(cleanup);
517
518 if priority >= 0 {
519 for planned in group_indices {
520 if self.returned.contains(&planned.index) {
521 continue;
522 }
523 all_skip = false;
524 batch.push(&planned);
525 }
526 }
527
528 for index in ungrouped {
529 batch.push_unconditional(index);
530 }
531
532 if batch.is_empty() && (skipped || all_skip) {
536 self.next()
537 } else {
538 for entry in batch.entries.iter() {
539 self.returned.push(entry.index);
540 }
541 batch
542 }
543 }
544
545 fn cleanup(&mut self, cleanup: Cleanup) {
546 for group_p in cleanup.groups {
547 let index = self
548 .priorities
549 .iter()
550 .enumerate()
551 .find(|p| *p.1 == group_p)
552 .unwrap();
553
554 self.priorities.remove(index.0);
555 self.groups.remove(&group_p);
556 }
557
558 for (group_p, tunable_p) in cleanup.tunables {
559 if let Some(group) = self.groups.get_mut(&group_p) {
560 let index = group
561 .priorities
562 .iter()
563 .enumerate()
564 .find(|p| *p.1 == tunable_p)
565 .unwrap();
566 group.priorities.remove(index.0);
567 group.indices.remove(&tunable_p);
568 }
569 }
570 }
571
572 fn group_plan_next(&mut self, priority: i8) -> (Vec<Planned>, Cleanup) {
573 let group_plan = self.groups.get_mut(&priority).expect("To be filled");
574 let within_group_prio = group_plan.priorities.pop().unwrap();
575 let mut next_indices = group_plan.indices.remove(&within_group_prio).unwrap();
576 next_indices.sort_by_key(|planned| (core::cmp::Reverse(planned.priority), planned.index));
578
579 let mut cleanup_groups = Vec::new();
580 let mut cleanup_tunables = Vec::new();
581
582 for (pg, group) in self.groups.iter_mut() {
583 let mut num_empty_tunables = 0;
584 let num_tunables = group.priorities.len();
585
586 for (pt, indices) in group.indices.iter_mut() {
587 for n in &next_indices {
588 let entry = indices
589 .iter()
590 .position(|p| p.index == n.index && p.group == n.group);
591 if let Some(entry) = entry {
592 indices.remove(entry);
593 }
594 }
595
596 if indices.is_empty() {
597 num_empty_tunables += 1;
598 cleanup_tunables.push((*pg, *pt));
599 }
600 }
601
602 if num_empty_tunables == num_tunables {
603 cleanup_groups.push(*pg);
604 }
605 }
606
607 if within_group_prio < 0 {
608 next_indices.clear();
610 }
611
612 (
613 next_indices,
614 Cleanup {
615 groups: cleanup_groups,
616 tunables: cleanup_tunables,
617 skipped: within_group_prio < 0,
618 },
619 )
620 }
621}
622
623type PriorityFunc<K> = Arc<dyn Fn(&K) -> i8 + Send + Sync>;
624
625static GROUP_COUNTER: AtomicU32 = AtomicU32::new(0);
626
627#[cfg(test)]
628mod tests {
629 use core::fmt::Display;
630
631 use serde::{Deserialize, Serialize};
632
633 use super::*;
634
635 #[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
636 struct FakeAutotuneKey;
637
638 impl Display for FakeAutotuneKey {
639 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
640 f.write_str("FakeAutotuneKey")
641 }
642 }
643
644 impl AutotuneKey for FakeAutotuneKey {}
645
646 #[test_log::test]
647 fn test_plan_order() {
648 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
649 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
650
651 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
652 let tunable1 =
653 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
654 let tunable2 =
655 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
656 let tunable3 =
657 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
658
659 let key = FakeAutotuneKey;
660 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
661
662 assert_eq!(plan.next().indices(), vec![2, 0]);
664 assert_eq!(plan.next().indices(), vec![1]);
665 assert_eq!(plan.next().indices(), vec![3]);
666 assert!(plan.next().is_empty());
667 }
668
669 #[test_log::test]
670 fn test_plan_order_multi_groups_same_priority() {
671 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
672 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
673 let group2 = TuneGroup::<FakeAutotuneKey>::new("group2", |_| 1);
674
675 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
676 let tunable1 =
677 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
678 let tunable2 =
679 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
680 let tunable3 =
681 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
682 let tunable4 =
683 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group2, |_| 2);
684
685 let key = FakeAutotuneKey;
686 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
687
688 assert_eq!(plan.next().indices(), vec![2, 0]);
689 assert_eq!(plan.next().indices(), vec![1]);
690 assert_eq!(plan.next().indices(), vec![3, 4]);
691 assert!(plan.next().is_empty());
692 }
693
694 #[test_log::test]
695 fn test_plan_order_tunable_multiple_groups() {
696 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
697 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
698
699 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
700 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
701 .group(&group0, |_| 1)
702 .group(&group1, |_| 2);
703 let tunable2 =
704 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
705 let tunable3 =
706 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 3);
707
708 let key = FakeAutotuneKey;
709 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
710
711 assert_eq!(plan.next().indices(), vec![3, 0]);
712 assert_eq!(plan.next().indices(), vec![1]);
713 assert_eq!(plan.next().indices(), vec![2]);
714 assert!(plan.next().is_empty());
715 }
716
717 #[test_log::test]
718 fn test_plan_negative_priority() {
719 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
720 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
721
722 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
723 let tunable1 =
724 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
725 let tunable2 =
726 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
727 let tunable3 =
728 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
729
730 let key = FakeAutotuneKey;
731 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
732
733 assert_eq!(plan.next().indices(), vec![2, 0]);
734 assert_eq!(plan.next().indices(), vec![3]);
735 assert!(plan.next().is_empty());
736 }
737
738 #[cfg(persistence)]
739 #[test_log::test]
740 fn test_plan_account_lists_every_tunable_in_release_order() {
741 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
742 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
743
744 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
745 let tunable1 =
746 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
747 let tunable2 =
748 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
749 let tunable3 =
750 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
751
752 let key = FakeAutotuneKey;
753 let plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
754 let account = plan.account(&["t0", "t1", "t2", "t3"]);
755
756 let order: Vec<_> = account
757 .iter()
758 .map(|planned| (planned.name.as_str(), planned.batch))
759 .collect();
760 assert_eq!(
761 order,
762 vec![
763 ("t2", Some(0)),
764 ("t0", Some(0)),
765 ("t3", Some(1)),
766 ("t1", None)
767 ]
768 );
769 assert_eq!(
770 account[0].groups,
771 vec![Placement {
772 group: "group0".into(),
773 ordered: false,
774 group_priority: 2,
775 priority: 2,
776 }]
777 );
778 assert!(account[1].groups.is_empty());
779 let mut plan = plan;
781 assert_eq!(plan.next().indices(), vec![2, 0]);
782 }
783
784 #[test_log::test]
785 fn test_plan_no_group() {
786 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
787 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
788
789 let key = FakeAutotuneKey;
790 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
791
792 assert_eq!(plan.next().indices(), vec![0, 1]);
793 assert!(plan.next().is_empty());
794 }
795
796 #[test_log::test]
797 fn test_plan_falls_through_when_all_group_tunables_fail() {
798 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
802 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
803
804 let tunable0 =
805 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
806 let tunable1 =
807 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
808 let tunable2 =
809 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
810 let tunable3 =
811 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
812
813 let key = FakeAutotuneKey;
814 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
815
816 let mut all_returned: Vec<usize> = Vec::new();
817 loop {
818 let batch = plan.next();
819 if batch.is_empty() {
820 break;
821 }
822 all_returned.extend(batch.indices());
823 }
824
825 assert_eq!(all_returned, vec![1, 0, 3, 2]);
827 }
828
829 #[test_log::test]
830 fn test_plan_single_group_exhausts_all_intra_priorities() {
831 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 0);
834
835 let tunable0 =
836 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
837 let tunable1 =
838 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
839 let tunable2 =
840 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 3);
841
842 let key = FakeAutotuneKey;
843 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
844
845 assert_eq!(plan.next().indices(), vec![2]);
846 assert_eq!(plan.next().indices(), vec![1]);
847 assert_eq!(plan.next().indices(), vec![0]);
848 assert!(plan.next().is_empty());
849 }
850
851 #[test_log::test]
852 fn test_plan_all_negative_group_advances_to_next_group() {
853 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
856 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
857
858 let tunable0 =
859 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
860 let tunable1 =
861 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -2);
862 let tunable2 =
863 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
864
865 let key = FakeAutotuneKey;
866 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
867
868 assert_eq!(plan.next().indices(), vec![2]);
869 assert!(plan.next().is_empty());
870 }
871
872 #[test_log::test]
873 fn test_plan_no_group_tunables_only_emitted_once_even_on_failures() {
874 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
878 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
879
880 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
881 let tunable1 =
882 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
883 let tunable2 =
884 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
885
886 let key = FakeAutotuneKey;
887 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
888
889 assert_eq!(plan.next().indices(), vec![1, 0]);
890 assert_eq!(plan.next().indices(), vec![2]);
891 assert!(plan.next().is_empty());
892 }
893
894 #[test_log::test]
895 fn test_plan_multi_group_tunable_not_duplicated_across_failed_groups() {
896 let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
899 let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
900
901 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
902 .group(&group0, |_| 1)
903 .group(&group1, |_| 1);
904 let tunable1 =
905 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
906
907 let key = FakeAutotuneKey;
908 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
909
910 let mut all_returned: Vec<usize> = Vec::new();
911 loop {
912 let batch = plan.next();
913 if batch.is_empty() {
914 break;
915 }
916 all_returned.extend(batch.indices());
917 }
918
919 assert_eq!(all_returned, vec![0, 1]);
922 }
923
924 #[test_log::test]
925 fn test_plan_recurses_when_batch_is_fully_already_returned() {
926 let group_hi = TuneGroup::<FakeAutotuneKey>::new("hi", |_| 2);
936 let group_lo = TuneGroup::<FakeAutotuneKey>::new("lo", |_| 1);
937
938 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
940 .group(&group_hi, |_| 1)
941 .group(&group_lo, |_| 2);
942 let tunable1 =
943 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group_lo, |_| 1);
944
945 let key = FakeAutotuneKey;
946 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
947
948 assert_eq!(plan.next().indices(), vec![0]);
950 assert_eq!(plan.next().indices(), vec![1]);
954 assert!(plan.next().is_empty());
955 }
956
957 #[test_log::test]
958 fn test_plan_ordered_group_is_one_batch_best_first() {
959 let group =
961 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
962
963 let tunable0 =
964 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 1);
965 let tunable1 =
966 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 3);
967 let tunable2 =
968 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| -1);
969 let tunable3 =
970 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 3);
971 let tunable4 =
972 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 2);
973
974 let key = FakeAutotuneKey;
975 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
976
977 assert_eq!(plan.next().indices(), vec![1, 3, 4, 0]);
978 assert!(plan.next().is_empty());
979 }
980
981 #[test_log::test]
982 fn test_plan_batch_carries_every_group_of_an_entry() {
983 let patience = Patience {
986 min_measured: 10,
987 max_misses: 5,
988 };
989 let ordered = TuneGroup::<FakeAutotuneKey>::ordered(
990 "ordered",
991 |_| 1,
992 OrderedSettings::default().with_patience(patience),
993 );
994 let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
995
996 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
997 let tunable1 =
998 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 2);
999 let tunable2 =
1000 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
1001 let tunable3 =
1002 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
1003 let tunable4 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
1004 .group(&cutoff, |_| 2)
1005 .group(&ordered, |_| 1);
1006
1007 let key = FakeAutotuneKey;
1008 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
1009 let batch = plan.next();
1010 let entry = |index, groups: &[GroupId]| BatchEntry {
1011 index,
1012 groups: groups.to_vec(),
1013 };
1014
1015 assert_eq!(
1016 batch.entries,
1017 vec![
1018 entry(1, &[ordered.id]),
1019 entry(2, &[cutoff.id]),
1020 entry(4, &[cutoff.id, ordered.id]),
1021 entry(3, &[ordered.id]),
1022 entry(0, &[]),
1023 ]
1024 );
1025 assert_eq!(batch.patience, HashMap::from_iter([(ordered.id, patience)]));
1026 }
1027
1028 #[test_log::test]
1029 fn test_plan_entry_names_a_group_listed_twice_once() {
1030 let ordered =
1033 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1034
1035 let tunable = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
1036 .group(&ordered, |_| 2)
1037 .group(&ordered, |_| 1);
1038
1039 let key = FakeAutotuneKey;
1040 let mut plan = TunePlan::new(&key, &[tunable]);
1041
1042 assert_eq!(
1043 plan.next().entries,
1044 vec![BatchEntry {
1045 index: 0,
1046 groups: vec![ordered.id],
1047 }]
1048 );
1049 }
1050
1051 #[test_log::test]
1052 fn test_plan_ordered_group_keeps_the_group_cutoff() {
1053 let first =
1056 TuneGroup::<FakeAutotuneKey>::ordered("first", |_| 2, OrderedSettings::default());
1057 let fallback = TuneGroup::<FakeAutotuneKey>::new("fallback", |_| 1);
1058
1059 let tunable0 =
1060 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&first, |_| 1);
1061 let tunable1 =
1062 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&first, |_| 2);
1063 let tunable2 =
1064 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&fallback, |_| 1);
1065
1066 let key = FakeAutotuneKey;
1067 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
1068
1069 assert_eq!(plan.next().indices(), vec![1, 0]);
1070 assert_eq!(plan.next().indices(), vec![2]);
1071 assert!(plan.next().is_empty());
1072 }
1073
1074 #[test_log::test]
1075 fn test_plan_ordered_batch_leads_the_ungrouped_tunables() {
1076 let group =
1080 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1081
1082 let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
1083 let tunable1 =
1084 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 1);
1085 let tunable2 =
1086 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 5);
1087
1088 let key = FakeAutotuneKey;
1089 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
1090
1091 assert_eq!(plan.next().indices(), vec![2, 1, 0]);
1092 assert!(plan.next().is_empty());
1093 }
1094
1095 #[test_log::test]
1096 fn test_plan_ordered_batch_is_not_jumped_by_a_cutoff_group_beside_it() {
1097 let ordered =
1102 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1103 let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
1104
1105 let tunable0 =
1106 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 3);
1107 let tunable1 =
1108 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
1109 let tunable2 =
1110 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
1111
1112 let key = FakeAutotuneKey;
1113 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
1114
1115 assert_eq!(plan.next().indices(), vec![0, 1]);
1116 assert_eq!(plan.next().indices(), vec![2]);
1117 assert!(plan.next().is_empty());
1118 }
1119
1120 #[test_log::test]
1121 fn test_plan_cutoff_member_above_the_ordered_batch_still_leads() {
1122 let ordered =
1126 TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
1127 let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
1128
1129 let tunable0 =
1130 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 2);
1131 let tunable1 =
1132 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
1133 let tunable2 =
1134 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 3);
1135 let tunable3 =
1136 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
1137
1138 let key = FakeAutotuneKey;
1139 let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
1140
1141 assert_eq!(plan.next().indices(), vec![2]);
1142 assert_eq!(plan.next().indices(), vec![0, 3, 1]);
1143 assert!(plan.next().is_empty());
1144 }
1145
1146 #[test_log::test]
1147 fn test_plan_same_named_groups_do_not_strike_each_other_out() {
1148 let hi = TuneGroup::<FakeAutotuneKey>::new("shared", |_| 2);
1152 let lo = TuneGroup::<FakeAutotuneKey>::new("shared", |_| 1);
1153
1154 let tunable0 =
1155 Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&hi, |_| 1);
1156 let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
1157 .group(&hi, |_| -1)
1158 .group(&lo, |_| 1);
1159
1160 let key = FakeAutotuneKey;
1161 let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
1162
1163 assert_eq!(plan.next().indices(), vec![0]);
1164 assert_eq!(plan.next().indices(), vec![1]);
1165 assert!(plan.next().is_empty());
1166 }
1167
1168 fn fake_kernel(_: ()) -> Result<(), String> {
1169 Ok(())
1170 }
1171}