Skip to main content

oxicuda_backend/
backend_kind.rs

1//! Enumeration of the compute backends OxiCUDA can dispatch to, with a
2//! default preference ordering used by the selection logic.
3//!
4//! This is pure host-side metadata — it names the backends and ranks them;
5//! it does **not** prove any of them is present. Availability is decided by
6//! the [`crate::registry::BackendRegistry`], which combines this ordering
7//! with runtime probes supplied by each concrete backend crate.
8
9use std::fmt;
10
11/// One of the concrete compute backends OxiCUDA targets.
12///
13/// Variants are ordered from most- to least-preferred for a typical
14/// discrete-GPU workstation; see [`BackendKind::default_priority`].
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum BackendKind {
17    /// NVIDIA CUDA (`oxicuda`).
18    Cuda,
19    /// AMD ROCm / HIP (`oxicuda-rocm`).
20    Rocm,
21    /// Intel oneAPI Level Zero (`oxicuda-levelzero`).
22    LevelZero,
23    /// Khronos Vulkan compute (`oxicuda-vulkan`).
24    Vulkan,
25    /// Apple Metal (`oxicuda-metal`).
26    Metal,
27    /// W3C WebGPU (`oxicuda-webgpu`).
28    WebGpu,
29    /// Pure-Rust host fallback (the CPU reference backend).
30    Cpu,
31}
32
33impl BackendKind {
34    /// Every backend kind, in declaration order.
35    pub const ALL: [BackendKind; 7] = [
36        BackendKind::Cuda,
37        BackendKind::Rocm,
38        BackendKind::LevelZero,
39        BackendKind::Vulkan,
40        BackendKind::Metal,
41        BackendKind::WebGpu,
42        BackendKind::Cpu,
43    ];
44
45    /// Short lowercase identifier matching the backend's `name()`
46    /// (`"cuda"`, `"rocm"`, `"level_zero"`, `"vulkan"`, `"metal"`,
47    /// `"webgpu"`, `"cpu"`).
48    #[must_use]
49    pub const fn as_str(self) -> &'static str {
50        match self {
51            Self::Cuda => "cuda",
52            Self::Rocm => "rocm",
53            Self::LevelZero => "level_zero",
54            Self::Vulkan => "vulkan",
55            Self::Metal => "metal",
56            Self::WebGpu => "webgpu",
57            Self::Cpu => "cpu",
58        }
59    }
60
61    /// Default selection priority — **higher wins**.
62    ///
63    /// Native vendor stacks (CUDA, ROCm, Level Zero, Metal) outrank the
64    /// portable APIs (Vulkan, WebGPU), which in turn outrank the CPU
65    /// fallback. The CPU backend is deliberately the global minimum so it
66    /// is only ever chosen when nothing else is available.
67    #[must_use]
68    pub const fn default_priority(self) -> u32 {
69        match self {
70            Self::Cuda => 100,
71            Self::Rocm => 90,
72            Self::Metal => 85,
73            Self::LevelZero => 80,
74            Self::Vulkan => 60,
75            Self::WebGpu => 50,
76            Self::Cpu => 1,
77        }
78    }
79
80    /// `true` for every backend that targets a real GPU (i.e. everything
81    /// except [`BackendKind::Cpu`]).
82    #[must_use]
83    pub const fn is_gpu(self) -> bool {
84        !matches!(self, Self::Cpu)
85    }
86
87    /// Parse a backend kind from its short identifier (case-insensitive),
88    /// accepting a few common aliases (`"hip"`, `"l0"`, `"wgpu"`, `"host"`).
89    #[must_use]
90    pub fn from_name(name: &str) -> Option<Self> {
91        match name.trim().to_ascii_lowercase().as_str() {
92            "cuda" | "nvidia" => Some(Self::Cuda),
93            "rocm" | "hip" | "amd" => Some(Self::Rocm),
94            "level_zero" | "levelzero" | "l0" | "intel" => Some(Self::LevelZero),
95            "vulkan" | "vk" => Some(Self::Vulkan),
96            "metal" | "mtl" | "apple" => Some(Self::Metal),
97            "webgpu" | "wgpu" => Some(Self::WebGpu),
98            "cpu" | "host" | "reference" => Some(Self::Cpu),
99            _ => None,
100        }
101    }
102}
103
104impl fmt::Display for BackendKind {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.write_str(self.as_str())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use std::collections::HashSet;
114
115    #[test]
116    fn all_covers_every_variant_uniquely() {
117        let set: HashSet<_> = BackendKind::ALL.iter().copied().collect();
118        assert_eq!(set.len(), 7);
119        // ALL must include the CPU fallback and the four native stacks.
120        assert!(set.contains(&BackendKind::Cpu));
121        assert!(set.contains(&BackendKind::Cuda));
122        assert!(set.contains(&BackendKind::Metal));
123    }
124
125    #[test]
126    fn cpu_is_global_minimum_priority() {
127        let cpu = BackendKind::Cpu.default_priority();
128        for k in BackendKind::ALL {
129            if k != BackendKind::Cpu {
130                assert!(
131                    k.default_priority() > cpu,
132                    "{k} must outrank the CPU fallback"
133                );
134            }
135        }
136    }
137
138    #[test]
139    fn cuda_is_global_maximum_priority() {
140        let cuda = BackendKind::Cuda.default_priority();
141        for k in BackendKind::ALL {
142            if k != BackendKind::Cuda {
143                assert!(cuda >= k.default_priority());
144            }
145        }
146    }
147
148    #[test]
149    fn priority_ordering_is_strict_per_pair() {
150        // Native stacks outrank portable APIs.
151        assert!(BackendKind::Rocm.default_priority() > BackendKind::Vulkan.default_priority());
152        assert!(BackendKind::Metal.default_priority() > BackendKind::WebGpu.default_priority());
153        assert!(BackendKind::LevelZero.default_priority() > BackendKind::Vulkan.default_priority());
154    }
155
156    #[test]
157    fn is_gpu_is_true_for_all_but_cpu() {
158        for k in BackendKind::ALL {
159            assert_eq!(k.is_gpu(), k != BackendKind::Cpu);
160        }
161    }
162
163    #[test]
164    fn name_round_trips() {
165        for k in BackendKind::ALL {
166            assert_eq!(BackendKind::from_name(k.as_str()), Some(k));
167        }
168    }
169
170    #[test]
171    fn name_aliases_and_case() {
172        assert_eq!(BackendKind::from_name("HIP"), Some(BackendKind::Rocm));
173        assert_eq!(BackendKind::from_name(" L0 "), Some(BackendKind::LevelZero));
174        assert_eq!(BackendKind::from_name("WGPU"), Some(BackendKind::WebGpu));
175        assert_eq!(BackendKind::from_name("Host"), Some(BackendKind::Cpu));
176        assert_eq!(BackendKind::from_name("nvidia"), Some(BackendKind::Cuda));
177        assert_eq!(BackendKind::from_name("opencl"), None);
178    }
179
180    #[test]
181    fn display_matches_as_str() {
182        assert_eq!(BackendKind::LevelZero.to_string(), "level_zero");
183        assert_eq!(BackendKind::Cpu.to_string(), "cpu");
184    }
185}