use fuel_backend_contract::backend::BackendRuntime;
use fuel_ir::DeviceLocation;
use fuel_ir::probe::BackendId;
use super::{AlternativeSet, BackendRuntimeLookup, Candidate, RuntimeSelector};
pub const LOAD_TIER_IDLE: u8 = 0;
pub const LOAD_TIER_MODERATE: u8 = 1;
pub const LOAD_TIER_SATURATED: u8 = 2;
pub fn load_tier(count: u32, capacity: u32) -> u8 {
if count == 0 {
return LOAD_TIER_IDLE;
}
let capacity = capacity.max(1);
if count >= capacity {
return LOAD_TIER_SATURATED;
}
if count.saturating_mul(4) < capacity {
LOAD_TIER_IDLE
} else {
LOAD_TIER_MODERATE
}
}
pub type LoadLookup = BackendRuntimeLookup;
pub fn load_tier_for(lookup: Option<&LoadLookup>, c: &Candidate) -> u8 {
let Some(lookup) = lookup else {
return LOAD_TIER_IDLE;
};
let Some(handle) = lookup(c.backend, c.device) else {
return LOAD_TIER_IDLE;
};
load_tier_of_handle(handle.as_ref())
}
pub(crate) fn load_tier_of_handle(handle: &dyn BackendRuntime) -> u8 {
match handle.as_backend_streams() {
Some(streams) => match streams.pending_work_count() {
Some(count) => load_tier(count, streams.slot_capacity()),
None => LOAD_TIER_IDLE,
},
None => LOAD_TIER_IDLE,
}
}
#[derive(Clone)]
pub struct DeviceLoadSelector {
lookup: Option<LoadLookup>,
}
impl DeviceLoadSelector {
pub fn new(lookup: Option<LoadLookup>) -> Self {
Self { lookup }
}
}
impl std::fmt::Debug for DeviceLoadSelector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeviceLoadSelector")
.field("lookup", &self.lookup.as_ref().map(|_| "<closure>"))
.finish()
}
}
impl RuntimeSelector for DeviceLoadSelector {
fn select<'a>(&self, set: &'a AlternativeSet) -> Option<&'a Candidate> {
let alts = set.alternatives();
if alts.is_empty() {
return None;
}
let mut best: Option<(u8, usize)> = None;
for (i, c) in alts.iter().enumerate() {
let key = (load_tier_for(self.lookup.as_ref(), c), i);
if best.is_none_or(|b| key < b) {
best = Some(key);
}
}
match best {
Some((_, i)) => alts.get(i),
None => set.winner(),
}
}
}
pub struct MockBackendStreams {
pub pending: Option<u32>,
pub capacity: u32,
}
impl MockBackendStreams {
pub fn new(pending: Option<u32>, capacity: u32) -> Self {
Self { pending, capacity }
}
}
impl BackendRuntime for MockBackendStreams {
fn available_bytes(&self) -> Option<u64> {
None
}
fn total_bytes(&self) -> Option<u64> {
None
}
fn as_backend_streams(&self) -> Option<&dyn fuel_backend_contract::backend::BackendStreams> {
Some(self)
}
}
impl fuel_backend_contract::backend::BackendStreams for MockBackendStreams {
fn pending_work_count(&self) -> Option<u32> {
self.pending
}
fn slot_capacity(&self) -> u32 {
self.capacity
}
fn flush(&self) -> fuel_ir::Result<()> {
Ok(())
}
}
pub fn mock_load_lookup(entries: Vec<(BackendId, Option<u32>, u32)>) -> LoadLookup {
use std::sync::Arc;
Arc::new(move |b: BackendId, _d: DeviceLocation| {
entries
.iter()
.find(|(eb, _, _)| *eb == b)
.map(|&(_, pending, capacity)| {
Box::new(MockBackendStreams::new(pending, capacity)) as super::BackendRuntimeHandle
})
})
}
pub struct MockCombinedRuntime {
pub available: Option<u64>,
pub total: Option<u64>,
pub pending: Option<u32>,
pub capacity: u32,
}
impl BackendRuntime for MockCombinedRuntime {
fn available_bytes(&self) -> Option<u64> {
self.available
}
fn total_bytes(&self) -> Option<u64> {
self.total
}
fn as_backend_streams(&self) -> Option<&dyn fuel_backend_contract::backend::BackendStreams> {
Some(self)
}
}
impl fuel_backend_contract::backend::BackendStreams for MockCombinedRuntime {
fn pending_work_count(&self) -> Option<u32> {
self.pending
}
fn slot_capacity(&self) -> u32 {
self.capacity
}
fn flush(&self) -> fuel_ir::Result<()> {
Ok(())
}
}
pub type MockCombinedEntry = (BackendId, Option<u64>, Option<u64>, Option<u32>, u32);
pub fn mock_combined_lookup(entries: Vec<MockCombinedEntry>) -> LoadLookup {
use std::sync::Arc;
Arc::new(move |b: BackendId, _d: DeviceLocation| {
entries.iter().find(|(eb, _, _, _, _)| *eb == b).map(
|&(_, available, total, pending, capacity)| {
Box::new(MockCombinedRuntime {
available,
total,
pending,
capacity,
}) as super::BackendRuntimeHandle
},
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fused::{CostEstimate, PrecisionGuarantee};
use crate::kernel::{KernelCaps, OpParams};
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, device: DeviceLocation) -> Candidate {
Candidate {
kernel: noop_kernel,
caps: KernelCaps::empty(),
backend,
device,
precision: PrecisionGuarantee::PRIMITIVE_DETERMINISTIC_CPU,
static_cost: CostEstimate {
flops: 0,
bytes_moved: 0,
kernel_overhead_ns: 0,
},
inbound_transfer_ns: 0,
op_params: OpParams::None,
coupling: Vec::new(),
kernel_source: "",
kernel_revision_hash: 0,
}
}
#[test]
fn load_tier_zero_is_idle() {
assert_eq!(load_tier(0, 1), LOAD_TIER_IDLE);
assert_eq!(load_tier(0, 8), LOAD_TIER_IDLE);
}
#[test]
fn load_tier_at_or_above_capacity_is_saturated() {
assert_eq!(load_tier(1, 1), LOAD_TIER_SATURATED);
assert_eq!(load_tier(8, 8), LOAD_TIER_SATURATED);
assert_eq!(load_tier(100, 1), LOAD_TIER_SATURATED);
assert_eq!(load_tier(9, 8), LOAD_TIER_SATURATED);
}
#[test]
fn load_tier_splits_idle_and_moderate_at_quarter() {
assert_eq!(load_tier(1, 8), LOAD_TIER_IDLE);
assert_eq!(load_tier(2, 8), LOAD_TIER_MODERATE);
assert_eq!(load_tier(7, 8), LOAD_TIER_MODERATE);
}
#[test]
fn load_tier_single_slot_is_binary() {
assert_eq!(load_tier(0, 1), LOAD_TIER_IDLE);
assert_eq!(load_tier(1, 1), LOAD_TIER_SATURATED);
assert_eq!(load_tier(5, 1), LOAD_TIER_SATURATED);
}
#[test]
fn load_tier_zero_capacity_is_safe() {
assert_eq!(load_tier(0, 0), LOAD_TIER_IDLE);
assert_eq!(load_tier(1, 0), LOAD_TIER_SATURATED);
}
#[test]
fn load_tier_for_no_lookup_is_idle() {
let c = make_candidate(BackendId::Cuda, DeviceLocation::Cuda { gpu_id: 0 });
assert_eq!(load_tier_for(None, &c), LOAD_TIER_IDLE);
}
#[test]
fn load_tier_for_absent_backend_is_idle() {
let lookup = mock_load_lookup(vec![(BackendId::Cuda, Some(8), 1)]);
let cpu = make_candidate(BackendId::Cpu, DeviceLocation::Cpu);
assert_eq!(load_tier_for(Some(&lookup), &cpu), LOAD_TIER_IDLE);
}
#[test]
fn load_tier_for_none_count_is_idle() {
let lookup = mock_load_lookup(vec![(BackendId::Cuda, None, 1)]);
let cuda = make_candidate(BackendId::Cuda, DeviceLocation::Cuda { gpu_id: 0 });
assert_eq!(load_tier_for(Some(&lookup), &cuda), LOAD_TIER_IDLE);
}
#[test]
fn load_tier_for_reads_saturated() {
let lookup = mock_load_lookup(vec![(BackendId::Cuda, Some(4), 1)]);
let cuda = make_candidate(BackendId::Cuda, DeviceLocation::Cuda { gpu_id: 0 });
assert_eq!(load_tier_for(Some(&lookup), &cuda), LOAD_TIER_SATURATED);
}
#[test]
fn selector_no_signal_picks_winner() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(
BackendId::Cuda,
DeviceLocation::Cuda { gpu_id: 0 },
));
set.push(make_candidate(
BackendId::Vulkan,
DeviceLocation::Vulkan { gpu_id: 0 },
));
let sel = DeviceLoadSelector::new(None);
assert_eq!(
sel.select(&set).expect("non-empty").backend,
BackendId::Cuda
);
}
#[test]
fn selector_flips_to_unloaded_arm() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(
BackendId::Cuda,
DeviceLocation::Cuda { gpu_id: 0 },
));
set.push(make_candidate(
BackendId::Vulkan,
DeviceLocation::Vulkan { gpu_id: 0 },
));
let lookup = mock_load_lookup(vec![
(BackendId::Cuda, Some(4), 1),
(BackendId::Vulkan, Some(0), 1),
]);
let sel = DeviceLoadSelector::new(Some(lookup));
assert_eq!(
sel.select(&set).expect("non-empty").backend,
BackendId::Vulkan,
"saturated arm-0 demoted below idle arm-1",
);
}
#[test]
fn selector_equal_load_keeps_winner() {
let mut set = AlternativeSet::empty();
set.push(make_candidate(
BackendId::Cuda,
DeviceLocation::Cuda { gpu_id: 0 },
));
set.push(make_candidate(
BackendId::Vulkan,
DeviceLocation::Vulkan { gpu_id: 0 },
));
let lookup = mock_load_lookup(vec![
(BackendId::Cuda, Some(4), 1),
(BackendId::Vulkan, Some(4), 1),
]);
let sel = DeviceLoadSelector::new(Some(lookup));
assert_eq!(
sel.select(&set).expect("non-empty").backend,
BackendId::Cuda
);
}
#[test]
fn selector_empty_set_is_none() {
let sel = DeviceLoadSelector::new(None);
assert!(sel.select(&AlternativeSet::empty()).is_none());
}
#[test]
fn debug_does_not_panic() {
let sel = DeviceLoadSelector::new(Some(mock_load_lookup(vec![])));
assert!(format!("{sel:?}").contains("DeviceLoadSelector"));
}
}