1use alloc::format;
2use alloc::string::String;
3use alloc::vec::Vec;
4#[cfg(cube_backend)]
5use burn_backend::cubecl::{Device as CubeDevice, RuntimeId};
6
7#[cfg(any(feature = "ndarray", feature = "flex"))]
10use alloc::vec;
11
12#[cfg(feature = "autodiff")]
13use burn_backend::distributed::{DistributedParamId, DistributedParams};
14use burn_backend::{
15 AutodiffBackend, Backend, BackendGraph, BackendTypes, DType, ExecutionError, MemoryPoolUsage,
16 ProfileDuration, ProfileOptions, ProfileToken, SlicedPoolReport,
17};
18
19#[derive(Debug, Clone)]
25pub enum DispatchGraph {
26 #[cfg(not(backend_enabled))]
27 #[doc(hidden)]
28 Unavailable(crate::NoBackend),
29 #[cfg(cube_backend)]
31 Cube(BackendGraph<Cube>),
32
33 #[cfg(feature = "flex")]
35 Flex(BackendGraph<Flex>),
36
37 #[cfg(feature = "ndarray")]
39 NdArray(BackendGraph<NdArray>),
40
41 #[cfg(feature = "tch")]
43 LibTorch(BackendGraph<LibTorch>),
44
45 #[cfg(feature = "remote")]
47 Remote(BackendGraph<Remote>),
48 #[cfg(feature = "capture")]
50 Capture(BackendGraph<Capture>),
51}
52
53fn graph_dispatch_err(reason: alloc::string::String) -> ExecutionError {
55 ExecutionError::WithContext { reason }
56}
57
58macro_rules! graph_stop_capture_arms {
61 ($device:expr; $([$Backend:ident, $cfg:meta]),*) => {
62 match $device {
63 $(
64 #[cfg($cfg)]
65 $crate::DispatchDevice::$Backend(device) => {
66 <$crate::backends::$Backend as Backend>::graph_stop_capture(device)
67 .map(DispatchGraph::$Backend)
68 }
69 )*
70 #[allow(unreachable_patterns)]
71 other => Err(graph_dispatch_err(format!(
72 "Graph capture is not supported for device {other:?}"
73 ))),
74 }
75 };
76}
77
78macro_rules! graph_replay_arms {
82 ($device:expr, $graph:expr; $([$Backend:ident, $cfg:meta]),*) => {
83 match ($device, $graph) {
84 $(
85 #[cfg($cfg)]
86 ($crate::DispatchDevice::$Backend(device), DispatchGraph::$Backend(graph)) => {
87 unsafe {
90 <$crate::backends::$Backend as Backend>::graph_replay(device, graph)
91 }
92 }
93 )*
94 #[allow(unreachable_patterns)]
95 (device, _) => Err(graph_dispatch_err(format!(
96 "The graph was not captured on the backend of device {device:?}"
97 ))),
98 }
99 };
100}
101
102#[cfg(feature = "autodiff")]
103macro_rules! is_tracked_arms {
104 ($tensor:expr; $([$Backend:ident, $cfg:meta]),*) => {
105 match &$tensor.kind {
106 DispatchTensorKind::Autodiff(inner) => match &**inner {
107 $(
108 #[cfg($cfg)]
109 DispatchTensorKind::$Backend(tensor) => tensor.as_autodiff().is_tracked(),
110 )*
111 DispatchTensorKind::Autodiff(_) => {
112 unreachable!("Autodiff should not wrap an autodiff tensor")
113 }
114 #[allow(unreachable_patterns)]
115 _ => false,
116 },
117 _ => false,
118 }
119 };
120}
121
122#[cfg(all(feature = "autodiff", executing_backend))]
123use alloc::boxed::Box;
124#[cfg(feature = "autodiff")]
125use burn_autodiff::grads::Gradients;
126
127#[cfg(feature = "autodiff")]
128use crate::DispatchAutodiffContext;
129#[allow(unused)]
130use crate::DispatchDeviceId;
131#[allow(unused)]
132use crate::DispatchTensorKind;
133#[cfg(feature = "flex")]
134use crate::devices::FlexDevice;
135#[cfg(feature = "tch")]
136use crate::devices::LibTorchDevice;
137#[cfg(feature = "ndarray")]
138use crate::devices::NdArrayDevice;
139use crate::{DispatchDevice, DispatchTensor, backends::*};
140
141#[derive(Debug, Default, Clone)]
166pub struct Dispatch;
167
168#[cfg(feature = "autodiff")]
169impl Dispatch {
170 #[doc(hidden)]
172 pub fn is_tracked(tensor: &DispatchTensor) -> bool {
173 backend_list!(is_tracked_arms, tensor)
174 }
175}
176
177impl BackendTypes for Dispatch {
178 type Device = DispatchDevice;
179
180 type FloatTensorPrimitive = DispatchTensor;
181 type IntTensorPrimitive = DispatchTensor;
182 type BoolTensorPrimitive = DispatchTensor;
183 type QuantizedTensorPrimitive = DispatchTensor;
184
185 type GraphPrimitive = DispatchGraph;
186}
187
188impl Backend for Dispatch {
189 fn name(device: &Self::Device) -> String {
190 let inner = dispatch_device!(device, |device| B::name(device));
191 format!("dispatch<{inner}>")
192 }
193
194 fn seed(device: &Self::Device, seed: u64) {
195 dispatch_device!(device, |device| B::seed(device, seed))
196 }
197
198 fn sync(device: &Self::Device) -> Result<(), ExecutionError> {
199 dispatch_device!(device, |device| B::sync(device))
200 }
201
202 fn profile<O: Send + 'static>(
203 device: &Self::Device,
204 options: ProfileOptions,
205 func: impl FnOnce() -> O + Send,
206 ) -> Result<(O, ProfileDuration), ExecutionError> {
207 dispatch_device!(device, |device| B::profile(device, options, func))
208 }
209
210 fn profile_start(device: &Self::Device) -> Result<Option<ProfileToken>, ExecutionError> {
211 dispatch_device!(device, |device| B::profile_start(device))
212 }
213
214 fn profile_end(
215 device: &Self::Device,
216 token: ProfileToken,
217 options: ProfileOptions,
218 ) -> Result<ProfileDuration, ExecutionError> {
219 dispatch_device!(device, |device| B::profile_end(device, token, options))
220 }
221
222 fn profile_abandon(device: &Self::Device, token: ProfileToken) {
223 dispatch_device!(device, |device| B::profile_abandon(device, token))
224 }
225
226 fn graph_prepare(device: &Self::Device) -> Result<(), ExecutionError> {
227 dispatch_device!(device, |device| B::graph_prepare(device))
228 }
229
230 fn graph_start_capture(device: &Self::Device) -> Result<(), ExecutionError> {
231 dispatch_device!(device, |device| B::graph_start_capture(device))
232 }
233
234 fn graph_stop_capture(device: &Self::Device) -> Result<DispatchGraph, ExecutionError> {
235 backend_list!(graph_stop_capture_arms, device)
236 }
237
238 unsafe fn graph_replay(
239 device: &Self::Device,
240 graph: &DispatchGraph,
241 ) -> Result<(), ExecutionError> {
242 backend_list!(graph_replay_arms, device, graph)
243 }
244
245 fn dtype_usage(device: &Self::Device, dtype: DType) -> burn_backend::DTypeUsageSet {
246 dispatch_device!(device, |device| B::dtype_usage(device, dtype))
247 }
248
249 fn ad_enabled(device: &Self::Device) -> bool {
250 match device {
251 #[cfg(feature = "autodiff")]
252 DispatchDevice::Autodiff(_) => true,
253 _ => false,
254 }
255 }
256
257 fn device_count(type_id: u16) -> usize {
258 let (dispatch_id, backend_type_id) = DispatchDevice::decode_type_id(type_id);
259 match dispatch_id {
260 #[cfg(cube_backend)]
261 DispatchDeviceId::Cube => Cube::device_count(backend_type_id),
262 #[cfg(feature = "flex")]
263 DispatchDeviceId::Flex => Flex::device_count(backend_type_id),
264 #[cfg(feature = "ndarray")]
265 DispatchDeviceId::NdArray => NdArray::device_count(backend_type_id),
266 #[cfg(feature = "tch")]
267 DispatchDeviceId::LibTorch => LibTorch::device_count(backend_type_id),
268 #[cfg(feature = "remote")]
269 DispatchDeviceId::Remote => Remote::device_count(backend_type_id),
270 #[cfg(feature = "capture")]
271 DispatchDeviceId::Capture => Capture::device_count(backend_type_id),
272 _ => unreachable!("No backend feature enabled."),
273 }
274 }
275
276 fn memory_persistent_allocations<
277 Output: Send,
278 Input: Send,
279 Func: Fn(Input) -> Output + Send,
280 >(
281 device: &Self::Device,
282 input: Input,
283 func: Func,
284 ) -> Output {
285 dispatch_device!(device, |device| B::memory_persistent_allocations(
286 device, input, func
287 ))
288 }
289
290 fn memory_cleanup(device: &Self::Device) {
291 dispatch_device!(device, |device| B::memory_cleanup(device))
292 }
293
294 fn memory_pool_report(device: &Self::Device) -> Option<Vec<SlicedPoolReport>> {
295 dispatch_device!(device, |device| B::memory_pool_report(device))
296 }
297
298 fn memory_pool_usage(device: &Self::Device) -> Option<MemoryPoolUsage> {
299 dispatch_device!(device, |device| B::memory_pool_usage(device))
300 }
301
302 fn staging<'a, Iter>(data: Iter, device: &Self::Device)
303 where
304 Iter: Iterator<Item = &'a mut burn_backend::TensorData>,
305 {
306 dispatch_device!(device, |device| B::staging(data, device))
307 }
308
309 fn supports_dtype(device: &Self::Device, dtype: DType) -> bool {
310 dispatch_device!(device, |device| B::supports_dtype(device, dtype))
311 }
312
313 fn flush(device: &Self::Device) -> Result<(), ExecutionError> {
314 dispatch_device!(device, |device| B::flush(device))
315 }
316}
317
318#[cfg(feature = "autodiff")]
319fn disable_autodiff_context(context: DispatchAutodiffContext) -> DispatchAutodiffContext {
320 assert!(
321 matches!(context, DispatchAutodiffContext::Enabled(_)),
322 "tensor is already on the inner backend"
323 );
324 DispatchAutodiffContext::Disabled
325}
326
327#[cfg(feature = "autodiff")]
328fn enable_autodiff_context(context: DispatchAutodiffContext) -> DispatchAutodiffContext {
329 assert_eq!(
330 context,
331 DispatchAutodiffContext::Disabled,
332 "tensor is already associated with autodiff"
333 );
334 DispatchAutodiffContext::Enabled(crate::GradientCheckpointingStrategy::Disabled)
335}
336
337#[cfg(feature = "autodiff")]
338#[cfg_attr(not(executing_backend), allow(unused_variables, unreachable_code))]
341impl AutodiffBackend for Dispatch {
342 type InnerBackend = Dispatch;
343
344 type Gradients = Gradients;
345
346 fn backward(tensor: DispatchTensor) -> Self::Gradients {
347 let DispatchTensor { kind, .. } = tensor;
348
349 match kind {
350 DispatchTensorKind::Autodiff(tensor) => match *tensor {
351 #[cfg(not(backend_enabled))]
352 DispatchTensorKind::Unavailable(never) => never.unreachable(),
353 #[cfg(cube_backend)]
354 DispatchTensorKind::Cube(tensor) => tensor.autodiff().backward(),
355 #[cfg(feature = "flex")]
356 DispatchTensorKind::Flex(tensor) => tensor.autodiff().backward(),
357 #[cfg(feature = "ndarray")]
358 DispatchTensorKind::NdArray(tensor) => tensor.autodiff().backward(),
359 #[cfg(feature = "tch")]
360 DispatchTensorKind::LibTorch(tensor) => tensor.autodiff().backward(),
361 #[cfg(feature = "remote")]
362 DispatchTensorKind::Remote(tensor) => tensor.autodiff().backward(),
363 #[cfg(feature = "capture")]
364 DispatchTensorKind::Capture(_) => {
365 panic!("Capture tensors do not support autodiff")
366 }
367 DispatchTensorKind::Autodiff(_) => {
368 panic!("Autodiff should not wrap an autodiff tensor.")
369 }
370 },
371 _ => panic!("Requires autodiff tensor."),
372 }
373 }
374
375 fn grad(tensor: &DispatchTensor, grads: &Self::Gradients) -> Option<DispatchTensor> {
376 let DispatchTensor { kind, .. } = tensor;
377 let grad: Option<DispatchTensorKind> = match &kind {
378 DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
379 #[cfg(not(backend_enabled))]
380 DispatchTensorKind::Unavailable(never) => never.unreachable(),
381 #[cfg(cube_backend)]
382 DispatchTensorKind::Cube(tensor) => tensor
383 .as_autodiff()
384 .grad(grads)
385 .map(|t| DispatchTensorKind::Cube(crate::BackendTensor::Float(t))),
386 #[cfg(feature = "flex")]
387 DispatchTensorKind::Flex(tensor) => tensor
388 .as_autodiff()
389 .grad(grads)
390 .map(|t| DispatchTensorKind::Flex(crate::BackendTensor::Float(t))),
391 #[cfg(feature = "ndarray")]
392 DispatchTensorKind::NdArray(tensor) => tensor
393 .as_autodiff()
394 .grad(grads)
395 .map(|t| DispatchTensorKind::NdArray(crate::BackendTensor::Float(t))),
396 #[cfg(feature = "tch")]
397 DispatchTensorKind::LibTorch(tensor) => tensor
398 .as_autodiff()
399 .grad(grads)
400 .map(|t| DispatchTensorKind::LibTorch(crate::BackendTensor::Float(t))),
401 #[cfg(feature = "remote")]
402 DispatchTensorKind::Remote(tensor) => tensor
403 .as_autodiff()
404 .grad(grads)
405 .map(|t| DispatchTensorKind::Remote(crate::BackendTensor::Float(t))),
406 #[cfg(feature = "capture")]
407 DispatchTensorKind::Capture(_) => {
408 panic!("Capture tensors do not support autodiff")
409 }
410 DispatchTensorKind::Autodiff(_) => {
411 panic!("Autodiff should not wrap an autodiff tensor.")
412 }
413 },
414 _ => panic!("Requires autodiff tensor."),
415 };
416 grad.map(|kind| DispatchTensor {
417 kind,
418 autodiff: DispatchAutodiffContext::Disabled,
419 })
420 }
421
422 fn grad_remove(tensor: &DispatchTensor, grads: &mut Self::Gradients) -> Option<DispatchTensor> {
423 let DispatchTensor { kind, .. } = tensor;
424 let grad: Option<DispatchTensorKind> = match &kind {
425 DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
426 #[cfg(not(backend_enabled))]
427 DispatchTensorKind::Unavailable(never) => never.unreachable(),
428 #[cfg(cube_backend)]
429 DispatchTensorKind::Cube(tensor) => tensor
430 .as_autodiff()
431 .grad_remove(grads)
432 .map(|t| DispatchTensorKind::Cube(crate::BackendTensor::Float(t))),
433 #[cfg(feature = "flex")]
434 DispatchTensorKind::Flex(tensor) => tensor
435 .as_autodiff()
436 .grad_remove(grads)
437 .map(|t| DispatchTensorKind::Flex(crate::BackendTensor::Float(t))),
438 #[cfg(feature = "ndarray")]
439 DispatchTensorKind::NdArray(tensor) => tensor
440 .as_autodiff()
441 .grad_remove(grads)
442 .map(|t| DispatchTensorKind::NdArray(crate::BackendTensor::Float(t))),
443 #[cfg(feature = "tch")]
444 DispatchTensorKind::LibTorch(tensor) => tensor
445 .as_autodiff()
446 .grad_remove(grads)
447 .map(|t| DispatchTensorKind::LibTorch(crate::BackendTensor::Float(t))),
448 #[cfg(feature = "remote")]
449 DispatchTensorKind::Remote(tensor) => tensor
450 .as_autodiff()
451 .grad_remove(grads)
452 .map(|t| DispatchTensorKind::Remote(crate::BackendTensor::Float(t))),
453 #[cfg(feature = "capture")]
454 DispatchTensorKind::Capture(_) => {
455 panic!("Capture tensors do not support autodiff")
456 }
457 DispatchTensorKind::Autodiff(_) => {
458 panic!("Autodiff should not wrap an autodiff tensor.")
459 }
460 },
461 _ => panic!("Requires autodiff tensor."),
462 };
463 grad.map(|kind| DispatchTensor {
464 kind,
465 autodiff: DispatchAutodiffContext::Disabled,
466 })
467 }
468
469 fn grad_replace(tensor: &DispatchTensor, grads: &mut Self::Gradients, grad: DispatchTensor) {
470 let DispatchTensor { kind, .. } = tensor;
471 let DispatchTensor {
472 kind: grad,
473 autodiff,
474 } = grad;
475 assert_eq!(
476 autodiff,
477 DispatchAutodiffContext::Disabled,
478 "replacement gradients must use the inner backend"
479 );
480
481 match &kind {
482 DispatchTensorKind::Autodiff(inner_kind) => match (&**inner_kind, grad) {
483 #[cfg(cube_backend)]
484 (DispatchTensorKind::Cube(tensor), DispatchTensorKind::Cube(grad)) => {
485 tensor.as_autodiff().grad_replace(grads, grad.float())
486 }
487 #[cfg(feature = "flex")]
488 (DispatchTensorKind::Flex(tensor), DispatchTensorKind::Flex(grad)) => {
489 tensor.as_autodiff().grad_replace(grads, grad.float())
490 }
491 #[cfg(feature = "ndarray")]
492 (DispatchTensorKind::NdArray(tensor), DispatchTensorKind::NdArray(grad)) => {
493 tensor.as_autodiff().grad_replace(grads, grad.float())
494 }
495 #[cfg(feature = "remote")]
496 (DispatchTensorKind::Remote(tensor), DispatchTensorKind::Remote(grad)) => {
497 tensor.as_autodiff().grad_replace(grads, grad.float())
498 }
499 (DispatchTensorKind::Autodiff(_), _) => {
500 panic!("Autodiff should not wrap an autodiff tensor.")
501 }
502 (t, g) => panic!(
504 "The provided tensors are not on the same backend. Got backends {t:?} and {g:?}."
505 ),
506 },
507 _ => panic!("Requires autodiff tensor."),
508 }
509 }
510
511 fn inner(tensor: DispatchTensor) -> DispatchTensor {
512 let DispatchTensor { kind, autodiff } = tensor;
513 assert!(
514 matches!(autodiff, DispatchAutodiffContext::Enabled(_)),
515 "Requires autodiff tensor."
516 );
517
518 let kind = match kind {
519 DispatchTensorKind::Autodiff(inner_kind) => match *inner_kind {
520 #[cfg(not(backend_enabled))]
521 DispatchTensorKind::Unavailable(ref never) => never.unreachable(),
522 #[cfg(cube_backend)]
523 DispatchTensorKind::Cube(tensor) => DispatchTensorKind::Cube(
524 crate::BackendTensor::Float(tensor.autodiff().into_primitive()),
525 ),
526 #[cfg(feature = "flex")]
527 DispatchTensorKind::Flex(tensor) => DispatchTensorKind::Flex(
528 crate::BackendTensor::Float(tensor.autodiff().into_primitive()),
529 ),
530 #[cfg(feature = "ndarray")]
531 DispatchTensorKind::NdArray(tensor) => DispatchTensorKind::NdArray(
532 crate::BackendTensor::Float(tensor.autodiff().into_primitive()),
533 ),
534 #[cfg(feature = "tch")]
535 DispatchTensorKind::LibTorch(tensor) => DispatchTensorKind::LibTorch(
536 crate::BackendTensor::Float(tensor.autodiff().into_primitive()),
537 ),
538 #[cfg(feature = "remote")]
539 DispatchTensorKind::Remote(tensor) => DispatchTensorKind::Remote(
540 crate::BackendTensor::Float(tensor.autodiff().into_primitive()),
541 ),
542 #[cfg(feature = "capture")]
543 DispatchTensorKind::Capture(_) => {
544 panic!("Capture tensors do not support autodiff")
545 }
546 DispatchTensorKind::Autodiff(_) => {
547 panic!("Autodiff should not wrap an autodiff tensor.")
548 }
549 },
550 _ => panic!("Requires autodiff tensor."),
551 };
552 DispatchTensor {
553 kind,
554 autodiff: DispatchAutodiffContext::Disabled,
555 }
556 }
557
558 fn int_inner(mut tensor: DispatchTensor) -> DispatchTensor {
559 tensor.autodiff = disable_autodiff_context(tensor.autodiff);
560 tensor
561 }
562
563 fn bool_inner(mut tensor: DispatchTensor) -> DispatchTensor {
564 tensor.autodiff = disable_autodiff_context(tensor.autodiff);
565 tensor
566 }
567
568 fn q_inner(mut tensor: DispatchTensor) -> DispatchTensor {
569 tensor.autodiff = disable_autodiff_context(tensor.autodiff);
570 tensor
571 }
572
573 fn from_inner(tensor: DispatchTensor) -> DispatchTensor {
574 let DispatchTensor { kind, autodiff } = tensor;
575 assert_eq!(
576 autodiff,
577 DispatchAutodiffContext::Disabled,
578 "tensor is already associated with autodiff"
579 );
580
581 let kind = match kind {
582 #[cfg(cube_backend)]
583 DispatchTensorKind::Cube(tensor) => {
584 DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cube(
585 crate::BackendTensor::Autodiff(Autodiff::<Cube>::from_inner(tensor.float())),
586 )))
587 }
588 #[cfg(feature = "flex")]
589 DispatchTensorKind::Flex(tensor) => {
590 DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Flex(
591 crate::BackendTensor::Autodiff(Autodiff::<Flex>::from_inner(tensor.float())),
592 )))
593 }
594 #[cfg(feature = "ndarray")]
595 DispatchTensorKind::NdArray(tensor) => {
596 DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::NdArray(
597 crate::BackendTensor::Autodiff(Autodiff::<NdArray>::from_inner(tensor.float())),
598 )))
599 }
600 #[cfg(feature = "tch")]
601 DispatchTensorKind::LibTorch(tensor) => DispatchTensorKind::Autodiff(Box::new(
602 DispatchTensorKind::LibTorch(crate::BackendTensor::Autodiff(
603 Autodiff::<LibTorch>::from_inner(tensor.float()),
604 )),
605 )),
606 #[cfg(feature = "remote")]
607 DispatchTensorKind::Remote(tensor) => {
608 DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Remote(
609 crate::BackendTensor::Autodiff(Autodiff::<Remote>::from_inner(tensor.float())),
610 )))
611 }
612 #[cfg(feature = "capture")]
613 DispatchTensorKind::Capture(_) => {
614 panic!("Capture tensors do not support autodiff")
615 }
616 DispatchTensorKind::Autodiff(_) => {
617 panic!("Autodiff should not wrap an autodiff tensor.")
618 }
619 };
620
621 DispatchTensor {
622 kind,
623 autodiff: DispatchAutodiffContext::Enabled(
624 crate::GradientCheckpointingStrategy::Disabled,
625 ),
626 }
627 }
628
629 fn int_from_inner(mut tensor: DispatchTensor) -> DispatchTensor {
630 tensor.autodiff = enable_autodiff_context(tensor.autodiff);
631 tensor
632 }
633
634 fn bool_from_inner(mut tensor: DispatchTensor) -> DispatchTensor {
635 tensor.autodiff = enable_autodiff_context(tensor.autodiff);
636 tensor
637 }
638
639 fn q_from_inner(mut tensor: DispatchTensor) -> DispatchTensor {
640 tensor.autodiff = enable_autodiff_context(tensor.autodiff);
641 tensor
642 }
643
644 #[allow(unused_variables, unreachable_code)]
647 fn set_distributed_params(
648 tensor: DispatchTensor,
649 param_id: DistributedParamId,
650 ) -> DispatchTensor {
651 let DispatchTensor { kind, autodiff } = tensor;
652 assert!(
653 matches!(autodiff, DispatchAutodiffContext::Enabled(_)),
654 "Requires autodiff tensor."
655 );
656
657 let kind = match kind {
658 DispatchTensorKind::Autodiff(inner_kind) => match *inner_kind {
659 #[cfg(not(backend_enabled))]
660 DispatchTensorKind::Unavailable(ref never) => never.unreachable(),
661 #[cfg(cube_backend)]
662 DispatchTensorKind::Cube(tensor) => {
663 DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cube(
664 crate::BackendTensor::Autodiff(Autodiff::<Cube>::set_distributed_params(
665 tensor.as_autodiff().clone(),
666 param_id,
667 )),
668 )))
669 }
670 #[cfg(feature = "remote")]
671 DispatchTensorKind::Remote(tensor) => {
672 DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Remote(
673 crate::BackendTensor::Autodiff(Autodiff::<Remote>::set_distributed_params(
674 tensor.as_autodiff().clone(),
675 param_id,
676 )),
677 )))
678 }
679 DispatchTensorKind::Autodiff(_) => {
680 panic!("Autodiff should not wrap an autodiff tensor.")
681 }
682 #[allow(unreachable_patterns)]
683 other => {
684 panic!("Distributed operations are not supported for tensor kind {other:?}")
685 }
686 },
687 _ => panic!("Requires autodiff tensor."),
688 };
689
690 DispatchTensor { kind, autodiff }
691 }
692
693 #[allow(unused_variables)]
694 fn distributed_params(tensor: &DispatchTensor) -> Option<DistributedParams> {
695 let DispatchTensor { kind, autodiff: _ } = tensor;
696
697 match &kind {
698 DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
699 #[cfg(not(backend_enabled))]
700 DispatchTensorKind::Unavailable(never) => never.unreachable(),
701 #[cfg(cube_backend)]
702 DispatchTensorKind::Cube(tensor) => {
703 Autodiff::<Cube>::distributed_params(tensor.as_autodiff())
704 }
705 #[cfg(feature = "remote")]
706 DispatchTensorKind::Remote(tensor) => {
707 Autodiff::<Remote>::distributed_params(tensor.as_autodiff())
708 }
709
710 DispatchTensorKind::Autodiff(_) => {
711 panic!("Autodiff should not wrap an autodiff tensor.")
712 }
713 #[allow(unreachable_patterns)]
715 _ => None,
716 },
717 _ => panic!("Requires autodiff tensor."),
718 }
719 }
720
721 #[allow(unused_variables)]
722 fn is_distributed(tensor: &DispatchTensor) -> bool {
723 let DispatchTensor { kind, autodiff: _ } = tensor;
724
725 match &kind {
726 DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
727 #[cfg(not(backend_enabled))]
728 DispatchTensorKind::Unavailable(never) => never.unreachable(),
729 #[cfg(cube_backend)]
730 DispatchTensorKind::Cube(tensor) => {
731 Autodiff::<Cube>::is_distributed(tensor.as_autodiff())
732 }
733 #[cfg(feature = "remote")]
734 DispatchTensorKind::Remote(tensor) => {
735 Autodiff::<Remote>::is_distributed(tensor.as_autodiff())
736 }
737
738 DispatchTensorKind::Autodiff(_) => {
739 panic!("Autodiff should not wrap an autodiff tensor.")
740 }
741 #[allow(unreachable_patterns)]
743 _ => false,
744 },
745 _ => panic!("Requires autodiff tensor."),
746 }
747 }
748}
749
750#[cfg(all(test, feature = "autodiff", feature = "flex"))]
751mod autodiff_context_tests {
752 use super::*;
753 use crate::{DispatchAutodiffContext, DispatchTensorKind, GradientCheckpointingStrategy};
754 use alloc::vec;
755 use burn_backend::{
756 TensorData, TensorMetadata, TensorPrimitive,
757 ops::{BoolTensorOps, FloatTensorOps, IntTensorOps, ModuleOps, QTensorOps},
758 quantization::QuantScheme,
759 tensor::{FloatTensor, IntTensor},
760 };
761 use burn_backend_extension::backend_dispatch;
762
763 #[backend_dispatch]
764 impl Dispatch {
765 fn direct_float(tensor: FloatTensor<Self>) -> FloatTensor<Self> {
766 tensor
767 }
768
769 fn conditional_float_route(
770 int: IntTensor<Self>,
771 float: Option<FloatTensor<Self>>,
772 ) -> IntTensor<Self> {
773 assert_eq!(float.is_some(), B::ad_enabled(&int.device()));
776 int
777 }
778
779 fn optional_routing_tensor(
780 first: Option<FloatTensor<Self>>,
781 second: Option<FloatTensor<Self>>,
782 ) -> FloatTensor<Self> {
783 first.or(second).expect("test requires one tensor")
784 }
785
786 fn vector_routing_tensor(
787 first: Vec<IntTensor<Self>>,
788 second: Vec<IntTensor<Self>>,
789 ) -> IntTensor<Self> {
790 first
791 .into_iter()
792 .chain(second)
793 .next()
794 .expect("test requires one tensor")
795 }
796 }
797
798 fn device(strategy: GradientCheckpointingStrategy) -> DispatchDevice {
799 DispatchDevice::autodiff_with_gradient_checkpointing(
800 DispatchDevice::Flex(Default::default()),
801 strategy,
802 )
803 }
804
805 fn inner_device() -> DispatchDevice {
806 DispatchDevice::Flex(Default::default())
807 }
808
809 fn float(values: [f32; 2], device: &DispatchDevice) -> DispatchTensor {
810 Dispatch::float_from_data(TensorData::from(values), device)
811 }
812
813 fn float_2d(values: [[f32; 2]; 2], device: &DispatchDevice) -> DispatchTensor {
814 Dispatch::float_from_data(TensorData::from(values), device)
815 }
816
817 fn assert_enabled_float(tensor: &DispatchTensor, strategy: GradientCheckpointingStrategy) {
818 assert_eq!(tensor.autodiff, DispatchAutodiffContext::Enabled(strategy));
819 assert!(matches!(tensor.kind, DispatchTensorKind::Autodiff(_)));
820 let DispatchDevice::Autodiff(device) = tensor.device() else {
821 panic!("enabled float should report an autodiff device")
822 };
823 assert_eq!(device.checkpointing, strategy);
824 }
825
826 #[test]
827 fn enabled_float_creation_is_untracked_for_both_strategies() {
828 for strategy in [
829 GradientCheckpointingStrategy::Disabled,
830 GradientCheckpointingStrategy::Balanced,
831 ] {
832 let tensor = float([1.0, 2.0], &device(strategy));
833 assert_enabled_float(&tensor, strategy);
834 assert!(!Dispatch::float_is_require_grad(&tensor));
835 }
836 }
837
838 #[test]
839 fn enabled_int_bool_and_float_conversions_preserve_association() {
840 let strategy = GradientCheckpointingStrategy::Balanced;
841 let device = device(strategy);
842 let int = Dispatch::int_from_data(TensorData::from([1i32, 2]), &device);
843 assert_eq!(int.autodiff, DispatchAutodiffContext::Enabled(strategy));
844 assert!(!matches!(int.kind, DispatchTensorKind::Autodiff(_)));
845 assert!(matches!(int.device(), DispatchDevice::Autodiff(_)));
846
847 let converted = Dispatch::int_into_float(int, burn_backend::FloatDType::F32);
848 assert_enabled_float(&converted, strategy);
849
850 let boolean = Dispatch::bool_from_data(TensorData::from([true, false]), &device);
851 assert_eq!(boolean.autodiff, DispatchAutodiffContext::Enabled(strategy));
852 assert!(!matches!(boolean.kind, DispatchTensorKind::Autodiff(_)));
853 let converted = Dispatch::bool_into_float(boolean, burn_backend::FloatDType::F32);
854 assert_enabled_float(&converted, strategy);
855 }
856
857 #[test]
858 fn uniform_contexts_propagate_through_fixed_arity_and_vector_ops() {
859 let strategy = GradientCheckpointingStrategy::Disabled;
860 let lhs = float([1.0, 2.0], &device(strategy));
861 let rhs = float([3.0, 4.0], &device(strategy));
862
863 let added = Dispatch::float_add(lhs.clone(), rhs.clone());
864 assert_enabled_float(&added, strategy);
865
866 let concatenated = Dispatch::float_cat(vec![lhs.clone(), rhs], 0);
867 assert_enabled_float(&concatenated, strategy);
868
869 let normalized = Dispatch::layer_norm(
870 lhs,
871 float([1.0, 1.0], &device(strategy)),
872 Some(float([0.0, 0.0], &device(strategy))),
873 1e-5,
874 );
875 assert_enabled_float(&normalized, strategy);
876 }
877
878 #[test]
879 fn optional_and_vector_routing_candidates_skip_empty_inputs() {
880 let strategy = GradientCheckpointingStrategy::Balanced;
881 let float = float([1.0, 2.0], &device(strategy));
882 let output = Dispatch::optional_routing_tensor(None, Some(float));
883 assert_enabled_float(&output, strategy);
884
885 let int = Dispatch::int_from_data(TensorData::from([1i32, 2]), &device(strategy));
886 let output = Dispatch::vector_routing_tensor(vec![], vec![int]);
887 assert_eq!(output.autodiff, DispatchAutodiffContext::Enabled(strategy));
888 }
889
890 #[test]
891 fn optional_float_only_uses_autodiff_when_present() {
892 let strategy = GradientCheckpointingStrategy::Balanced;
893 let int = Dispatch::int_from_data(TensorData::from([1i32, 2]), &device(strategy));
894 let output = Dispatch::conditional_float_route(int, None);
895 assert_eq!(output.autodiff, DispatchAutodiffContext::Enabled(strategy));
896
897 let int = Dispatch::int_from_data(TensorData::from([1i32, 2]), &inner_device());
898 let float = float([1.0, 2.0], &device(strategy));
899 let output = Dispatch::conditional_float_route(int, Some(float));
900 assert_eq!(output.autodiff, DispatchAutodiffContext::Enabled(strategy));
901 }
902
903 #[test]
904 #[should_panic(expected = "an enabled float tensor must use an autodiff primitive")]
905 fn direct_routing_tensor_validates_its_context() {
906 let mut malformed = float([1.0, 2.0], &inner_device());
907 malformed.autodiff =
908 DispatchAutodiffContext::Enabled(GradientCheckpointingStrategy::Disabled);
909 let _ = Dispatch::direct_float(malformed);
910 }
911
912 #[test]
913 #[should_panic(expected = "Gradient checkpointing strategy mismatch")]
914 fn fixed_arity_inputs_reject_mismatched_checkpointing_strategies() {
915 let lhs = float([1.0, 2.0], &device(GradientCheckpointingStrategy::Balanced));
916 let rhs = float([3.0, 4.0], &device(GradientCheckpointingStrategy::Disabled));
917 let _ = Dispatch::float_add(lhs, rhs);
918 }
919
920 #[test]
921 fn disabled_and_enabled_integer_contexts_merge_in_both_orders() {
922 for strategy in [
923 GradientCheckpointingStrategy::Disabled,
924 GradientCheckpointingStrategy::Balanced,
925 ] {
926 let lhs = Dispatch::int_from_data(TensorData::from([1i32, 2]), &inner_device());
927 let rhs = Dispatch::int_from_data(TensorData::from([3i32, 4]), &device(strategy));
928 let output = Dispatch::int_add(lhs, rhs);
929 assert_eq!(output.autodiff, DispatchAutodiffContext::Enabled(strategy));
930
931 let lhs = Dispatch::int_from_data(TensorData::from([1i32, 2]), &device(strategy));
932 let rhs = Dispatch::int_from_data(TensorData::from([3i32, 4]), &inner_device());
933 let output = Dispatch::int_add(lhs, rhs);
934 assert_eq!(output.autodiff, DispatchAutodiffContext::Enabled(strategy));
935 }
936 }
937
938 #[test]
939 fn disabled_and_enabled_float_contexts_merge_in_both_orders() {
940 for strategy in [
941 GradientCheckpointingStrategy::Disabled,
942 GradientCheckpointingStrategy::Balanced,
943 ] {
944 let enabled = float([1.0, 2.0], &device(strategy));
945 let disabled = float([3.0, 4.0], &inner_device());
946
947 let output = Dispatch::float_add(enabled, disabled);
948 assert_enabled_float(&output, strategy);
949
950 let enabled = float([1.0, 2.0], &device(strategy));
951 let disabled = float([3.0, 4.0], &inner_device());
952 let output = Dispatch::float_add(disabled, enabled);
953 assert_enabled_float(&output, strategy);
954 }
955 }
956
957 #[test]
958 fn disabled_and_enabled_contexts_merge_across_vector_inputs() {
959 let strategy = GradientCheckpointingStrategy::Balanced;
960 let disabled = float([1.0, 2.0], &inner_device());
961 let enabled = float([3.0, 4.0], &device(strategy));
962
963 let output = Dispatch::float_cat(vec![disabled, enabled], 0);
964 assert_enabled_float(&output, strategy);
965 }
966
967 #[test]
968 #[should_panic(expected = "Gradient checkpointing strategy mismatch")]
969 fn vector_inputs_reject_mismatched_checkpointing_strategies() {
970 let balanced = float([1.0, 2.0], &device(GradientCheckpointingStrategy::Balanced));
971 let disabled = float([3.0, 4.0], &device(GradientCheckpointingStrategy::Disabled));
972 let _ = Dispatch::float_cat(vec![balanced, disabled], 0);
973 }
974
975 #[test]
976 #[should_panic(expected = "Gradient checkpointing strategy mismatch")]
977 fn q_matmul_rejects_mismatched_checkpointing_strategies() {
978 let balanced = Dispatch::quantize_dynamic(
979 float([1.0, 2.0], &device(GradientCheckpointingStrategy::Balanced)),
980 &QuantScheme::default(),
981 );
982 let disabled = Dispatch::quantize_dynamic(
983 float([3.0, 4.0], &device(GradientCheckpointingStrategy::Disabled)),
984 &QuantScheme::default(),
985 );
986 let _ = Dispatch::q_matmul(
987 TensorPrimitive::QFloat(balanced),
988 TensorPrimitive::QFloat(disabled),
989 );
990 }
991
992 #[test]
993 fn q_matmul_merges_disabled_and_enabled_contexts() {
994 let strategy = GradientCheckpointingStrategy::Balanced;
995 let disabled = Dispatch::quantize_dynamic(
996 float_2d([[1.0, 2.0], [3.0, 4.0]], &inner_device()),
997 &QuantScheme::default(),
998 );
999 let enabled = Dispatch::quantize_dynamic(
1000 float_2d([[1.0, 0.0], [0.0, 1.0]], &device(strategy)),
1001 &QuantScheme::default(),
1002 );
1003 let output = Dispatch::q_matmul(
1004 TensorPrimitive::QFloat(disabled),
1005 TensorPrimitive::QFloat(enabled),
1006 );
1007 let output = match output {
1008 TensorPrimitive::QFloat(output) | TensorPrimitive::Float(output) => output,
1009 };
1010
1011 assert_eq!(output.autodiff, DispatchAutodiffContext::Enabled(strategy));
1012 }
1013
1014 #[test]
1015 fn inner_transitions_clear_and_restore_context() {
1016 let tensor = float([1.0, 2.0], &device(GradientCheckpointingStrategy::Balanced));
1017 let inner = <Dispatch as AutodiffBackend>::inner(tensor);
1018 assert_eq!(inner.autodiff, DispatchAutodiffContext::Disabled);
1019 assert!(!matches!(inner.kind, DispatchTensorKind::Autodiff(_)));
1020
1021 let enabled = <Dispatch as AutodiffBackend>::from_inner(inner);
1022 assert_enabled_float(&enabled, GradientCheckpointingStrategy::Disabled);
1023
1024 let int = Dispatch::int_from_data(
1025 TensorData::from([1i32, 2]),
1026 &device(GradientCheckpointingStrategy::Balanced),
1027 );
1028 let int = <Dispatch as AutodiffBackend>::int_inner(int);
1029 assert_eq!(int.autodiff, DispatchAutodiffContext::Disabled);
1030 let int = <Dispatch as AutodiffBackend>::int_from_inner(int);
1031 assert_eq!(
1032 int.autodiff,
1033 DispatchAutodiffContext::Enabled(GradientCheckpointingStrategy::Disabled)
1034 );
1035 }
1036
1037 #[test]
1038 fn gradients_are_inner_backend_tensors() {
1039 let x = Dispatch::float_set_require_grad(
1040 float([2.0, 3.0], &device(GradientCheckpointingStrategy::Balanced)),
1041 true,
1042 );
1043 let output = Dispatch::float_mul(x.clone(), x.clone());
1044 let gradients = <Dispatch as AutodiffBackend>::backward(output);
1045 let gradient = <Dispatch as AutodiffBackend>::grad(&x, &gradients).unwrap();
1046 assert_eq!(gradient.autodiff, DispatchAutodiffContext::Disabled);
1047 assert!(!matches!(gradient.kind, DispatchTensorKind::Autodiff(_)));
1048 }
1049}
1050
1051#[cfg(not(feature = "autodiff"))]
1053impl AutodiffBackend for Dispatch {
1054 type InnerBackend = Dispatch;
1055
1056 type Gradients = bool;
1057
1058 fn backward(_tensor: DispatchTensor) -> Self::Gradients {
1059 unimplemented!("Requires `autodiff` feature")
1060 }
1061
1062 fn grad(_tensor: &DispatchTensor, _grads: &Self::Gradients) -> Option<DispatchTensor> {
1063 unimplemented!("Requires `autodiff` feature")
1064 }
1065
1066 fn grad_remove(
1067 _tensor: &DispatchTensor,
1068 _grads: &mut Self::Gradients,
1069 ) -> Option<DispatchTensor> {
1070 unimplemented!("Requires `autodiff` feature")
1071 }
1072
1073 fn grad_replace(_tensor: &DispatchTensor, _grads: &mut Self::Gradients, _grad: DispatchTensor) {
1074 unimplemented!("Requires `autodiff` feature")
1075 }
1076
1077 fn inner(_tensor: DispatchTensor) -> DispatchTensor {
1078 unimplemented!("Requires `autodiff` feature")
1079 }
1080
1081 fn int_inner(_tensor: DispatchTensor) -> DispatchTensor {
1082 unimplemented!("Requires `autodiff` feature")
1083 }
1084
1085 fn bool_inner(_tensor: DispatchTensor) -> DispatchTensor {
1086 unimplemented!("Requires `autodiff` feature")
1087 }
1088
1089 fn q_inner(_tensor: DispatchTensor) -> DispatchTensor {
1090 unimplemented!("Requires `autodiff` feature")
1091 }
1092
1093 fn from_inner(_tensor: DispatchTensor) -> DispatchTensor {
1094 unimplemented!("Requires `autodiff` feature")
1095 }
1096
1097 fn int_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
1098 unimplemented!("Requires `autodiff` feature")
1099 }
1100
1101 fn bool_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
1102 unimplemented!("Requires `autodiff` feature")
1103 }
1104
1105 fn q_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
1106 unimplemented!("Requires `autodiff` feature")
1107 }
1108}
1109
1110impl Dispatch {
1111 pub fn enumerate(type_id: DispatchDeviceId) -> Vec<DispatchDevice> {
1113 match type_id {
1115 #[cfg(cube_backend)]
1116 DispatchDeviceId::Cube => CubeDevice::enumerate_all()
1117 .into_iter()
1118 .filter(|device| cube_runtime_enabled(device.runtime()))
1119 .map(DispatchDevice::Cube)
1120 .collect(),
1121 #[cfg(feature = "flex")]
1122 DispatchDeviceId::Flex => vec![FlexDevice.into()],
1123 #[cfg(feature = "ndarray")]
1124 DispatchDeviceId::NdArray => vec![NdArrayDevice::Cpu.into()],
1125 #[cfg(feature = "tch")]
1126 DispatchDeviceId::LibTorch => (0..LibTorch::device_count(0))
1127 .map(|i| LibTorchDevice::Cuda(i).into())
1128 .collect(),
1129 #[cfg(feature = "remote")]
1130 DispatchDeviceId::Remote => Vec::new(),
1132 #[cfg(feature = "capture")]
1133 DispatchDeviceId::Capture => Vec::new(),
1136 _ => unreachable!("No backend feature enabled."),
1137 }
1138 }
1139
1140 #[cfg(cube_backend)]
1148 pub fn enumerate_cube(runtime: RuntimeId) -> Vec<DispatchDevice> {
1149 cube_devices(runtime)
1150 .into_iter()
1151 .map(DispatchDevice::Cube)
1152 .collect()
1153 }
1154}
1155
1156#[cfg(cube_backend)]
1163fn cube_runtime_enabled(runtime: RuntimeId) -> bool {
1164 match runtime {
1165 RuntimeId::Cuda => cfg!(feature = "cuda"),
1166 RuntimeId::Hip => cfg!(feature = "rocm"),
1167 RuntimeId::Wgpu => cfg!(any(
1168 feature = "wgpu",
1169 feature = "vulkan",
1170 feature = "metal",
1171 feature = "webgpu"
1172 )),
1173 RuntimeId::Cpu => cfg!(feature = "cpu"),
1174 RuntimeId::Metal => false,
1177 }
1178}
1179
1180#[cfg(cube_backend)]
1183pub(crate) fn cube_devices(runtime: RuntimeId) -> Vec<CubeDevice> {
1184 if !cube_runtime_enabled(runtime) {
1185 return Vec::new();
1186 }
1187
1188 CubeDevice::enumerate_all()
1189 .into_iter()
1190 .filter(|device| device.runtime() == runtime)
1191 .collect()
1192}