1use burn_backend::{DeviceId, DeviceOps, DeviceSettings};
2
3#[allow(unused_imports)] use crate::devices::*;
5
6#[cfg(feature = "capture")]
7use burn_capture::CaptureDevice;
8#[cfg(feature = "group")]
9use burn_group::{GroupDevice, GroupError};
10
11#[cfg(feature = "autodiff")]
12use alloc::boxed::Box;
13
14#[cfg(feature = "cubecl")]
17use alloc::vec::Vec;
18#[cfg(feature = "cubecl")]
19use burn_backend::cubecl::{DeviceIdentity, ThroughputError, ThroughputKey, ThroughputValue};
20#[cfg(cube_backend)]
23use burn_backend::cubecl::measure_peak_throughput;
24
25#[derive(Clone, Eq)]
42pub enum DispatchDevice {
43 #[cfg(not(backend_enabled))]
44 #[doc(hidden)]
45 Unavailable(crate::NoBackend),
46 #[cfg(cube_backend)]
49 Cube(CubeDevice),
50
51 #[cfg(feature = "flex")]
53 Flex(FlexDevice),
54
55 #[cfg(feature = "remote")]
57 Remote(RemoteDevice),
58
59 #[cfg(feature = "capture")]
61 Capture(CaptureDevice),
62
63 #[cfg(feature = "group")]
65 Group(GroupDevice),
66
67 #[cfg(feature = "autodiff")]
69 Autodiff(AutodiffDevice),
70}
71
72#[cfg(feature = "cubecl")]
73impl DispatchDevice {
74 pub fn identity(&self) -> Option<DeviceIdentity> {
77 match self {
78 #[cfg(cube_backend)]
79 DispatchDevice::Cube(device) => Some(device.client().properties().identity.clone()),
80 #[cfg(feature = "autodiff")]
81 DispatchDevice::Autodiff(device) => device.inner.identity(),
82 #[allow(unreachable_patterns)]
83 _ => None,
84 }
85 }
86
87 #[cfg_attr(not(cube_backend), allow(unused_variables))]
96 pub fn performance_stats(
97 &self,
98 keys: &[ThroughputKey],
99 ) -> Vec<Result<ThroughputValue, ThroughputError>> {
100 match self {
103 #[cfg(not(backend_enabled))]
104 Self::Unavailable(never) => never.unreachable(),
105 #[cfg(cube_backend)]
106 DispatchDevice::Cube(device) => {
107 let client = device.client();
108 keys.iter()
109 .map(|key| measure_peak_throughput(&client, *key))
110 .collect()
111 }
112 #[cfg(feature = "autodiff")]
114 DispatchDevice::Autodiff(device) => device.performance_stats(keys),
115
116 #[cfg(feature = "flex")]
118 DispatchDevice::Flex(_) => Vec::new(),
119
120 #[cfg(feature = "remote")]
122 DispatchDevice::Remote(_) => Vec::new(),
123 #[cfg(feature = "group")]
125 DispatchDevice::Group(_) => Vec::new(),
126 #[cfg(feature = "capture")]
127 DispatchDevice::Capture(_) => Vec::new(),
128 }
129 }
130}
131
132#[cfg(feature = "autodiff")]
133#[derive(Debug, Clone)]
138pub struct AutodiffDevice {
139 pub(crate) inner: Box<DispatchDevice>,
140 pub(crate) checkpointing: GradientCheckpointingStrategy,
141}
142
143#[cfg(feature = "autodiff")]
152impl PartialEq for AutodiffDevice {
153 fn eq(&self, other: &Self) -> bool {
154 self.inner == other.inner
155 }
156}
157
158#[cfg(feature = "autodiff")]
159impl Eq for AutodiffDevice {}
160
161#[cfg(feature = "autodiff")]
162impl AutodiffDevice {
163 pub(crate) fn new(
164 device: DispatchDevice,
165 checkpointing: GradientCheckpointingStrategy,
166 ) -> Self {
167 Self {
168 inner: Box::new(device),
169 checkpointing,
170 }
171 }
172
173 pub fn inner(self) -> DispatchDevice {
175 *self.inner
176 }
177
178 pub fn gradient_checkpointing_strategy(&self) -> GradientCheckpointingStrategy {
180 self.checkpointing
181 }
182}
183
184#[cfg(feature = "autodiff")]
185impl core::ops::Deref for AutodiffDevice {
187 type Target = DispatchDevice;
188
189 fn deref(&self) -> &Self::Target {
190 &self.inner
191 }
192}
193
194#[allow(missing_docs)]
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
196#[repr(u8)]
198pub enum GradientCheckpointingStrategy {
199 Balanced,
201 #[default]
203 Disabled,
204}
205
206impl core::fmt::Debug for DispatchDevice {
207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208 match self {
209 #[cfg(not(backend_enabled))]
210 Self::Unavailable(never) => never.unreachable(),
211 #[cfg(cube_backend)]
212 Self::Cube(device) => f.debug_tuple("Cube").field(device).finish(),
213 #[cfg(feature = "flex")]
214 Self::Flex(device) => f.debug_tuple("Flex").field(device).finish(),
215 #[cfg(feature = "remote")]
216 Self::Remote(device) => f.debug_tuple("Remote").field(device).finish(),
217 #[cfg(feature = "group")]
218 Self::Group(device) => f.debug_tuple("Group").field(device).finish(),
219 #[cfg(feature = "capture")]
220 Self::Capture(device) => f.debug_tuple("Capture").field(device).finish(),
221 #[cfg(feature = "autodiff")]
222 Self::Autodiff(device) => f
224 .debug_struct("Autodiff")
225 .field("device", &device.inner)
226 .field("checkpointing", &device.checkpointing)
227 .finish(),
228 }
229 }
230}
231
232impl Default for DispatchDevice {
233 #[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" => {
254 #[cfg(feature = "cuda")]
255 return Self::Cube(CubeDevice::Cuda(Default::default()));
256 panic!(
257 "BURN_DEVICE=cuda requested, but the 'cuda' feature is not enabled."
258 );
259 }
260 "rocm" => {
261 #[cfg(feature = "rocm")]
262 return Self::Cube(CubeDevice::Hip(Default::default()));
263 panic!(
264 "BURN_DEVICE=rocm requested, but the 'rocm' feature is not enabled."
265 );
266 }
267 "metal" => {
268 #[cfg(feature = "metal")]
269 return Self::Cube(CubeDevice::Wgpu(
270 WgpuDevice::default().on(WgpuBackend::Metal),
271 ));
272 panic!(
273 "BURN_DEVICE=metal requested, but the 'metal' feature is not enabled."
274 );
275 }
276 "vulkan" => {
277 #[cfg(feature = "vulkan")]
278 return Self::Cube(CubeDevice::Wgpu(
279 WgpuDevice::default().on(WgpuBackend::Vulkan),
280 ));
281 panic!(
282 "BURN_DEVICE=vulkan requested, but the 'vulkan' feature is not enabled."
283 );
284 }
285 "webgpu" => {
286 #[cfg(feature = "webgpu")]
287 return Self::Cube(CubeDevice::Wgpu(
288 WgpuDevice::default().on(WgpuBackend::WebGpu),
289 ));
290 panic!(
291 "BURN_DEVICE=webgpu requested, but the 'webgpu' feature is not enabled."
292 );
293 }
294 "wgpu" => {
295 #[cfg(feature = "wgpu")]
296 return Self::Cube(CubeDevice::Wgpu(Default::default()));
297 panic!(
298 "BURN_DEVICE=wgpu requested, but the 'wgpu' feature is not enabled."
299 );
300 }
301 "cpu" => {
302 #[cfg(feature = "cpu")]
303 return Self::Cube(CubeDevice::Cpu(Default::default()));
304 panic!("BURN_DEVICE=cpu requested, but the 'cpu' feature is not enabled.");
305 }
306 "remote" => {
307 #[cfg(feature = "remote")]
308 return Self::Remote(RemoteDevice::default());
309 panic!(
310 "BURN_DEVICE=remote requested, but the 'remote' feature is not enabled."
311 );
312 }
313 "flex" => {
314 #[cfg(feature = "flex")]
315 return Self::Flex(FlexDevice);
316 panic!(
317 "BURN_DEVICE=flex requested, but the 'flex' feature is not enabled."
318 );
319 }
320 _ => panic!("Unknown BURN_DEVICE override: '{}'.", device_str),
321 }
322 }
323 }
324
325 #[cfg(feature = "cuda")]
331 return Self::Cube(CubeDevice::Cuda(Default::default()));
332
333 #[cfg(feature = "metal")]
334 return Self::Cube(CubeDevice::Wgpu(Default::default()));
335
336 #[cfg(feature = "rocm")]
337 return Self::Cube(CubeDevice::Hip(Default::default()));
338
339 #[cfg(feature = "vulkan")]
340 return Self::Cube(CubeDevice::Wgpu(Default::default()));
341
342 #[cfg(feature = "webgpu")]
343 return Self::Cube(CubeDevice::Wgpu(Default::default()));
344
345 #[cfg(feature = "wgpu")]
346 return Self::Cube(CubeDevice::Wgpu(Default::default()));
347
348 #[cfg(feature = "cpu")]
349 return Self::Cube(CubeDevice::Cpu(Default::default()));
350
351 #[cfg(feature = "flex")]
352 return Self::Flex(FlexDevice);
353
354 #[cfg(feature = "remote")]
355 return Self::Remote(RemoteDevice::default());
356
357 panic!(
358 "No execution backend is enabled. Enable a Burn backend feature such as `flex`, \
359 `wgpu`, or `cuda`. To record a graph without executing it, enable `capture` \
360 and use Device::capture()."
361 );
362 }
363}
364
365impl PartialEq for DispatchDevice {
366 fn eq(&self, other: &Self) -> bool {
371 match (self, other) {
372 #[cfg(feature = "autodiff")]
374 (DispatchDevice::Autodiff(a), DispatchDevice::Autodiff(b)) => {
375 a.inner.as_ref() == b.inner.as_ref()
376 }
377 #[cfg(feature = "autodiff")]
379 (DispatchDevice::Autodiff(a), b) => a.inner.as_ref() == b,
380 #[cfg(feature = "autodiff")]
381 (a, DispatchDevice::Autodiff(b)) => a == b.inner.as_ref(),
382 #[cfg(cube_backend)]
383 (Self::Cube(a), Self::Cube(b)) => a == b,
384 #[cfg(feature = "flex")]
385 (Self::Flex(a), Self::Flex(b)) => a == b,
386 #[cfg(feature = "remote")]
387 (Self::Remote(a), Self::Remote(b)) => a == b,
388 #[cfg(feature = "group")]
389 (Self::Group(a), Self::Group(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 = "group")]
428 pub fn group(devices: alloc::vec::Vec<DispatchDevice>) -> Result<Self, GroupError> {
429 if let Some(error) = devices.iter().find_map(Self::refused_as_member) {
430 return Err(error);
431 }
432 let group = match devices.first() {
433 #[cfg(cube_backend)]
434 Some(Self::Cube(_)) => {
435 let members = group_members!(devices, Cube)?;
436 GroupDevice::with_member_settings::<crate::backends::Cube>(&members)
437 }
438 #[cfg(feature = "flex")]
439 Some(Self::Flex(_)) => {
440 let members = group_members!(devices, Flex)?;
441 GroupDevice::with_member_settings::<crate::backends::Flex>(&members)
442 }
443 Some(other) => unreachable!("{other:?} was refused as a member"),
444 None => Err(GroupError::NoDevices),
445 }?;
446 Ok(Self::Group(group))
447 }
448
449 #[cfg(feature = "group")]
451 fn refused_as_member(&self) -> Option<GroupError> {
452 match self {
453 #[cfg(cube_backend)]
454 Self::Cube(_) => None,
455 #[cfg(feature = "flex")]
456 Self::Flex(_) => None,
457 #[cfg(feature = "autodiff")]
458 Self::Autodiff(_) => Some(GroupError::AutodiffMember),
459 #[cfg(feature = "remote")]
460 Self::Remote(_) => Some(GroupError::RemoteMember),
461 #[allow(unreachable_patterns)]
462 other => Some(GroupError::UnsupportedBackend {
463 device: alloc::format!("{other:?}"),
464 }),
465 }
466 }
467
468 #[cfg(feature = "autodiff")]
469 pub fn autodiff(device: impl Into<DispatchDevice>) -> DispatchDevice {
472 Self::autodiff_with_gradient_checkpointing(device, GradientCheckpointingStrategy::Disabled)
473 }
474 #[cfg(feature = "autodiff")]
475 pub fn autodiff_with_gradient_checkpointing(
478 device: impl Into<DispatchDevice>,
479 checkpointing: GradientCheckpointingStrategy,
480 ) -> DispatchDevice {
481 let device = device.into();
482 DispatchDevice::Autodiff(AutodiffDevice::new(device, checkpointing))
483 }
484
485 pub fn fusion_worker(&self) -> Option<DeviceId> {
495 match self {
496 #[cfg(cube_backend)]
497 DispatchDevice::Cube(device) => burn_cubecl::FUSED.then(|| device.to_id()),
498 #[cfg(feature = "autodiff")]
499 DispatchDevice::Autodiff(device) => device.inner.fusion_worker(),
500 #[allow(unreachable_patterns)]
501 _ => None,
502 }
503 }
504
505 pub fn inner(self) -> Self {
507 #[cfg(feature = "autodiff")]
508 if let DispatchDevice::Autodiff(device) = self {
509 return *device.inner;
510 }
511
512 self
513 }
514
515 fn backend_id(&self) -> DispatchDeviceId {
517 match self {
518 #[cfg(not(backend_enabled))]
519 Self::Unavailable(never) => never.unreachable(),
520 #[cfg(cube_backend)]
521 Self::Cube(_) => DispatchDeviceId::Cube,
522 #[cfg(feature = "flex")]
523 Self::Flex(_) => DispatchDeviceId::Flex,
524 #[cfg(feature = "remote")]
525 Self::Remote(_) => DispatchDeviceId::Remote,
526 #[cfg(feature = "group")]
527 Self::Group(_) => DispatchDeviceId::Group,
528 #[cfg(feature = "capture")]
529 Self::Capture(_) => DispatchDeviceId::Capture,
530 #[cfg(feature = "autodiff")]
531 Self::Autodiff(device) => device.inner.backend_id(),
532 }
533 }
534
535 fn encode_type_id(&self, backend_type_id: u16) -> u16 {
537 let internal_type_id = backend_type_id & INTERNAL_ID_MASK;
539 let backend = u16::from(self.backend_id()) << BACKEND_SHIFT;
541 backend | internal_type_id
542 }
543
544 pub(crate) fn decode_type_id(type_id: u16) -> (DispatchDeviceId, u16) {
546 let backend_raw = type_id >> BACKEND_SHIFT;
547 let internal_type_id = type_id & INTERNAL_ID_MASK;
548
549 let backend = DispatchDeviceId::try_from(backend_raw).expect("Unknown DispatchDevice ID");
550
551 (backend, internal_type_id)
552 }
553}
554
555#[allow(missing_docs)]
556#[derive(Debug, Clone, Copy, PartialEq, Eq)]
557#[repr(u16)]
558pub enum DispatchDeviceId {
559 Cube = 0,
561 Flex = 4,
562 Remote = 10,
565 Capture = 11,
566 Group = 12,
567}
568
569impl From<DispatchDeviceId> for u16 {
570 fn from(variant: DispatchDeviceId) -> Self {
571 variant as u16
572 }
573}
574
575impl TryFrom<u16> for DispatchDeviceId {
576 type Error = ();
577
578 fn try_from(value: u16) -> Result<Self, Self::Error> {
579 match value {
580 #[cfg(cube_backend)]
581 0 => Ok(Self::Cube),
582 #[cfg(feature = "flex")]
583 4 => Ok(Self::Flex),
584 #[cfg(feature = "remote")]
585 10 => Ok(Self::Remote),
586 #[cfg(feature = "group")]
587 12 => Ok(Self::Group),
588 #[cfg(feature = "capture")]
589 11 => Ok(Self::Capture),
590 _ => Err(()),
591 }
592 }
593}
594
595impl DeviceOps for DispatchDevice {
596 fn defaults(&self) -> DeviceSettings {
597 match self {
598 #[cfg(not(backend_enabled))]
599 Self::Unavailable(never) => never.unreachable(),
600 #[cfg(cube_backend)]
601 Self::Cube(device) => device.defaults(),
602 #[cfg(feature = "flex")]
603 Self::Flex(device) => device.defaults(),
604 #[cfg(feature = "remote")]
605 Self::Remote(device) => device.defaults(),
606 #[cfg(feature = "group")]
607 Self::Group(device) => device.defaults(),
608 #[cfg(feature = "capture")]
609 Self::Capture(device) => device.defaults(),
610 #[cfg(feature = "autodiff")]
611 Self::Autodiff(device) => device.inner.defaults(),
612 }
613 }
614}
615
616impl burn_backend::Device for DispatchDevice {
617 fn from_id(mut device_id: DeviceId) -> Self {
618 let (dispatch_id, backend_type_id) = Self::decode_type_id(device_id.type_id);
619 device_id.type_id = backend_type_id;
620
621 #[allow(unreachable_patterns)]
622 match dispatch_id {
623 #[cfg(cube_backend)]
624 DispatchDeviceId::Cube => Self::Cube(burn_backend::Device::from_id(device_id)),
625 #[cfg(feature = "flex")]
626 DispatchDeviceId::Flex => Self::Flex(FlexDevice::from_id(device_id)),
627 #[cfg(feature = "remote")]
628 DispatchDeviceId::Remote => Self::Remote(RemoteDevice::from_id(device_id)),
629 #[cfg(feature = "group")]
630 DispatchDeviceId::Group => Self::Group(GroupDevice::from_id(device_id)),
631 #[cfg(feature = "capture")]
632 DispatchDeviceId::Capture => Self::Capture(CaptureDevice::from_id(device_id)),
633 _ => unreachable!("No backend feature enabled."),
634 }
635 }
636
637 fn to_id(&self) -> DeviceId {
638 let mut device_id: DeviceId = match self {
639 #[cfg(not(backend_enabled))]
640 Self::Unavailable(never) => never.unreachable(),
641 #[cfg(cube_backend)]
642 Self::Cube(device) => device.to_id(),
643 #[cfg(feature = "flex")]
644 Self::Flex(device) => device.to_id(),
645 #[cfg(feature = "remote")]
646 Self::Remote(device) => device.to_id(),
647 #[cfg(feature = "group")]
648 Self::Group(device) => device.to_id(),
649 #[cfg(feature = "capture")]
650 Self::Capture(device) => device.to_id(),
651 #[cfg(feature = "autodiff")]
652 Self::Autodiff(device) => device.inner.to_id(),
653 };
654 device_id.type_id = self.encode_type_id(device_id.type_id);
655 device_id
656 }
657}
658
659#[cfg(cube_backend)]
661impl From<CubeDevice> for DispatchDevice {
662 fn from(device: CubeDevice) -> Self {
663 DispatchDevice::Cube(device)
664 }
665}
666
667#[cfg(feature = "cpu")]
671impl From<CpuDevice> for DispatchDevice {
672 fn from(device: CpuDevice) -> Self {
673 DispatchDevice::Cube(CubeDevice::Cpu(device))
674 }
675}
676
677#[cfg(feature = "cuda")]
678impl From<CudaDevice> for DispatchDevice {
679 fn from(device: CudaDevice) -> Self {
680 DispatchDevice::Cube(CubeDevice::Cuda(device))
681 }
682}
683
684#[cfg(feature = "rocm")]
685impl From<RocmDevice> for DispatchDevice {
686 fn from(device: RocmDevice) -> Self {
687 DispatchDevice::Cube(CubeDevice::Hip(device))
688 }
689}
690
691#[cfg(any(
692 feature = "wgpu",
693 feature = "metal",
694 feature = "vulkan",
695 feature = "webgpu"
696))]
697impl From<WgpuDevice> for DispatchDevice {
698 fn from(device: WgpuDevice) -> Self {
699 DispatchDevice::Cube(CubeDevice::Wgpu(device))
700 }
701}
702
703#[cfg(feature = "flex")]
704impl From<FlexDevice> for DispatchDevice {
705 fn from(device: FlexDevice) -> Self {
706 DispatchDevice::Flex(device)
707 }
708}
709
710#[cfg(feature = "group")]
711impl From<GroupDevice> for DispatchDevice {
712 fn from(device: GroupDevice) -> Self {
713 DispatchDevice::Group(device)
714 }
715}
716
717#[cfg(feature = "remote")]
718impl From<RemoteDevice> for DispatchDevice {
719 fn from(device: RemoteDevice) -> Self {
720 DispatchDevice::Remote(device)
721 }
722}
723
724#[cfg(all(test, not(backend_enabled)))]
725mod no_backend_tests {
726 #[test]
727 #[should_panic(expected = "No execution backend is enabled. Enable a Burn backend feature")]
728 fn default_requires_backend() {
729 super::DispatchDevice::default();
730 }
731}
732
733#[cfg(all(test, any(feature = "capture", feature = "remote-websocket")))]
734mod tests {
735 use super::*;
736 use burn_backend::Device;
737
738 #[test]
739 #[cfg(feature = "remote-websocket")]
740 fn remote_device_id_round_trips_through_dispatch() {
741 let device = DispatchDevice::from(RemoteDevice::default());
742 let restored = DispatchDevice::from_id(device.to_id());
743
744 assert_eq!(restored, device);
745 }
746
747 #[test]
748 #[cfg(feature = "capture")]
749 fn capture_device_id_round_trips_through_dispatch() {
750 let device = DispatchDevice::capture();
751 let restored = DispatchDevice::from_id(device.to_id());
752
753 assert_eq!(restored, device);
754 }
755}