rlx_models_core/
device_capabilities.rs1use anyhow::{Result, bail};
24use rlx_runtime::{Device, memory_estimate};
25
26pub const STANDARD_DEVICES: &[Device] = &[
29 Device::Cpu,
30 Device::Metal,
31 Device::Mlx,
32 Device::Cuda,
33 Device::Rocm,
34 Device::Gpu,
35 Device::Vulkan,
36];
37
38pub const STANDARD_DEVICE_NAMES: &str = "auto|cpu|metal|mps|mlx|cuda|rocm|hip|gpu|wgpu|vulkan";
40
41pub const LM_DEVICE_NAMES: &str = "auto|cpu|metal|mps|mlx|cuda|rocm|hip|gpu|wgpu|vulkan|coreml|ane";
43
44pub const LM_INFERENCE_DEVICE_PRIORITY: &[Device] = &[
46 Device::Cuda,
47 Device::Rocm,
48 Device::Mlx,
49 Device::Metal,
50 Device::Gpu,
51 Device::Vulkan,
52 Device::Cpu,
53];
54
55pub fn pick_lm_device() -> Device {
57 let avail = rlx_runtime::available_devices();
58 for &d in LM_INFERENCE_DEVICE_PRIORITY {
59 if avail.contains(&d) {
60 return d;
61 }
62 }
63 Device::Cpu
64}
65
66pub fn resolve_lm_device_str(family: &str, s: &str) -> Result<Device> {
68 let key = s.trim().to_ascii_lowercase();
69 if key == "auto" || key.is_empty() {
70 return Ok(pick_lm_device());
71 }
72 let d = std::str::FromStr::from_str(s.trim()).map_err(|e| anyhow::anyhow!("{e}"))?;
73 validate_lm_device(family, d)?;
74 Ok(d)
75}
76
77pub fn is_standard_device(device: Device) -> bool {
79 STANDARD_DEVICES.contains(&device)
80}
81
82pub fn is_lm_device(device: Device) -> bool {
84 is_standard_device(device) || device == Device::Ane
85}
86
87pub fn validate_standard_device(family: &str, device: Device) -> Result<()> {
89 if is_standard_device(device) {
90 Ok(())
91 } else {
92 bail!(
93 "{family}: device {device:?} is not supported \
94 (use {STANDARD_DEVICE_NAMES})"
95 )
96 }
97}
98
99pub fn validate_lm_device(family: &str, device: Device) -> Result<()> {
101 if device == Device::Ane {
102 #[cfg(feature = "coreml")]
103 return Ok(());
104 #[cfg(not(feature = "coreml"))]
105 bail!(
106 "{family}: device Ane requires the `coreml` feature \
107 (enable `coreml` or `apple-silicon` on this crate)"
108 );
109 }
110 validate_standard_device(family, device)
111}
112
113pub fn device_memory_for_moe_offload(device: Device) -> Option<(usize, usize)> {
119 if let (Ok(free), Ok(total)) = (
120 std::env::var("RLX_CUDA_FREE_BYTES"),
121 std::env::var("RLX_CUDA_TOTAL_BYTES"),
122 ) {
123 if let (Ok(f), Ok(t)) = (free.parse(), total.parse()) {
124 return Some((f, t));
125 }
126 }
127 if let (Ok(free), Ok(total)) = (
128 std::env::var("RLX_DEVICE_FREE_BYTES"),
129 std::env::var("RLX_DEVICE_TOTAL_BYTES"),
130 ) {
131 if let (Ok(f), Ok(t)) = (free.parse(), total.parse()) {
132 return Some((f, t));
133 }
134 }
135 match device {
136 Device::Metal | Device::Mlx | Device::Ane => {
137 memory_estimate::available_unified_memory().map(|t| (t, t))
138 }
139 Device::Cuda | Device::Rocm | Device::Gpu | Device::Vulkan => {
140 memory_estimate::available_unified_memory().map(|t| (t, t))
141 }
142 _ => None,
143 }
144}
145
146pub fn validate_sam_device(family: &str, device: Device) -> Result<()> {
148 if device == Device::Tpu || is_standard_device(device) {
149 Ok(())
150 } else {
151 bail!(
152 "{family}: device {device:?} is not supported \
153 (use {STANDARD_DEVICE_NAMES} or tpu)"
154 )
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn standard_set_covers_cli_backends() {
164 for dev in STANDARD_DEVICES {
165 assert!(is_standard_device(*dev));
166 }
167 assert!(!is_standard_device(Device::Tpu));
168 }
169
170 #[test]
171 fn pick_lm_device_is_available() {
172 let picked = pick_lm_device();
173 assert!(rlx_runtime::is_available(picked));
174 assert!(is_lm_device(picked));
175 }
176
177 #[test]
178 fn resolve_auto_picks_fastest() {
179 let d = resolve_lm_device_str("test", "auto").unwrap();
180 assert_eq!(d, pick_lm_device());
181 }
182}