use std::sync::Arc;
use fuel_ir::DType;
use fuel_ir::dispatch::{OpKind, SizeClass};
use super::{
AlternativeSet, Candidate, JudgeOracle, RuntimeSelector, composite_ns, default_backend_rates,
};
#[derive(Clone)]
pub struct JudgeAwareSelector {
judge: Arc<dyn JudgeOracle>,
op: OpKind,
dtype: DType,
size_class: SizeClass,
}
impl JudgeAwareSelector {
pub fn new(
judge: Arc<dyn JudgeOracle>,
op: OpKind,
dtype: DType,
size_class: SizeClass,
) -> Self {
Self {
judge,
op,
dtype,
size_class,
}
}
pub fn op(&self) -> OpKind {
self.op
}
pub fn dtype(&self) -> DType {
self.dtype
}
pub fn size_class(&self) -> SizeClass {
self.size_class
}
fn measured_latency(&self, c: &Candidate) -> Option<u64> {
self.judge.measured_latency_ns(
self.op,
self.dtype,
self.size_class,
c.backend,
c.kernel_source,
c.kernel_revision_hash,
)
}
}
impl std::fmt::Debug for JudgeAwareSelector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JudgeAwareSelector")
.field("judge", &"<dyn JudgeOracle>")
.field("op", &self.op)
.field("dtype", &self.dtype)
.field("size_class", &self.size_class)
.finish()
}
}
impl RuntimeSelector for JudgeAwareSelector {
fn select<'a>(&self, set: &'a AlternativeSet) -> Option<&'a Candidate> {
let alts = set.alternatives();
if alts.is_empty() {
return None;
}
let (best_idx, _) = alts
.iter()
.enumerate()
.map(|(i, c)| {
let score = self
.measured_latency(c)
.unwrap_or_else(|| {
let (cr, bw) = default_backend_rates(c.backend);
composite_ns(&c.static_cost, cr, bw)
})
.saturating_add(c.inbound_transfer_ns);
(i, score)
})
.min_by_key(|&(idx, score)| (score, idx))
.expect("non-empty after the early return");
alts.get(best_idx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fused::{CostEstimate, PrecisionGuarantee};
use crate::kernel::{KernelCaps, OpParams};
use crate::ranker::HashMapJudge;
use fuel_ir::probe::BackendId;
use fuel_ir::{DeviceLocation, Layout, Result};
use fuel_memory::Storage;
use std::sync::{Arc, RwLock};
fn noop_kernel(
_i: &[Arc<RwLock<Storage>>],
_o: &mut [Arc<RwLock<Storage>>],
_l: &[Layout],
_p: &OpParams,
) -> Result<()> {
Ok(())
}
fn make_candidate(backend: BackendId, cost: u64) -> Candidate {
Candidate {
kernel: noop_kernel,
caps: KernelCaps::empty(),
backend,
device: DeviceLocation::Cpu,
precision: PrecisionGuarantee::PRIMITIVE_DETERMINISTIC_CPU,
static_cost: CostEstimate {
flops: cost,
bytes_moved: cost,
kernel_overhead_ns: 0,
},
inbound_transfer_ns: 0,
op_params: OpParams::None,
coupling: Vec::new(),
kernel_source: "",
kernel_revision_hash: 0,
}
}
fn make_selector(judge: HashMapJudge) -> JudgeAwareSelector {
JudgeAwareSelector::new(Arc::new(judge), OpKind::MatMul, DType::F32, SizeClass(16))
}
#[test]
fn empty_judge_falls_back_to_winner() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 100));
set.push(make_candidate(BackendId::Cpu, 200));
let sel = make_selector(HashMapJudge::new());
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Cuda,
"no measurements → static winner",
);
}
#[test]
fn judge_measurement_flips_picker_order() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 100));
set.push(make_candidate(BackendId::Cpu, 200));
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cuda,
"",
10_000_000,
0,
);
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"",
1_000_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Cpu,
"Judge says CPU is 10x faster → CPU wins despite static cost",
);
}
#[test]
fn single_measurement_competes_uniformly_against_unmeasured() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 100));
set.push(make_candidate(BackendId::Cpu, 200));
set.push(make_candidate(BackendId::Vulkan, 300));
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"",
500_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Cuda,
"slow measurement does NOT trump a faster unmeasured static cost",
);
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 1_000_000));
set.push(make_candidate(BackendId::Cpu, 2_000_000));
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"",
500,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Cpu,
"fast measurement beats slower unmeasured static cost",
);
}
#[test]
fn all_measured_picks_lowest_latency() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 100));
set.push(make_candidate(BackendId::Cpu, 200));
set.push(make_candidate(BackendId::Vulkan, 300));
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cuda,
"",
10_000,
0,
);
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"",
5_000,
0,
);
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Vulkan,
"",
1_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Vulkan,
"lowest latency wins regardless of static cost order",
);
}
#[test]
fn judge_key_must_match_selector_context() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 100));
set.push(make_candidate(BackendId::Cpu, 200));
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::AddElementwise, DType::F32,
SizeClass(16),
BackendId::Cpu,
"",
1_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Cuda,
"Judge has no entry at our (op,dtype,size) → static winner",
);
}
#[test]
fn latency_tie_breaks_toward_static_winner() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(BackendId::Cuda, 100));
set.push(make_candidate(BackendId::Cpu, 200));
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cuda,
"",
5_000,
0,
);
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"",
5_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.backend,
BackendId::Cuda,
"tie on latency → earlier original index wins (static-rank tiebreak)",
);
}
#[test]
fn empty_set_returns_none() {
let set = AlternativeSet::empty();
let sel = make_selector(HashMapJudge::new());
assert!(sel.select(&set).is_none());
}
#[test]
fn debug_does_not_panic() {
let sel = make_selector(HashMapJudge::new());
let s = format!("{sel:?}");
assert!(s.contains("JudgeAwareSelector"));
assert!(s.contains("MatMul"));
}
#[test]
fn accessors_round_trip_construction() {
let sel = JudgeAwareSelector::new(
Arc::new(HashMapJudge::new()),
OpKind::AddElementwise,
DType::BF16,
SizeClass(7),
);
assert_eq!(sel.op(), OpKind::AddElementwise);
assert_eq!(sel.dtype(), DType::BF16);
assert_eq!(sel.size_class(), SizeClass(7));
}
#[test]
fn distinct_kernel_sources_get_distinct_measurements() {
let mut set = AlternativeSet::empty();
let mut c_aocl = make_candidate(BackendId::Cpu, 500);
c_aocl.kernel_source = "aocl";
let mut c_mkl = make_candidate(BackendId::Cpu, 500);
c_mkl.kernel_source = "mkl";
set.push(c_aocl);
set.push(c_mkl);
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"aocl",
10_000_000,
0,
);
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"mkl",
1_000_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(pick.backend, BackendId::Cpu);
assert_eq!(
pick.kernel_source, "mkl",
"selector threads kernel_source into the Judge so siblings rank independently",
);
let mut set = AlternativeSet::empty();
let mut c_aocl = make_candidate(BackendId::Cpu, 500);
c_aocl.kernel_source = "aocl";
let mut c_mkl = make_candidate(BackendId::Cpu, 500);
c_mkl.kernel_source = "mkl";
set.push(c_aocl);
set.push(c_mkl);
let mut judge = HashMapJudge::new();
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"aocl",
500_000,
0,
);
judge.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"mkl",
5_000_000,
0,
);
let sel = make_selector(judge);
let pick = sel.select(&set).expect("non-empty");
assert_eq!(
pick.kernel_source, "aocl",
"flipped measurements → other sibling wins, proving siblings are not collapsed",
);
}
}