Skip to main content

burn_dispatch/
tensor.rs

1use crate::{DispatchDevice, backends::*};
2
3#[cfg(feature = "autodiff")]
4use burn_autodiff::checkpoint::strategy::{
5    BalancedCheckpointing, CheckpointStrategy, NoCheckpointing,
6};
7use burn_backend::{Backend, BackendTypes, DType, Shape, TensorMetadata};
8
9use crate::GradientCheckpointingStrategy;
10#[cfg(feature = "autodiff")]
11use alloc::boxed::Box;
12#[cfg(feature = "autodiff")]
13use burn_backend::tensor::FloatTensor;
14
15use alloc::{format, string::String};
16
17// TODO: if we reduce the different associated types for float/int/bool/quantized tensor primitives down to a single
18// `B::TensorPrimitive` we can simplify this.
19
20/// Tensor which points to a backend tensor primitive kind.
21#[derive(Clone, Debug)]
22pub enum BackendTensor<B: BackendTypes> {
23    /// Float tensor handle.
24    Float(B::FloatTensorPrimitive),
25    /// Int tensor handle.
26    Int(B::IntTensorPrimitive),
27    /// Bool tensor handle.
28    Bool(B::BoolTensorPrimitive),
29    /// Quantized tensor handle.
30    Quantized(B::QuantizedTensorPrimitive),
31    #[cfg(feature = "autodiff")]
32    /// Autodiff float tensor handle.
33    Autodiff(FloatTensor<Autodiff<B>>),
34}
35
36impl<B: Backend> BackendTensor<B> {
37    /// Returns the inner float tensor primitive.
38    pub fn float(self) -> B::FloatTensorPrimitive {
39        match self {
40            BackendTensor::Float(tensor) => tensor,
41            BackendTensor::Int(_) => panic!("Should be float, got int"),
42            BackendTensor::Bool(_) => panic!("Should be float, got bool"),
43            BackendTensor::Quantized(_) => panic!("Should be float, got quantized"),
44            #[cfg(feature = "autodiff")]
45            BackendTensor::Autodiff(_) => panic!("Should be float, got autodiff"),
46        }
47    }
48    /// Returns the inner float tensor primitive.
49    pub fn as_float(&self) -> &B::FloatTensorPrimitive {
50        match self {
51            BackendTensor::Float(tensor) => tensor,
52            BackendTensor::Int(_) => panic!("Should be float, got int"),
53            BackendTensor::Bool(_) => panic!("Should be float, got bool"),
54            BackendTensor::Quantized(_) => panic!("Should be float, got quantized"),
55            #[cfg(feature = "autodiff")]
56            BackendTensor::Autodiff(_) => panic!("Should be float, got autodiff"),
57        }
58    }
59
60    /// Returns the inner int tensor primitive.
61    pub fn int(self) -> B::IntTensorPrimitive {
62        match self {
63            BackendTensor::Int(tensor) => tensor,
64            BackendTensor::Float(_) => panic!("Should be int, got float"),
65            BackendTensor::Bool(_) => panic!("Should be int, got bool"),
66            BackendTensor::Quantized(_) => panic!("Should be int, got quantized"),
67            #[cfg(feature = "autodiff")]
68            BackendTensor::Autodiff(_) => panic!("Should be int, got autodiff"),
69        }
70    }
71
72    /// Returns the inner bool tensor primitive.
73    pub fn bool(self) -> B::BoolTensorPrimitive {
74        match self {
75            BackendTensor::Bool(tensor) => tensor,
76            BackendTensor::Float(_) => panic!("Should be bool, got float"),
77            BackendTensor::Int(_) => panic!("Should be bool, got int"),
78            BackendTensor::Quantized(_) => panic!("Should be bool, got quantized"),
79            #[cfg(feature = "autodiff")]
80            BackendTensor::Autodiff(_) => panic!("Should be bool, got autodiff"),
81        }
82    }
83
84    /// Returns the inner quantized tensor primitive.
85    pub fn quantized(self) -> B::QuantizedTensorPrimitive {
86        match self {
87            BackendTensor::Quantized(tensor) => tensor,
88            _ => unreachable!(),
89        }
90    }
91
92    #[cfg(feature = "autodiff")]
93    /// Returns the inner autodiff tensor primitive.
94    pub fn autodiff(self) -> FloatTensor<Autodiff<B>> {
95        match self {
96            BackendTensor::Autodiff(tensor) => tensor,
97            // NOTE: this is the panicking code reached in tensor.rs:74:18:
98            _ => unreachable!(),
99        }
100    }
101
102    #[cfg(feature = "autodiff")]
103    /// Returns the inner autodiff tensor primitive.
104    pub fn as_autodiff(&self) -> &FloatTensor<Autodiff<B>> {
105        match self {
106            BackendTensor::Autodiff(tensor) => tensor,
107            _ => unreachable!(),
108        }
109    }
110
111    #[cfg(feature = "autodiff")]
112    /// Returns the inner autodiff tensor primitive.
113    pub fn autodiff_inner(self) -> B::FloatTensorPrimitive {
114        match self {
115            BackendTensor::Autodiff(tensor) => tensor.into_primitive(),
116            _ => unreachable!(),
117        }
118    }
119
120    /// Lift a handle for backend `B` into the equivalent handle for `Autodiff<B>`.
121    ///
122    /// An already-tracked float (`Autodiff`) becomes the `Float` handle of `Autodiff<B>`; int/bool/
123    /// quantized handles are re-tagged unchanged (those primitives are shared between `B` and
124    /// `Autodiff<B>`). An untracked `Float` handle is invalid here (under autodiff, float tensors
125    /// arrive tracked), so it panics.
126    #[cfg(feature = "autodiff")]
127    pub fn into_autodiff(self) -> BackendTensor<Autodiff<B>> {
128        match self {
129            BackendTensor::Autodiff(tensor) => BackendTensor::Float(tensor),
130            BackendTensor::Int(tensor) => BackendTensor::Int(tensor),
131            BackendTensor::Bool(tensor) => BackendTensor::Bool(tensor),
132            BackendTensor::Quantized(tensor) => BackendTensor::Quantized(tensor),
133            BackendTensor::Float(_) => {
134                unreachable!("an untracked float handle can't be lifted to Autodiff<B>")
135            }
136        }
137    }
138
139    /// Returns the tensor primitive kind name.
140    pub fn name(&self) -> &'static str {
141        match self {
142            BackendTensor::Float(_) => "Float",
143            BackendTensor::Int(_) => "Int",
144            BackendTensor::Bool(_) => "Bool",
145            BackendTensor::Quantized(_) => "Quantized",
146            #[cfg(feature = "autodiff")]
147            BackendTensor::Autodiff(_) => "Autodiff",
148        }
149    }
150}
151
152impl<B: BackendTypes> TensorMetadata for BackendTensor<B> {
153    type Device = B::Device;
154    fn device(&self) -> Self::Device {
155        match self {
156            BackendTensor::Float(tensor) => tensor.device(),
157            BackendTensor::Int(tensor) => tensor.device(),
158            BackendTensor::Bool(tensor) => tensor.device(),
159            BackendTensor::Quantized(tensor) => tensor.device(),
160            #[cfg(feature = "autodiff")]
161            BackendTensor::Autodiff(tensor) => tensor.device(),
162        }
163    }
164    fn dtype(&self) -> DType {
165        match self {
166            BackendTensor::Float(tensor) => tensor.dtype(),
167            BackendTensor::Int(tensor) => tensor.dtype(),
168            BackendTensor::Bool(tensor) => tensor.dtype(),
169            BackendTensor::Quantized(tensor) => tensor.dtype(),
170            #[cfg(feature = "autodiff")]
171            BackendTensor::Autodiff(tensor) => tensor.dtype(),
172        }
173    }
174
175    fn shape(&self) -> Shape {
176        match self {
177            BackendTensor::Float(tensor) => tensor.shape(),
178            BackendTensor::Int(tensor) => tensor.shape(),
179            BackendTensor::Bool(tensor) => tensor.shape(),
180            BackendTensor::Quantized(tensor) => tensor.shape(),
181            #[cfg(feature = "autodiff")]
182            BackendTensor::Autodiff(tensor) => tensor.shape(),
183        }
184    }
185
186    fn can_mut(&self) -> bool {
187        match self {
188            BackendTensor::Float(tensor) => tensor.can_mut(),
189            BackendTensor::Int(tensor) => tensor.can_mut(),
190            BackendTensor::Bool(tensor) => tensor.can_mut(),
191            BackendTensor::Quantized(tensor) => tensor.can_mut(),
192            #[cfg(feature = "autodiff")]
193            BackendTensor::Autodiff(tensor) => tensor.can_mut(),
194        }
195    }
196}
197
198/// The autodiff backend associated with a dispatch tensor.
199///
200/// This is backend metadata, not a statement that a float tensor requires
201/// gradients. Enabled float tensors may be tracked or untracked by autodiff.
202///
203/// Tensor inputs to one backend operation merge their autodiff contexts. A disabled context is
204/// compatible with an enabled one and is treated as a constant for that operation. Two enabled
205/// contexts must use the same gradient-checkpointing strategy.
206#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
207pub enum DispatchAutodiffContext {
208    /// Route the tensor through its concrete backend.
209    #[default]
210    Disabled,
211    /// Associate the tensor with an autodiff backend using the given strategy.
212    Enabled(GradientCheckpointingStrategy),
213}
214
215impl DispatchAutodiffContext {
216    /// Merge two tensor contexts into the context used to execute an operation and wrap its output.
217    ///
218    /// Disabled tensors don't acquire a new context themselves. When combined with an enabled
219    /// tensor, they are treated as constants while the operation and its output use the enabled
220    /// context.
221    ///
222    /// # Panics
223    ///
224    /// Panics when both contexts are enabled with different gradient-checkpointing strategies.
225    #[doc(hidden)]
226    pub fn merge(self, other: Self) -> Self {
227        match (self, other) {
228            (Self::Disabled, context) | (context, Self::Disabled) => context,
229            (Self::Enabled(lhs), Self::Enabled(rhs)) => {
230                assert_eq!(
231                    lhs, rhs,
232                    "Gradient checkpointing strategy mismatch: {lhs:?} vs {rhs:?}. Tensors in the same operation must share a strategy."
233                );
234                Self::Enabled(lhs)
235            }
236        }
237    }
238}
239
240/// A tensor that can dispatch operations to any enabled backend at runtime.
241#[derive(Clone, Debug)]
242pub struct DispatchTensor {
243    /// Tensor kind primitive.
244    pub kind: DispatchTensorKind,
245    /// Autodiff backend association for this tensor.
246    pub autodiff: DispatchAutodiffContext,
247}
248
249/// Internal representation of a [`DispatchTensor`].
250///
251/// This enum contains the concrete backend tensor for each enabled backend.
252/// It is not intended to be used directly; instead, it is manipulated by
253/// the dispatch system to route operations to the correct backend.
254///
255/// Each variant corresponds to a specific backend implementation.
256#[derive(Clone, Debug)]
257// With autodiff and a lone Cube or Remote backend, the only other variant is Autodiff's 8-byte
258// box, so the lint fires. Boxing backend tensors would cost every tensor an allocation.
259#[allow(clippy::large_enum_variant)]
260pub enum DispatchTensorKind {
261    #[cfg(not(backend_enabled))]
262    #[doc(hidden)]
263    Unavailable(crate::NoBackend),
264    /// A tensor on the [cubecl backend](Cube) — its device says which runtime.
265    #[cfg(cube_backend)]
266    Cube(BackendTensor<Cube>),
267
268    /// The [Flex backend](Flex) tensor.
269    #[cfg(feature = "flex")]
270    Flex(BackendTensor<Flex>),
271
272    /// The [NdArray backend](NdArray) tensor.
273    #[cfg(feature = "ndarray")]
274    NdArray(BackendTensor<NdArray>),
275
276    /// The [LibTorch backend](LibTorch) tensor.
277    #[cfg(feature = "tch")]
278    LibTorch(BackendTensor<LibTorch>),
279
280    /// The [Remote backend](Remote) tensor (lives on a remote server).
281    #[cfg(feature = "remote")]
282    Remote(BackendTensor<Remote>),
283    /// A tensor recorded by the capture backend.
284    #[cfg(feature = "capture")]
285    Capture(BackendTensor<Capture>),
286
287    /// The [autodiff enabled backend](Autodiff) tensor.
288    #[cfg(feature = "autodiff")]
289    Autodiff(Box<DispatchTensorKind>),
290}
291
292impl TensorMetadata for DispatchTensorKind {
293    type Device = DispatchDevice;
294
295    fn dtype(&self) -> DType {
296        match self {
297            #[cfg(not(backend_enabled))]
298            Self::Unavailable(never) => never.unreachable(),
299            #[cfg(cube_backend)]
300            Self::Cube(tensor) => tensor.dtype(),
301            #[cfg(feature = "flex")]
302            Self::Flex(tensor) => tensor.dtype(),
303            #[cfg(feature = "ndarray")]
304            Self::NdArray(tensor) => tensor.dtype(),
305            #[cfg(feature = "tch")]
306            Self::LibTorch(tensor) => tensor.dtype(),
307            #[cfg(feature = "remote")]
308            Self::Remote(tensor) => tensor.dtype(),
309            #[cfg(feature = "capture")]
310            Self::Capture(tensor) => tensor.dtype(),
311            #[cfg(feature = "autodiff")]
312            Self::Autodiff(tensor) => tensor.dtype(),
313        }
314    }
315
316    fn shape(&self) -> Shape {
317        match self {
318            #[cfg(not(backend_enabled))]
319            Self::Unavailable(never) => never.unreachable(),
320            #[cfg(cube_backend)]
321            Self::Cube(tensor) => tensor.shape(),
322            #[cfg(feature = "flex")]
323            Self::Flex(tensor) => tensor.shape(),
324            #[cfg(feature = "ndarray")]
325            Self::NdArray(tensor) => tensor.shape(),
326            #[cfg(feature = "tch")]
327            Self::LibTorch(tensor) => tensor.shape(),
328            #[cfg(feature = "remote")]
329            Self::Remote(tensor) => tensor.shape(),
330            #[cfg(feature = "capture")]
331            Self::Capture(tensor) => tensor.shape(),
332            #[cfg(feature = "autodiff")]
333            Self::Autodiff(tensor) => tensor.shape(),
334        }
335    }
336
337    fn device(&self) -> DispatchDevice {
338        match self {
339            #[cfg(not(backend_enabled))]
340            Self::Unavailable(never) => never.unreachable(),
341            #[cfg(cube_backend)]
342            DispatchTensorKind::Cube(tensor) => DispatchDevice::Cube(tensor.device()),
343            #[cfg(feature = "flex")]
344            DispatchTensorKind::Flex(tensor) => DispatchDevice::Flex(tensor.device()),
345            #[cfg(feature = "ndarray")]
346            DispatchTensorKind::NdArray(tensor) => DispatchDevice::NdArray(tensor.device()),
347            #[cfg(feature = "tch")]
348            DispatchTensorKind::LibTorch(tensor) => DispatchDevice::LibTorch(tensor.device()),
349            #[cfg(feature = "remote")]
350            DispatchTensorKind::Remote(tensor) => DispatchDevice::Remote(tensor.device()),
351            #[cfg(feature = "capture")]
352            DispatchTensorKind::Capture(tensor) => DispatchDevice::Capture(tensor.device()),
353            #[cfg(feature = "autodiff")]
354            DispatchTensorKind::Autodiff(tensor) => DispatchDevice::autodiff(tensor.device()),
355        }
356    }
357
358    fn can_mut(&self) -> bool {
359        match self {
360            #[cfg(not(backend_enabled))]
361            Self::Unavailable(never) => never.unreachable(),
362            #[cfg(cube_backend)]
363            Self::Cube(tensor) => tensor.can_mut(),
364            #[cfg(feature = "flex")]
365            Self::Flex(tensor) => tensor.can_mut(),
366            #[cfg(feature = "ndarray")]
367            Self::NdArray(tensor) => tensor.can_mut(),
368            #[cfg(feature = "tch")]
369            Self::LibTorch(tensor) => tensor.can_mut(),
370            #[cfg(feature = "remote")]
371            Self::Remote(tensor) => tensor.can_mut(),
372            #[cfg(feature = "capture")]
373            Self::Capture(tensor) => tensor.can_mut(),
374            #[cfg(feature = "autodiff")]
375            Self::Autodiff(tensor) => tensor.can_mut(),
376        }
377    }
378}
379
380impl TensorMetadata for DispatchTensor {
381    fn dtype(&self) -> DType {
382        self.kind.dtype()
383    }
384
385    fn shape(&self) -> Shape {
386        self.kind.shape()
387    }
388
389    fn can_mut(&self) -> bool {
390        self.kind.can_mut()
391    }
392
393    type Device = DispatchDevice;
394
395    fn device(&self) -> Self::Device {
396        #[allow(unused_mut)]
397        let mut device = self.kind.device();
398
399        #[cfg(feature = "autodiff")]
400        match (&self.kind, self.autodiff) {
401            (DispatchTensorKind::Autodiff(_), DispatchAutodiffContext::Disabled) => {
402                panic!("an autodiff float primitive must have an enabled autodiff context")
403            }
404            (DispatchTensorKind::Autodiff(_), DispatchAutodiffContext::Enabled(strategy)) => {
405                let DispatchDevice::Autodiff(device) = &mut device else {
406                    unreachable!("autodiff primitive must report an autodiff device")
407                };
408                device.checkpointing = strategy;
409            }
410            (_, DispatchAutodiffContext::Enabled(strategy)) => {
411                if self.dtype().is_float() {
412                    panic!("an enabled float tensor must use an autodiff primitive")
413                }
414                device = DispatchDevice::autodiff(device);
415                let DispatchDevice::Autodiff(device) = &mut device else {
416                    unreachable!()
417                };
418                device.checkpointing = strategy;
419            }
420            (_, DispatchAutodiffContext::Disabled) => {}
421        }
422
423        device
424    }
425}
426
427impl DispatchTensorKind {
428    /// Returns the backend tensor kind name.
429    pub(crate) fn name(&self) -> &'static str {
430        match self {
431            #[cfg(not(backend_enabled))]
432            Self::Unavailable(never) => never.unreachable(),
433            #[cfg(cube_backend)]
434            DispatchTensorKind::Cube(_) => "Cube",
435            #[cfg(feature = "flex")]
436            DispatchTensorKind::Flex(_) => "Flex",
437            #[cfg(feature = "ndarray")]
438            DispatchTensorKind::NdArray(_) => "NdArray",
439            #[cfg(feature = "tch")]
440            DispatchTensorKind::LibTorch(_) => "LibTorch",
441            #[cfg(feature = "remote")]
442            DispatchTensorKind::Remote(_) => "Remote",
443            #[cfg(feature = "capture")]
444            DispatchTensorKind::Capture(_) => "Capture",
445            #[cfg(feature = "autodiff")]
446            DispatchTensorKind::Autodiff(_) => "Autodiff",
447        }
448    }
449}
450
451#[cfg(feature = "autodiff")]
452trait IntoGradientCheckpointingStrategy {
453    const STRATEGY: GradientCheckpointingStrategy;
454}
455
456#[cfg(feature = "autodiff")]
457impl IntoGradientCheckpointingStrategy for NoCheckpointing {
458    const STRATEGY: GradientCheckpointingStrategy = GradientCheckpointingStrategy::Disabled;
459}
460
461#[cfg(feature = "autodiff")]
462impl IntoGradientCheckpointingStrategy for BalancedCheckpointing {
463    const STRATEGY: GradientCheckpointingStrategy = GradientCheckpointingStrategy::Balanced;
464}
465
466/// Trait to execute runtime routing conversions between the dynamic dispatch layer and specific backends.
467pub trait DispatchKindConversion<B: Backend> {
468    /// Attempts to extract a backend-specific [`BackendTensor`] wrapper from a generic, dynamically-routed [`DispatchTensor`].
469    ///
470    /// # Errors
471    ///
472    /// Returns an error if the dynamic routing state does not match the requested backend `B`.
473    fn try_into_backend(tensor: DispatchTensor) -> Result<BackendTensor<B>, String>;
474
475    /// Encapsulates a backend-specific tensor variant back into a globally routing [`DispatchTensor`].
476    fn from_backend(tensor: BackendTensor<B>) -> DispatchTensor;
477}
478
479macro_rules! impl_dispatch_conversion {
480    ($backend:ident, $cfg:meta) => {
481        #[cfg($cfg)]
482        impl DispatchKindConversion<$backend> for DispatchTensor {
483            fn try_into_backend(tensor: DispatchTensor) -> Result<BackendTensor<$backend>, String> {
484                if tensor.autodiff != DispatchAutodiffContext::Disabled {
485                    return Err(format!(
486                        "Expected concrete {} backend with disabled autodiff context, got {:?}",
487                        stringify!($backend),
488                        tensor.autodiff,
489                    ));
490                }
491                // The catch-all is unreachable in single-backend builds (the enum then has one
492                // variant), but required when several backend features are enabled.
493                #[allow(unreachable_patterns)]
494                match tensor.kind {
495                    DispatchTensorKind::$backend(t) => Ok(t),
496                    other => Err(format!(
497                        "Expected {} tensor, got variant: {}",
498                        stringify!($backend),
499                        other.name()
500                    )),
501                }
502            }
503
504            fn from_backend(tensor: BackendTensor<$backend>) -> DispatchTensor {
505                DispatchTensor {
506                    kind: DispatchTensorKind::$backend(tensor),
507                    autodiff: DispatchAutodiffContext::Disabled,
508                }
509            }
510        }
511
512        #[cfg(all($cfg, feature = "autodiff"))]
513        impl<C: CheckpointStrategy + IntoGradientCheckpointingStrategy>
514            DispatchKindConversion<Autodiff<$backend, C>> for DispatchTensor
515        {
516            fn try_into_backend(
517                tensor: DispatchTensor,
518            ) -> Result<BackendTensor<Autodiff<$backend, C>>, String> {
519                if tensor.autodiff != DispatchAutodiffContext::Enabled(C::STRATEGY) {
520                    return Err(format!(
521                        "Expected {:?} autodiff context, got {:?}",
522                        C::STRATEGY,
523                        tensor.autodiff
524                    ));
525                }
526                match tensor.kind {
527                    DispatchTensorKind::Autodiff(t) => match *t {
528                        DispatchTensorKind::$backend(t) => match t {
529                            // Encode as `BackendTensor::Float` for `Autodiff<B, C>`
530                            BackendTensor::Autodiff(t) => Ok(BackendTensor::Float(t)),
531                            other => Err(format!(
532                                "Expected Autodiff {} float tensor, got Autodiff variant: {}",
533                                stringify!($backend),
534                                other.name()
535                            )),
536                        },
537                        other => Err(format!(
538                            "Expected Autodiff {} tensor, got Autodiff variant: {}",
539                            stringify!($backend),
540                            other.name()
541                        )),
542                    },
543                    DispatchTensorKind::$backend(t) => match t {
544                        BackendTensor::Int(t) => Ok(BackendTensor::Int(t)),
545                        BackendTensor::Bool(t) => Ok(BackendTensor::Bool(t)),
546                        BackendTensor::Quantized(t) => Ok(BackendTensor::Quantized(t)),
547                        BackendTensor::Float(_) => Err(format!(
548                            "Expected Autodiff {} float tensor, got unwrapped float",
549                            stringify!($backend),
550                        )),
551                        BackendTensor::Autodiff(_) => Err(format!(
552                            "Expected Autodiff {} tensor, got invalid inner autodiff variant",
553                            stringify!($backend),
554                        )),
555                    },
556                    #[allow(unreachable_patterns)]
557                    other => Err(format!(
558                        "Expected Autodiff tensor, got backend: {}",
559                        other.name()
560                    )),
561                }
562            }
563
564            fn from_backend(tensor: BackendTensor<Autodiff<$backend, C>>) -> DispatchTensor {
565                // Unwrap the Autodiff backend representation back into the inner hardware representation
566                let kind = match tensor {
567                    // Inverse: Wrap the `Float` variant back into the backend's `Autodiff` primitive variant
568                    BackendTensor::Float(t) => {
569                        let ad_tensor = BackendTensor::Autodiff(t);
570                        // Wrap in the concrete backend's dispatch container
571                        let inner_dispatch = DispatchTensorKind::$backend(ad_tensor);
572                        // Re-apply the outer Autodiff dispatch wrapper
573                        DispatchTensorKind::Autodiff(Box::new(inner_dispatch))
574                    }
575
576                    // Pass-throughs for non-differentiable types
577                    BackendTensor::Int(t) => DispatchTensorKind::$backend(BackendTensor::Int(t)),
578                    BackendTensor::Bool(t) => DispatchTensorKind::$backend(BackendTensor::Bool(t)),
579                    BackendTensor::Quantized(t) => {
580                        DispatchTensorKind::$backend(BackendTensor::Quantized(t))
581                    }
582
583                    BackendTensor::Autodiff(_) => {
584                        panic!("Unexpected Autodiff variant provided to `from_backend`",)
585                    }
586                };
587
588                DispatchTensor {
589                    kind,
590                    autodiff: DispatchAutodiffContext::Enabled(C::STRATEGY),
591                }
592            }
593        }
594    };
595}
596
597// One invocation per dispatch variant. Every cubecl runtime is the same `Cube`
598// backend, so they share the one impl rather than getting seven identical ones.
599impl_dispatch_conversion!(Cube, cube_backend);
600impl_dispatch_conversion!(Flex, feature = "flex");
601impl_dispatch_conversion!(Remote, feature = "remote");
602impl_dispatch_conversion!(Capture, feature = "capture");
603impl_dispatch_conversion!(NdArray, feature = "ndarray");
604impl_dispatch_conversion!(LibTorch, feature = "tch");