tracel-ai/burn · error
Autodiff tensor cannot be moved between backends.
Error message
Autodiff tensor cannot be moved between backends.
What it means
The `@autodiff` arm of the backend_matrix! macro only supports moving an autodiff (AutodiffTensor) between devices on the SAME backend; the catch-all arm panics with unimplemented! for any cross-backend move. Cross-backend transport of gradient-tracking tensors is not implemented yet (marked `// TODO: should be possible`).
Source
Thrown at crates/burn-dispatch/src/macros.rs:371
) => {{
match ($tensor, $device) {
// --- Same backend to_device ---
$(
#[cfg($src_cfg)]
($crate::DispatchTensorKind::$B1(tensor), $crate::DispatchDevice::$B1(d)) => {
let kind = $crate::DispatchTensorKind::Autodiff(alloc::boxed::Box::new($crate::DispatchTensorKind::$B1($crate::BackendTensor::Autodiff(
with_autodiff_backend!($B1, $ckp, |B| {
B::$to_device(tensor.autodiff(), d)
})
))));
$crate::DispatchTensor {
kind,
autodiff: $crate::DispatchAutodiffContext::Enabled($ckp),
}
}
)*
// TODO: should be possible
(_, _) => unimplemented!("Autodiff tensor cannot be moved between backends.")
}
}};
}
/// Handles float tensor movement between devices (that might support autodiff).
macro_rules! float_to_device {
($kind:ident, $inner_fn:ident, $tensor:expr, $device:expr, $to_device:ident, |$inner:ident, $device_ident:ident| $body:expr) => {
backend_matrix!(
float_to_device_arms,
$tensor,
$device,
$to_device,
|$inner, $device_ident| $body
)
};
}
/// Unwraps a `Vec<DispatchTensor>` for a known backend.View on GitHub (pinned to d16f7ba2ed)
Solutions
- Detach first: call `<Dispatch as AutodiffBackend>::inner(tensor)` (or backward before moving) to get a non-autodiff tensor, move it with to_device, then re-wrap with from_inner on the destination.
- Move the tensor to the target backend BEFORE enabling require_grad, or recreate the tensor on the target backend from its data (TensorData) and re-enable gradients there.
- Restructure the pipeline so each autodiff subgraph stays on a single backend.
Example fix
// before let moved = autodiff_tensor.to_device(other_backend_device); // panics // after let inner_t = <Dispatch as AutodiffBackend>::inner(autodiff_tensor); let moved_inner = inner_t.to_device(other_backend_device); let moved = <Dispatch as AutodiffBackend>::from_inner(moved_inner);
Defensive patterns
Strategy: validation
Validate before calling
// Check the tensor's backend kind against the destination device before moving
fn can_move_autodiff(tensor: &DispatchTensor, dst: &DispatchDevice) -> bool {
!matches!(tensor.kind, DispatchTensorKind::Autodiff(_))
|| match (&tensor.kind, dst) {
(DispatchTensorKind::Autodiff(_), DispatchDevice::Wgpu(_)) => matches!(tensor.kind, DispatchTensorKind::Autodiff(_)),
_ => !matches!(tensor.kind, DispatchTensorKind::Autodiff(_)),
}
} Type guard
fn is_autodiff_tensor(t: &DispatchTensor) -> bool {
matches!(t.kind, DispatchTensorKind::Autodiff(_))
} Try / catch
// Panics cannot be caught; validate before calling to_device
if is_autodiff_tensor(&t) && !same_backend(&t, &dst_device) {
let inner_t = <Dispatch as AutodiffBackend>::inner(t);
let moved = <Dispatch as AutodiffBackend>::from_inner(inner_t.to_device(dst_device));
} else {
let moved = t.to_device(dst_device);
} Prevention
- Detach via inner() before any cross-backend to_device on training tensors.
- Design pipelines so each autodiff subgraph is confined to one backend/device type.
- Enable gradients only after tensors are on their final backend.
- Wrap cross-backend moves in a helper that always does the inner/from_inner round trip.
When it happens
Trigger: Calling `tensor.to_device(other_device)` (via float_to_device/backend_matrix @autodiff arms) where the tensor's DispatchTensorKind::Autodiff lives on backend B1 but the target device belongs to a different backend B2, e.g. moving a Wgpu autodiff tensor to a LibTorch/Cube device (crates/burn-dispatch/src/macros.rs:371).
Common situations: Multi-GPU/multi-backend setups that shuffle tensors between a GPU backend and another accelerator or CPU backend while autodiff (training) is active; deploying one model partition per backend.
Related errors
- Can't differentiate avg pool 2d backward.
- Can't differentiate max pool2d with indices backward.
- Can't differentiate adaptive avg pool2d backward.
- Can't differentiate adaptive avg pool3d backward.
- Can't differentiate interpolate backward.
AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05).
Data as JSON: /api/errors/48a71b9e0b45013a.
Report an issue: GitHub.