1use std::str::FromStr;
2
3#[derive(Debug, Default, Clone, PartialEq, Eq)]
12pub enum Device {
13 #[default]
15 Default,
16 Discrete { index: usize },
21 Integrated { index: usize },
23 Virtual { index: usize },
25 Cpu,
28}
29
30impl FromStr for Device {
31 type Err = String;
32
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
39 let (kind, idx) = s.split_once(':').unwrap_or((s, "0"));
40 let index: usize = idx
41 .parse()
42 .map_err(|_| format!("invalid device index '{idx}' in '{s}'"))?;
43 match kind {
44 "default" => Ok(Device::Default),
45 "discrete" => Ok(Device::Discrete { index }),
46 "integrated" => Ok(Device::Integrated { index }),
47 "virtual" => Ok(Device::Virtual { index }),
48 "cpu" => Ok(Device::Cpu),
49 other => Err(format!(
50 "unknown device kind '{other}'; expected default, discrete[:N], integrated[:N], virtual[:N], or cpu"
51 )),
52 }
53 }
54}
55
56#[cfg(feature = "cuda")]
57impl Device {
58 pub fn to_cuda(&self) -> Result<cubecl::cuda::CudaDevice, anyhow::Error> {
59 match self {
60 Device::Default => Ok(cubecl::cuda::CudaDevice { index: 0 }),
61 Device::Discrete { index } => Ok(cubecl::cuda::CudaDevice { index: *index }),
62 other => Err(anyhow::anyhow!(
63 "device {other:?} is not supported on the CUDA runtime; use `default` or `discrete[:N]`"
64 )),
65 }
66 }
67}
68
69#[cfg(feature = "rocm")]
70impl Device {
71 pub fn to_amd(&self) -> Result<cubecl::hip::AmdDevice, anyhow::Error> {
72 match self {
73 Device::Default => Ok(cubecl::hip::AmdDevice { index: 0 }),
74 Device::Discrete { index } => Ok(cubecl::hip::AmdDevice { index: *index }),
75 other => Err(anyhow::anyhow!(
76 "device {other:?} is not supported on the ROCm runtime; use `default` or `discrete[:N]`"
77 )),
78 }
79 }
80}
81
82#[cfg(any(feature = "vulkan", feature = "metal"))]
83impl Device {
84 pub fn to_wgpu(&self) -> Result<cubecl::wgpu::WgpuDevice, anyhow::Error> {
85 use cubecl::wgpu::WgpuDevice;
86 Ok(match self {
87 Device::Default => WgpuDevice::DefaultDevice,
88 Device::Discrete { index } => WgpuDevice::DiscreteGpu(*index),
89 Device::Integrated { index } => WgpuDevice::IntegratedGpu(*index),
90 Device::Virtual { index } => WgpuDevice::VirtualGpu(*index),
91 Device::Cpu => WgpuDevice::Cpu,
92 })
93 }
94}
95
96#[cfg(feature = "cpu")]
97impl Device {
98 pub fn to_cpu(&self) -> Result<cubecl::cpu::CpuDevice, anyhow::Error> {
99 match self {
100 Device::Default | Device::Cpu => Ok(cubecl::cpu::CpuDevice),
101 other => Err(anyhow::anyhow!(
102 "device {other:?} is not supported on the CPU runtime; use `default` or `cpu`"
103 )),
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn parse_default() {
114 assert_eq!("default".parse::<Device>().unwrap(), Device::Default);
115 }
116
117 #[test]
118 fn parse_discrete_with_and_without_index() {
119 assert_eq!(
120 "discrete".parse::<Device>().unwrap(),
121 Device::Discrete { index: 0 },
122 );
123 assert_eq!(
124 "discrete:3".parse::<Device>().unwrap(),
125 Device::Discrete { index: 3 },
126 );
127 }
128
129 #[test]
130 fn parse_integrated_virtual_cpu() {
131 assert_eq!(
132 "integrated:1".parse::<Device>().unwrap(),
133 Device::Integrated { index: 1 },
134 );
135 assert_eq!(
136 "virtual:2".parse::<Device>().unwrap(),
137 Device::Virtual { index: 2 },
138 );
139 assert_eq!("cpu".parse::<Device>().unwrap(), Device::Cpu);
140 }
141
142 #[test]
143 fn parse_rejects_unknown_kind() {
144 assert!("unicorn".parse::<Device>().is_err());
145 }
146
147 #[test]
148 fn parse_rejects_non_numeric_index() {
149 assert!("discrete:abc".parse::<Device>().is_err());
150 }
151
152 #[test]
153 fn default_is_default_variant() {
154 assert_eq!(Device::default(), Device::Default);
155 }
156
157 #[cfg(feature = "cpu")]
158 #[test]
159 fn cpu_runtime_rejects_gpu_variants() {
160 assert!(Device::Default.to_cpu().is_ok());
161 assert!(Device::Cpu.to_cpu().is_ok());
162 assert!(Device::Discrete { index: 0 }.to_cpu().is_err());
163 }
164}