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