tracel-ai/burn · error

an autodiff float primitive must have an enabled autodiff co

Error message

an autodiff float primitive must have an enabled autodiff context

What it means

An autodiff float tensor (DispatchTensorKind::Autodiff) was matched with an Autodiff device, but its `autodiff` field held DispatchAutodiffContext::Disabled (or otherwise not Enabled) when the macro required a live checkpointing context. To move or operate on an autodiff float primitive the dispatcher needs the enabled autodiff context (carrying the checkpointer) to route gradients; its absence is an invariant violation, so the macro panics.

Source

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

            |$inner, $device_ident| $body
        )
    };
}

/// Match arm generator for `float_to_device`.
///
/// Similar to `to_device_arms`, but float tensors are checked for autodiff support.
macro_rules! float_to_device_arms {
    (
        $tensor:expr, $device:expr, $to_device:ident, |$inner:ident, $device_ident:ident| $body:expr;
        $( [$B1:ident, $src_cfg:meta] => [ $( [$B2:ident, $dst_cfg:meta] ),+ ] );*
    ) => {
        #[allow(unreachable_patterns)]
        match ($tensor.kind, $device) {
            #[cfg(feature = "autodiff")]
            ($crate::DispatchTensorKind::Autodiff(kind), $crate::DispatchDevice::Autodiff(device)) => {
                let $crate::DispatchAutodiffContext::Enabled(ckp) = $tensor.autodiff else {
                    panic!("an autodiff float primitive must have an enabled autodiff context")
                };
                float_to_device_arms!(
                    @autodiff
                    *kind, &**device, ckp, $to_device;
                    $([$B1, $src_cfg]);*
                )

            }
            // Capture is deliberately absent from the cross-backend matrix. Same-backend movement
            // remains available; CaptureBackend decides whether the particular device transfer is
            // valid (computed tensors can only remain in their capture session).
            #[cfg(feature = "capture")]
            ($crate::DispatchTensorKind::Capture(kind), $crate::DispatchDevice::Capture(d)) => {
                $crate::DispatchTensor {
                    kind: $crate::DispatchTensorKind::Capture($crate::BackendTensor::Float(
                        $crate::backends::Capture::$to_device(kind.float(), d)
                    )),
                    autodiff: $tensor.autodiff,

View on GitHub (pinned to d16f7ba2ed)

Solutions

  1. Create and use the tensor inside an enabled autodiff context (Autodiff::backend `context` / grad-enabled scope) so the autodiff field is DispatchAutodiffContext::Enabled(checkpointer).
  2. Detach the tensor to a plain backend tensor (`.inner()`) if gradient tracking is not needed — then no autodiff context is required.
  3. Reorder code so the to-device move happens while the autodiff context is still alive, not after it is dropped.
  4. In custom dispatch code, match on the context and handle Disabled explicitly rather than relying on the panic path.

Example fix

// before
let t = tensor_factory(); // autodiff context Disabled
let ctx = autodiff.enable();
t.to_device(&dev); // panics: context not enabled for this tensor

// after
let ctx = autodiff.enable();
let t = tensor_factory(); // created under Enabled context
let moved = t.to_device(&dev);
Defensive patterns

Strategy: validation

Validate before calling

// before the move
if matches!(tensor.kind, DispatchTensorKind::Autodiff(_))
    && !tensor.autodiff_enabled() {
    panic!("enter an enabled autodiff context (or detach) before moving autodiff tensors");
}

Type guard

fn has_enabled_context(t: &DispatchPrimitive) -> bool {
    matches!(t.autodiff, DispatchAutodiffContext::Enabled(_))
}

Prevention

When it happens

Trigger: Calling the autodiff to-device / operation macro (macros.rs:270) with a tensor whose autodiff context was never initialized or was already closed (Disabled), while both tensor.kind and target device are autodiff — e.g. a tensor created outside an AutodiffEnabled context then used inside one, or the context dropped before the transfer.

Common situations: Constructing tensors before entering `context`/grad-enabled scope and moving them after the scope exits; manually assembling Dispatch primitives in tests without setting the Enabled context; checkpoint builder lifecycle bugs where the checkpointer is consumed too early.

Related errors


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