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.

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.

Appendix: 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)