tracel-ai/burn · error

Cannot move a tensor from a capture device

Error message

Cannot move a tensor from a capture device

What it means

Capture tensors are placeholders recorded during graph/program capture (the `capture` feature); they never hold materialized device data. Attempting to move such a tensor OFF a capture device is intentionally forbidden — the comment in the source notes capture is one-way: initialized values may be moved onto a capture device, but captured tensors cannot be moved back because there is no underlying data.

Source

Thrown at crates/burn-dispatch/src/macros.rs:235

                                $crate::BackendTensor::$kind($body)
                            ),
                            autodiff: $crate::DispatchAutodiffContext::Enabled(
                                device_ad.checkpointing,
                            ),
                        }

                    },
                )+
            )*
            #[cfg(feature = "autodiff")]
            (_, $crate::DispatchDevice::Autodiff(_)) => unreachable!("Autodiff should not wrap an autodiff device."),
            #[cfg(feature = "autodiff")]
            ($crate::DispatchTensorKind::Autodiff(..), _) => panic!("Operation not marked for autodiff."),
            // Capture is intentionally one-way: initialized values can be moved onto a
            // capture device, but captured tensors have no materialized data to move back.
            #[cfg(feature = "capture")]
            ($crate::DispatchTensorKind::Capture(_), _) => {
                panic!("Cannot move a tensor from a capture device")
            }
        }
    };
}

/// Handles tensor movement between devices, supporting both same-backend transfers
/// and cross-backend dispatches.
macro_rules! to_device {
    ($kind:ident, $inner_fn:ident, $tensor:expr, $device:expr, $to_device:ident, |$inner:ident, $device_ident:ident| $body:expr) => {
        backend_matrix!(
            to_device_arms,
            $kind,
            $inner_fn,
            $tensor,
            $device,
            $to_device,
            |$inner, $device_ident| $body
        )

View on GitHub (pinned to d16f7ba2ed)

Solutions

  1. Do not move capture-kind tensors off the capture device; materialize them by executing/replaying the captured graph first, then move the resulting real tensor.
  2. Restructure code so device transfers happen before capture begins or after replay completes.
  3. Guard transfers with a kind check and skip or re-record capture tensors instead of moving them.
  4. If the data exists elsewhere, reference the original initialized tensor rather than its captured placeholder.

Example fix

// before
captured_tensor.to_device(&gpu_device); // Capture kind -> panic

// after
let real = replay(captured_graph); // materialize data first
real.to_device(&gpu_device);
Defensive patterns

Strategy: type-guard

Validate before calling

fn can_move(t: &DispatchPrimitive) -> bool {
    !matches!(t.kind, DispatchTensorKind::Capture(_))
}

Type guard

fn is_capture(t: &DispatchPrimitive) -> bool {
    matches!(t.kind, DispatchTensorKind::Capture(_))
}

Prevention

When it happens

Trigger: Invoking a to-device / cross-device move (macros.rs:235 arm) where the source tensor's DispatchTensorKind is Capture(_) — e.g. taking a tensor produced during capture and calling `to_device(other_device)` on it.

Common situations: Post-capture replay code accidentally treating captured placeholders as real tensors; exporting or checkpointing parts of a captured graph by moving tensors to a live device; mixing capture-mode and execution-mode code paths in the same session.

Related errors


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