{"record":{"id":"8dff2e47c80181e7","repo":"tracel-ai/burn","slug":"cannot-move-between-autodiff-and-non-autodiff-inst","errorCode":null,"errorMessage":"Cannot move between autodiff and non-autodiff instances.","messagePattern":"Cannot move between autodiff and non-autodiff instances\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/burn-dispatch/src/macros.rs","lineNumber":338,"sourceCode":"            // --- Cross backend arms ---\n            // This loop generates the grid of combinations\n            $(\n                $(\n                    #[cfg(all($src_cfg, $dst_cfg))]\n                    ($crate::DispatchTensorKind::$B1(kind), $crate::DispatchDevice::$B2($device_ident)) => {\n                        type B1 = $crate::backends::$B1;\n                        type B2 = $crate::backends::$B2;\n                        let $inner = kind.float();\n\n                        $crate::DispatchTensor {\n                            kind: $crate::DispatchTensorKind::$B2($crate::BackendTensor::Float($body)),\n                            autodiff: $tensor.autodiff,\n                        }\n                    }\n                )+\n            )*\n            #[cfg(feature = \"autodiff\")]\n            ($crate::DispatchTensorKind::Autodiff(..), _) | (_, $crate::DispatchDevice::Autodiff(_)) => panic!(\"Cannot move between autodiff and non-autodiff instances.\"),\n            // Capture is intentionally one-way: initialized values can be moved onto a\n            // capture device, but captured tensors have no materialized data to move back.\n            #[cfg(feature = \"capture\")]\n            ($crate::DispatchTensorKind::Capture(_), _) => {\n                panic!(\"Cannot move a tensor from a capture device\")\n            }\n        }\n    };\n\n    // Autodiff(DispatchTensor)\n    (\n        @autodiff\n        $tensor:expr, $device:expr, $ckp:expr, $to_device:ident;\n        $( [$B1:ident, $src_cfg:meta] );*\n    ) => {{\n        match ($tensor, $device) {\n            // --- Same backend to_device ---\n            $(","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/tracel-ai/burn/blob/d16f7ba2ed0d41408189384044cc886fb4c8f957/crates/burn-dispatch/src/macros.rs#L320-L356","documentation":"A cross-device tensor move was attempted where exactly one side is autodiff: either the tensor is an Autodiff-kind tensor while the target device is a plain backend, or the tensor is plain while the target is an Autodiff device. The dispatcher requires both sides to agree on autodiffness — gradients/checkpointer state cannot be created or dropped implicitly mid-transfer, so the mismatch panics.","triggerScenarios":"Calling to_device (macros.rs:338 arm) with (Autodiff tensor, plain device) or (plain tensor, DispatchDevice::Autodiff(_)) — e.g. moving a tensor tracked by autodiff onto a raw backend device, or pushing an inference tensor onto an autodiff-wrapped device.","commonSituations":"Mixing inference-mode and training-mode device handles in the same trainer; building model/server code where one path uses Autodiff<Backend> and another uses Backend and tensors cross between them; hot-swapping devices after toggling the autodiff feature.","solutions":["Match autodiffness on both sides: to move off autodiff, detach first (`.inner()`) and move the plain tensor to the plain device.","To move a plain tensor into autodiff tracking, wrap the target as DispatchDevice::Autodiff and recreate/track the tensor under the enabled autodiff context.","Use a single device type throughout a pipeline (either always Autodiff<Backend> during training or always Backend during inference).","Check device construction helpers so the same feature flags/config produce consistent device wrappers."],"exampleFix":"// before\nautodiff_tensor.to_device(&plain_backend_device); // mismatch -> panic\n\n// after\nlet plain = autodiff_tensor.inner(); // detach gradients\nplain.to_device(&plain_backend_device);","handlingStrategy":"type-guard","validationCode":"fn sides_agree(t: &DispatchPrimitive, d: &DispatchDevice) -> bool {\n    let t_auto = matches!(t.kind, DispatchTensorKind::Autodiff(_));\n    let d_auto = matches!(d, DispatchDevice::Autodiff(_));\n    t_auto == d_auto\n}","typeGuard":"fn compatible_move(t: &DispatchPrimitive, d: &DispatchDevice) -> bool {\n    matches!(t.kind, DispatchTensorKind::Autodiff(_)) == matches!(d, DispatchDevice::Autodiff(_))\n}","tryCatchPattern":null,"preventionTips":["Detach autodiff tensors before moving to plain backend devices.","Use one device wrapper type per pipeline stage (train: Autodiff<Backend>, infer: Backend).","Add a debug_assert comparing autodiffness of tensor and device at move sites."],"tags":["rust","autodiff","tensor","device-transfer","burn"],"backgroundTag":"autodiff-device-mismatch","analyzedSha":"d16f7ba2ed0d41408189384044cc886fb4c8f957","analyzedAt":"2026-09-05T13:19:14.260Z","contentChangedAt":"2026-09-05T13:19:14.260Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}