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#[derive(Clone, Debug)]
22pub enum BackendTensor<B: BackendTypes> {
23 Float(B::FloatTensorPrimitive),
25 Int(B::IntTensorPrimitive),
27 Bool(B::BoolTensorPrimitive),
29 Quantized(B::QuantizedTensorPrimitive),
31 #[cfg(feature = "autodiff")]
32 Autodiff(FloatTensor<Autodiff<B>>),
34}
35
36impl<B: Backend> BackendTensor<B> {
37 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 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 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 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 pub fn quantized(self) -> B::QuantizedTensorPrimitive {
86 match self {
87 BackendTensor::Quantized(tensor) => tensor,
88 _ => unreachable!(),
89 }
90 }
91
92 #[cfg(feature = "autodiff")]
93 pub fn autodiff(self) -> FloatTensor<Autodiff<B>> {
95 match self {
96 BackendTensor::Autodiff(tensor) => tensor,
97 _ => unreachable!(),
99 }
100 }
101
102 #[cfg(feature = "autodiff")]
103 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 pub fn autodiff_inner(self) -> B::FloatTensorPrimitive {
114 match self {
115 BackendTensor::Autodiff(tensor) => tensor.into_primitive(),
116 _ => unreachable!(),
117 }
118 }
119
120 #[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 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
207pub enum DispatchAutodiffContext {
208 #[default]
210 Disabled,
211 Enabled(GradientCheckpointingStrategy),
213}
214
215impl DispatchAutodiffContext {
216 #[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#[derive(Clone, Debug)]
242pub struct DispatchTensor {
243 pub kind: DispatchTensorKind,
245 pub autodiff: DispatchAutodiffContext,
247}
248
249#[derive(Clone, Debug)]
257#[allow(clippy::large_enum_variant)]
260pub enum DispatchTensorKind {
261 #[cfg(not(backend_enabled))]
262 #[doc(hidden)]
263 Unavailable(crate::NoBackend),
264 #[cfg(cube_backend)]
266 Cube(BackendTensor<Cube>),
267
268 #[cfg(feature = "flex")]
270 Flex(BackendTensor<Flex>),
271
272 #[cfg(feature = "ndarray")]
274 NdArray(BackendTensor<NdArray>),
275
276 #[cfg(feature = "tch")]
278 LibTorch(BackendTensor<LibTorch>),
279
280 #[cfg(feature = "remote")]
282 Remote(BackendTensor<Remote>),
283 #[cfg(feature = "capture")]
285 Capture(BackendTensor<Capture>),
286
287 #[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 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
466pub trait DispatchKindConversion<B: Backend> {
468 fn try_into_backend(tensor: DispatchTensor) -> Result<BackendTensor<B>, String>;
474
475 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 #[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 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 let kind = match tensor {
567 BackendTensor::Float(t) => {
569 let ad_tensor = BackendTensor::Autodiff(t);
570 let inner_dispatch = DispatchTensorKind::$backend(ad_tensor);
572 DispatchTensorKind::Autodiff(Box::new(inner_dispatch))
574 }
575
576 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
597impl_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");