tracel-ai/burn · error
an autodiff float primitive must have an enabled autodiff…
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.
Solutions
- Create and use the tensor inside an enabled autodiff context (Autodiff::backend `context` / grad-enabled scope) so the autodiff field is DispatchAutodiffContext::Enabled(checkpointer).
- Detach the tensor to a plain backend tensor (`.inner()`) if gradient tracking is not needed — then no autodiff context is required.
- Reorder code so the to-device move happens while the autodiff context is still alive, not after it is dropped.
- 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
- Create autodiff tensors only inside an enabled grad context.
- Drop or detach autodiff tensors before the context exits.
- In tests, construct primitives through library helpers rather than by hand.
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
- Cannot move between autodiff and non-autodiff instances.
- Operation not marked for autodiff.
- an autodiff float primitive must have an enabled autodiff…
- an autodiff float primitive must have an enabled autodiff…
- an enabled float tensor must use an autodiff primitive
AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05).
Data as JSON: /api/errors/a7d9297e340ae8ed.
Report an issue: GitHub.
Appendix: 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)