1use burn_backend::{DeviceId, DeviceOps, DeviceSettings};
2
3#[allow(unused_imports)] use crate::devices::*;
5
6#[cfg(feature = "capture")]
7use burn_capture::CaptureDevice;
8
9#[cfg(feature = "autodiff")]
10use alloc::boxed::Box;
11
12#[cfg(feature = "cubecl")]
15use alloc::vec::Vec;
16#[cfg(feature = "cubecl")]
17use burn_backend::cubecl::{DeviceIdentity, ThroughputError, ThroughputKey, ThroughputValue};
18#[cfg(cube_backend)]
21use burn_backend::cubecl::measure_peak_throughput;
22
23#[derive(Clone, Eq)]
40pub enum DispatchDevice {
41 #[cfg(not(backend_enabled))]
42 #[doc(hidden)]
43 Unavailable(crate::NoBackend),
44 #[cfg(cube_backend)]
47 Cube(CubeDevice),
48
49 #[cfg(feature = "flex")]
51 Flex(FlexDevice),
52
53 #[cfg(feature = "ndarray")]
55 NdArray(NdArrayDevice),
56
57 #[cfg(feature = "tch")]
59 LibTorch(LibTorchDevice),
60
61 #[cfg(feature = "remote")]
63 Remote(RemoteDevice),
64
65 #[cfg(feature = "capture")]
67 Capture(CaptureDevice),
68
69 #[cfg(feature = "autodiff")]
71 Autodiff(AutodiffDevice),
72}
73
74#[cfg(feature = "cubecl")]
75impl DispatchDevice {
76 pub fn identity(&self) -> Option<DeviceIdentity> {
79 match self {
80 #[cfg(cube_backend)]
81 DispatchDevice::Cube(device) => Some(device.client().properties().identity.clone()),
82 #[cfg(feature = "autodiff")]
83 DispatchDevice::Autodiff(device) => device.inner.identity(),
84 #[allow(unreachable_patterns)]
85 _ => None,
86 }
87 }
88
89 #[cfg_attr(not(cube_backend), allow(unused_variables))]
98 pub fn performance_stats(
99 &self,
100 keys: &[ThroughputKey],
101 ) -> Vec<Result<ThroughputValue, ThroughputError>> {
102 match self {
105 #[cfg(not(backend_enabled))]
106 Self::Unavailable(never) => never.unreachable(),
107 #[cfg(cube_backend)]
108 DispatchDevice::Cube(device) => {
109 let client = device.client();
110 keys.iter()
111 .map(|key| measure_peak_throughput(&client, *key))
112 .collect()
113 }
114 #[cfg(feature = "autodiff")]
116 DispatchDevice::Autodiff(device) => device.performance_stats(keys),
117
118 #[cfg(feature = "flex")]
120 DispatchDevice::Flex(_) => Vec::new(),
121 #[cfg(feature = "ndarray")]
122 DispatchDevice::NdArray(_) => Vec::new(),
123 #[cfg(feature = "tch")]
124 DispatchDevice::LibTorch(_) => Vec::new(),
125
126 #[cfg(feature = "remote")]
128 DispatchDevice::Remote(_) => Vec::new(),
129 #[cfg(feature = "capture")]
130 DispatchDevice::Capture(_) => Vec::new(),
131 }
132 }
133}
134
135#[cfg(feature = "autodiff")]
136#[derive(Debug, Clone)]
141pub struct AutodiffDevice {
142 pub(crate) inner: Box<DispatchDevice>,
143 pub(crate) checkpointing: GradientCheckpointingStrategy,
144}
145
146#[cfg(feature = "autodiff")]
155impl PartialEq for AutodiffDevice {
156 fn eq(&self, other: &Self) -> bool {
157 self.inner == other.inner
158 }
159}
160
161#[cfg(feature = "autodiff")]
162impl Eq for AutodiffDevice {}
163
164#[cfg(feature = "autodiff")]
165impl AutodiffDevice {
166 pub(crate) fn new(
167 device: DispatchDevice,
168 checkpointing: GradientCheckpointingStrategy,
169 ) -> Self {
170 Self {
171 inner: Box::new(device),
172 checkpointing,
173 }
174 }
175
176 pub fn inner(self) -> DispatchDevice {
178 *self.inner
179 }
180
181 pub fn gradient_checkpointing_strategy(&self) -> GradientCheckpointingStrategy {
183 self.checkpointing
184 }
185}
186
187#[cfg(feature = "autodiff")]
188impl core::ops::Deref for AutodiffDevice {
190 type Target = DispatchDevice;
191
192 fn deref(&self) -> &Self::Target {
193 &self.inner
194 }
195}
196
197#[allow(missing_docs)]
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
199#[repr(u8)]
201pub enum GradientCheckpointingStrategy {
202 Balanced,
204 #[default]
206 Disabled,
207}
208
209impl core::fmt::Debug for DispatchDevice {
210 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
211 match self {
212 #[cfg(not(backend_enabled))]
213 Self::Unavailable(never) => never.unreachable(),
214 #[cfg(cube_backend)]
215 Self::Cube(device) => f.debug_tuple("Cube").field(device).finish(),
216 #[cfg(feature = "flex")]
217 Self::Flex(device) => f.debug_tuple("Flex").field(device).finish(),
218 #[cfg(feature = "ndarray")]
219 Self::NdArray(device) => f.debug_tuple("NdArray").field(device).finish(),
220 #[cfg(feature = "tch")]
221 Self::LibTorch(device) => f.debug_tuple("LibTorch").field(device).finish(),
222 #[cfg(feature = "remote")]
223 Self::Remote(device) => f.debug_tuple("Remote").field(device).finish(),
224 #[cfg(feature = "capture")]
225 Self::Capture(device) => f.debug_tuple("Capture").field(device).finish(),
226 #[cfg(feature = "autodiff")]
227 Self::Autodiff(device) => f
229 .debug_struct("Autodiff")
230 .field("device", &device.inner)
231 .field("checkpointing", &device.checkpointing)
232 .finish(),
233 }
234 }
235}
236
237impl Default for DispatchDevice {
238 #[allow(unreachable_code)]
244 fn default() -> Self {
245 #[cfg(feature = "std")]
248 {
249 if let Ok(device_str) = std::env::var("BURN_DEVICE") {
250 match device_str.to_lowercase().as_str() {
251 "cuda" => {
255 #[cfg(feature = "cuda")]
256 return Self::Cube(CubeDevice::Cuda(Default::default()));
257 panic!(
258 "BURN_DEVICE=cuda requested, but the 'cuda' feature is not enabled."
259 );
260 }
261 "rocm" => {
262 #[cfg(feature = "rocm")]
263 return Self::Cube(CubeDevice::Hip(Default::default()));
264 panic!(
265 "BURN_DEVICE=rocm requested, but the 'rocm' feature is not enabled."
266 );
267 }
268 "metal" | "vulkan" | "webgpu" | "wgpu" => {
269 #[cfg(any(
270 feature = "metal",
271 feature = "vulkan",
272 feature = "webgpu",
273 feature = "wgpu"
274 ))]
275 return Self::Cube(CubeDevice::Wgpu(Default::default()));
276 panic!(
277 "BURN_DEVICE={device_str} requested, but no wgpu feature is enabled."
278 );
279 }
280 "cpu" => {
281 #[cfg(feature = "cpu")]
282 return Self::Cube(CubeDevice::Cpu(Default::default()));
283 panic!("BURN_DEVICE=cpu requested, but the 'cpu' feature is not enabled.");
284 }
285 "tch" => {
286 #[cfg(feature = "tch")]
287 return Self::LibTorch(LibTorchDevice::default());
288 panic!("BURN_DEVICE=tch requested, but the 'tch' feature is not enabled.");
289 }
290 "remote" => {
291 #[cfg(feature = "remote")]
292 return Self::Remote(RemoteDevice::default());
293 panic!(
294 "BURN_DEVICE=remote requested, but the 'remote' feature is not enabled."
295 );
296 }
297 "flex" => {
298 #[cfg(feature = "flex")]
299 return Self::Flex(FlexDevice);
300 panic!(
301 "BURN_DEVICE=flex requested, but the 'flex' feature is not enabled."
302 );
303 }
304 "ndarray" => {
305 #[cfg(feature = "ndarray")]
306 return Self::NdArray(NdArrayDevice::default());
307 panic!(
308 "BURN_DEVICE=ndarray requested, but the 'ndarray' feature is not enabled."
309 );
310 }
311 _ => panic!("Unknown BURN_DEVICE override: '{}'.", device_str),
312 }
313 }
314 }
315
316 #[cfg(feature = "cuda")]
322 return Self::Cube(CubeDevice::Cuda(Default::default()));
323
324 #[cfg(feature = "metal")]
325 return Self::Cube(CubeDevice::Wgpu(Default::default()));
326
327 #[cfg(feature = "rocm")]
328 return Self::Cube(CubeDevice::Hip(Default::default()));
329
330 #[cfg(feature = "vulkan")]
331 return Self::Cube(CubeDevice::Wgpu(Default::default()));
332
333 #[cfg(feature = "webgpu")]
334 return Self::Cube(CubeDevice::Wgpu(Default::default()));
335
336 #[cfg(feature = "wgpu")]
337 return Self::Cube(CubeDevice::Wgpu(Default::default()));
338
339 #[cfg(feature = "cpu")]
340 return Self::Cube(CubeDevice::Cpu(Default::default()));
341
342 #[cfg(feature = "tch")]
343 return Self::LibTorch(LibTorchDevice::default());
344
345 #[cfg(feature = "flex")]
347 return Self::Flex(FlexDevice);
348
349 #[cfg(feature = "remote")]
350 return Self::Remote(RemoteDevice::default());
351
352 #[cfg(feature = "ndarray")]
353 return Self::NdArray(NdArrayDevice::default());
354
355 panic!(
356 "No execution backend is enabled. Enable a Burn backend feature such as `flex`, \
357 `wgpu`, or `cuda`. To record a graph without executing it, enable `capture` \
358 and use Device::capture()."
359 );
360 }
361}
362
363impl PartialEq for DispatchDevice {
364 fn eq(&self, other: &Self) -> bool {
369 match (self, other) {
370 #[cfg(feature = "autodiff")]
372 (DispatchDevice::Autodiff(a), DispatchDevice::Autodiff(b)) => {
373 a.inner.as_ref() == b.inner.as_ref()
374 }
375 #[cfg(feature = "autodiff")]
377 (DispatchDevice::Autodiff(a), b) => a.inner.as_ref() == b,
378 #[cfg(feature = "autodiff")]
379 (a, DispatchDevice::Autodiff(b)) => a == b.inner.as_ref(),
380 #[cfg(cube_backend)]
381 (Self::Cube(a), Self::Cube(b)) => a == b,
382 #[cfg(feature = "flex")]
383 (Self::Flex(a), Self::Flex(b)) => a == b,
384 #[cfg(feature = "ndarray")]
385 (Self::NdArray(a), Self::NdArray(b)) => a == b,
386 #[cfg(feature = "tch")]
387 (Self::LibTorch(a), Self::LibTorch(b)) => a == b,
388 #[cfg(feature = "remote")]
389 (Self::Remote(a), Self::Remote(b)) => a == b,
390 #[cfg(feature = "capture")]
391 (Self::Capture(a), Self::Capture(b)) => a == b,
392 #[allow(unreachable_patterns)]
393 (_, _) => false,
394 }
395 }
396}
397
398const INTERNAL_ID_MASK: u16 = 0x00FF;
399const BACKEND_SHIFT: u32 = 8;
400
401impl DispatchDevice {
402 #[cfg(feature = "capture")]
404 #[doc(hidden)]
405 pub fn capture() -> Self {
406 Self::Capture(CaptureDevice::default())
407 }
408
409 #[cfg(feature = "autodiff")]
410 pub fn autodiff(device: impl Into<DispatchDevice>) -> DispatchDevice {
413 Self::autodiff_with_gradient_checkpointing(device, GradientCheckpointingStrategy::Disabled)
414 }
415 #[cfg(feature = "autodiff")]
416 pub fn autodiff_with_gradient_checkpointing(
419 device: impl Into<DispatchDevice>,
420 checkpointing: GradientCheckpointingStrategy,
421 ) -> DispatchDevice {
422 let device = device.into();
423 DispatchDevice::Autodiff(AutodiffDevice::new(device, checkpointing))
424 }
425
426 pub fn inner(self) -> Self {
428 #[cfg(feature = "autodiff")]
429 if let DispatchDevice::Autodiff(device) = self {
430 return *device.inner;
431 }
432
433 self
434 }
435
436 fn backend_id(&self) -> DispatchDeviceId {
438 match self {
439 #[cfg(not(backend_enabled))]
440 Self::Unavailable(never) => never.unreachable(),
441 #[cfg(cube_backend)]
442 Self::Cube(_) => DispatchDeviceId::Cube,
443 #[cfg(feature = "flex")]
444 Self::Flex(_) => DispatchDeviceId::Flex,
445 #[cfg(feature = "ndarray")]
446 Self::NdArray(_) => DispatchDeviceId::NdArray,
447 #[cfg(feature = "tch")]
448 Self::LibTorch(_) => DispatchDeviceId::LibTorch,
449 #[cfg(feature = "remote")]
450 Self::Remote(_) => DispatchDeviceId::Remote,
451 #[cfg(feature = "capture")]
452 Self::Capture(_) => DispatchDeviceId::Capture,
453 #[cfg(feature = "autodiff")]
454 Self::Autodiff(device) => device.inner.backend_id(),
455 }
456 }
457
458 fn encode_type_id(&self, backend_type_id: u16) -> u16 {
460 let internal_type_id = backend_type_id & INTERNAL_ID_MASK;
462 let backend = u16::from(self.backend_id()) << BACKEND_SHIFT;
464 backend | internal_type_id
465 }
466
467 pub(crate) fn decode_type_id(type_id: u16) -> (DispatchDeviceId, u16) {
469 let backend_raw = type_id >> BACKEND_SHIFT;
470 let internal_type_id = type_id & INTERNAL_ID_MASK;
471
472 let backend = DispatchDeviceId::try_from(backend_raw).expect("Unknown DispatchDevice ID");
473
474 (backend, internal_type_id)
475 }
476}
477
478#[allow(missing_docs)]
479#[derive(Debug, Clone, Copy, PartialEq, Eq)]
480#[repr(u16)]
481pub enum DispatchDeviceId {
482 Cube = 0,
484 Flex = 4,
485 LibTorch = 5,
486 NdArray = 6,
487 Remote = 10,
488 Capture = 11,
489}
490
491impl From<DispatchDeviceId> for u16 {
492 fn from(variant: DispatchDeviceId) -> Self {
493 variant as u16
494 }
495}
496
497impl TryFrom<u16> for DispatchDeviceId {
498 type Error = ();
499
500 fn try_from(value: u16) -> Result<Self, Self::Error> {
501 match value {
502 #[cfg(cube_backend)]
503 0 => Ok(Self::Cube),
504 #[cfg(feature = "flex")]
505 4 => Ok(Self::Flex),
506 #[cfg(feature = "tch")]
507 5 => Ok(Self::LibTorch),
508 #[cfg(feature = "ndarray")]
509 6 => Ok(Self::NdArray),
510 #[cfg(feature = "remote")]
511 10 => Ok(Self::Remote),
512 #[cfg(feature = "capture")]
513 11 => Ok(Self::Capture),
514 _ => Err(()),
515 }
516 }
517}
518
519impl DeviceOps for DispatchDevice {
520 fn defaults(&self) -> DeviceSettings {
521 match self {
522 #[cfg(not(backend_enabled))]
523 Self::Unavailable(never) => never.unreachable(),
524 #[cfg(cube_backend)]
525 Self::Cube(device) => device.defaults(),
526 #[cfg(feature = "flex")]
527 Self::Flex(device) => device.defaults(),
528 #[cfg(feature = "ndarray")]
529 Self::NdArray(device) => device.defaults(),
530 #[cfg(feature = "tch")]
531 Self::LibTorch(device) => device.defaults(),
532 #[cfg(feature = "remote")]
533 Self::Remote(device) => device.defaults(),
534 #[cfg(feature = "capture")]
535 Self::Capture(device) => device.defaults(),
536 #[cfg(feature = "autodiff")]
537 Self::Autodiff(device) => device.inner.defaults(),
538 }
539 }
540}
541
542impl burn_backend::Device for DispatchDevice {
543 fn from_id(mut device_id: DeviceId) -> Self {
544 let (dispatch_id, backend_type_id) = Self::decode_type_id(device_id.type_id);
545 device_id.type_id = backend_type_id;
546
547 match dispatch_id {
548 #[cfg(cube_backend)]
549 DispatchDeviceId::Cube => Self::Cube(burn_backend::Device::from_id(device_id)),
550 #[cfg(feature = "flex")]
551 DispatchDeviceId::Flex => Self::Flex(FlexDevice::from_id(device_id)),
552 #[cfg(feature = "ndarray")]
553 DispatchDeviceId::NdArray => Self::NdArray(NdArrayDevice::from_id(device_id)),
554 #[cfg(feature = "tch")]
555 DispatchDeviceId::LibTorch => Self::LibTorch(LibTorchDevice::from_id(device_id)),
556 #[cfg(feature = "remote")]
557 DispatchDeviceId::Remote => Self::Remote(RemoteDevice::from_id(device_id)),
558 #[cfg(feature = "capture")]
559 DispatchDeviceId::Capture => Self::Capture(CaptureDevice::from_id(device_id)),
560 _ => unreachable!("No backend feature enabled."),
561 }
562 }
563
564 fn to_id(&self) -> DeviceId {
565 let mut device_id: DeviceId = match self {
566 #[cfg(not(backend_enabled))]
567 Self::Unavailable(never) => never.unreachable(),
568 #[cfg(cube_backend)]
569 Self::Cube(device) => device.to_id(),
570 #[cfg(feature = "flex")]
571 Self::Flex(device) => device.to_id(),
572 #[cfg(feature = "ndarray")]
573 Self::NdArray(device) => device.to_id(),
574 #[cfg(feature = "tch")]
575 Self::LibTorch(device) => device.to_id(),
576 #[cfg(feature = "remote")]
577 Self::Remote(device) => device.to_id(),
578 #[cfg(feature = "capture")]
579 Self::Capture(device) => device.to_id(),
580 #[cfg(feature = "autodiff")]
581 Self::Autodiff(device) => device.inner.to_id(),
582 };
583 device_id.type_id = self.encode_type_id(device_id.type_id);
584 device_id
585 }
586}
587
588#[cfg(cube_backend)]
590impl From<CubeDevice> for DispatchDevice {
591 fn from(device: CubeDevice) -> Self {
592 DispatchDevice::Cube(device)
593 }
594}
595
596#[cfg(feature = "cpu")]
600impl From<CpuDevice> for DispatchDevice {
601 fn from(device: CpuDevice) -> Self {
602 DispatchDevice::Cube(CubeDevice::Cpu(device))
603 }
604}
605
606#[cfg(feature = "cuda")]
607impl From<CudaDevice> for DispatchDevice {
608 fn from(device: CudaDevice) -> Self {
609 DispatchDevice::Cube(CubeDevice::Cuda(device))
610 }
611}
612
613#[cfg(feature = "rocm")]
614impl From<RocmDevice> for DispatchDevice {
615 fn from(device: RocmDevice) -> Self {
616 DispatchDevice::Cube(CubeDevice::Hip(device))
617 }
618}
619
620#[cfg(any(
621 feature = "wgpu",
622 feature = "metal",
623 feature = "vulkan",
624 feature = "webgpu"
625))]
626impl From<WgpuDevice> for DispatchDevice {
627 fn from(device: WgpuDevice) -> Self {
628 DispatchDevice::Cube(CubeDevice::Wgpu(device))
629 }
630}
631
632#[cfg(feature = "flex")]
633impl From<FlexDevice> for DispatchDevice {
634 fn from(device: FlexDevice) -> Self {
635 DispatchDevice::Flex(device)
636 }
637}
638
639#[cfg(feature = "ndarray")]
640impl From<NdArrayDevice> for DispatchDevice {
641 fn from(device: NdArrayDevice) -> Self {
642 DispatchDevice::NdArray(device)
643 }
644}
645
646#[cfg(feature = "tch")]
647impl From<LibTorchDevice> for DispatchDevice {
648 fn from(device: LibTorchDevice) -> Self {
649 DispatchDevice::LibTorch(device)
650 }
651}
652
653#[cfg(feature = "remote")]
654impl From<RemoteDevice> for DispatchDevice {
655 fn from(device: RemoteDevice) -> Self {
656 DispatchDevice::Remote(device)
657 }
658}
659
660#[cfg(all(test, not(backend_enabled)))]
661mod no_backend_tests {
662 #[test]
663 #[should_panic(expected = "No execution backend is enabled. Enable a Burn backend feature")]
664 fn default_requires_backend() {
665 super::DispatchDevice::default();
666 }
667}
668
669#[cfg(all(test, feature = "capture"))]
670mod tests {
671 use super::*;
672 use burn_backend::Device;
673
674 #[test]
675 fn capture_device_id_round_trips_through_dispatch() {
676 let device = DispatchDevice::capture();
677 let restored = DispatchDevice::from_id(device.to_id());
678
679 assert_eq!(restored, device);
680 }
681}