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