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        // The replacement gradient is an inner-backend tensor, so it carries no
467        // checkpointing strategy of its own. Only a gradient obtained from `grad()` does,
468        // because that getter copies the source tensor's strategy onto its result.
469        // Comparing the two therefore only holds when a gradient is round-tripped through
470        // `grad()`, which the public API does not require.
471        let DispatchTensor { kind, .. } = tensor;
472        let DispatchTensor { kind: grad, .. } = grad;
473
474        match &kind {
475            DispatchTensorKind::Autodiff(inner_kind) => match (&**inner_kind, grad) {
476                #[cfg(feature = "cpu")]
477                (DispatchTensorKind::Cpu(tensor), DispatchTensorKind::Cpu(grad)) => {
478                    tensor.as_autodiff().grad_replace(grads, grad.float())
479                }
480                #[cfg(feature = "cuda")]
481                (DispatchTensorKind::Cuda(tensor), DispatchTensorKind::Cuda(grad)) => {
482                    tensor.as_autodiff().grad_replace(grads, grad.float())
483                }
484                #[cfg(feature = "metal")]
485                (DispatchTensorKind::Metal(tensor), DispatchTensorKind::Metal(grad)) => {
486                    tensor.as_autodiff().grad_replace(grads, grad.float())
487                }
488                #[cfg(feature = "rocm")]
489                (DispatchTensorKind::Rocm(tensor), DispatchTensorKind::Rocm(grad)) => {
490                    tensor.as_autodiff().grad_replace(grads, grad.float())
491                }
492                #[cfg(feature = "vulkan")]
493                (DispatchTensorKind::Vulkan(tensor), DispatchTensorKind::Vulkan(grad)) => {
494                    tensor.as_autodiff().grad_replace(grads, grad.float())
495                }
496                #[cfg(feature = "wgpu")]
497                (DispatchTensorKind::Wgpu(tensor), DispatchTensorKind::Wgpu(grad)) => {
498                    tensor.as_autodiff().grad_replace(grads, grad.float())
499                }
500                #[cfg(feature = "webgpu")]
501                (DispatchTensorKind::WebGpu(tensor), DispatchTensorKind::WebGpu(grad)) => {
502                    tensor.as_autodiff().grad_replace(grads, grad.float())
503                }
504                #[cfg(any(feature = "flex", default_backend))]
505                (DispatchTensorKind::Flex(tensor), DispatchTensorKind::Flex(grad)) => {
506                    tensor.as_autodiff().grad_replace(grads, grad.float())
507                }
508                #[cfg(feature = "ndarray")]
509                (DispatchTensorKind::NdArray(tensor), DispatchTensorKind::NdArray(grad)) => {
510                    tensor.as_autodiff().grad_replace(grads, grad.float())
511                }
512                #[cfg(feature = "remote")]
513                (DispatchTensorKind::Remote(tensor), DispatchTensorKind::Remote(grad)) => {
514                    tensor.as_autodiff().grad_replace(grads, grad.float())
515                }
516                (DispatchTensorKind::Autodiff(_), _) => {
517                    panic!("Autodiff should not wrap an autodiff tensor.")
518                }
519                // TODO: distributed message?
520                (t, g) => panic!(
521                    "The provided tensors are not on the same backend. Got backends {t:?} and {g:?}."
522                ),
523            },
524            _ => panic!("Requires autodiff tensor."),
525        }
526    }
527
528    fn inner(tensor: DispatchTensor) -> DispatchTensor {
529        let DispatchTensor {
530            kind,
531            checkpointing: _,
532        } = tensor;
533
534        let kind = match kind {
535            DispatchTensorKind::Autodiff(inner_kind) => match *inner_kind {
536                #[cfg(feature = "cpu")]
537                DispatchTensorKind::Cpu(tensor) => DispatchTensorKind::Cpu(
538                    crate::BackendTensor::Float(tensor.autodiff().primitive),
539                ),
540                #[cfg(feature = "cuda")]
541                DispatchTensorKind::Cuda(tensor) => DispatchTensorKind::Cuda(
542                    crate::BackendTensor::Float(tensor.autodiff().primitive),
543                ),
544                #[cfg(feature = "metal")]
545                DispatchTensorKind::Metal(tensor) => DispatchTensorKind::Metal(
546                    crate::BackendTensor::Float(tensor.autodiff().primitive),
547                ),
548                #[cfg(feature = "rocm")]
549                DispatchTensorKind::Rocm(tensor) => DispatchTensorKind::Rocm(
550                    crate::BackendTensor::Float(tensor.autodiff().primitive),
551                ),
552                #[cfg(feature = "vulkan")]
553                DispatchTensorKind::Vulkan(tensor) => DispatchTensorKind::Vulkan(
554                    crate::BackendTensor::Float(tensor.autodiff().primitive),
555                ),
556                #[cfg(feature = "wgpu")]
557                DispatchTensorKind::Wgpu(tensor) => DispatchTensorKind::Wgpu(
558                    crate::BackendTensor::Float(tensor.autodiff().primitive),
559                ),
560                #[cfg(feature = "webgpu")]
561                DispatchTensorKind::WebGpu(tensor) => DispatchTensorKind::WebGpu(
562                    crate::BackendTensor::Float(tensor.autodiff().primitive),
563                ),
564                #[cfg(any(feature = "flex", default_backend))]
565                DispatchTensorKind::Flex(tensor) => DispatchTensorKind::Flex(
566                    crate::BackendTensor::Float(tensor.autodiff().primitive),
567                ),
568                #[cfg(feature = "ndarray")]
569                DispatchTensorKind::NdArray(tensor) => DispatchTensorKind::NdArray(
570                    crate::BackendTensor::Float(tensor.autodiff().primitive),
571                ),
572                #[cfg(feature = "tch")]
573                DispatchTensorKind::LibTorch(tensor) => DispatchTensorKind::LibTorch(
574                    crate::BackendTensor::Float(tensor.autodiff().primitive),
575                ),
576                #[cfg(feature = "remote")]
577                DispatchTensorKind::Remote(tensor) => DispatchTensorKind::Remote(
578                    crate::BackendTensor::Float(tensor.autodiff().primitive),
579                ),
580                DispatchTensorKind::Autodiff(_) => {
581                    panic!("Autodiff should not wrap an autodiff tensor.")
582                }
583            },
584            _ => panic!("Requires autodiff tensor."),
585        };
586        DispatchTensor {
587            kind,
588            checkpointing: None,
589        }
590    }
591
592    fn int_inner(tensor: DispatchTensor) -> DispatchTensor {
593        tensor
594    }
595
596    fn bool_inner(tensor: DispatchTensor) -> DispatchTensor {
597        tensor
598    }
599
600    fn q_inner(tensor: DispatchTensor) -> DispatchTensor {
601        tensor
602    }
603
604    fn from_inner(tensor: DispatchTensor) -> DispatchTensor {
605        let DispatchTensor {
606            kind,
607            checkpointing,
608        } = tensor;
609
610        let kind = match kind {
611            #[cfg(feature = "cpu")]
612            DispatchTensorKind::Cpu(tensor) => {
613                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cpu(
614                    crate::BackendTensor::Autodiff(Autodiff::<Cpu>::from_inner(tensor.float())),
615                )))
616            }
617            #[cfg(feature = "cuda")]
618            DispatchTensorKind::Cuda(tensor) => {
619                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cuda(
620                    crate::BackendTensor::Autodiff(Autodiff::<Cuda>::from_inner(tensor.float())),
621                )))
622            }
623            #[cfg(feature = "metal")]
624            DispatchTensorKind::Metal(tensor) => {
625                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Metal(
626                    crate::BackendTensor::Autodiff(Autodiff::<Metal>::from_inner(tensor.float())),
627                )))
628            }
629            #[cfg(feature = "rocm")]
630            DispatchTensorKind::Rocm(tensor) => {
631                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Rocm(
632                    crate::BackendTensor::Autodiff(Autodiff::<Rocm>::from_inner(tensor.float())),
633                )))
634            }
635            #[cfg(feature = "vulkan")]
636            DispatchTensorKind::Vulkan(tensor) => {
637                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Vulkan(
638                    crate::BackendTensor::Autodiff(Autodiff::<Vulkan>::from_inner(tensor.float())),
639                )))
640            }
641            #[cfg(feature = "wgpu")]
642            DispatchTensorKind::Wgpu(tensor) => {
643                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Wgpu(
644                    crate::BackendTensor::Autodiff(Autodiff::<Wgpu>::from_inner(tensor.float())),
645                )))
646            }
647            #[cfg(feature = "webgpu")]
648            DispatchTensorKind::WebGpu(tensor) => {
649                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::WebGpu(
650                    crate::BackendTensor::Autodiff(Autodiff::<WebGpu>::from_inner(tensor.float())),
651                )))
652            }
653            #[cfg(any(feature = "flex", default_backend))]
654            DispatchTensorKind::Flex(tensor) => {
655                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Flex(
656                    crate::BackendTensor::Autodiff(Autodiff::<Flex>::from_inner(tensor.float())),
657                )))
658            }
659            #[cfg(feature = "ndarray")]
660            DispatchTensorKind::NdArray(tensor) => {
661                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::NdArray(
662                    crate::BackendTensor::Autodiff(Autodiff::<NdArray>::from_inner(tensor.float())),
663                )))
664            }
665            #[cfg(feature = "tch")]
666            DispatchTensorKind::LibTorch(tensor) => DispatchTensorKind::Autodiff(Box::new(
667                DispatchTensorKind::LibTorch(crate::BackendTensor::Autodiff(
668                    Autodiff::<LibTorch>::from_inner(tensor.float()),
669                )),
670            )),
671            #[cfg(feature = "remote")]
672            DispatchTensorKind::Remote(tensor) => {
673                DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Remote(
674                    crate::BackendTensor::Autodiff(Autodiff::<Remote>::from_inner(tensor.float())),
675                )))
676            }
677            DispatchTensorKind::Autodiff(_) => {
678                panic!("Autodiff should not wrap an autodiff tensor.")
679            }
680        };
681
682        // TODO: should use C::STRATEGY
683        let checkpointing = if let Some(strategy) = checkpointing {
684            Some(strategy)
685        } else {
686            Some(crate::CheckpointingStrategy::None)
687        };
688        DispatchTensor {
689            kind,
690            checkpointing,
691        }
692    }
693
694    fn int_from_inner(tensor: DispatchTensor) -> DispatchTensor {
695        tensor
696    }
697
698    fn bool_from_inner(tensor: DispatchTensor) -> DispatchTensor {
699        tensor
700    }
701
702    fn q_from_inner(tensor: DispatchTensor) -> DispatchTensor {
703        tensor
704    }
705
706    // Only the collective-capable backends (Cuda/Remote) carry distributed params; in builds
707    // without them the match arms cfg out, leaving the bindings unused and the tail unreachable.
708    #[allow(unused_variables, unreachable_code)]
709    fn set_distributed_params(
710        tensor: DispatchTensor,
711        param_id: DistributedParamId,
712    ) -> DispatchTensor {
713        let DispatchTensor {
714            kind,
715            checkpointing,
716        } = tensor;
717
718        let kind = match kind {
719            DispatchTensorKind::Autodiff(inner_kind) => match *inner_kind {
720                #[cfg(feature = "cuda")]
721                DispatchTensorKind::Cuda(tensor) => {
722                    DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Cuda(
723                        crate::BackendTensor::Autodiff(Autodiff::<Cuda>::set_distributed_params(
724                            tensor.as_autodiff().clone(),
725                            param_id,
726                        )),
727                    )))
728                }
729                #[cfg(feature = "remote")]
730                DispatchTensorKind::Remote(tensor) => {
731                    DispatchTensorKind::Autodiff(Box::new(DispatchTensorKind::Remote(
732                        crate::BackendTensor::Autodiff(Autodiff::<Remote>::set_distributed_params(
733                            tensor.as_autodiff().clone(),
734                            param_id,
735                        )),
736                    )))
737                }
738                DispatchTensorKind::Autodiff(_) => {
739                    panic!("Autodiff should not wrap an autodiff tensor.")
740                }
741                other => {
742                    panic!("Distributed operations are not supported for tensor kind {other:?}")
743                }
744            },
745            _ => panic!("Requires autodiff tensor."),
746        };
747
748        let checkpointing = if let Some(strategy) = checkpointing {
749            Some(strategy)
750        } else {
751            Some(crate::CheckpointingStrategy::None)
752        };
753        DispatchTensor {
754            kind,
755            checkpointing,
756        }
757    }
758
759    #[allow(unused_variables)]
760    fn distributed_params(tensor: &DispatchTensor) -> Option<DistributedParams> {
761        let DispatchTensor {
762            kind,
763            checkpointing: _,
764        } = tensor;
765
766        match &kind {
767            DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
768                #[cfg(feature = "cuda")]
769                DispatchTensorKind::Cuda(tensor) => {
770                    tensor.as_autodiff().node.distributed_params.clone()
771                }
772                #[cfg(feature = "remote")]
773                DispatchTensorKind::Remote(tensor) => {
774                    tensor.as_autodiff().node.distributed_params.clone()
775                }
776
777                DispatchTensorKind::Autodiff(_) => {
778                    panic!("Autodiff should not wrap an autodiff tensor.")
779                }
780                // Backends without distributed support never carry distributed params.
781                _ => None,
782            },
783            _ => panic!("Requires autodiff tensor."),
784        }
785    }
786
787    #[allow(unused_variables)]
788    fn is_distributed(tensor: &DispatchTensor) -> bool {
789        let DispatchTensor {
790            kind,
791            checkpointing: _,
792        } = tensor;
793
794        match &kind {
795            DispatchTensorKind::Autodiff(inner_kind) => match &**inner_kind {
796                #[cfg(feature = "cuda")]
797                DispatchTensorKind::Cuda(tensor) => {
798                    tensor.as_autodiff().node.distributed_params.is_some()
799                }
800                #[cfg(feature = "remote")]
801                DispatchTensorKind::Remote(tensor) => {
802                    tensor.as_autodiff().node.distributed_params.is_some()
803                }
804
805                DispatchTensorKind::Autodiff(_) => {
806                    panic!("Autodiff should not wrap an autodiff tensor.")
807                }
808                // Backends without distributed support are never distributed.
809                _ => false,
810            },
811            _ => panic!("Requires autodiff tensor."),
812        }
813    }
814}
815
816// NOTE: placeholder for autodiff module requirements
817#[cfg(not(feature = "autodiff"))]
818impl AutodiffBackend for Dispatch {
819    type InnerBackend = Dispatch;
820
821    type Gradients = bool;
822
823    fn backward(_tensor: DispatchTensor) -> Self::Gradients {
824        unimplemented!("Requires `autodiff` feature")
825    }
826
827    fn grad(_tensor: &DispatchTensor, _grads: &Self::Gradients) -> Option<DispatchTensor> {
828        unimplemented!("Requires `autodiff` feature")
829    }
830
831    fn grad_remove(
832        _tensor: &DispatchTensor,
833        _grads: &mut Self::Gradients,
834    ) -> Option<DispatchTensor> {
835        unimplemented!("Requires `autodiff` feature")
836    }
837
838    fn grad_replace(_tensor: &DispatchTensor, _grads: &mut Self::Gradients, _grad: DispatchTensor) {
839        unimplemented!("Requires `autodiff` feature")
840    }
841
842    fn inner(_tensor: DispatchTensor) -> DispatchTensor {
843        unimplemented!("Requires `autodiff` feature")
844    }
845
846    fn int_inner(_tensor: DispatchTensor) -> DispatchTensor {
847        unimplemented!("Requires `autodiff` feature")
848    }
849
850    fn bool_inner(_tensor: DispatchTensor) -> DispatchTensor {
851        unimplemented!("Requires `autodiff` feature")
852    }
853
854    fn q_inner(_tensor: DispatchTensor) -> DispatchTensor {
855        unimplemented!("Requires `autodiff` feature")
856    }
857
858    fn from_inner(_tensor: DispatchTensor) -> DispatchTensor {
859        unimplemented!("Requires `autodiff` feature")
860    }
861
862    fn int_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
863        unimplemented!("Requires `autodiff` feature")
864    }
865
866    fn bool_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
867        unimplemented!("Requires `autodiff` feature")
868    }
869
870    fn q_from_inner(_tensor: DispatchTensor) -> DispatchTensor {
871        unimplemented!("Requires `autodiff` feature")
872    }
873}
874
875impl Dispatch {
876    /// List all available devices of the specified [type id](DispatchDeviceId).
877    pub fn enumerate(type_id: DispatchDeviceId) -> Vec<DispatchDevice> {
878        // TODO: right now this assumes `type_id = 0`, but WgpuDevice and LibTorchDevice have other types.
879        match type_id {
880            #[cfg(feature = "cpu")]
881            DispatchDeviceId::Cpu => vec![CpuDevice.into()],
882            #[cfg(feature = "cuda")]
883            DispatchDeviceId::Cuda => (0..Cuda::device_count(0))
884                .map(|i| CudaDevice::new(i).into())
885                .collect(),
886            #[cfg(feature = "metal")]
887            DispatchDeviceId::Metal => (0..Metal::device_count(0))
888                .map(|i| DispatchDevice::Metal(WgpuDevice::DiscreteGpu(i)))
889                .collect(),
890            #[cfg(feature = "rocm")]
891            DispatchDeviceId::Rocm => (0..Rocm::device_count(0))
892                .map(|i| RocmDevice::new(i).into())
893                .collect(),
894            #[cfg(feature = "vulkan")]
895            DispatchDeviceId::Vulkan => (0..Vulkan::device_count(0))
896                .map(|i| DispatchDevice::Vulkan(WgpuDevice::DiscreteGpu(i)))
897                .collect(),
898            #[cfg(feature = "wgpu")]
899            DispatchDeviceId::Wgpu => (0..Wgpu::device_count(0))
900                .map(|i| DispatchDevice::Wgpu(WgpuDevice::DiscreteGpu(i)))
901                .collect(),
902            #[cfg(feature = "webgpu")]
903            DispatchDeviceId::WebGpu => (0..WebGpu::device_count(0))
904                .map(|i| DispatchDevice::WebGpu(WgpuDevice::DiscreteGpu(i)))
905                .collect(),
906            #[cfg(any(feature = "flex", default_backend))]
907            DispatchDeviceId::Flex => vec![FlexDevice.into()],
908            #[cfg(feature = "ndarray")]
909            DispatchDeviceId::NdArray => vec![NdArrayDevice::Cpu.into()],
910            #[cfg(feature = "tch")]
911            DispatchDeviceId::LibTorch => (0..LibTorch::device_count(0))
912                .map(|i| LibTorchDevice::Cuda(i).into())
913                .collect(),
914            #[cfg(feature = "remote")]
915            // Remote devices are keyed by a network address, which the type-id-only
916            // `enumerate` can't carry. Use [`Dispatch::enumerate_remote_websocket`] to list the devices
917            // behind a given address.
918            DispatchDeviceId::Remote => Vec::new(),
919            _ => unreachable!("No backend feature enabled."),
920        }
921    }
922
923    /// List every device hosted by the remote server at `address`.
924    ///
925    /// Unlike [`enumerate`](Self::enumerate), remote devices are identified by a network
926    /// address rather than enumerable local hardware, so they need a dedicated entry point.
927    /// Connecting to the server (required to learn its device count) happens here; see
928    /// [`RemoteDevice::enumerate_websocket`].
929    ///
930    /// Websocket-only: Iroh peers are addressed by endpoint identity, not a URL string.
931    #[cfg(feature = "remote-websocket")]
932    pub fn enumerate_remote_websocket(address: &str) -> Vec<DispatchDevice> {
933        RemoteDevice::enumerate_websocket(address)
934            .into_iter()
935            .map(DispatchDevice::Remote)
936            .collect()
937    }
938}