use rlx_driver::Device;
use rlx_ir::Graph;
pub fn device_arena_limit(device: Device) -> Option<u64> {
match device {
Device::Rocm => Some(u32::MAX as u64),
Device::Gpu | Device::Vulkan => Some(u32::MAX as u64),
_ => None,
}
}
pub fn arena_budget(device: Device, headroom: f64) -> Option<u64> {
let abi = device_arena_limit(device);
let capacity = crate::device_vram_bytes(device).map(|v| (v as f64 * headroom) as u64);
match (abi, capacity) {
(Some(a), Some(c)) => Some(a.min(c)),
(Some(a), None) => Some(a),
(None, Some(c)) => Some(c),
(None, None) => None,
}
}
pub fn plan_arena_bytes(graph: &Graph, device: Device) -> Option<usize> {
match device {
#[cfg(feature = "rocm")]
Device::Rocm => {
let opts = crate::CompileOptions::default();
let (g, _io) =
crate::backend::rocm_backend::prepare_rocm_exec_graph(graph.clone(), &opts, false);
Some(rlx_rocm::arena::plan_f32_uniform(&g, 16).arena_size)
}
_ => Some(
rlx_compile::memory::plan_memory_with_options(
graph,
128,
rlx_compile::memory::MemoryPlanOptions::inference(),
)
.arena_size,
),
}
}
pub fn fit_batch<F>(device: Device, requested: usize, headroom: f64, mut build: F) -> usize
where
F: FnMut(usize) -> Option<Graph>,
{
let Some(budget) = arena_budget(device, headroom) else {
return requested;
};
let mut b = requested.max(1);
while b > 1 {
let Some(g) = build(b) else { return b };
match plan_arena_bytes(&g, device) {
Some(bytes) if (bytes as u64) <= budget => return b,
Some(_) => b /= 2,
None => return b,
}
}
requested.min(1).max(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rocm_and_wgpu_declare_a_four_gib_ceiling_others_do_not() {
assert_eq!(device_arena_limit(Device::Rocm), Some(u32::MAX as u64));
assert_eq!(device_arena_limit(Device::Gpu), Some(u32::MAX as u64));
assert_eq!(device_arena_limit(Device::Vulkan), Some(u32::MAX as u64));
assert_eq!(device_arena_limit(Device::Cuda), None);
assert_eq!(device_arena_limit(Device::Cpu), None);
assert_eq!(device_arena_limit(Device::Metal), None);
}
#[test]
fn a_budget_never_exceeds_the_abi_ceiling() {
if let Some(b) = arena_budget(Device::Rocm, 1.0) {
assert!(b <= u32::MAX as u64, "ROCm budget {b} exceeds its ABI cap");
}
}
#[test]
fn fit_batch_is_a_no_op_without_a_budget() {
let n = fit_batch(Device::Cpu, 8, 0.6, |_| None);
assert!(n <= 8 && n >= 1, "batch {n} out of range");
}
#[test]
fn fit_batch_halves_until_it_fits() {
let mut calls = 0usize;
let n = fit_batch(Device::Rocm, 8, 0.6, |_| {
calls += 1;
None });
assert!(n >= 1, "batch must stay positive");
assert!(calls <= 4, "should halve, not decrement: {calls} builds");
}
}