use super::{AutotuneError, AutotuneKey, TuneFn, TuneInputs};
use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::{string::String, sync::Arc, vec, vec::Vec};
use core::fmt::Display;
use core::sync::atomic::{AtomicU32, Ordering};
use cubecl_environment::collections::HashMap;
pub struct Tunable<K, F: TuneInputs, Output, Id = ()> {
pub(crate) function: TuneFn<F, Output>,
groups: Vec<(TuneGroup<K>, PriorityFunc<K>)>,
identity: Id,
}
impl<K, F: TuneInputs, Output: 'static> Tunable<K, F, Output> {
pub fn new<Func, Err>(name: &str, func: Func) -> Self
where
Err: Into<String> + 'static,
Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
{
Self::build(name, (), func)
}
}
impl<K, F: TuneInputs, Output: 'static, Id> Tunable<K, F, Output, Id> {
pub fn identified<Func, Err>(identity: Id, func: Func) -> Self
where
Id: Display,
Err: Into<String> + 'static,
Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
{
Self::build(&identity.to_string(), identity, func)
}
fn build<Func, Err>(name: &str, identity: Id, func: Func) -> Self
where
Err: Into<String> + 'static,
Func: for<'a> Fn(<F as TuneInputs>::At<'a>) -> Result<Output, Err> + Send + Sync + 'static,
{
let name: String = name.into();
let name_for_err = name.clone();
Self {
function: TuneFn::new(
name,
Box::new(move |inputs| {
func(inputs).map_err(|err| AutotuneError::Unknown {
name: name_for_err.to_string(),
err: err.into(),
})
}),
),
groups: Vec::new(),
identity,
}
}
pub(crate) fn identity(&self) -> &Id {
&self.identity
}
pub fn group(
mut self,
group: &TuneGroup<K>,
priority: impl Fn(&K) -> i8 + Send + Sync + 'static,
) -> Self {
self.groups.push((group.clone(), Arc::new(priority)));
self
}
}
pub struct TuneGroup<K> {
id: GroupId,
name: Arc<String>,
pub(crate) priority: PriorityFunc<K>,
order: GroupOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct GroupId(u32);
impl GroupId {
#[cfg(test)]
pub(crate) fn new(id: u32) -> Self {
Self(id)
}
}
#[derive(Debug, Clone, Copy)]
enum GroupOrder {
Cutoff,
Ordered(OrderedSettings),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct OrderedSettings {
pub patience: Option<Patience>,
}
impl OrderedSettings {
pub fn with_patience(mut self, patience: Patience) -> Self {
self.patience = Some(patience);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Patience {
pub min_measured: usize,
pub max_misses: usize,
}
impl<K> core::fmt::Debug for TuneGroup<K> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TuneGroup")
.field("id", &self.id)
.field("name", &self.name)
.finish()
}
}
impl<K> Clone for TuneGroup<K> {
fn clone(&self) -> Self {
Self {
id: self.id,
name: self.name.clone(),
priority: self.priority.clone(),
order: self.order,
}
}
}
impl<K> TuneGroup<K> {
pub fn new(name: &str, f: impl Fn(&K) -> i8 + Send + Sync + 'static) -> Self {
Self::build(name, f, GroupOrder::Cutoff)
}
pub fn ordered(
name: &str,
f: impl Fn(&K) -> i8 + Send + Sync + 'static,
settings: OrderedSettings,
) -> Self {
Self::build(name, f, GroupOrder::Ordered(settings))
}
fn build(name: &str, f: impl Fn(&K) -> i8 + Send + Sync + 'static, order: GroupOrder) -> Self {
let id = GroupId(GROUP_COUNTER.fetch_add(1, Ordering::Relaxed));
Self {
id,
name: Arc::new(name.into()),
priority: Arc::new(f),
order,
}
}
fn is_ordered(&self) -> bool {
matches!(self.order, GroupOrder::Ordered(_))
}
fn patience(&self) -> Option<Patience> {
match self.order {
GroupOrder::Ordered(settings) => settings.patience,
GroupOrder::Cutoff => None,
}
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct Batch {
pub(crate) entries: Vec<BatchEntry>,
pub(crate) patience: HashMap<GroupId, Patience>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct BatchEntry {
pub(crate) index: usize,
pub(crate) groups: Vec<GroupId>,
}
impl Batch {
pub(crate) fn unconditional(indices: impl IntoIterator<Item = usize>) -> Self {
let mut batch = Self::default();
for index in indices {
batch.push_unconditional(index);
}
batch
}
pub(crate) fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub(crate) fn indices(&self) -> Vec<usize> {
self.entries.iter().map(|entry| entry.index).collect()
}
fn push(&mut self, planned: &Planned) {
match self.entries.iter_mut().find(|e| e.index == planned.index) {
Some(entry) if entry.groups.contains(&planned.group) => {}
Some(entry) => entry.groups.push(planned.group),
None => self.entries.push(BatchEntry {
index: planned.index,
groups: vec![planned.group],
}),
}
}
fn push_unconditional(&mut self, index: usize) {
self.entries.push(BatchEntry {
index,
groups: Vec::new(),
});
}
}
#[derive(Debug, Clone)]
pub(crate) struct TunePlan {
priorities: Vec<i8>,
no_groups: Vec<usize>,
groups: HashMap<i8, GroupPlan>,
returned: Vec<usize>,
patience: HashMap<GroupId, Patience>,
#[cfg(persistence)]
placements: Vec<Vec<Placement>>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Placement {
pub group: String,
pub ordered: bool,
pub group_priority: i8,
pub priority: i8,
}
#[derive(Default, Debug, Clone)]
struct GroupPlan {
priorities: Vec<i8>,
indices: HashMap<i8, Vec<Planned>>,
}
#[derive(Debug, Clone)]
struct Planned {
index: usize,
group: GroupId,
priority: i8,
}
#[derive(Debug)]
struct Cleanup {
groups: Vec<i8>,
tunables: Vec<(i8, i8)>,
skipped: bool,
}
impl TunePlan {
pub fn new<K: AutotuneKey, F: TuneInputs, Out, Id>(
key: &K,
tunables: &[Tunable<K, F, Out, Id>],
) -> Self {
let mut priorities = Vec::<i8>::new();
let mut no_groups = Vec::new();
let mut groups = HashMap::<i8, GroupPlan>::new();
let mut priced = Vec::new();
let mut ordered_levels = HashMap::<GroupId, i8>::new();
let mut patience = HashMap::<GroupId, Patience>::new();
#[cfg(persistence)]
let mut placements = vec![Vec::new(); tunables.len()];
for (index, tunable) in tunables.iter().enumerate() {
if tunable.groups.is_empty() {
no_groups.push(index);
continue;
}
for (group, within_group_priority_fn) in tunable.groups.iter() {
if let Some(group_patience) = group.patience() {
patience.insert(group.id, group_patience);
}
let group_priority = (group.priority)(key);
let priority = within_group_priority_fn(key);
#[cfg(persistence)]
placements[index].push(Placement {
group: group.name.to_string(),
ordered: group.is_ordered(),
group_priority,
priority,
});
if group.is_ordered() && priority >= 0 {
let level = ordered_levels.entry(group.id).or_insert(priority);
*level = (*level).max(priority);
}
priced.push((index, group, group_priority, priority));
}
}
for (index, group, group_priority, priority) in priced {
if !priorities.contains(&group_priority) {
priorities.push(group_priority);
}
let group_plan = match groups.get_mut(&group_priority) {
Some(val) => val,
None => {
groups.insert(group_priority, GroupPlan::default());
groups.get_mut(&group_priority).unwrap()
}
};
let level = match group.is_ordered() && priority >= 0 {
true => ordered_levels[&group.id],
false => priority,
};
let planned = Planned {
index,
group: group.id,
priority,
};
if group_plan.priorities.contains(&level) {
group_plan.indices.get_mut(&level).unwrap().push(planned);
} else {
group_plan.priorities.push(level);
group_plan.indices.insert(level, vec![planned]);
}
}
priorities.sort();
for group in groups.iter_mut() {
group.1.priorities.sort();
}
Self {
priorities,
no_groups,
groups,
returned: Vec::new(),
patience,
#[cfg(persistence)]
placements,
}
}
#[cfg(persistence)]
pub(crate) fn account(&self, names: &[&str]) -> Vec<super::record::PlannedCandidate> {
let mut plan = self.clone();
let batches = core::iter::from_fn(|| {
Some(plan.next())
.filter(|batch| !batch.is_empty())
.map(|batch| batch.indices())
});
let mut account: Vec<_> = batches
.enumerate()
.flat_map(|(batch, indices)| indices.into_iter().map(move |index| (index, Some(batch))))
.collect();
let excluded =
(0..names.len()).filter(|index| !account.iter().any(|(seen, _)| seen == index));
account.extend(excluded.map(|index| (index, None)).collect::<Vec<_>>());
account
.into_iter()
.map(|(index, batch)| super::record::PlannedCandidate {
name: names[index].to_string(),
index,
batch,
groups: self.placements[index].clone(),
})
.collect()
}
pub(crate) fn next(&mut self) -> Batch {
let ungrouped = core::mem::take(&mut self.no_groups);
let priority = self.priorities.last();
let priority = match priority {
Some(val) => *val,
None => return Batch::unconditional(ungrouped),
};
let mut batch = Batch {
entries: Vec::new(),
patience: self.patience.clone(),
};
let (group_indices, cleanup) = self.group_plan_next(priority);
let skipped = cleanup.skipped || priority < 0;
let mut all_skip = true;
self.cleanup(cleanup);
if priority >= 0 {
for planned in group_indices {
if self.returned.contains(&planned.index) {
continue;
}
all_skip = false;
batch.push(&planned);
}
}
for index in ungrouped {
batch.push_unconditional(index);
}
if batch.is_empty() && (skipped || all_skip) {
self.next()
} else {
for entry in batch.entries.iter() {
self.returned.push(entry.index);
}
batch
}
}
fn cleanup(&mut self, cleanup: Cleanup) {
for group_p in cleanup.groups {
let index = self
.priorities
.iter()
.enumerate()
.find(|p| *p.1 == group_p)
.unwrap();
self.priorities.remove(index.0);
self.groups.remove(&group_p);
}
for (group_p, tunable_p) in cleanup.tunables {
if let Some(group) = self.groups.get_mut(&group_p) {
let index = group
.priorities
.iter()
.enumerate()
.find(|p| *p.1 == tunable_p)
.unwrap();
group.priorities.remove(index.0);
group.indices.remove(&tunable_p);
}
}
}
fn group_plan_next(&mut self, priority: i8) -> (Vec<Planned>, Cleanup) {
let group_plan = self.groups.get_mut(&priority).expect("To be filled");
let within_group_prio = group_plan.priorities.pop().unwrap();
let mut next_indices = group_plan.indices.remove(&within_group_prio).unwrap();
next_indices.sort_by_key(|planned| (core::cmp::Reverse(planned.priority), planned.index));
let mut cleanup_groups = Vec::new();
let mut cleanup_tunables = Vec::new();
for (pg, group) in self.groups.iter_mut() {
let mut num_empty_tunables = 0;
let num_tunables = group.priorities.len();
for (pt, indices) in group.indices.iter_mut() {
for n in &next_indices {
let entry = indices
.iter()
.position(|p| p.index == n.index && p.group == n.group);
if let Some(entry) = entry {
indices.remove(entry);
}
}
if indices.is_empty() {
num_empty_tunables += 1;
cleanup_tunables.push((*pg, *pt));
}
}
if num_empty_tunables == num_tunables {
cleanup_groups.push(*pg);
}
}
if within_group_prio < 0 {
next_indices.clear();
}
(
next_indices,
Cleanup {
groups: cleanup_groups,
tunables: cleanup_tunables,
skipped: within_group_prio < 0,
},
)
}
}
type PriorityFunc<K> = Arc<dyn Fn(&K) -> i8 + Send + Sync>;
static GROUP_COUNTER: AtomicU32 = AtomicU32::new(0);
#[cfg(test)]
mod tests {
use core::fmt::Display;
use serde::{Deserialize, Serialize};
use super::*;
#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
struct FakeAutotuneKey;
impl Display for FakeAutotuneKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("FakeAutotuneKey")
}
}
impl AutotuneKey for FakeAutotuneKey {}
#[test_log::test]
fn test_plan_order() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
assert_eq!(plan.next().indices(), vec![2, 0]);
assert_eq!(plan.next().indices(), vec![1]);
assert_eq!(plan.next().indices(), vec![3]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_order_multi_groups_same_priority() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let group2 = TuneGroup::<FakeAutotuneKey>::new("group2", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
let tunable4 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group2, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
assert_eq!(plan.next().indices(), vec![2, 0]);
assert_eq!(plan.next().indices(), vec![1]);
assert_eq!(plan.next().indices(), vec![3, 4]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_order_tunable_multiple_groups() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
.group(&group0, |_| 1)
.group(&group1, |_| 2);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 3);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
assert_eq!(plan.next().indices(), vec![3, 0]);
assert_eq!(plan.next().indices(), vec![1]);
assert_eq!(plan.next().indices(), vec![2]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_negative_priority() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
assert_eq!(plan.next().indices(), vec![2, 0]);
assert_eq!(plan.next().indices(), vec![3]);
assert!(plan.next().is_empty());
}
#[cfg(persistence)]
#[test_log::test]
fn test_plan_account_lists_every_tunable_in_release_order() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
let key = FakeAutotuneKey;
let plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
let account = plan.account(&["t0", "t1", "t2", "t3"]);
let order: Vec<_> = account
.iter()
.map(|planned| (planned.name.as_str(), planned.batch))
.collect();
assert_eq!(
order,
vec![
("t2", Some(0)),
("t0", Some(0)),
("t3", Some(1)),
("t1", None)
]
);
assert_eq!(
account[0].groups,
vec![Placement {
group: "group0".into(),
ordered: false,
group_priority: 2,
priority: 2,
}]
);
assert!(account[1].groups.is_empty());
let mut plan = plan;
assert_eq!(plan.next().indices(), vec![2, 0]);
}
#[test_log::test]
fn test_plan_no_group() {
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
assert_eq!(plan.next().indices(), vec![0, 1]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_falls_through_when_all_group_tunables_fail() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
let mut all_returned: Vec<usize> = Vec::new();
loop {
let batch = plan.next();
if batch.is_empty() {
break;
}
all_returned.extend(batch.indices());
}
assert_eq!(all_returned, vec![1, 0, 3, 2]);
}
#[test_log::test]
fn test_plan_single_group_exhausts_all_intra_priorities() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 0);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 3);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
assert_eq!(plan.next().indices(), vec![2]);
assert_eq!(plan.next().indices(), vec![1]);
assert_eq!(plan.next().indices(), vec![0]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_all_negative_group_advances_to_next_group() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -1);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| -2);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
assert_eq!(plan.next().indices(), vec![2]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_no_group_tunables_only_emitted_once_even_on_failures() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 2);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group1, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
assert_eq!(plan.next().indices(), vec![1, 0]);
assert_eq!(plan.next().indices(), vec![2]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_multi_group_tunable_not_duplicated_across_failed_groups() {
let group0 = TuneGroup::<FakeAutotuneKey>::new("group0", |_| 1);
let group1 = TuneGroup::<FakeAutotuneKey>::new("group1", |_| 2);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
.group(&group0, |_| 1)
.group(&group1, |_| 1);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group0, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
let mut all_returned: Vec<usize> = Vec::new();
loop {
let batch = plan.next();
if batch.is_empty() {
break;
}
all_returned.extend(batch.indices());
}
assert_eq!(all_returned, vec![0, 1]);
}
#[test_log::test]
fn test_plan_recurses_when_batch_is_fully_already_returned() {
let group_hi = TuneGroup::<FakeAutotuneKey>::new("hi", |_| 2);
let group_lo = TuneGroup::<FakeAutotuneKey>::new("lo", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
.group(&group_hi, |_| 1)
.group(&group_lo, |_| 2);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group_lo, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
assert_eq!(plan.next().indices(), vec![0]);
assert_eq!(plan.next().indices(), vec![1]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_ordered_group_is_one_batch_best_first() {
let group =
TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 1);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 3);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| -1);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 3);
let tunable4 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
assert_eq!(plan.next().indices(), vec![1, 3, 4, 0]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_batch_carries_every_group_of_an_entry() {
let patience = Patience {
min_measured: 10,
max_misses: 5,
};
let ordered = TuneGroup::<FakeAutotuneKey>::ordered(
"ordered",
|_| 1,
OrderedSettings::default().with_patience(patience),
);
let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 2);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
let tunable4 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
.group(&cutoff, |_| 2)
.group(&ordered, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3, tunable4]);
let batch = plan.next();
let entry = |index, groups: &[GroupId]| BatchEntry {
index,
groups: groups.to_vec(),
};
assert_eq!(
batch.entries,
vec![
entry(1, &[ordered.id]),
entry(2, &[cutoff.id]),
entry(4, &[cutoff.id, ordered.id]),
entry(3, &[ordered.id]),
entry(0, &[]),
]
);
assert_eq!(batch.patience, HashMap::from_iter([(ordered.id, patience)]));
}
#[test_log::test]
fn test_plan_entry_names_a_group_listed_twice_once() {
let ordered =
TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
let tunable = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
.group(&ordered, |_| 2)
.group(&ordered, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable]);
assert_eq!(
plan.next().entries,
vec![BatchEntry {
index: 0,
groups: vec![ordered.id],
}]
);
}
#[test_log::test]
fn test_plan_ordered_group_keeps_the_group_cutoff() {
let first =
TuneGroup::<FakeAutotuneKey>::ordered("first", |_| 2, OrderedSettings::default());
let fallback = TuneGroup::<FakeAutotuneKey>::new("fallback", |_| 1);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&first, |_| 1);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&first, |_| 2);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&fallback, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
assert_eq!(plan.next().indices(), vec![1, 0]);
assert_eq!(plan.next().indices(), vec![2]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_ordered_batch_leads_the_ungrouped_tunables() {
let group =
TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
let tunable0 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&group, |_| 5);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
assert_eq!(plan.next().indices(), vec![2, 1, 0]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_ordered_batch_is_not_jumped_by_a_cutoff_group_beside_it() {
let ordered =
TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 3);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2]);
assert_eq!(plan.next().indices(), vec![0, 1]);
assert_eq!(plan.next().indices(), vec![2]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_cutoff_member_above_the_ordered_batch_still_leads() {
let ordered =
TuneGroup::<FakeAutotuneKey>::ordered("ordered", |_| 1, OrderedSettings::default());
let cutoff = TuneGroup::<FakeAutotuneKey>::new("cutoff", |_| 1);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 2);
let tunable1 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&ordered, |_| 1);
let tunable2 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 3);
let tunable3 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&cutoff, |_| 2);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1, tunable2, tunable3]);
assert_eq!(plan.next().indices(), vec![2]);
assert_eq!(plan.next().indices(), vec![0, 3, 1]);
assert!(plan.next().is_empty());
}
#[test_log::test]
fn test_plan_same_named_groups_do_not_strike_each_other_out() {
let hi = TuneGroup::<FakeAutotuneKey>::new("shared", |_| 2);
let lo = TuneGroup::<FakeAutotuneKey>::new("shared", |_| 1);
let tunable0 =
Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel).group(&hi, |_| 1);
let tunable1 = Tunable::<FakeAutotuneKey, (), ()>::new("fake", fake_kernel)
.group(&hi, |_| -1)
.group(&lo, |_| 1);
let key = FakeAutotuneKey;
let mut plan = TunePlan::new(&key, &[tunable0, tunable1]);
assert_eq!(plan.next().indices(), vec![0]);
assert_eq!(plan.next().indices(), vec![1]);
assert!(plan.next().is_empty());
}
fn fake_kernel(_: ()) -> Result<(), String> {
Ok(())
}
}