tracel-ai/burn · error
Autodiff should not wrap an autodiff device.
Error message
Autodiff should not wrap an autodiff device.
What it means
This panic is generated by the backend-dispatch proc macro in expand_creation. It fires when a DispatchDevice::Autodiff device's inner device is itself an Autodiff variant, i.e. a doubly-nested autodiff device. The macro assumes autodiff wraps exactly one concrete backend, so double wrapping is an unrecoverable invariant violation.
Source
Thrown at crates/burn-backend-extension/src/dispatch.rs:342
fn expand_creation(
device: &syn::Ident,
output: &OperationOutput,
body: &syn::Block,
) -> TokenStream {
let direct_arms = BACKENDS
.iter()
.map(|backend| creation_arm(backend, output, body, false));
let autodiff_arms = BACKENDS
.iter()
.map(|backend| creation_arm(backend, output, body, true));
quote! {
match #device {
#(#direct_arms)*
#[cfg(feature = "autodiff")]
crate::DispatchDevice::Autodiff(__device) => match __device.inner.as_ref() {
#(#autodiff_arms)*
crate::DispatchDevice::Autodiff(_) => {
panic!("Autodiff should not wrap an autodiff device.")
}
#[allow(unreachable_patterns)]
__other => panic!("unsupported dispatch device: {__other:?}"),
},
#[allow(unreachable_patterns)]
__other => panic!("unsupported dispatch device: {__other:?}"),
}
}
}
fn creation_arm(
backend: &crate::BackendSpec,
output: &OperationOutput,
body: &syn::Block,
autodiff_device: bool,
) -> TokenStream {
let ident = syn::Ident::new(backend.name, proc_macro2::Span::call_site());
let cfg: TokenStream = backend.cfg.parse().expect("valid backend cfg");View on GitHub (pinned to d16f7ba2ed)
Solutions
- Do not wrap an autodiff device again; pass the concrete inner backend device to the Autodiff constructor
- Unwrap once: build Autodiff from the plain backend device (e.g. the Cpu/Wgpu/Cube device), not from another Autodiff device
- Check the code path that produced the device and remove the redundant wrapping layer
Example fix
// before let device = DispatchDevice::Autodiff(AutodiffDevice::new(DispatchDevice::Autodiff(inner))); // after let device = DispatchDevice::Autodiff(AutodiffDevice::new(inner));
Defensive patterns
Strategy: validation
Validate before calling
fn ensure_not_double_autodiff(device: &DispatchDevice) -> Result<(), String> {
if let DispatchDevice::Autodiff(inner) = device {
if matches!(inner.inner.as_ref(), DispatchDevice::Autodiff(_)) {
return Err("Autodiff device must not wrap another Autodiff device".into());
}
}
Ok(())
} Type guard
fn is_concrete_device(d: &DispatchDevice) -> bool {
!matches!(d, DispatchDevice::Autodiff(_))
} Prevention
- Always build Autodiff devices from a concrete backend device via the library constructors
- Add an assertion/debug check that unwrap-inner of Autodiff is concrete
- Centralize device construction in one helper to avoid accidental double wrapping
When it happens
Trigger: Constructing or creating a tensor/operation with DispatchDevice::Autodiff whose inner .inner is DispatchDevice::Autodiff(_) — e.g. wrapping an already-wrapped autodiff device when building the dispatch device.
Common situations: Hand-constructing a DispatchDevice instead of using the provided constructors; migrating code across versions where the wrapper API changed; double-applying an autodiff adapter around a backend device.
Related errors
- unsupported dispatch device: {__other:?}
- an autodiff float primitive must have an enabled autodiff co
- an enabled float tensor must use an autodiff primitive
- Can't differentiate avg pool 2d backward.
- Can't differentiate max pool2d with indices backward.
AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05).
Data as JSON: /api/errors/60db1da2885d2394.
Report an issue: GitHub.