tracel-ai/burn · error

Autodiff should not wrap an autodiff tensor.

Error message

Autodiff should not wrap an autodiff tensor.

What it means

This panic is emitted by code generated by the burn-backend-extension dispatch macro. The AutodiffKindMatch route is generated such that if the runtime dispatch tensor already carries an Autodiff kind, wrapping it again in autodiff is invalid (autodiff must wrap a plain backend, never another autodiff backend), so the generated match arm panics.

Source

Thrown at crates/burn-backend-extension/src/dispatch.rs:197

    source_kind: TokenStream,
    inner_kind: TokenStream,
) -> TokenStream {
    let float_inputs = float_input_presence(operation, source);
    let backends = dispatch_backends();
    let source_is_non_float = source.is_some_and(|source| {
        matches!(
            source.kind,
            InputKind::Tensor { kind, .. } if kind != TensorKind::Float
        )
    });
    let paths = routing::RoutingPaths::dispatch();
    let selected =
        source.map(|_| syn::Ident::new("__burn_selected", proc_macro2::Span::call_site()));
    let autodiff_match = (!source_is_non_float).then(|| routing::AutodiffKindMatch {
        cfg_attr: Some(quote!(#[cfg(feature = "autodiff")])),
        inner_kind,
        nested_autodiff: Some(quote! {
            panic!("Autodiff should not wrap an autodiff tensor.")
        }),
        fallback: quote!(panic!("unsupported dispatch backend")),
    });
    let arms = routing::backend_kind_arms(
        &paths,
        &backends,
        selected.as_ref(),
        autodiff_match,
        |_, backend, autodiff| {
            backend_route_body(backend, operation, &float_inputs, autodiff, source)
        },
    );
    let direct_arms = arms.concrete;
    let autodiff_route = if source_is_non_float {
        quote! {
            #[cfg(feature = "autodiff")]
            crate::DispatchTensorKind::Autodiff(_) => {
                panic!("only float tensors may use an autodiff primitive")

View on GitHub (pinned to d16f7ba2ed)

Solutions

  1. Remove the double wrap: use Autodiff<B> once at the outermost boundary only
  2. Pass the inner (non-autodiff) tensor into the dispatch call, unwrapping any AutodiffTensor first
  3. Disable the autodiff feature for the dispatch macro invocation if the call site is not a training boundary
  4. Fix generic bounds so autodiff wrapping happens only where the kind is not already Autodiff

Example fix

// before
let x = AutodiffTensor::from(AutodiffTensor::from(inner_tensor)); // nested
// after
let x = AutodiffTensor::from(inner_tensor);
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure you never double-wrap: if T is already Autodiff<...>, do not wrap again
fn assert_single_wrap<B: Backend>(_t: &AutodiffTensor<B>) {} // compile-time: B must be a plain backend

Prevention

When it happens

Trigger: Passing a tensor already typed as Autodiff<B, C> through the dispatch machinery that tries to re-wrap it as autodiff (nested autodiff); misconfigured backend generics that resolve the autodiff arm twice.

Common situations: Double-wrapping backends like Autodiff<Autodiff<Wgpu>>; macro-generated dispatch functions receiving autodiff tensors from an outer layer; backend-agnostic code compiled with the autodiff feature fed already-tracked tensors.

Related errors


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