tracel-ai/burn · error

an enabled float tensor must use an autodiff primitive

Error message

an enabled float tensor must use an autodiff primitive

What it means

This panic fires inside dispatch's q_matmul when one matmul operand is a Float tensor whose autodiff context is Enabled, but the tensor's backend primitive is not an autodiff primitive. Burn maintains an invariant pairing: a float tensor primitive that supports autodiff must always be accompanied by an enabled autodiff context (and vice versa). Mixing a plain float primitive with an enabled autodiff context means internal state got out of sync, so the library panics rather than silently skipping gradient tracking.

Source

Thrown at crates/burn-dispatch/src/ops/qtensor.rs:302

            (TensorPrimitive::QFloat(lhs), TensorPrimitive::QFloat(rhs)) => {
                let autodiff = lhs.autodiff.merge(rhs.autodiff);
                // With no float input, the first tensor is the routing tensor.
                backend_list!(q_matmul_qq_arms, lhs, rhs, autodiff)
            }
            (TensorPrimitive::Float(lhs), TensorPrimitive::QFloat(rhs)) => {
                let autodiff = lhs.autodiff.merge(rhs.autodiff);
                #[cfg(feature = "autodiff")]
                match (
                    matches!(&lhs.kind, DispatchTensorKind::Autodiff(_)),
                    lhs.autodiff,
                ) {
                    (true, DispatchAutodiffContext::Enabled(_))
                    | (false, DispatchAutodiffContext::Disabled) => {}
                    (true, DispatchAutodiffContext::Disabled) => {
                        panic!("an autodiff float primitive must have an enabled autodiff context")
                    }
                    (false, DispatchAutodiffContext::Enabled(_)) => {
                        panic!("an enabled float tensor must use an autodiff primitive")
                    }
                }
                backend_list!(q_matmul_fq_arms, lhs, rhs, autodiff)
            }
            (TensorPrimitive::QFloat(lhs), TensorPrimitive::Float(rhs)) => {
                let autodiff = lhs.autodiff.merge(rhs.autodiff);
                #[cfg(feature = "autodiff")]
                match (
                    matches!(&rhs.kind, DispatchTensorKind::Autodiff(_)),
                    rhs.autodiff,
                ) {
                    (true, DispatchAutodiffContext::Enabled(_))
                    | (false, DispatchAutodiffContext::Disabled) => {}
                    (true, DispatchAutodiffContext::Disabled) => {
                        panic!("an autodiff float primitive must have an enabled autodiff context")
                    }
                    (false, DispatchAutodiffContext::Enabled(_)) => {
                        panic!("an enabled float tensor must use an autodiff primitive")

View on GitHub (pinned to d16f7ba2ed)

Solutions

  1. Ensure every Float tensor carrying DispatchAutodiffContext::Enabled is created from an autodiff backend primitive (DispatchTensorKind::Autodiff) so the kind and context agree.
  2. If the tensor truly is a non-autodiff float primitive, construct it with DispatchAutodiffContext::Disabled.
  3. When mixing operands, verify that merging autodiff contexts (lhs.autodiff.merge(rhs.autodiff)) matches the actual primitive kinds of both sides.
  4. If this arises inside burn itself, report it — it indicates a broken invariant in the dispatch layer, not user error in a normal op call.

Example fix

// before (mismatched: plain primitive + enabled context)
let t = BackendTensor::Float(float_primitive); // primitive kind: Float
let t = TensorPrimitive::Float { kind: DispatchTensorKind::Float(..), autodiff: DispatchAutodiffContext::Enabled(ctx) };
// after (consistent)
let t = TensorPrimitive::Float { kind: DispatchTensorKind::Autodiff(autodiff_primitive), autodiff: DispatchAutodiffContext::Enabled(ctx) };
Defensive patterns

Strategy: validation

Validate before calling

// before q_matmul on float operands
fn ensure_ad_consistent(t: &TensorPrimitive<DispatchBackend>) -> Result<(), String> {
    let is_ad = matches!(t.tensor().kind, DispatchTensorKind::Autodiff(_));
    let ctx_enabled = matches!(&t.tensor().autodiff, DispatchAutodiffContext::Enabled(_));
    if is_ad == ctx_enabled { Ok(()) } else { Err(format!("autodiff kind/context mismatch: kind_ad={is_ad}, ctx_enabled={ctx_enabled}")) }
}

Type guard

fn is_autodiff_float<B: Backend>(t: &BackendTensor<B>) -> bool {
    matches!(t, BackendTensor::Autodiff(_))
        || matches!(t, BackendTensor::Float(f) if matches!(f.kind, DispatchTensorKind::Autodiff(_)))
}

Prevention

When it happens

Trigger: Calling matmul between two quantized-float (QFloat) tensors or float/QFloat mixes where one TensorPrimitive::Float was built from a non-autodiff backend primitive while its `autodiff` field is DispatchAutodiffContext::Enabled — i.e., the Float tensor's `is_autodiff()`-style check (matches! on DispatchTensorKind::Autodiff) is false but `rhs.autodiff`/`lhs.autodiff` is Enabled.

Common situations: Constructing dispatch tensors manually with mismatched primitive/kind and autodiff context; wrapping a plain (non-autodiff) backend tensor in an Enabled autodiff context; bugs in custom backend adapters or capture/replay paths that rebuild TensorPrimitive::Float incorrectly.

Related errors


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