use anyhow::{Result, bail};
use rlx_runtime::{Device, memory_estimate};
pub const STANDARD_DEVICES: &[Device] = &[
Device::Cpu,
Device::Metal,
Device::Mlx,
Device::Cuda,
Device::Rocm,
Device::Gpu,
Device::Vulkan,
];
pub const STANDARD_DEVICE_NAMES: &str = "auto|cpu|metal|mps|mlx|cuda|rocm|hip|gpu|wgpu|vulkan";
pub const LM_DEVICE_NAMES: &str = "auto|cpu|metal|mps|mlx|cuda|rocm|hip|gpu|wgpu|vulkan|coreml|ane";
pub const LM_INFERENCE_DEVICE_PRIORITY: &[Device] = &[
Device::Cuda,
Device::Rocm,
Device::Mlx,
Device::Metal,
Device::Gpu,
Device::Vulkan,
Device::Cpu,
];
pub fn pick_lm_device() -> Device {
let avail = rlx_runtime::available_devices();
for &d in LM_INFERENCE_DEVICE_PRIORITY {
if avail.contains(&d) {
return d;
}
}
Device::Cpu
}
pub fn resolve_lm_device_str(family: &str, s: &str) -> Result<Device> {
let key = s.trim().to_ascii_lowercase();
if key == "auto" || key.is_empty() {
return Ok(pick_lm_device());
}
let d = std::str::FromStr::from_str(s.trim()).map_err(|e| anyhow::anyhow!("{e}"))?;
validate_lm_device(family, d)?;
Ok(d)
}
pub fn is_standard_device(device: Device) -> bool {
STANDARD_DEVICES.contains(&device)
}
pub fn is_lm_device(device: Device) -> bool {
is_standard_device(device) || device == Device::Ane
}
pub fn validate_standard_device(family: &str, device: Device) -> Result<()> {
if is_standard_device(device) {
Ok(())
} else {
bail!(
"{family}: device {device:?} is not supported \
(use {STANDARD_DEVICE_NAMES})"
)
}
}
pub fn validate_lm_device(family: &str, device: Device) -> Result<()> {
if device == Device::Ane {
#[cfg(feature = "coreml")]
return Ok(());
#[cfg(not(feature = "coreml"))]
bail!(
"{family}: device Ane requires the `coreml` feature \
(enable `coreml` or `apple-silicon` on this crate)"
);
}
validate_standard_device(family, device)
}
pub fn device_memory_for_moe_offload(device: Device) -> Option<(usize, usize)> {
if let (Ok(free), Ok(total)) = (
std::env::var("RLX_CUDA_FREE_BYTES"),
std::env::var("RLX_CUDA_TOTAL_BYTES"),
) {
if let (Ok(f), Ok(t)) = (free.parse(), total.parse()) {
return Some((f, t));
}
}
if let (Ok(free), Ok(total)) = (
std::env::var("RLX_DEVICE_FREE_BYTES"),
std::env::var("RLX_DEVICE_TOTAL_BYTES"),
) {
if let (Ok(f), Ok(t)) = (free.parse(), total.parse()) {
return Some((f, t));
}
}
match device {
Device::Metal | Device::Mlx | Device::Ane => {
memory_estimate::available_unified_memory().map(|t| (t, t))
}
Device::Cuda | Device::Rocm | Device::Gpu | Device::Vulkan => {
memory_estimate::available_unified_memory().map(|t| (t, t))
}
_ => None,
}
}
pub fn validate_sam_device(family: &str, device: Device) -> Result<()> {
if device == Device::Tpu || is_standard_device(device) {
Ok(())
} else {
bail!(
"{family}: device {device:?} is not supported \
(use {STANDARD_DEVICE_NAMES} or tpu)"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn standard_set_covers_cli_backends() {
for dev in STANDARD_DEVICES {
assert!(is_standard_device(*dev));
}
assert!(!is_standard_device(Device::Tpu));
}
#[test]
fn pick_lm_device_is_available() {
let picked = pick_lm_device();
assert!(rlx_runtime::is_available(picked));
assert!(is_lm_device(picked));
}
#[test]
fn resolve_auto_picks_fastest() {
let d = resolve_lm_device_str("test", "auto").unwrap();
assert_eq!(d, pick_lm_device());
}
}