oxidelake_core/
hardware.rs1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8use crate::EngineError;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[non_exhaustive]
17pub enum BackendKind {
18 CpuSimd,
20 Cuda,
22 Metal,
24}
25
26impl BackendKind {
27 pub const ALL: [BackendKind; 3] = [BackendKind::Cuda, BackendKind::Metal, BackendKind::CpuSimd];
29
30 pub const fn as_str(self) -> &'static str {
33 match self {
34 BackendKind::CpuSimd => "cpu",
35 BackendKind::Cuda => "cuda",
36 BackendKind::Metal => "metal",
37 }
38 }
39
40 pub const fn is_gpu(self) -> bool {
42 !matches!(self, BackendKind::CpuSimd)
43 }
44}
45
46impl fmt::Display for BackendKind {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(self.as_str())
49 }
50}
51
52impl FromStr for BackendKind {
53 type Err = EngineError;
54
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 match s.trim().to_ascii_lowercase().as_str() {
57 "cpu" | "cpusimd" | "cpu-simd" | "cpu_simd" => Ok(BackendKind::CpuSimd),
58 "cuda" => Ok(BackendKind::Cuda),
59 "metal" => Ok(BackendKind::Metal),
60 other => Err(EngineError::plan(format!(
61 "unknown backend '{other}'; expected one of cpu, cuda, metal"
62 ))),
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
69pub struct DeviceId {
70 pub backend: BackendKind,
72 pub ordinal: u32,
74}
75
76impl DeviceId {
77 pub const CPU: DeviceId = DeviceId {
79 backend: BackendKind::CpuSimd,
80 ordinal: 0,
81 };
82
83 pub const fn new(backend: BackendKind, ordinal: u32) -> Self {
85 Self { backend, ordinal }
86 }
87}
88
89impl fmt::Display for DeviceId {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{}:{}", self.backend, self.ordinal)
92 }
93}
94
95#[cfg(test)]
96#[allow(clippy::unwrap_used, clippy::expect_used)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn parses_all_names_case_insensitively() {
102 assert_eq!("cpu".parse::<BackendKind>().unwrap(), BackendKind::CpuSimd);
103 assert_eq!(" CUDA ".parse::<BackendKind>().unwrap(), BackendKind::Cuda);
104 assert_eq!("Metal".parse::<BackendKind>().unwrap(), BackendKind::Metal);
105 assert!("tpu".parse::<BackendKind>().is_err());
106 }
107
108 #[test]
109 fn display_round_trips_through_parse() {
110 for kind in BackendKind::ALL {
111 assert_eq!(kind.as_str().parse::<BackendKind>().unwrap(), kind);
112 }
113 }
114
115 #[test]
116 fn device_id_display() {
117 assert_eq!(DeviceId::new(BackendKind::Cuda, 1).to_string(), "cuda:1");
118 assert_eq!(DeviceId::CPU.to_string(), "cpu:0");
119 assert!(!BackendKind::CpuSimd.is_gpu());
120 assert!(BackendKind::Metal.is_gpu());
121 }
122}