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)]
249 fn default() -> Self {
250 #[cfg(feature = "std")]
253 {
254 if let Ok(device_str) = std::env::var("BURN_DEVICE") {
255 match device_str.to_lowercase().as_str() {
256 "cuda" => {
259 #[cfg(feature = "cuda")]
260 return Self::Cube(CubeDevice::Cuda(Default::default()));
261 panic!(
262 "BURN_DEVICE=cuda requested, but the 'cuda' feature is not enabled."
263 );
264 }
265 "rocm" => {
266 #[cfg(feature = "rocm")]
267 return Self::Cube(CubeDevice::Hip(Default::default()));
268 panic!(
269 "BURN_DEVICE=rocm requested, but the 'rocm' feature is not enabled."
270 );
271 }
272 "metal" => {
273 #[cfg(feature = "metal")]
274 return Self::Cube(CubeDevice::Wgpu(
275 WgpuDevice::default().on(WgpuBackend::Metal),
276 ));
277 panic!(
278 "BURN_DEVICE=metal requested, but the 'metal' feature is not enabled."
279 );
280 }
281 "vulkan" => {
282 #[cfg(feature = "vulkan")]
283 return Self::Cube(CubeDevice::Wgpu(
284 WgpuDevice::default().on(WgpuBackend::Vulkan),
285 ));
286 panic!(
287 "BURN_DEVICE=vulkan requested, but the 'vulkan' feature is not enabled."
288 );
289 }
290 "webgpu" => {
291 #[cfg(feature = "webgpu")]
292 return Self::Cube(CubeDevice::Wgpu(
293 WgpuDevice::default().on(WgpuBackend::WebGpu),
294 ));
295 panic!(
296 "BURN_DEVICE=webgpu requested, but the 'webgpu' feature is not enabled."
297 );
298 }
299 "wgpu" => {
300 #[cfg(feature = "wgpu")]
301 return Self::Cube(CubeDevice::Wgpu(Default::default()));
302 panic!(
303 "BURN_DEVICE=wgpu requested, but the 'wgpu' feature is not enabled."
304 );
305 }
306 "cpu" => {
307 #[cfg(feature = "cpu")]
308 return Self::Cube(CubeDevice::Cpu(Default::default()));
309 panic!("BURN_DEVICE=cpu requested, but the 'cpu' feature is not enabled.");
310 }
311 "tch" => {
312 #[cfg(feature = "tch")]
313 return Self::LibTorch(LibTorchDevice::default());
314 panic!("BURN_DEVICE=tch requested, but the 'tch' feature is not enabled.");
315 }
316 "remote" => {
317 #[cfg(feature = "remote")]
318 return Self::Remote(RemoteDevice::default());
319 panic!(
320 "BURN_DEVICE=remote requested, but the 'remote' feature is not enabled."
321 );
322 }
323 "flex" => {
324 #[cfg(feature = "flex")]
325 return Self::Flex(FlexDevice);
326 panic!(
327 "BURN_DEVICE=flex requested, but the 'flex' feature is not enabled."
328 );
329 }
330 "ndarray" => {
331 #[cfg(feature = "ndarray")]
332 return Self::NdArray(NdArrayDevice::default());
333 panic!(
334 "BURN_DEVICE=ndarray requested, but the 'ndarray' feature is not enabled."
335 );
336 }
337 _ => panic!("Unknown BURN_DEVICE override: '{}'.", device_str),
338 }
339 }
340 }
341
342 #[cfg(feature = "cuda")]
348 return Self::Cube(CubeDevice::Cuda(Default::default()));
349
350 #[cfg(feature = "metal")]
351 return Self::Cube(CubeDevice::Wgpu(Default::default()));
352
353 #[cfg(feature = "rocm")]
354 return Self::Cube(CubeDevice::Hip(Default::default()));
355
356 #[cfg(feature = "vulkan")]
357 return Self::Cube(CubeDevice::Wgpu(Default::default()));
358
359 #[cfg(feature = "webgpu")]
360 return Self::Cube(CubeDevice::Wgpu(Default::default()));
361
362 #[cfg(feature = "wgpu")]
363 return Self::Cube(CubeDevice::Wgpu(Default::default()));
364
365 #[cfg(feature = "cpu")]
366 return Self::Cube(CubeDevice::Cpu(Default::default()));
367
368 #[cfg(feature = "tch")]
369 return Self::LibTorch(LibTorchDevice::default());
370
371 #[cfg(feature = "flex")]
373 return Self::Flex(FlexDevice);
374
375 #[cfg(feature = "remote")]
376 return Self::Remote(RemoteDevice::default());
377
378 #[cfg(feature = "ndarray")]
379 return Self::NdArray(NdArrayDevice::default());
380
381 panic!(
382 "No execution backend is enabled. Enable a Burn backend feature such as `flex`, \
383 `wgpu`, or `cuda`. To record a graph without executing it, enable `capture` \
384 and use Device::capture()."
385 );
386 }
387}
388
389impl PartialEq for DispatchDevice {
390 fn eq(&self, other: &Self) -> bool {
395 match (self, other) {
396 #[cfg(feature = "autodiff")]
398 (DispatchDevice::Autodiff(a), DispatchDevice::Autodiff(b)) => {
399 a.inner.as_ref() == b.inner.as_ref()
400 }
401 #[cfg(feature = "autodiff")]
403 (DispatchDevice::Autodiff(a), b) => a.inner.as_ref() == b,
404 #[cfg(feature = "autodiff")]
405 (a, DispatchDevice::Autodiff(b)) => a == b.inner.as_ref(),
406 #[cfg(cube_backend)]
407 (Self::Cube(a), Self::Cube(b)) => a == b,
408 #[cfg(feature = "flex")]
409 (Self::Flex(a), Self::Flex(b)) => a == b,
410 #[cfg(feature = "ndarray")]
411 (Self::NdArray(a), Self::NdArray(b)) => a == b,
412 #[cfg(feature = "tch")]
413 (Self::LibTorch(a), Self::LibTorch(b)) => a == b,
414 #[cfg(feature = "remote")]
415 (Self::Remote(a), Self::Remote(b)) => a == b,
416 #[cfg(feature = "capture")]
417 (Self::Capture(a), Self::Capture(b)) => a == b,
418 #[allow(unreachable_patterns)]
419 (_, _) => false,
420 }
421 }
422}
423
424const INTERNAL_ID_MASK: u16 = 0x00FF;
425const BACKEND_SHIFT: u32 = 8;
426
427impl DispatchDevice {
428 #[cfg(feature = "capture")]
430 #[doc(hidden)]
431 pub fn capture() -> Self {
432 Self::Capture(CaptureDevice::default())
433 }
434
435 #[cfg(feature = "autodiff")]
436 pub fn autodiff(device: impl Into<DispatchDevice>) -> DispatchDevice {
439 Self::autodiff_with_gradient_checkpointing(device, GradientCheckpointingStrategy::Disabled)
440 }
441 #[cfg(feature = "autodiff")]
442 pub fn autodiff_with_gradient_checkpointing(
445 device: impl Into<DispatchDevice>,
446 checkpointing: GradientCheckpointingStrategy,
447 ) -> DispatchDevice {
448 let device = device.into();
449 DispatchDevice::Autodiff(AutodiffDevice::new(device, checkpointing))
450 }
451
452 pub fn inner(self) -> Self {
454 #[cfg(feature = "autodiff")]
455 if let DispatchDevice::Autodiff(device) = self {
456 return *device.inner;
457 }
458
459 self
460 }
461
462 fn backend_id(&self) -> DispatchDeviceId {
464 match self {
465 #[cfg(not(backend_enabled))]
466 Self::Unavailable(never) => never.unreachable(),
467 #[cfg(cube_backend)]
468 Self::Cube(_) => DispatchDeviceId::Cube,
469 #[cfg(feature = "flex")]
470 Self::Flex(_) => DispatchDeviceId::Flex,
471 #[cfg(feature = "ndarray")]
472 Self::NdArray(_) => DispatchDeviceId::NdArray,
473 #[cfg(feature = "tch")]
474 Self::LibTorch(_) => DispatchDeviceId::LibTorch,
475 #[cfg(feature = "remote")]
476 Self::Remote(_) => DispatchDeviceId::Remote,
477 #[cfg(feature = "capture")]
478 Self::Capture(_) => DispatchDeviceId::Capture,
479 #[cfg(feature = "autodiff")]
480 Self::Autodiff(device) => device.inner.backend_id(),
481 }
482 }
483
484 fn encode_type_id(&self, backend_type_id: u16) -> u16 {
486 let internal_type_id = backend_type_id & INTERNAL_ID_MASK;
488 let backend = u16::from(self.backend_id()) << BACKEND_SHIFT;
490 backend | internal_type_id
491 }
492
493 pub(crate) fn decode_type_id(type_id: u16) -> (DispatchDeviceId, u16) {
495 let backend_raw = type_id >> BACKEND_SHIFT;
496 let internal_type_id = type_id & INTERNAL_ID_MASK;
497
498 let backend = DispatchDeviceId::try_from(backend_raw).expect("Unknown DispatchDevice ID");
499
500 (backend, internal_type_id)
501 }
502}
503
504#[allow(missing_docs)]
505#[derive(Debug, Clone, Copy, PartialEq, Eq)]
506#[repr(u16)]
507pub enum DispatchDeviceId {
508 Cube = 0,
510 Flex = 4,
511 LibTorch = 5,
512 NdArray = 6,
513 Remote = 10,
514 Capture = 11,
515}
516
517impl From<DispatchDeviceId> for u16 {
518 fn from(variant: DispatchDeviceId) -> Self {
519 variant as u16
520 }
521}
522
523impl TryFrom<u16> for DispatchDeviceId {
524 type Error = ();
525
526 fn try_from(value: u16) -> Result<Self, Self::Error> {
527 match value {
528 #[cfg(cube_backend)]
529 0 => Ok(Self::Cube),
530 #[cfg(feature = "flex")]
531 4 => Ok(Self::Flex),
532 #[cfg(feature = "tch")]
533 5 => Ok(Self::LibTorch),
534 #[cfg(feature = "ndarray")]
535 6 => Ok(Self::NdArray),
536 #[cfg(feature = "remote")]
537 10 => Ok(Self::Remote),
538 #[cfg(feature = "capture")]
539 11 => Ok(Self::Capture),
540 _ => Err(()),
541 }
542 }
543}
544
545impl DeviceOps for DispatchDevice {
546 fn defaults(&self) -> DeviceSettings {
547 match self {
548 #[cfg(not(backend_enabled))]
549 Self::Unavailable(never) => never.unreachable(),
550 #[cfg(cube_backend)]
551 Self::Cube(device) => device.defaults(),
552 #[cfg(feature = "flex")]
553 Self::Flex(device) => device.defaults(),
554 #[cfg(feature = "ndarray")]
555 Self::NdArray(device) => device.defaults(),
556 #[cfg(feature = "tch")]
557 Self::LibTorch(device) => device.defaults(),
558 #[cfg(feature = "remote")]
559 Self::Remote(device) => device.defaults(),
560 #[cfg(feature = "capture")]
561 Self::Capture(device) => device.defaults(),
562 #[cfg(feature = "autodiff")]
563 Self::Autodiff(device) => device.inner.defaults(),
564 }
565 }
566}
567
568impl burn_backend::Device for DispatchDevice {
569 fn from_id(mut device_id: DeviceId) -> Self {
570 let (dispatch_id, backend_type_id) = Self::decode_type_id(device_id.type_id);
571 device_id.type_id = backend_type_id;
572
573 match dispatch_id {
574 #[cfg(cube_backend)]
575 DispatchDeviceId::Cube => Self::Cube(burn_backend::Device::from_id(device_id)),
576 #[cfg(feature = "flex")]
577 DispatchDeviceId::Flex => Self::Flex(FlexDevice::from_id(device_id)),
578 #[cfg(feature = "ndarray")]
579 DispatchDeviceId::NdArray => Self::NdArray(NdArrayDevice::from_id(device_id)),
580 #[cfg(feature = "tch")]
581 DispatchDeviceId::LibTorch => Self::LibTorch(LibTorchDevice::from_id(device_id)),
582 #[cfg(feature = "remote")]
583 DispatchDeviceId::Remote => Self::Remote(RemoteDevice::from_id(device_id)),
584 #[cfg(feature = "capture")]
585 DispatchDeviceId::Capture => Self::Capture(CaptureDevice::from_id(device_id)),
586 _ => unreachable!("No backend feature enabled."),
587 }
588 }
589
590 fn to_id(&self) -> DeviceId {
591 let mut device_id: DeviceId = match self {
592 #[cfg(not(backend_enabled))]
593 Self::Unavailable(never) => never.unreachable(),
594 #[cfg(cube_backend)]
595 Self::Cube(device) => device.to_id(),
596 #[cfg(feature = "flex")]
597 Self::Flex(device) => device.to_id(),
598 #[cfg(feature = "ndarray")]
599 Self::NdArray(device) => device.to_id(),
600 #[cfg(feature = "tch")]
601 Self::LibTorch(device) => device.to_id(),
602 #[cfg(feature = "remote")]
603 Self::Remote(device) => device.to_id(),
604 #[cfg(feature = "capture")]
605 Self::Capture(device) => device.to_id(),
606 #[cfg(feature = "autodiff")]
607 Self::Autodiff(device) => device.inner.to_id(),
608 };
609 device_id.type_id = self.encode_type_id(device_id.type_id);
610 device_id
611 }
612}
613
614#[cfg(cube_backend)]
616impl From<CubeDevice> for DispatchDevice {
617 fn from(device: CubeDevice) -> Self {
618 DispatchDevice::Cube(device)
619 }
620}
621
622#[cfg(feature = "cpu")]
626impl From<CpuDevice> for DispatchDevice {
627 fn from(device: CpuDevice) -> Self {
628 DispatchDevice::Cube(CubeDevice::Cpu(device))
629 }
630}
631
632#[cfg(feature = "cuda")]
633impl From<CudaDevice> for DispatchDevice {
634 fn from(device: CudaDevice) -> Self {
635 DispatchDevice::Cube(CubeDevice::Cuda(device))
636 }
637}
638
639#[cfg(feature = "rocm")]
640impl From<RocmDevice> for DispatchDevice {
641 fn from(device: RocmDevice) -> Self {
642 DispatchDevice::Cube(CubeDevice::Hip(device))
643 }
644}
645
646#[cfg(any(
647 feature = "wgpu",
648 feature = "metal",
649 feature = "vulkan",
650 feature = "webgpu"
651))]
652impl From<WgpuDevice> for DispatchDevice {
653 fn from(device: WgpuDevice) -> Self {
654 DispatchDevice::Cube(CubeDevice::Wgpu(device))
655 }
656}
657
658#[cfg(feature = "flex")]
659impl From<FlexDevice> for DispatchDevice {
660 fn from(device: FlexDevice) -> Self {
661 DispatchDevice::Flex(device)
662 }
663}
664
665#[cfg(feature = "ndarray")]
666impl From<NdArrayDevice> for DispatchDevice {
667 fn from(device: NdArrayDevice) -> Self {
668 DispatchDevice::NdArray(device)
669 }
670}
671
672#[cfg(feature = "tch")]
673impl From<LibTorchDevice> for DispatchDevice {
674 fn from(device: LibTorchDevice) -> Self {
675 DispatchDevice::LibTorch(device)
676 }
677}
678
679#[cfg(feature = "remote")]
680impl From<RemoteDevice> for DispatchDevice {
681 fn from(device: RemoteDevice) -> Self {
682 DispatchDevice::Remote(device)
683 }
684}
685
686#[cfg(all(test, not(backend_enabled)))]
687mod no_backend_tests {
688 #[test]
689 #[should_panic(expected = "No execution backend is enabled. Enable a Burn backend feature")]
690 fn default_requires_backend() {
691 super::DispatchDevice::default();
692 }
693}
694
695#[cfg(all(test, any(feature = "capture", feature = "remote-websocket")))]
696mod tests {
697 use super::*;
698 use burn_backend::Device;
699
700 #[test]
701 #[cfg(feature = "remote-websocket")]
702 fn remote_device_id_round_trips_through_dispatch() {
703 let device = DispatchDevice::from(RemoteDevice::default());
704 let restored = DispatchDevice::from_id(device.to_id());
705
706 assert_eq!(restored, device);
707 }
708
709 #[test]
710 #[cfg(feature = "capture")]
711 fn capture_device_id_round_trips_through_dispatch() {
712 let device = DispatchDevice::capture();
713 let restored = DispatchDevice::from_id(device.to_id());
714
715 assert_eq!(restored, device);
716 }
717}