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

  1. Do not wrap an autodiff device again; pass the concrete inner backend device to the Autodiff constructor
  2. Unwrap once: build Autodiff from the plain backend device (e.g. the Cpu/Wgpu/Cube device), not from another Autodiff device
  3. 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

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


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