Skip to main content

burn_dispatch/
backend.rs

1use alloc::format;
2use alloc::string::String;
3use alloc::vec::Vec;
4
5#[cfg(any(
6    feature = "cpu",
7    feature = "ndarray",
8    feature = "flex",
9    default_backend
10))]
11use alloc::vec;
12
13#[cfg(feature = "autodiff")]
14use burn_backend::distributed::{DistributedParamId, DistributedParams};
15use burn_backend::{AutodiffBackend, Backend, BackendGraph, BackendTypes, DType, ExecutionError};
16
17/// A captured graph from one of the dispatched backends (see
18/// [`BackendTypes::GraphPrimitive`]).
19///
20/// Like [`DispatchTensorKind`], one variant per enabled backend: the graph is
21/// captured by, and can only replay on, the backend it was recorded on.
22#[derive(Debug, Clone)]
23pub enum DispatchGraph {
24    /// A graph captured on the [CPU backend](Cpu).
25    #[cfg(feature = "cpu")]
26    Cpu(BackendGraph<Cpu>),
27
28    /// A graph captured on the [CUDA backend](Cuda).
29    #[cfg(feature = "cuda")]
30    Cuda(BackendGraph<Cuda>),
31
32    /// A graph captured on the [Metal backend](Metal).
33    #[cfg(feature = "metal")]
34    Metal(BackendGraph<Metal>),
35
36    /// A graph captured on the [ROCm backend](Rocm).
37    #[cfg(feature = "rocm")]
38    Rocm(BackendGraph<Rocm>),
39
40    /// A graph captured on the [Vulkan backend](Vulkan).
41    #[cfg(feature = "vulkan")]
42    Vulkan(BackendGraph<Vulkan>),
43
44    /// A graph captured on the [Wgpu backend](Wgpu).
45    #[cfg(feature = "wgpu")]
46    Wgpu(BackendGraph<Wgpu>),
47
48    /// A graph captured on the [WebGPU backend](WebGpu).
49    #[cfg(feature = "webgpu")]
50    WebGpu(BackendGraph<WebGpu>),
51
52    /// A graph captured on the [Flex backend](Flex).
53    #[cfg(any(feature = "flex", default_backend))]
54    Flex(BackendGraph<Flex>),
55
56    /// A graph captured on the [NdArray backend](NdArray).
57    #[cfg(feature = "ndarray")]
58    NdArray(BackendGraph<NdArray>),
59
60    /// A graph captured on the [LibTorch backend](LibTorch).
61    #[cfg(feature = "tch")]
62    LibTorch(BackendGraph<LibTorch>),
63
64    /// A graph captured on the [Remote backend](Remote).
65    #[cfg(feature = "remote")]
66    Remote(BackendGraph<Remote>),
67}
68
69/// The error returned when a graph operation cannot be dispatched.
70fn graph_dispatch_err(reason: alloc::string::String) -> ExecutionError {
71    ExecutionError::WithContext { reason }
72}
73
74/// Match arm generator for [`Backend::graph_stop_capture`] on [`Dispatch`]:
75/// each backend's captured graph is wrapped in its [`DispatchGraph`] variant.
76macro_rules! graph_stop_capture_arms {
77    ($device:expr; $([$Backend:ident, $cfg:meta]),*) => {
78        match $device {
79            $(
80                #[cfg($cfg)]
81                $crate::DispatchDevice::$Backend(device) => {
82                    <$crate::backends::$Backend as Backend>::graph_stop_capture(device)
83                        .map(DispatchGraph::$Backend)
84                }
85            )*
86            #[allow(unreachable_patterns)]
87            other => Err(graph_dispatch_err(format!(
88                "Graph capture is not supported for device {other:?}"
89            ))),
90        }
91    };
92}
93
94/// Match arm generator for [`Backend::graph_replay`] on [`Dispatch`]: the graph
95/// variant must match the device's backend, since a graph only replays on the
96/// backend that captured it.
97macro_rules! graph_replay_arms {
98    ($device:expr, $graph:expr; $([$Backend:ident, $cfg:meta]),*) => {
99        match ($device, $graph) {
100            $(
101                #[cfg($cfg)]
102                ($crate::DispatchDevice::$Backend(device), DispatchGraph::$Backend(graph)) => {
103                    // Safety: forwarded verbatim from `Dispatch::graph_replay`'s
104                    // own contract.
105                    unsafe {
106                        <$crate::backends::$Backend as Backend>::graph_replay(device, graph)
107                    }
108                }
109            )*
110            #[allow(unreachable_patterns)]
111            (device, _) => Err(graph_dispatch_err(format!(
112                "The graph was not captured on the backend of device {device:?}"
113            ))),
114        }
115    };
116}
117
118#[cfg(feature = "autodiff")]
119use alloc::boxed::Box;
120#[cfg(feature = "autodiff")]
121use burn_autodiff::grads::Gradients;
122
123#[allow(unused)]
124use crate::DispatchDeviceId;
125#[allow(unused)]
126use crate::DispatchTensorKind;
127use crate::backends::*;
128use crate::{DispatchDevice, DispatchTensor};
129
130/// The main execution backend in Burn.
131///
132/// [`Dispatch`] acts as a global backend that can manage multiple underlying
133/// backends (e.g., `Cpu`, `Cuda`, `Wgpu`, `Metal`, etc.).
134/// It is responsible for:
135/// - Dispatching tensor operations to the appropriate backend.
136/// - Managing cross-backend tensor transfers.
137///
138/// Essentially, [`Dispatch`] is the single entry point for executing tensor operations
139/// in a backend-agnostic way. It allows Burn to provide a unified, global backend
140/// for users while still leveraging multiple specialized backends under the hood.
141///
142/// # Example
143///
144/// ```ignore
145/// use burn::Dispatch;
146/// use burn::DispatchDevice;
147///
148/// // Select the device to execute operations on
149/// let device = DispatchDevice::Cuda(Default::default());
150///
151/// // Create a tensor using the global backend
152/// let t = Tensor::<Dispatch, 2>::zeros([128, 128], &device);
153/// ```
154#[derive(Debug, Default, Clone)]
155pub struct Dispatch;
156
157impl BackendTypes for Dispatch {
158    type Device = DispatchDevice;
159
160    type FloatTensorPrimitive = DispatchTensor;
161    type IntTensorPrimitive = DispatchTensor;
162    type BoolTensorPrimitive = DispatchTensor;
163    type QuantizedTensorPrimitive = DispatchTensor;
164
165    type GraphPrimitive = DispatchGraph;
166}
167
168impl Backend for Dispatch {
169    fn name(device: &Self::Device) -> String {
170        let inner = dispatch_device!(device, |device| B::name(device));
171        format!("dispatch<{inner}>")
172    }
173
174    fn seed(device: &Self::Device, seed: u64) {
175        dispatch_device!(device, |device| B::seed(device, seed))
176    }
177
178    fn sync(device: &Self::Device) -> Result<(), ExecutionError> {
179        dispatch_device!(device, |device| B::sync(device))
180    }
181
182    fn graph_prepare(device: &Self::Device) -> Result<(), ExecutionError> {
183        dispatch_device!(device, |device| B::graph_prepare(device))
184    }
185
186    fn graph_start_capture(device: &Self::Device) -> Result<(), ExecutionError> {
187        dispatch_device!(device, |device| B::graph_start_capture(device))
188    }
189
190    fn graph_stop_capture(device: &Self::Device) -> Result<DispatchGraph, ExecutionError> {
191        backend_list!(graph_stop_capture_arms, device)
192    }
193
194    unsafe fn graph_replay(
195        device: &Self::Device,
196        graph: &DispatchGraph,
197    ) -> Result<(), ExecutionError> {
198        backend_list!(graph_replay_arms, device, graph)
199    }
200
201    fn dtype_usage(device: &Self::Device, dtype: DType) -> burn_backend::DTypeUsageSet {
202        dispatch_device!(device, |device| B::dtype_usage(device, dtype))
203    }
204
205    fn ad_enabled(device: &Self::Device) -> bool {
206        match device {
207            #[cfg(feature = "autodiff")]
208            DispatchDevice::Autodiff(_) => true,
209            _ => false,
210        }
211    }
212
213    fn device_count(type_id: u16) -> usize {
214        let (dispatch_id, backend_type_id) = DispatchDevice::decode_type_id(type_id);
215        match dispatch_id {
216            #[cfg(feature = "cpu")]
217            DispatchDeviceId::Cpu => Cpu::device_count(backend_type_id),
218            #[cfg(feature = "cuda")]
219            DispatchDeviceId::Cuda => Cuda::device_count(backend_type_id),
220            #[cfg(feature = "metal")]
221            DispatchDeviceId::Metal => Metal::device_count(backend_type_id),
222            #[cfg(feature = "rocm")]
223            DispatchDeviceId::Rocm => Rocm::device_count(backend_type_id),
224            #[cfg(feature = "vulkan")]
225            DispatchDeviceId::Vulkan => Vulkan::device_count(backend_type_id),
226            #[cfg(feature = "wgpu")]
227            DispatchDeviceId::Wgpu => Wgpu::device_count(backend_type_id),
228            #[cfg(feature = "webgpu")]
229            DispatchDeviceId::WebGpu => WebGpu::device_count(backend_type_id),
230            #[cfg(any(feature = "flex", default_backend))]
231            DispatchDeviceId::Flex => Flex::device_count(backend_type_id),
232            #[cfg(feature = "ndarray")]
233            DispatchDeviceId::NdArray => NdArray::device_count(backend_type_id),
234            #[cfg(feature = "tch")]
235            DispatchDeviceId::LibTorch => LibTorch::device_count(backend_type_id),
236            #[cfg(feature = "remote")]
237            DispatchDeviceId::Remote => Remote::device_count(backend_type_id),
238            _ => unreachable!("No backend feature enabled."),
239        }
240    }
241
242    fn memory_persistent_allocations<
243        Output: Send,
244        Input: Send,
245        Func: Fn(Input) -> Output + Send,
246    >(
247        device: &Self::Device,
248        input: Input,
249        func: Func,
250    ) -> Output {
251        dispatch_device!(device, |device| B::memory_persistent_allocations(
252            device, input, func
253        ))
254    }
255
256    fn memory_cleanup(device: &Self::Device) {
257        dispatch_device!(device, |device| B::memory_cleanup(device))
258    }
259
260    fn staging<'a, Iter>(data: Iter, device: &Self::Device)
261    where
262        Iter: Iterator<Item = &'a mut burn_backend::TensorData>,
263    {
264        dispatch_device!(device, |device| B::staging(data, device))
265    }
266
267    fn supports_dtype(device: &Self::Device, dtype: DType) -> bool {
268        dispatch_device!(device, |device| B::supports_dtype(device, dtype))
269    }
270
271    fn flush(device: &Self::Device) {
272        dispatch_device!(device, |device| B::flush(device))
273    }
274}
275
276#[cfg(feature = "autodiff")]
277impl AutodiffBackend for Dispatch {
278    type InnerBackend = Dispatch;
279
280    type Gradients = Gradients;
281
282    fn backward(tensor: DispatchTensor) -> Self::Gradients {
283        let DispatchTensor { kind, .. } = tensor;
284
285        match kind {
286            DispatchTensorKind::Autodiff(tensor) => match *tensor {
287                #[cfg(feature = "cpu")]
288                DispatchTensorKind::Cpu(tensor) => tensor.autodiff().backward(),
289                #[cfg(feature = "cuda")]
290                DispatchTensorKind::Cuda(tensor) => tensor.autodiff().backward(),
291                #[cfg(feature = "metal")]
292                DispatchTensorKind::Metal(tensor) => tensor.autodiff().backward(),
293                #[cfg(feature = "rocm")]
294                DispatchTensorKind::Rocm(tensor) => tensor.autodiff().backward(),
295                #[cfg(feature = "vulkan")]
296                DispatchTensorKind::Vulkan(tensor) => tensor.autodiff().backward(),
297                #[cfg(feature = "wgpu")]
298                DispatchTensorKind::Wgpu(tensor) => tensor.autodiff().backward(),
299                #[cfg(feature = "webgpu")]
300                DispatchTensorKind::WebGpu(tensor) => tensor.autodiff().backward(),
301                #[cfg(any(feature = "flex", default_backend))]
302                DispatchTensorKind::Flex(tensor) => tensor.autodiff().backward(),
303                #[cfg(feature = "ndarray")]
304                DispatchTensorKind::NdArray(tensor) => tensor.autodiff().backward(),
305                #[cfg(feature = "tch")]
306                DispatchTensorKind::LibTorch(tensor) => tensor.autodiff().backward(),
307                #[cfg(feature = "remote")]
308                DispatchTensorKind::Remote(tensor) => tensor.autodiff().backward(),
309                DispatchTensorKind::Autodiff(_) => {
310                    panic!("Autodiff should not wrap an autodiff tensor.")
311                }
312            },
313            _ => panic!("Requires autodiff tensor."),
314        }
315    }
316
317    fn grad(tensor: &DispatchTensor, grads: &Self::Gradients) -> Option<DispatchTensor> {
318        let DispatchTensor {
319            kind,
320            checkpointing,
321        } = tensor;
322        let grad: Option<DispatchTensorKind> = match &kind {
323            DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
324                #[cfg(feature = "cpu")]
325                DispatchTensorKind::Cpu(tensor) => tensor
326                    .as_autodiff()
327                    .grad(grads)
328                    .map(|t| DispatchTensorKind::Cpu(crate::BackendTensor::Float(t))),
329                #[cfg(feature = "cuda")]
330                DispatchTensorKind::Cuda(tensor) => tensor
331                    .as_autodiff()
332                    .grad(grads)
333                    .map(|t| DispatchTensorKind::Cuda(crate::BackendTensor::Float(t))),
334                #[cfg(feature = "metal")]
335                DispatchTensorKind::Metal(tensor) => tensor
336                    .as_autodiff()
337                    .grad(grads)
338                    .map(|t| DispatchTensorKind::Metal(crate::BackendTensor::Float(t))),
339                #[cfg(feature = "rocm")]
340                DispatchTensorKind::Rocm(tensor) => tensor
341                    .as_autodiff()
342                    .grad(grads)
343                    .map(|t| DispatchTensorKind::Rocm(crate::BackendTensor::Float(t))),
344                #[cfg(feature = "vulkan")]
345                DispatchTensorKind::Vulkan(tensor) => tensor
346                    .as_autodiff()
347                    .grad(grads)
348                    .map(|t| DispatchTensorKind::Vulkan(crate::BackendTensor::Float(t))),
349                #[cfg(feature = "wgpu")]
350                DispatchTensorKind::Wgpu(tensor) => tensor
351                    .as_autodiff()
352                    .grad(grads)
353                    .map(|t| DispatchTensorKind::Wgpu(crate::BackendTensor::Float(t))),
354                #[cfg(feature = "webgpu")]
355                DispatchTensorKind::WebGpu(tensor) => tensor
356                    .as_autodiff()
357                    .grad(grads)
358                    .map(|t| DispatchTensorKind::WebGpu(crate::BackendTensor::Float(t))),
359                #[cfg(any(feature = "flex", default_backend))]
360                DispatchTensorKind::Flex(tensor) => tensor
361                    .as_autodiff()
362                    .grad(grads)
363                    .map(|t| DispatchTensorKind::Flex(crate::BackendTensor::Float(t))),
364                #[cfg(feature = "ndarray")]
365                DispatchTensorKind::NdArray(tensor) => tensor
366                    .as_autodiff()
367                    .grad(grads)
368                    .map(|t| DispatchTensorKind::NdArray(crate::BackendTensor::Float(t))),
369                #[cfg(feature = "tch")]
370                DispatchTensorKind::LibTorch(tensor) => tensor
371                    .as_autodiff()
372                    .grad(grads)
373                    .map(|t| DispatchTensorKind::LibTorch(crate::BackendTensor::Float(t))),
374                #[cfg(feature = "remote")]
375                DispatchTensorKind::Remote(tensor) => tensor
376                    .as_autodiff()
377                    .grad(grads)
378                    .map(|t| DispatchTensorKind::Remote(crate::BackendTensor::Float(t))),
379                DispatchTensorKind::Autodiff(_) => {
380                    panic!("Autodiff should not wrap an autodiff tensor.")
381                }
382            },
383            _ => panic!("Requires autodiff tensor."),
384        };
385        grad.map(|kind| DispatchTensor {
386            kind,
387            checkpointing: *checkpointing,
388        })
389    }
390
391    fn grad_remove(tensor: &DispatchTensor, grads: &mut Self::Gradients) -> Option<DispatchTensor> {
392        let DispatchTensor {
393            kind,
394            checkpointing,
395        } = tensor;
396        let grad: Option<DispatchTensorKind> = match &kind {
397            DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
398                #[cfg(feature = "cpu")]
399                DispatchTensorKind::Cpu(tensor) => tensor
400                    .as_autodiff()
401                    .grad_remove(grads)
402                    .map(|t| DispatchTensorKind::Cpu(crate::BackendTensor::Float(t))),
403                #[cfg(feature = "cuda")]
404                DispatchTensorKind::Cuda(tensor) => tensor
405                    .as_autodiff()
406                    .grad_remove(grads)
407                    .map(|t| DispatchTensorKind::Cuda(crate::BackendTensor::Float(t))),
408                #[cfg(feature = "metal")]
409                DispatchTensorKind::Metal(tensor) => tensor
410                    .as_autodiff()
411                    .grad_remove(grads)
412                    .map(|t| DispatchTensorKind::Metal(crate::BackendTensor::Float(t))),
413                #[cfg(feature = "rocm")]
414                DispatchTensorKind::Rocm(tensor) => tensor
415                    .as_autodiff()
416                    .grad_remove(grads)
417                    .map(|t| DispatchTensorKind::Rocm(crate::BackendTensor::Float(t))),
418                #[cfg(feature = "vulkan")]
419                DispatchTensorKind::Vulkan(tensor) => tensor
420                    .as_autodiff()
421                    .grad_remove(grads)
422                    .map(|t| DispatchTensorKind::Vulkan(crate::BackendTensor::Float(t))),
423                #[cfg(feature = "wgpu")]
424                DispatchTensorKind::Wgpu(tensor) => tensor
425                    .as_autodiff()
426                    .grad_remove(grads)
427                    .map(|t| DispatchTensorKind::Wgpu(crate::BackendTensor::Float(t))),
428                #[cfg(feature = "webgpu")]
429                DispatchTensorKind::WebGpu(tensor) => tensor
430                    .as_autodiff()
431                    .grad_remove(grads)
432                    .map(|t| DispatchTensorKind::WebGpu(crate::BackendTensor::Float(t))),
433                #[cfg(any(feature = "flex", default_backend))]
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                DispatchTensorKind::Autodiff(_) => {
454                    panic!("Autodiff should not wrap an autodiff tensor.")
455                }
456            },
457            _ => panic!("Requires autodiff tensor."),
458        };
459        grad.map(|kind| DispatchTensor {
460            kind,
461            checkpointing: *checkpointing,
462        })
463    }
464
465    fn grad_replace(tensor: &DispatchTensor, grads: &mut Self::Gradients, grad: DispatchTensor) {
466        let DispatchTensor {
467            kind,
468            checkpointing,
469        } = tensor;
470        let DispatchTensor {
471            kind: grad,
472            checkpointing: grad_ckp,
473        } = grad;
474        debug_assert_eq!(checkpointing, &grad_ckp);
475
476        match &kind {
477            DispatchTensorKind::Autodiff(inner_kind) => match (&**inner_kind, grad) {
478                #[cfg(feature = "cpu")]
479                (DispatchTensorKind::Cpu(tensor), DispatchTensorKind::Cpu(grad)) => {
480                    tensor.as_autodiff().grad_replace(grads, grad.float())
481                }
482                #[cfg(feature = "cuda")]
483                (DispatchTensorKind::Cuda(tensor), DispatchTensorKind::Cuda(grad)) => {
484                    tensor.as_autodiff().grad_replace(grads, grad.float())
485                }
486                #[cfg(feature = "metal")]
487                (DispatchTensorKind::Metal(tensor), DispatchTensorKind::Metal(grad)) => {
488                    tensor.as_autodiff().grad_replace(grads, grad.float())
489                }
490                #[cfg(feature = "rocm")]
491                (DispatchTensorKind::Rocm(tensor), DispatchTensorKind::Rocm(grad)) => {
492                    tensor.as_autodiff().grad_replace(grads, grad.float())
493                }
494                #[cfg(feature = "vulkan")]
495                (DispatchTensorKind::Vulkan(tensor), DispatchTensorKind::Vulkan(grad)) => {
496                    tensor.as_autodiff().grad_replace(grads, grad.float())
497                }
498                #[cfg(feature = "wgpu")]
499                (DispatchTensorKind::Wgpu(tensor), DispatchTensorKind::Wgpu(grad)) => {
500                    tensor.as_autodiff().grad_replace(grads, grad.float())
501                }
502                #[cfg(feature = "webgpu")]
503                (DispatchTensorKind::WebGpu(tensor), DispatchTensorKind::WebGpu(grad)) => {
504                    tensor.as_autodiff().grad_replace(grads, grad.float())
505                }
506                #[cfg(any(feature = "flex", default_backend))]
507                (DispatchTensorKind::Flex(tensor), DispatchTensorKind::Flex(grad)) => {
508                    tensor.as_autodiff().grad_replace(grads, grad.float())
509                }
510                #[cfg(feature = "ndarray")]
511                (DispatchTensorKind::NdArray(tensor), DispatchTensorKind::NdArray(grad)) => {
512                    tensor.as_autodiff().grad_replace(grads, grad.float())
513                }
514                #[cfg(feature = "remote")]
515                (DispatchTensorKind::Remote(tensor), DispatchTensorKind::Remote(grad)) => {
516                    tensor.as_autodiff().grad_replace(grads, grad.float())
517                }
518                (DispatchTensorKind::Autodiff(_), _) => {
519                    panic!("Autodiff should not wrap an autodiff tensor.")
520                }
521                // TODO: distributed message?
522                (t, g) => panic!(
523                    "The provided tensors are not on the same backend. Got backends {t:?} and {g:?}."
524                ),
525            },
526            _ => panic!("Requires autodiff tensor."),
527        }
528    }
529
530    fn inner(tensor: DispatchTensor) -> DispatchTensor {
531        let DispatchTensor {
532            kind,
533            checkpointing: _,
534        } = tensor;
535
536        let kind = match kind {
537            DispatchTensorKind::Autodiff(inner_kind) => match *inner_kind {
538                #[cfg(feature = "cpu")]
539                DispatchTensorKind::Cpu(tensor) => DispatchTensorKind::Cpu(
540                    crate::BackendTensor::Float(tensor.autodiff().primitive),
541                ),
542                #[cfg(feature = "cuda")]
543                DispatchTensorKind::Cuda(tensor) => DispatchTensorKind::Cuda(
544                    crate::BackendTensor::Float(tensor.autodiff().primitive),
545                ),
546                #[cfg(feature = "metal")]
547                DispatchTensorKind::Metal(tensor) => DispatchTensorKind::Metal(
548                    crate::BackendTensor::Float(tensor.autodiff().primitive),
549                ),
550                #[cfg(feature = "rocm")]
551                DispatchTensorKind::Rocm(tensor) => DispatchTensorKind::Rocm(
552                    crate::BackendTensor::Float(tensor.autodiff().primitive),
553                ),
554                #[cfg(feature = "vulkan")]
555                DispatchTensorKind::Vulkan(tensor) => DispatchTensorKind::Vulkan(
556                    crate::BackendTensor::Float(tensor.autodiff().primitive),
557                ),
558                #[cfg(feature = "wgpu")]
559                DispatchTensorKind::Wgpu(tensor) => DispatchTensorKind::Wgpu(
560                    crate::BackendTensor::Float(tensor.autodiff().primitive),
561                ),
562                #[cfg(feature = "webgpu")]
563                DispatchTensorKind::WebGpu(tensor) => DispatchTensorKind::WebGpu(
564                    crate::BackendTensor::Float(tensor.autodiff().primitive),
565                ),
566                #[cfg(any(feature = "flex", default_backend))]
567                DispatchTensorKind::Flex(tensor) => DispatchTensorKind::Flex(
568                    crate::BackendTensor::Float(tensor.autodiff().primitive),
569                ),
570                #[cfg(feature = "ndarray")]
571                DispatchTensorKind::NdArray(tensor) => DispatchTensorKind::NdArray(
572                    crate::BackendTensor::Float(tensor.autodiff().primitive),
573                ),
574                #[cfg(feature = "tch")]
575                DispatchTensorKind::LibTorch(tensor) => DispatchTensorKind::LibTorch(
576                    crate::BackendTensor::Float(tensor.autodiff().primitive),
577                ),
578                #[cfg(feature = "remote")]
579                DispatchTensorKind::Remote(tensor) => DispatchTensorKind::Remote(
580                    crate::BackendTensor::Float(tensor.autodiff().primitive),
581                ),
582                DispatchTensorKind::Autodiff(_) => {
583                    panic!("Autodiff should not wrap an autodiff tensor.")
584                }
585            },
586            _ => panic!("Requires autodiff tensor."),
587        };
588        DispatchTensor {
589            kind,
590            checkpointing: None,
591        }
592    }
593
594    fn int_inner(tensor: DispatchTensor) -> DispatchTensor {
595        tensor
596    }
597
598    fn bool_inner(tensor: DispatchTensor) -> DispatchTensor {
599        tensor
600    }
601
602    fn q_inner(tensor: DispatchTensor) -> DispatchTensor {
603        tensor
604    }
605
606    fn from_inner(tensor: DispatchTensor) -> DispatchTensor {
607        let DispatchTensor {
608            kind,
609            checkpointing,
610        } = tensor;
611
612        let kind = match kind {
613            #[cfg(feature = "cpu")]
614            DispatchTensorKind::Cpu(tensor) => {
615                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cpu(
616                    crate::BackendTensor::Autodiff(Autodiff::<Cpu>::from_inner(tensor.float())),
617                )))
618            }
619            #[cfg(feature = "cuda")]
620            DispatchTensorKind::Cuda(tensor) => {
621                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cuda(
622                    crate::BackendTensor::Autodiff(Autodiff::<Cuda>::from_inner(tensor.float())),
623                )))
624            }
625            #[cfg(feature = "metal")]
626            DispatchTensorKind::Metal(tensor) => {
627                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Metal(
628                    crate::BackendTensor::Autodiff(Autodiff::<Metal>::from_inner(tensor.float())),
629                )))
630            }
631            #[cfg(feature = "rocm")]
632            DispatchTensorKind::Rocm(tensor) => {
633                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Rocm(
634                    crate::BackendTensor::Autodiff(Autodiff::<Rocm>::from_inner(tensor.float())),
635                )))
636            }
637            #[cfg(feature = "vulkan")]
638            DispatchTensorKind::Vulkan(tensor) => {
639                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Vulkan(
640                    crate::BackendTensor::Autodiff(Autodiff::<Vulkan>::from_inner(tensor.float())),
641                )))
642            }
643            #[cfg(feature = "wgpu")]
644            DispatchTensorKind::Wgpu(tensor) => {
645                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Wgpu(
646                    crate::BackendTensor::Autodiff(Autodiff::<Wgpu>::from_inner(tensor.float())),
647                )))
648            }
649            #[cfg(feature = "webgpu")]
650            DispatchTensorKind::WebGpu(tensor) => {
651                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::WebGpu(
652                    crate::BackendTensor::Autodiff(Autodiff::<WebGpu>::from_inner(tensor.float())),
653                )))
654            }
655            #[cfg(any(feature = "flex", default_backend))]
656            DispatchTensorKind::Flex(tensor) => {
657                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Flex(
658                    crate::BackendTensor::Autodiff(Autodiff::<Flex>::from_inner(tensor.float())),
659                )))
660            }
661            #[cfg(feature = "ndarray")]
662            DispatchTensorKind::NdArray(tensor) => {
663                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::NdArray(
664                    crate::BackendTensor::Autodiff(Autodiff::<NdArray>::from_inner(tensor.float())),
665                )))
666            }
667            #[cfg(feature = "tch")]
668            DispatchTensorKind::LibTorch(tensor) => DispatchTensorKind::Autodiff(Box::new(
669                DispatchTensorKind::LibTorch(crate::BackendTensor::Autodiff(
670                    Autodiff::<LibTorch>::from_inner(tensor.float()),
671                )),
672            )),
673            #[cfg(feature = "remote")]
674            DispatchTensorKind::Remote(tensor) => {
675                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Remote(
676                    crate::BackendTensor::Autodiff(Autodiff::<Remote>::from_inner(tensor.float())),
677                )))
678            }
679            DispatchTensorKind::Autodiff(_) => {
680                panic!("Autodiff should not wrap an autodiff tensor.")
681            }
682        };
683
684        // TODO: should use C::STRATEGY
685        let checkpointing = if let Some(strategy) = checkpointing {
686            Some(strategy)
687        } else {
688            Some(crate::CheckpointingStrategy::None)
689        };
690        DispatchTensor {
691            kind,
692            checkpointing,
693        }
694    }
695
696    fn int_from_inner(tensor: DispatchTensor) -> DispatchTensor {
697        tensor
698    }
699
700    fn bool_from_inner(tensor: DispatchTensor) -> DispatchTensor {
701        tensor
702    }
703
704    fn q_from_inner(tensor: DispatchTensor) -> DispatchTensor {
705        tensor
706    }
707
708    // Only the collective-capable backends (Cuda/Remote) carry distributed params; in builds
709    // without them the match arms cfg out, leaving the bindings unused and the tail unreachable.
710    #[allow(unused_variables, unreachable_code)]
711    fn set_distributed_params(
712        tensor: DispatchTensor,
713        param_id: DistributedParamId,
714    ) -> DispatchTensor {
715        let DispatchTensor {
716            kind,
717            checkpointing,
718        } = tensor;
719
720        let kind = match kind {
721            DispatchTensorKind::Autodiff(inner_kind) => match *inner_kind {
722                #[cfg(feature = "cuda")]
723                DispatchTensorKind::Cuda(tensor) => {
724                    DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cuda(
725                        crate::BackendTensor::Autodiff(Autodiff::<Cuda>::set_distributed_params(
726                            tensor.as_autodiff().clone(),
727                            param_id,
728                        )),
729                    )))
730                }
731                #[cfg(feature = "remote")]
732                DispatchTensorKind::Remote(tensor) => {
733                    DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Remote(
734                        crate::BackendTensor::Autodiff(Autodiff::<Remote>::set_distributed_params(
735                            tensor.as_autodiff().clone(),
736                            param_id,
737                        )),
738                    )))
739                }
740                DispatchTensorKind::Autodiff(_) => {
741                    panic!("Autodiff should not wrap an autodiff tensor.")
742                }
743                other => {
744                    panic!("Distributed operations are not supported for tensor kind {other:?}")
745                }
746            },
747            _ => panic!("Requires autodiff tensor."),
748        };
749
750        let checkpointing = if let Some(strategy) = checkpointing {
751            Some(strategy)
752        } else {
753            Some(crate::CheckpointingStrategy::None)
754        };
755        DispatchTensor {
756            kind,
757            checkpointing,
758        }
759    }
760
761    #[allow(unused_variables)]
762    fn distributed_params(tensor: &DispatchTensor) -> Option<DistributedParams> {
763        let DispatchTensor {
764            kind,
765            checkpointing: _,
766        } = tensor;
767
768        match &kind {
769            DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
770                #[cfg(feature = "cuda")]
771                DispatchTensorKind::Cuda(tensor) => {
772                    tensor.as_autodiff().node.distributed_params.clone()
773                }
774                #[cfg(feature = "remote")]
775                DispatchTensorKind::Remote(tensor) => {
776                    tensor.as_autodiff().node.distributed_params.clone()
777                }
778
779                DispatchTensorKind::Autodiff(_) => {
780                    panic!("Autodiff should not wrap an autodiff tensor.")
781                }
782                // Backends without distributed support never carry distributed params.
783                _ => None,
784            },
785            _ => panic!("Requires autodiff tensor."),
786        }
787    }
788
789    #[allow(unused_variables)]
790    fn is_distributed(tensor: &DispatchTensor) -> bool {
791        let DispatchTensor {
792            kind,
793            checkpointing: _,
794        } = tensor;
795
796        match &kind {
797            DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
798                #[cfg(feature = "cuda")]
799                DispatchTensorKind::Cuda(tensor) => {
800                    tensor.as_autodiff().node.distributed_params.is_some()
801                }
802                #[cfg(feature = "remote")]
803                DispatchTensorKind::Remote(tensor) => {
804                    tensor.as_autodiff().node.distributed_params.is_some()
805                }
806
807                DispatchTensorKind::Autodiff(_) => {
808                    panic!("Autodiff should not wrap an autodiff tensor.")
809                }
810                // Backends without distributed support are never distributed.
811                _ => false,
812            },
813            _ => panic!("Requires autodiff tensor."),
814        }
815    }
816}
817
818// NOTE: placeholder for autodiff module requirements
819#[cfg(not(feature = "autodiff"))]
820impl AutodiffBackend for Dispatch {
821    type InnerBackend = Dispatch;
822
823    type Gradients = bool;
824
825    fn backward(_tensor: DispatchTensor) -> Self::Gradients {
826        unimplemented!("Requires `autodiff` feature")
827    }
828
829    fn grad(_tensor: &DispatchTensor, _grads: &Self::Gradients) -> Option<DispatchTensor> {
830        unimplemented!("Requires `autodiff` feature")
831    }
832
833    fn grad_remove(
834        _tensor: &DispatchTensor,
835        _grads: &mut Self::Gradients,
836    ) -> Option<DispatchTensor> {
837        unimplemented!("Requires `autodiff` feature")
838    }
839
840    fn grad_replace(_tensor: &DispatchTensor, _grads: &mut Self::Gradients, _grad: DispatchTensor) {
841        unimplemented!("Requires `autodiff` feature")
842    }
843
844    fn inner(_tensor: DispatchTensor) -> DispatchTensor {
845        unimplemented!("Requires `autodiff` feature")
846    }
847
848    fn int_inner(_tensor: DispatchTensor) -> DispatchTensor {
849        unimplemented!("Requires `autodiff` feature")
850    }
851
852    fn bool_inner(_tensor: DispatchTensor) -> DispatchTensor {
853        unimplemented!("Requires `autodiff` feature")
854    }
855
856    fn q_inner(_tensor: DispatchTensor) -> DispatchTensor {
857        unimplemented!("Requires `autodiff` feature")
858    }
859
860    fn from_inner(_tensor: DispatchTensor) -> DispatchTensor {
861        unimplemented!("Requires `autodiff` feature")
862    }
863
864    fn int_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
865        unimplemented!("Requires `autodiff` feature")
866    }
867
868    fn bool_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
869        unimplemented!("Requires `autodiff` feature")
870    }
871
872    fn q_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
873        unimplemented!("Requires `autodiff` feature")
874    }
875}
876
877impl Dispatch {
878    /// List all available devices of the specified [type id](DispatchDeviceId).
879    pub fn enumerate(type_id: DispatchDeviceId) -> Vec<DispatchDevice> {
880        // TODO: right now this assumes `type_id = 0`, but WgpuDevice and LibTorchDevice have other types.
881        match type_id {
882            #[cfg(feature = "cpu")]
883            DispatchDeviceId::Cpu => vec![CpuDevice.into()],
884            #[cfg(feature = "cuda")]
885            DispatchDeviceId::Cuda => (0..Cuda::device_count(0))
886                .map(|i| CudaDevice::new(i).into())
887                .collect(),
888            #[cfg(feature = "metal")]
889            DispatchDeviceId::Metal => (0..Metal::device_count(0))
890                .map(|i| DispatchDevice::Metal(WgpuDevice::DiscreteGpu(i)))
891                .collect(),
892            #[cfg(feature = "rocm")]
893            DispatchDeviceId::Rocm => (0..Rocm::device_count(0))
894                .map(|i| RocmDevice::new(i).into())
895                .collect(),
896            #[cfg(feature = "vulkan")]
897            DispatchDeviceId::Vulkan => (0..Vulkan::device_count(0))
898                .map(|i| DispatchDevice::Vulkan(WgpuDevice::DiscreteGpu(i)))
899                .collect(),
900            #[cfg(feature = "wgpu")]
901            DispatchDeviceId::Wgpu => (0..Wgpu::device_count(0))
902                .map(|i| DispatchDevice::Wgpu(WgpuDevice::DiscreteGpu(i)))
903                .collect(),
904            #[cfg(feature = "webgpu")]
905            DispatchDeviceId::WebGpu => (0..WebGpu::device_count(0))
906                .map(|i| DispatchDevice::WebGpu(WgpuDevice::DiscreteGpu(i)))
907                .collect(),
908            #[cfg(any(feature = "flex", default_backend))]
909            DispatchDeviceId::Flex => vec![FlexDevice.into()],
910            #[cfg(feature = "ndarray")]
911            DispatchDeviceId::NdArray => vec![NdArrayDevice::Cpu.into()],
912            #[cfg(feature = "tch")]
913            DispatchDeviceId::LibTorch => (0..LibTorch::device_count(0))
914                .map(|i| LibTorchDevice::Cuda(i).into())
915                .collect(),
916            #[cfg(feature = "remote")]
917            // Remote devices are keyed by a network address, which the type-id-only
918            // `enumerate` can't carry. Use [`Dispatch::enumerate_remote_websocket`] to list the devices
919            // behind a given address.
920            DispatchDeviceId::Remote => Vec::new(),
921            _ => unreachable!("No backend feature enabled."),
922        }
923    }
924
925    /// List every device hosted by the remote server at `address`.
926    ///
927    /// Unlike [`enumerate`](Self::enumerate), remote devices are identified by a network
928    /// address rather than enumerable local hardware, so they need a dedicated entry point.
929    /// Connecting to the server (required to learn its device count) happens here; see
930    /// [`RemoteDevice::enumerate_websocket`].
931    ///
932    /// Websocket-only: Iroh peers are addressed by endpoint identity, not a URL string.
933    #[cfg(feature = "remote-websocket")]
934    pub fn enumerate_remote_websocket(address: &str) -> Vec<DispatchDevice> {
935        RemoteDevice::enumerate_websocket(address)
936            .into_iter()
937            .map(DispatchDevice::Remote)
938            .collect()
939    }
940}