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

  1. 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.
  2. 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.
  3. 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

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


AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05). Data as JSON: /api/errors/48a71b9e0b45013a. Report an issue: GitHub.