Skip to main content

rlx_models_core/
device_capabilities.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Shared backend policy for RLX model crates.
17//!
18//! Every model family in this workspace targets the same seven execution
19//! backends. Call [`validate_standard_device`] at runner / loader build
20//! time; enable matching `rlx-runtime` features on the model crate
21//! (`metal`, `mlx`, `cuda`, `rocm`, `gpu`, `vulkan`, or `all-backends`).
22
23use anyhow::{Result, bail};
24use rlx_runtime::{Device, memory_estimate};
25
26/// Backends every model crate is expected to support when the matching
27/// `rlx-runtime` feature is enabled at build time.
28pub 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
38/// CLI / help string for `--device`.
39pub const STANDARD_DEVICE_NAMES: &str = "auto|cpu|metal|mps|mlx|cuda|rocm|hip|gpu|wgpu|vulkan";
40
41/// [`STANDARD_DEVICE_NAMES`] plus CoreML / ANE when the `coreml` feature is enabled.
42pub const LM_DEVICE_NAMES: &str = "auto|cpu|metal|mps|mlx|cuda|rocm|hip|gpu|wgpu|vulkan|coreml|ane";
43
44/// Preferred causal-LM inference order on this host (excludes ANE — request `coreml` explicitly).
45pub 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
55/// Best available accelerator for text LM inference (CUDA → ROCm → MLX → Metal → … → CPU).
56pub 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
66/// Parse `--device auto` (or empty) via [`pick_lm_device`]; otherwise delegate to `FromStr`.
67pub 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
77/// True when `device` is in [`STANDARD_DEVICES`].
78pub fn is_standard_device(device: Device) -> bool {
79    STANDARD_DEVICES.contains(&device)
80}
81
82/// Causal LM runners: standard backends plus CoreML (`Device::Ane`) with `coreml`.
83pub fn is_lm_device(device: Device) -> bool {
84    is_standard_device(device) || device == Device::Ane
85}
86
87/// Fail fast on exotic runtime devices (TPU, ANE, OpenGL, …).
88pub 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
99/// Like [`validate_standard_device`], but allows `Device::Ane` when built with `coreml`.
100pub 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
113/// `(free_bytes, total_bytes)` for TIDE MoE VRAM budget sizing.
114///
115/// Override with `RLX_CUDA_FREE_BYTES` / `RLX_CUDA_TOTAL_BYTES` or
116/// `RLX_DEVICE_FREE_BYTES` / `RLX_DEVICE_TOTAL_BYTES`. On Apple Silicon
117/// (Metal / MLX), falls back to unified memory when env vars are unset.
118pub 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
146/// SAM v1 also documents `tpu` on [`rlx_sam::Sam::from_safetensors_on`].
147pub 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}