use fuel_ir::dispatch::{OpKind, SizeClass};
use fuel_ir::probe::BackendId;
use fuel_ir::{DType, DeviceLocation};
use smallvec::SmallVec;
use super::candidate::Candidate;
use super::cost_vector::CostVector;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DecisionContext {
pub op: OpKind,
pub principal_dtype: DType,
pub size_class: SizeClass,
}
pub const KEEP_PER_DEVICE: usize = 32;
#[derive(Clone, Debug)]
pub struct AlternativeSet {
candidates: SmallVec<[Candidate; 4]>,
context: Option<DecisionContext>,
}
impl AlternativeSet {
pub fn empty() -> Self {
Self {
candidates: SmallVec::new(),
context: None,
}
}
pub fn from_candidates(candidates: Vec<Candidate>) -> Self {
Self {
candidates: SmallVec::from_vec(candidates),
context: None,
}
}
pub fn set_context(&mut self, ctx: DecisionContext) {
self.context = Some(ctx);
}
pub fn context(&self) -> Option<&DecisionContext> {
self.context.as_ref()
}
pub fn push(&mut self, c: Candidate) {
self.candidates.push(c);
}
pub fn len(&self) -> usize {
self.candidates.len()
}
pub fn is_empty(&self) -> bool {
self.candidates.is_empty()
}
pub fn alternatives(&self) -> &[Candidate] {
&self.candidates
}
pub fn retain_indices(&mut self, indices: &[usize]) {
debug_assert!(
indices.windows(2).all(|w| w[0] < w[1]),
"retain_indices: input must be sorted strictly ascending; got {indices:?}",
);
debug_assert!(
indices.iter().all(|&i| i < self.candidates.len()),
"retain_indices: out-of-range index in {indices:?} (len={})",
self.candidates.len(),
);
let mut iter = indices.iter().copied().peekable();
let mut idx = 0;
self.candidates.retain(|_| {
let keep = matches!(iter.peek(), Some(&i) if i == idx);
if keep {
iter.next();
}
idx += 1;
keep
});
}
pub fn retain_per_device_frontier(&mut self, keep: usize) {
let keep = keep.max(1);
let n = self.candidates.len();
if n == 0 {
return;
}
let winner_device = self.candidates[0].device;
let vectors: Vec<CostVector> = self
.candidates
.iter()
.map(CostVector::from_candidate)
.collect();
let mut device_buckets: Vec<(DeviceLocation, Vec<usize>)> = Vec::new();
for i in 0..n {
let dev = self.candidates[i].device;
if let Some(entry) = device_buckets.iter_mut().find(|(d, _)| *d == dev) {
entry.1.push(i);
} else {
device_buckets.push((dev, vec![i]));
}
}
let mut keep_set: Vec<usize> = Vec::with_capacity(n);
for (_dev, bucket) in &device_buckets {
let mut frontier: Vec<usize> = bucket
.iter()
.copied()
.filter(|&i| {
!bucket
.iter()
.any(|&j| j != i && vectors[j].dominates(&vectors[i]))
})
.collect();
let mut backends_in_bucket: Vec<BackendId> = Vec::new();
for &i in bucket {
let b = self.candidates[i].backend;
if !backends_in_bucket.contains(&b) {
backends_in_bucket.push(b);
}
}
for &b in &backends_in_bucket {
let on_frontier = frontier.iter().any(|&i| self.candidates[i].backend == b);
if !on_frontier
&& let Some(&best) = bucket
.iter()
.filter(|&&i| self.candidates[i].backend == b)
.min_by_key(|&&i| vectors[i].total_order_key())
{
frontier.push(best);
}
}
if !self.candidates.is_empty() && _dev == &winner_device && !frontier.contains(&0) {
frontier.push(0);
}
if frontier.len() > keep {
crowding_cap(
&mut frontier,
&vectors,
&self.candidates,
keep,
winner_device,
);
}
keep_set.extend(frontier);
}
keep_set.sort_unstable();
keep_set.dedup();
debug_assert!(
keep_set.contains(&0),
"retain_per_device_frontier: arm-0 winner must be retained",
);
debug_assert!(
keep_set.len() <= keep.saturating_mul(device_buckets.len()),
"retain_per_device_frontier: total {} exceeds keep×devices = {}×{}",
keep_set.len(),
keep,
device_buckets.len(),
);
self.retain_indices(&keep_set);
self.rank_by_cost();
}
pub fn rank_by_cost(&mut self) {
self.candidates
.sort_by_key(|c| CostVector::from_candidate(c).total_order_key());
}
pub fn rank_by_composite_cost(&mut self) {
self.rank_by_cost();
}
pub fn set_static_cost(&mut self, index: usize, cost: crate::fused::CostEstimate) {
debug_assert!(
index < self.candidates.len(),
"set_static_cost: index {index} out of range (len={})",
self.candidates.len(),
);
self.candidates[index].static_cost = cost;
}
pub fn set_inbound_transfer_ns(&mut self, index: usize, ns: u64) {
debug_assert!(
index < self.candidates.len(),
"set_inbound_transfer_ns: index {index} out of range (len={})",
self.candidates.len(),
);
self.candidates[index].inbound_transfer_ns = ns;
}
pub fn winner(&self) -> Option<&Candidate> {
self.candidates.first()
}
}
fn crowding_cap(
frontier: &mut Vec<usize>,
vectors: &[CostVector],
candidates: &[Candidate],
keep: usize,
winner_device: DeviceLocation,
) {
while frontier.len() > keep {
let dist = crowding_distances(frontier, vectors);
let winner_present = frontier.contains(&0);
let mut backend_counts: std::collections::HashMap<BackendId, usize> =
std::collections::HashMap::new();
for &i in frontier.iter() {
*backend_counts.entry(candidates[i].backend).or_insert(0) += 1;
}
let mut victim: Option<(usize, f64)> = None; for (pos, &i) in frontier.iter().enumerate() {
let is_winner = i == 0 && winner_present && candidates[i].device == winner_device;
let is_sole_backend = backend_counts
.get(&candidates[i].backend)
.copied()
.unwrap_or(0)
<= 1;
if is_winner || is_sole_backend {
continue;
}
let d = dist[pos];
match victim {
Some((_, best)) if best <= d => {}
_ => victim = Some((pos, d)),
}
}
match victim {
Some((pos, _)) => {
frontier.remove(pos);
}
None => break,
}
}
}
fn crowding_distances(frontier: &[usize], vectors: &[CostVector]) -> Vec<f64> {
let m = frontier.len();
let mut dist = vec![0.0f64; m];
if m <= 2 {
return vec![f64::INFINITY; m];
}
let axes: [fn(&CostVector) -> f64; 5] = [
|v| v.time as f64,
|v| v.memory.host_ram_bytes as f64,
|v| v.memory.device_vram_bytes as f64,
|v| v.precision as f64,
|v| v.accuracy.rank() as f64,
];
for axis in axes {
let mut order: Vec<usize> = (0..m).collect();
order.sort_by(|&a, &b| {
let va = axis(&vectors[frontier[a]]);
let vb = axis(&vectors[frontier[b]]);
va.partial_cmp(&vb).unwrap_or(std::cmp::Ordering::Equal)
});
let min = axis(&vectors[frontier[order[0]]]);
let max = axis(&vectors[frontier[order[m - 1]]]);
let range = max - min;
dist[order[0]] = f64::INFINITY;
dist[order[m - 1]] = f64::INFINITY;
if range <= 0.0 {
continue;
}
for k in 1..(m - 1) {
let prev = axis(&vectors[frontier[order[k - 1]]]);
let next = axis(&vectors[frontier[order[k + 1]]]);
dist[order[k]] += (next - prev) / range;
}
}
dist
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fused::{CostEstimate, PrecisionGuarantee};
use crate::kernel::{KernelCaps, OpParams};
use fuel_ir::probe::BackendId;
use fuel_ir::{DeviceLocation, Layout, Result};
use fuel_memory::Storage;
use std::sync::{Arc, RwLock};
fn noop(
_i: &[Arc<RwLock<Storage>>],
_o: &mut [Arc<RwLock<Storage>>],
_l: &[Layout],
_p: &OpParams,
) -> Result<()> {
Ok(())
}
fn dummy_candidate(flops: u64) -> Candidate {
Candidate {
kernel: noop,
caps: KernelCaps::empty(),
backend: BackendId::Cpu,
device: DeviceLocation::Cpu,
precision: PrecisionGuarantee::PRIMITIVE_DETERMINISTIC_CPU,
static_cost: CostEstimate {
flops,
bytes_moved: 0,
kernel_overhead_ns: 0,
},
inbound_transfer_ns: 0,
op_params: OpParams::None,
coupling: Vec::new(),
kernel_source: "",
kernel_revision_hash: 0,
}
}
fn cand(
device: DeviceLocation,
backend: BackendId,
flops: u64,
bytes: u64,
precision: PrecisionGuarantee,
) -> Candidate {
Candidate {
backend,
device,
precision,
static_cost: CostEstimate {
flops,
bytes_moved: bytes,
kernel_overhead_ns: 0,
},
..dummy_candidate(0)
}
}
fn cuda(id: usize) -> DeviceLocation {
DeviceLocation::Cuda { gpu_id: id }
}
#[test]
fn rank_by_cost_preserves_single_precision_winner() {
use super::super::cost::{composite_ns, default_backend_rates};
let mk = |flops: u64, bytes: u64, overhead: u32, inbound: u64| Candidate {
inbound_transfer_ns: inbound,
static_cost: CostEstimate {
flops,
bytes_moved: bytes,
kernel_overhead_ns: overhead,
},
..dummy_candidate(0)
};
let cands = vec![
mk(300, 0, 0, 0),
mk(100, 0, 0, 50), mk(200, 800, 0, 0), mk(0, 4000, 10, 90), mk(120, 0, 0, 0),
];
let mut expected = cands.clone();
expected.sort_by_key(|c| {
let (cr, bw) = default_backend_rates(c.backend);
composite_ns(&c.static_cost, cr, bw).saturating_add(c.inbound_transfer_ns)
});
let expected_flops: Vec<u64> = expected.iter().map(|c| c.static_cost.flops).collect();
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
let got_flops: Vec<u64> = set
.alternatives()
.iter()
.map(|c| c.static_cost.flops)
.collect();
assert_eq!(
got_flops, expected_flops,
"single-precision cost-vector rank matches the old composite scalar order",
);
}
#[test]
fn rank_includes_inbound_transfer_term() {
let mut s = AlternativeSet::from_candidates(vec![
Candidate {
inbound_transfer_ns: 5_000,
..dummy_candidate(100)
},
dummy_candidate(200),
]);
s.rank_by_composite_cost();
assert_eq!(
s.winner().unwrap().static_cost.flops,
200,
"200 ns total beats 100 ns kernel + 5 µs transfer",
);
}
#[test]
fn empty_has_no_winner() {
let s = AlternativeSet::empty();
assert!(s.is_empty());
assert_eq!(s.len(), 0);
assert!(s.winner().is_none());
}
#[test]
fn push_grows_and_first_is_winner() {
let mut s = AlternativeSet::empty();
s.push(dummy_candidate(10));
s.push(dummy_candidate(20));
assert_eq!(s.len(), 2);
assert_eq!(s.winner().unwrap().static_cost.flops, 10);
}
#[test]
fn from_candidates_seeds_and_preserves_order() {
let s = AlternativeSet::from_candidates(vec![
dummy_candidate(1),
dummy_candidate(2),
dummy_candidate(3),
]);
assert_eq!(s.len(), 3);
let flops: Vec<u64> = s
.alternatives()
.iter()
.map(|c| c.static_cost.flops)
.collect();
assert_eq!(flops, vec![1, 2, 3]);
}
#[test]
fn retain_indices_keeps_selected_entries() {
let mut s = AlternativeSet::from_candidates((0..5).map(dummy_candidate).collect());
s.retain_indices(&[0, 2, 4]);
let flops: Vec<u64> = s
.alternatives()
.iter()
.map(|c| c.static_cost.flops)
.collect();
assert_eq!(flops, vec![0, 2, 4]);
}
#[test]
fn retain_indices_empty_clears_set() {
let mut s = AlternativeSet::from_candidates(vec![dummy_candidate(1), dummy_candidate(2)]);
s.retain_indices(&[]);
assert!(s.is_empty());
}
#[test]
fn retain_indices_keep_all_is_identity() {
let mut s = AlternativeSet::from_candidates(vec![
dummy_candidate(1),
dummy_candidate(2),
dummy_candidate(3),
]);
s.retain_indices(&[0, 1, 2]);
assert_eq!(s.len(), 3);
}
#[test]
#[should_panic(expected = "sorted strictly ascending")]
fn retain_indices_unsorted_panics_in_debug() {
let mut s = AlternativeSet::from_candidates(vec![dummy_candidate(1), dummy_candidate(2)]);
s.retain_indices(&[1, 0]);
}
#[test]
#[should_panic(expected = "out-of-range")]
fn retain_indices_out_of_range_panics_in_debug() {
let mut s = AlternativeSet::from_candidates(vec![dummy_candidate(1)]);
s.retain_indices(&[5]);
}
#[test]
fn context_round_trips_and_survives_mutation() {
use fuel_ir::DType;
use fuel_ir::dispatch::{OpKind, SizeClass};
let mut s = AlternativeSet::from_candidates(vec![
dummy_candidate(1),
dummy_candidate(2),
dummy_candidate(3),
]);
assert!(s.context().is_none(), "fresh sets carry no context");
let ctx = DecisionContext {
op: OpKind::MatMul,
principal_dtype: DType::F32,
size_class: SizeClass(16),
};
s.set_context(ctx);
assert_eq!(s.context(), Some(&ctx));
s.retain_indices(&[0, 2]);
assert_eq!(s.context(), Some(&ctx), "context survives retain",);
}
#[test]
fn retains_per_device_frontier_not_global_top_n() {
let cands = vec![
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
100,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
200,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
300,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
cuda(0),
BackendId::Cuda,
10_000,
0,
PrecisionGuarantee::REFERENCE,
),
];
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(KEEP_PER_DEVICE);
let has_cuda = set
.alternatives()
.iter()
.any(|c| matches!(c.device, DeviceLocation::Cuda { .. }));
let has_cpu = set
.alternatives()
.iter()
.any(|c| c.device == DeviceLocation::Cpu);
assert!(
has_cuda,
"slow CUDA path must NOT be stranded by a global top-N"
);
assert!(has_cpu, "CPU path survives");
assert_eq!(set.winner().unwrap().static_cost.flops, 100);
}
#[test]
fn drops_within_device_dominated_candidate() {
let cands = vec![
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
100,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
500,
0,
PrecisionGuarantee::UNAUDITED,
),
];
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(KEEP_PER_DEVICE);
assert_eq!(set.len(), 1, "dominated same-backend candidate dropped");
assert_eq!(set.winner().unwrap().static_cost.flops, 100);
}
#[test]
fn crowding_caps_oversized_frontier_keeping_spread() {
let keep = 4usize;
let mut cands = Vec::new();
let n = 10u64;
for k in 0..n {
let rel = 10f64.powi(-(2 + k as i32)); let p = PrecisionGuarantee {
bit_stable_on_same_hardware: true,
max_ulp: None,
max_relative: Some(rel),
max_absolute: None,
notes: "frontier",
};
cands.push(cand(
DeviceLocation::Cpu,
BackendId::Cpu,
100 + k * 100,
0,
p,
));
}
let fastest_time_flops = 100; let slowest_time_flops = 100 + (n - 1) * 100;
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(keep);
assert_eq!(set.len(), keep, "capped to exactly keep");
let times: Vec<u64> = set
.alternatives()
.iter()
.map(|c| c.static_cost.flops)
.collect();
assert!(
times.contains(&fastest_time_flops),
"fastest (time-axis min) boundary survives crowding; got {times:?}",
);
assert!(
times.contains(&slowest_time_flops),
"slowest (time-axis max) boundary survives crowding; got {times:?}",
);
}
#[test]
fn at_least_one_per_device_and_total_bounded() {
let keep = 3usize;
let mut cands = Vec::new();
for d in 0..3usize {
let dev = if d == 0 { DeviceLocation::Cpu } else { cuda(d) };
let backend = if d == 0 {
BackendId::Cpu
} else {
BackendId::Cuda
};
for k in 0..6u64 {
let rel = 10f64.powi(-(2 + (6 - k) as i32));
let p = PrecisionGuarantee {
bit_stable_on_same_hardware: true,
max_ulp: None,
max_relative: Some(rel),
max_absolute: None,
notes: "x",
};
cands.push(cand(dev, backend, 100 + k * 50 + d as u64, 0, p));
}
}
let n_devices = 3;
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(keep);
for d in 0..3usize {
let dev = if d == 0 { DeviceLocation::Cpu } else { cuda(d) };
let count = set
.alternatives()
.iter()
.filter(|c| c.device == dev)
.count();
assert!(count >= 1, "device {dev:?} must keep ≥1 path");
}
assert!(
set.len() <= keep * n_devices,
"total {} ≤ keep×devices = {}",
set.len(),
keep * n_devices,
);
}
#[test]
fn never_strands_last_device_backend() {
let cands = vec![
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
100,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
DeviceLocation::Vulkan { gpu_id: 0 },
BackendId::Vulkan,
9_999,
0,
PrecisionGuarantee::UNAUDITED,
),
];
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(KEEP_PER_DEVICE);
let has_vulkan = set
.alternatives()
.iter()
.any(|c| matches!(c.device, DeviceLocation::Vulkan { .. }));
assert!(
has_vulkan,
"last Vulkan (device, backend) must never be stranded"
);
}
#[test]
fn never_strands_globally_dominated_sole_path() {
let cands = vec![
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
100,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
DeviceLocation::Metal { gpu_id: 0 },
BackendId::Metal,
9_999,
999_999,
PrecisionGuarantee::UNAUDITED,
),
];
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(KEEP_PER_DEVICE);
let backends: Vec<BackendId> = set.alternatives().iter().map(|c| c.backend).collect();
assert!(backends.contains(&BackendId::Cpu), "CPU dominator kept");
assert!(
backends.contains(&BackendId::Metal),
"dominated-but-sole Metal (device, backend) never stranded; got {backends:?}",
);
}
#[test]
fn arm0_winner_unchanged_by_retention() {
let cands = vec![
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
300,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
cuda(0),
BackendId::Cuda,
100,
0,
PrecisionGuarantee::REFERENCE,
),
cand(
DeviceLocation::Cpu,
BackendId::Cpu,
200,
0,
PrecisionGuarantee::REFERENCE,
),
];
let mut b1 = AlternativeSet::from_candidates(cands.clone());
b1.rank_by_cost();
let b1_winner = (b1.winner().unwrap().backend, b1.winner().unwrap().device);
let mut set = AlternativeSet::from_candidates(cands);
set.rank_by_cost();
set.retain_per_device_frontier(KEEP_PER_DEVICE);
let b2_winner = (set.winner().unwrap().backend, set.winner().unwrap().device);
assert_eq!(
b1_winner, b2_winner,
"arm-0 winner unchanged by B2 retention"
);
assert_eq!(set.winner().unwrap().static_cost.flops, 100);
}
#[test]
fn single_candidate_passthrough() {
let mut set = AlternativeSet::from_candidates(vec![dummy_candidate(42)]);
set.rank_by_cost();
set.retain_per_device_frontier(KEEP_PER_DEVICE);
assert_eq!(set.len(), 1);
assert_eq!(set.winner().unwrap().static_cost.flops, 42);
}
}