1use anyhow::{Result, bail};
7use rlx_runtime::{Device, is_available};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct OrpheusRuntimeDevice {
12 pub lm: Device,
14 pub snac_coreml: bool,
16}
17
18impl OrpheusRuntimeDevice {
19 pub fn snac_load_options(self) -> crate::decoder::SnacLoadOptions {
20 crate::decoder::SnacLoadOptions {
21 coreml: self.snac_coreml,
22 }
23 }
24}
25
26fn env_flag(name: &str) -> Option<bool> {
27 match std::env::var(name).ok().as_deref() {
28 Some("1") | Some("true") | Some("TRUE") => Some(true),
29 Some("0") | Some("false") | Some("FALSE") => Some(false),
30 _ => None,
31 }
32}
33
34pub fn lm_kv_decode_supported(device: Device) -> bool {
38 match device {
39 Device::Mlx => env_flag("ORPHEUS_MLX_KV") == Some(true),
40 Device::Metal | Device::Cuda | Device::Rocm | Device::Gpu | Device::Vulkan => true,
41 Device::Cpu => env_flag("ORPHEUS_FAST_LM") == Some(true),
42 _ => false,
43 }
44}
45
46pub fn preferred_synth_device() -> Device {
51 if is_available(Device::Metal) {
52 Device::Metal
53 } else if is_available(Device::Mlx) && lm_kv_decode_supported(Device::Mlx) {
54 Device::Mlx
55 } else if is_available(Device::Cuda) {
56 Device::Cuda
57 } else if is_available(Device::Rocm) {
58 Device::Rocm
59 } else if is_available(Device::Gpu) {
60 Device::Gpu
61 } else if is_available(Device::Vulkan) {
62 Device::Vulkan
63 } else {
64 Device::Cpu
65 }
66}
67
68#[cfg(feature = "llama")]
70pub fn synth_device_for_tests() -> Device {
71 if let Ok(s) = std::env::var("ORPHEUS_SYNTH_DEVICE") {
72 if let Ok(d) = rlx_cli::parse_device(s.trim()) {
73 return d;
74 }
75 }
76 preferred_synth_device()
77}
78
79#[cfg(feature = "llama")]
80pub fn parse_orpheus_device(s: &str) -> Result<OrpheusRuntimeDevice> {
81 resolve_orpheus_device(s)
82}
83
84#[cfg(feature = "llama")]
85pub fn resolve_orpheus_device(s: &str) -> Result<OrpheusRuntimeDevice> {
86 let s = s.trim();
87 if s.eq_ignore_ascii_case("auto") {
88 return Ok(OrpheusRuntimeDevice {
89 lm: preferred_synth_device(),
90 snac_coreml: false,
91 });
92 }
93 if s.eq_ignore_ascii_case("coreml") {
94 let lm = preferred_synth_device();
95 if !matches!(lm, Device::Metal | Device::Mlx | Device::Cpu) {
96 bail!("coreml SNAC path expects an Apple host with Metal or MLX; got lm={lm:?}");
97 }
98 return Ok(OrpheusRuntimeDevice {
99 lm,
100 snac_coreml: true,
101 });
102 }
103 Ok(OrpheusRuntimeDevice {
104 lm: rlx_cli::parse_device(s)?,
105 snac_coreml: false,
106 })
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn mlx_kv_off_by_default() {
115 assert!(!lm_kv_decode_supported(Device::Mlx));
116 }
117
118 #[test]
119 fn metal_kv_on_by_default() {
120 assert!(lm_kv_decode_supported(Device::Metal));
121 }
122
123 #[test]
124 fn wgpu_kv_on_by_default() {
125 assert!(lm_kv_decode_supported(Device::Gpu));
126 assert!(lm_kv_decode_supported(Device::Vulkan));
127 }
128
129 #[test]
130 fn resolve_coreml_sets_snac_flag() {
131 let rt = resolve_orpheus_device("coreml").unwrap();
132 assert!(rt.snac_coreml);
133 }
134}