use fuel_ir::DeviceLocation;
use crate::fused::PrecisionGuarantee;
use super::candidate::Candidate;
use super::cost::{composite_ns, default_backend_rates};
pub type PrecisionDigits = u16;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum AccuracyClass {
Unbounded,
Stable,
BoundedLoose,
BoundedTight,
CorrectlyRounded,
}
impl AccuracyClass {
pub fn rank(self) -> u8 {
match self {
AccuracyClass::Unbounded => 0,
AccuracyClass::Stable => 1,
AccuracyClass::BoundedLoose => 2,
AccuracyClass::BoundedTight => 3,
AccuracyClass::CorrectlyRounded => 4,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct MemoryTiers {
pub host_ram_bytes: u64,
pub device_vram_bytes: u64,
}
impl MemoryTiers {
pub fn for_placement(device: DeviceLocation, bytes: u64) -> Self {
match device {
DeviceLocation::Cpu => MemoryTiers {
host_ram_bytes: bytes,
device_vram_bytes: 0,
},
DeviceLocation::Cuda { .. }
| DeviceLocation::Vulkan { .. }
| DeviceLocation::Metal { .. } => MemoryTiers {
host_ram_bytes: 0,
device_vram_bytes: bytes,
},
}
}
fn no_worse_than(&self, other: &MemoryTiers) -> bool {
self.host_ram_bytes <= other.host_ram_bytes
&& self.device_vram_bytes <= other.device_vram_bytes
}
fn strictly_better_on_some(&self, other: &MemoryTiers) -> bool {
self.host_ram_bytes < other.host_ram_bytes
|| self.device_vram_bytes < other.device_vram_bytes
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CostVector {
pub time: u64,
pub memory: MemoryTiers,
pub precision: PrecisionDigits,
pub accuracy: AccuracyClass,
}
impl CostVector {
pub fn from_candidate(candidate: &Candidate) -> Self {
let (compute_rate, mem_bandwidth) = default_backend_rates(candidate.backend);
let time = composite_ns(&candidate.static_cost, compute_rate, mem_bandwidth)
.saturating_add(candidate.inbound_transfer_ns);
let memory =
MemoryTiers::for_placement(candidate.device, candidate.static_cost.bytes_moved);
CostVector {
time,
memory,
precision: precision_digits(&candidate.precision),
accuracy: accuracy_class(&candidate.precision),
}
}
pub fn dominates(&self, other: &CostVector) -> bool {
let no_worse = self.time <= other.time
&& self.memory.no_worse_than(&other.memory)
&& self.precision >= other.precision
&& self.accuracy >= other.accuracy;
if !no_worse {
return false;
}
self.time < other.time
|| self.memory.strictly_better_on_some(&other.memory)
|| self.precision > other.precision
|| self.accuracy > other.accuracy
}
pub fn total_order_key(&self) -> (u64, u16, u8, u64) {
let total_memory = self
.memory
.host_ram_bytes
.saturating_add(self.memory.device_vram_bytes);
(
self.time,
PrecisionDigits::MAX - self.precision,
u8::MAX - self.accuracy.rank(),
total_memory,
)
}
}
fn precision_digits(p: &PrecisionGuarantee) -> PrecisionDigits {
const MAX_DIGITS: PrecisionDigits = 18;
if let Some(rel) = p.max_relative {
if rel <= 0.0 {
return MAX_DIGITS;
}
let digits = (-rel.log10()).floor();
if digits.is_finite() && digits > 0.0 {
return (digits as u32).min(MAX_DIGITS as u32) as PrecisionDigits;
}
return 0;
}
if p.max_ulp == Some(0) {
return MAX_DIGITS;
}
if p.bit_stable_on_same_hardware {
return MAX_DIGITS / 2;
}
0
}
fn accuracy_class(p: &PrecisionGuarantee) -> AccuracyClass {
match p.max_ulp {
Some(0) => AccuracyClass::CorrectlyRounded,
Some(1..=2) => AccuracyClass::BoundedTight,
Some(_) => AccuracyClass::BoundedLoose,
None => {
if p.bit_stable_on_same_hardware {
AccuracyClass::Stable
} else {
AccuracyClass::Unbounded
}
}
}
}
#[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 candidate(
device: DeviceLocation,
cost: CostEstimate,
inbound: u64,
precision: PrecisionGuarantee,
) -> Candidate {
Candidate {
kernel: noop,
caps: KernelCaps::empty(),
backend: BackendId::Cpu,
device,
precision,
static_cost: cost,
inbound_transfer_ns: inbound,
op_params: OpParams::None,
coupling: Vec::new(),
kernel_source: "",
kernel_revision_hash: 0,
}
}
#[test]
fn cost_vector_time_axis_matches_old_scalar() {
let cost = CostEstimate {
flops: 1000,
bytes_moved: 4000,
kernel_overhead_ns: 50,
};
let c = candidate(
DeviceLocation::Cpu,
cost,
777,
PrecisionGuarantee::REFERENCE,
);
let v = CostVector::from_candidate(&c);
assert_eq!(v.time, 1050 + 777);
}
#[test]
fn cost_vector_memory_attributed_to_cpu_host_tier() {
let cost = CostEstimate {
flops: 0,
bytes_moved: 8192,
kernel_overhead_ns: 0,
};
let c = candidate(DeviceLocation::Cpu, cost, 0, PrecisionGuarantee::REFERENCE);
let v = CostVector::from_candidate(&c);
assert_eq!(v.memory.host_ram_bytes, 8192, "CPU loads host RAM");
assert_eq!(v.memory.device_vram_bytes, 0, "no VRAM on CPU");
}
#[test]
fn cost_vector_memory_attributed_to_gpu_vram_tier() {
let cost = CostEstimate {
flops: 0,
bytes_moved: 8192,
kernel_overhead_ns: 0,
};
let c = candidate(
DeviceLocation::Cuda { gpu_id: 0 },
cost,
0,
PrecisionGuarantee::REFERENCE,
);
let v = CostVector::from_candidate(&c);
assert_eq!(v.memory.device_vram_bytes, 8192, "GPU loads VRAM");
assert_eq!(v.memory.host_ram_bytes, 0, "no host RAM on GPU");
}
#[test]
fn cost_vector_precision_and_accuracy_from_guarantee() {
let reference = candidate(
DeviceLocation::Cpu,
CostEstimate::default(),
0,
PrecisionGuarantee::REFERENCE,
);
let v = CostVector::from_candidate(&reference);
assert_eq!(v.accuracy, AccuracyClass::CorrectlyRounded);
assert_eq!(v.precision, 18, "ULP=0 / rel=0 → max digits");
let prim = candidate(
DeviceLocation::Cpu,
CostEstimate::default(),
0,
PrecisionGuarantee::PRIMITIVE_DETERMINISTIC_CPU,
);
let v = CostVector::from_candidate(&prim);
assert_eq!(v.accuracy, AccuracyClass::Stable);
assert_eq!(v.precision, 9, "bit-stable-no-bound → mid rung");
let unaudited = candidate(
DeviceLocation::Cpu,
CostEstimate::default(),
0,
PrecisionGuarantee::UNAUDITED,
);
let v = CostVector::from_candidate(&unaudited);
assert_eq!(v.accuracy, AccuracyClass::Unbounded);
assert_eq!(v.precision, 0);
let loose = PrecisionGuarantee {
bit_stable_on_same_hardware: true,
max_ulp: Some(4),
max_relative: None,
max_absolute: None,
notes: "loose",
};
let v = CostVector::from_candidate(&candidate(
DeviceLocation::Cpu,
CostEstimate::default(),
0,
loose,
));
assert_eq!(v.accuracy, AccuracyClass::BoundedLoose);
let tight = PrecisionGuarantee {
bit_stable_on_same_hardware: true,
max_ulp: Some(1),
max_relative: None,
max_absolute: None,
notes: "tight",
};
let v = CostVector::from_candidate(&candidate(
DeviceLocation::Cpu,
CostEstimate::default(),
0,
tight,
));
assert_eq!(v.accuracy, AccuracyClass::BoundedTight);
}
#[test]
fn dominates_clearly_dominated_pair() {
let a = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: 10,
device_vram_bytes: 0,
},
precision: 10,
accuracy: AccuracyClass::CorrectlyRounded,
};
let b = CostVector {
time: 200,
memory: MemoryTiers {
host_ram_bytes: 20,
device_vram_bytes: 0,
},
precision: 5,
accuracy: AccuracyClass::Stable,
};
assert!(a.dominates(&b), "a strictly better on every axis");
assert!(!b.dominates(&a), "dominated cannot dominate back");
}
#[test]
fn dominates_non_dominated_pair_is_false_both_ways() {
let fast_low_prec = CostVector {
time: 100,
memory: MemoryTiers::default(),
precision: 4,
accuracy: AccuracyClass::BoundedLoose,
};
let slow_high_prec = CostVector {
time: 500,
memory: MemoryTiers::default(),
precision: 12,
accuracy: AccuracyClass::CorrectlyRounded,
};
assert!(!fast_low_prec.dominates(&slow_high_prec));
assert!(!slow_high_prec.dominates(&fast_low_prec));
}
#[test]
fn dominates_equal_vectors_is_false() {
let a = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: 5,
device_vram_bytes: 5,
},
precision: 7,
accuracy: AccuracyClass::Stable,
};
assert!(!a.dominates(&a));
}
#[test]
fn dominates_memory_tradeoff_across_tiers_is_non_dominated() {
let host_light = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: 1,
device_vram_bytes: 100,
},
precision: 7,
accuracy: AccuracyClass::Stable,
};
let vram_light = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: 100,
device_vram_bytes: 1,
},
precision: 7,
accuracy: AccuracyClass::Stable,
};
assert!(!host_light.dominates(&vram_light));
assert!(!vram_light.dominates(&host_light));
}
#[test]
fn tie_break_precision_first_among_equal_time() {
let lower_prec = CostVector {
time: 100,
memory: MemoryTiers::default(),
precision: 5,
accuracy: AccuracyClass::CorrectlyRounded,
};
let higher_prec = CostVector {
time: 100,
memory: MemoryTiers::default(),
precision: 9,
accuracy: AccuracyClass::Unbounded,
};
assert!(
higher_prec.total_order_key() < lower_prec.total_order_key(),
"precision is the first tie-breaker",
);
}
#[test]
fn tie_break_accuracy_then_memory() {
let lo_acc = CostVector {
time: 100,
memory: MemoryTiers::default(),
precision: 7,
accuracy: AccuracyClass::Stable,
};
let hi_acc = CostVector {
time: 100,
memory: MemoryTiers::default(),
precision: 7,
accuracy: AccuracyClass::CorrectlyRounded,
};
assert!(hi_acc.total_order_key() < lo_acc.total_order_key());
let heavy = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: 1000,
device_vram_bytes: 0,
},
precision: 7,
accuracy: AccuracyClass::Stable,
};
let light = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: 10,
device_vram_bytes: 0,
},
precision: 7,
accuracy: AccuracyClass::Stable,
};
assert!(light.total_order_key() < heavy.total_order_key());
}
#[test]
fn total_order_key_is_time_first() {
let fast_but_worse = CostVector {
time: 100,
memory: MemoryTiers {
host_ram_bytes: u64::MAX,
device_vram_bytes: 0,
},
precision: 0,
accuracy: AccuracyClass::Unbounded,
};
let slow_but_better = CostVector {
time: 101,
memory: MemoryTiers::default(),
precision: 18,
accuracy: AccuracyClass::CorrectlyRounded,
};
assert!(
fast_but_worse.total_order_key() < slow_but_better.total_order_key(),
"time is the primary key",
);
}
}