use fuel_ir::DType;
use fuel_ir::dispatch::{OpKind, SizeClass};
use fuel_ir::probe::BackendId;
pub trait JudgeOracle: Send + Sync {
fn measured_latency_ns(
&self,
op: OpKind,
dtype: DType,
size_class: SizeClass,
backend: BackendId,
kernel_source: &str,
kernel_revision_hash: u64,
) -> Option<u64>;
}
#[derive(Debug, Default, Clone)]
pub struct HashMapJudge {
entries: std::collections::HashMap<(OpKind, DType, SizeClass, BackendId, String, u64), u64>,
}
impl HashMapJudge {
pub fn new() -> Self {
Self::default()
}
pub fn insert(
&mut self,
op: OpKind,
dtype: DType,
size_class: SizeClass,
backend: BackendId,
kernel_source: &str,
latency_ns: u64,
kernel_revision_hash: u64,
) {
self.entries.insert(
(
op,
dtype,
size_class,
backend,
kernel_source.to_string(),
kernel_revision_hash,
),
latency_ns,
);
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl JudgeOracle for HashMapJudge {
fn measured_latency_ns(
&self,
op: OpKind,
dtype: DType,
size_class: SizeClass,
backend: BackendId,
kernel_source: &str,
kernel_revision_hash: u64,
) -> Option<u64> {
self.entries
.get(&(
op,
dtype,
size_class,
backend,
kernel_source.to_string(),
kernel_revision_hash,
))
.copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hashmap_judge_round_trip() {
let mut j = HashMapJudge::new();
assert!(j.is_empty());
j.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cuda,
"cublas",
5_000_000,
0,
);
assert_eq!(j.len(), 1);
assert_eq!(
j.measured_latency_ns(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cuda,
"cublas",
0,
),
Some(5_000_000),
);
}
#[test]
fn hashmap_judge_miss_returns_none() {
let j = HashMapJudge::new();
assert!(
j.measured_latency_ns(
OpKind::AddElementwise,
DType::F32,
SizeClass(8),
BackendId::Cpu,
"",
0,
)
.is_none()
);
}
#[test]
fn hashmap_judge_last_write_wins_per_kernel_source() {
let mut j = HashMapJudge::new();
j.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"aocl",
1_000,
0,
);
j.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"aocl",
2_000,
0,
);
assert_eq!(
j.measured_latency_ns(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"aocl",
0,
),
Some(2_000),
);
j.insert(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"mkl",
1_500,
0,
);
assert_eq!(j.len(), 2);
assert_eq!(
j.measured_latency_ns(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"mkl",
0,
),
Some(1_500),
);
assert_eq!(
j.measured_latency_ns(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"aocl",
0,
),
Some(2_000),
);
assert!(
j.measured_latency_ns(
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cpu,
"portable-cpu",
0,
)
.is_none()
);
}
#[test]
fn hashmap_judge_distinguishes_backends_at_same_key() {
let mut j = HashMapJudge::new();
let (op, dt, sc) = (OpKind::MatMul, DType::F32, SizeClass(16));
j.insert(op, dt, sc, BackendId::Cpu, "", 1_000_000, 0);
j.insert(op, dt, sc, BackendId::Cuda, "", 100_000, 0);
assert_eq!(
j.measured_latency_ns(op, dt, sc, BackendId::Cpu, "", 0),
Some(1_000_000)
);
assert_eq!(
j.measured_latency_ns(op, dt, sc, BackendId::Cuda, "", 0),
Some(100_000)
);
assert!(
j.measured_latency_ns(op, dt, sc, BackendId::Vulkan, "", 0)
.is_none()
);
}
#[test]
fn trait_is_dyn_compatible() {
let j: Box<dyn JudgeOracle> = Box::new(HashMapJudge::new());
assert!(
j.measured_latency_ns(
OpKind::AddElementwise,
DType::F32,
SizeClass(0),
BackendId::Cpu,
"",
0,
)
.is_none()
);
}
#[test]
fn sibling_kernel_revisions_do_not_collide() {
let mut j = HashMapJudge::new();
let (op, dt, sc, be, src) = (
OpKind::MatMul,
DType::F32,
SizeClass(16),
BackendId::Cuda,
"baracuda",
);
j.insert(op, dt, sc, be, src, 5_000, 0x1111_1111_1111_1111);
j.insert(op, dt, sc, be, src, 9_000, 0x2222_2222_2222_2222);
assert_eq!(j.len(), 2, "distinct non-zero revisions are distinct cells");
assert_eq!(
j.measured_latency_ns(op, dt, sc, be, src, 0x1111_1111_1111_1111),
Some(5_000),
);
assert_eq!(
j.measured_latency_ns(op, dt, sc, be, src, 0x2222_2222_2222_2222),
Some(9_000),
);
assert!(j.measured_latency_ns(op, dt, sc, be, src, 0).is_none());
}
}