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