use anyhow::{Result, bail};
use rlx_runtime::{Device, is_available};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OrpheusRuntimeDevice {
pub lm: Device,
pub snac: crate::decoder::SnacExec,
}
impl OrpheusRuntimeDevice {
pub fn snac_load_options(self) -> crate::decoder::SnacLoadOptions {
crate::decoder::SnacLoadOptions { exec: self.snac }
}
}
pub fn default_snac_exec(lm: Device) -> crate::decoder::SnacExec {
use crate::decoder::SnacExec;
if let Ok(s) = std::env::var("ORPHEUS_SNAC_DEVICE") {
match s.trim().to_ascii_lowercase().as_str() {
"cpu" | "eager" => return SnacExec::CpuEager,
"metal" => return SnacExec::Metal,
"mlx" => return SnacExec::Mlx,
"wgpu" | "gpu" => return SnacExec::Wgpu,
"ane" | "coreml" => return SnacExec::Ane,
_ => {}
}
}
match lm {
Device::Metal => SnacExec::Metal,
Device::Mlx => SnacExec::Mlx,
Device::Gpu => SnacExec::Wgpu,
_ => SnacExec::CpuEager,
}
}
fn env_flag(name: &str) -> Option<bool> {
match std::env::var(name).ok().as_deref() {
Some("1") | Some("true") | Some("TRUE") => Some(true),
Some("0") | Some("false") | Some("FALSE") => Some(false),
_ => None,
}
}
pub fn lm_kv_decode_supported(device: Device) -> bool {
match device {
Device::Mlx => env_flag("ORPHEUS_MLX_KV") == Some(true),
Device::Metal | Device::Cuda | Device::Rocm | Device::Gpu | Device::Vulkan => true,
Device::Cpu => env_flag("ORPHEUS_FAST_LM") == Some(true),
_ => false,
}
}
pub fn preferred_synth_device() -> Device {
for device in [
Device::Metal,
Device::Cuda,
Device::Rocm,
Device::Gpu,
Device::Vulkan,
] {
if is_available(device) && lm_kv_decode_supported(device) {
return device;
}
}
if is_available(Device::Mlx) && lm_kv_decode_supported(Device::Mlx) {
return Device::Mlx;
}
Device::Cpu
}
pub fn preferred_synth_device_lenient() -> Device {
for device in [
Device::Metal,
Device::Cuda,
Device::Rocm,
Device::Gpu,
Device::Vulkan,
Device::Mlx,
] {
if is_available(device) {
return device;
}
}
Device::Cpu
}
#[cfg(feature = "llama")]
pub fn synth_device_for_tests() -> Device {
if let Ok(s) = std::env::var("ORPHEUS_SYNTH_DEVICE") {
if let Ok(d) = rlx_cli::parse_device(s.trim()) {
return d;
}
}
preferred_synth_device()
}
#[cfg(feature = "llama")]
pub fn parse_orpheus_device(s: &str) -> Result<OrpheusRuntimeDevice> {
resolve_orpheus_device(s)
}
#[cfg(feature = "llama")]
pub fn resolve_orpheus_device(s: &str) -> Result<OrpheusRuntimeDevice> {
let s = s.trim();
if s.eq_ignore_ascii_case("auto") {
let lm = preferred_synth_device();
return Ok(OrpheusRuntimeDevice {
lm,
snac: default_snac_exec(lm),
});
}
if s.eq_ignore_ascii_case("coreml") {
let lm = preferred_synth_device_lenient();
if !matches!(lm, Device::Metal | Device::Mlx | Device::Cpu) {
bail!("coreml SNAC path expects an Apple host with Metal or MLX; got lm={lm:?}");
}
return Ok(OrpheusRuntimeDevice {
lm,
snac: crate::decoder::SnacExec::Ane,
});
}
let lm = rlx_cli::parse_device(s)?;
let lm = if is_available(lm) {
lm
} else {
eprintln!("[orpheus] device {lm:?} unavailable on this host — using CPU");
Device::Cpu
};
Ok(OrpheusRuntimeDevice {
lm,
snac: default_snac_exec(lm),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mlx_kv_off_by_default() {
assert!(!lm_kv_decode_supported(Device::Mlx));
}
#[test]
fn metal_kv_on_by_default() {
assert!(lm_kv_decode_supported(Device::Metal));
}
#[test]
fn wgpu_kv_on_by_default() {
assert!(lm_kv_decode_supported(Device::Gpu));
assert!(lm_kv_decode_supported(Device::Vulkan));
}
#[test]
fn resolve_coreml_sets_snac_flag() {
let rt = resolve_orpheus_device("coreml").unwrap();
assert_eq!(rt.snac, crate::decoder::SnacExec::Ane);
}
#[test]
fn resolve_missing_cuda_falls_back_to_cpu() {
if is_available(Device::Cuda) {
return;
}
let rt = resolve_orpheus_device("cuda").expect("cuda should not error");
assert_eq!(rt.lm, Device::Cpu);
}
}