{"record":{"id":"48a71b9e0b45013a","repo":"tracel-ai/burn","slug":"autodiff-tensor-cannot-be-moved-between-backends","errorCode":null,"errorMessage":"Autodiff tensor cannot be moved between backends.","messagePattern":"Autodiff tensor cannot be moved between backends\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/burn-dispatch/src/macros.rs","lineNumber":371,"sourceCode":"    ) => {{\n        match ($tensor, $device) {\n            // --- Same backend to_device ---\n            $(\n                #[cfg($src_cfg)]\n                ($crate::DispatchTensorKind::$B1(tensor), $crate::DispatchDevice::$B1(d)) => {\n                    let kind = $crate::DispatchTensorKind::Autodiff(alloc::boxed::Box::new($crate::DispatchTensorKind::$B1($crate::BackendTensor::Autodiff(\n                        with_autodiff_backend!($B1, $ckp, |B| {\n                            B::$to_device(tensor.autodiff(), d)\n                        })\n                    ))));\n                    $crate::DispatchTensor {\n                        kind,\n                        autodiff: $crate::DispatchAutodiffContext::Enabled($ckp),\n                    }\n                }\n            )*\n            // TODO: should be possible\n            (_, _) => unimplemented!(\"Autodiff tensor cannot be moved between backends.\")\n        }\n    }};\n}\n\n/// Handles float tensor movement between devices (that might support autodiff).\nmacro_rules! float_to_device {\n    ($kind:ident, $inner_fn:ident, $tensor:expr, $device:expr, $to_device:ident, |$inner:ident, $device_ident:ident| $body:expr) => {\n        backend_matrix!(\n            float_to_device_arms,\n            $tensor,\n            $device,\n            $to_device,\n            |$inner, $device_ident| $body\n        )\n    };\n}\n\n/// Unwraps a `Vec<DispatchTensor>` for a known backend.","sourceCodeStart":353,"sourceCodeEnd":389,"githubUrl":"https://github.com/tracel-ai/burn/blob/d16f7ba2ed0d41408189384044cc886fb4c8f957/crates/burn-dispatch/src/macros.rs#L353-L389","documentation":"The `@autodiff` arm of the backend_matrix! macro only supports moving an autodiff (AutodiffTensor) between devices on the SAME backend; the catch-all arm panics with unimplemented! for any cross-backend move. Cross-backend transport of gradient-tracking tensors is not implemented yet (marked `// TODO: should be possible`).","triggerScenarios":"Calling `tensor.to_device(other_device)` (via float_to_device/backend_matrix @autodiff arms) where the tensor's DispatchTensorKind::Autodiff lives on backend B1 but the target device belongs to a different backend B2, e.g. moving a Wgpu autodiff tensor to a LibTorch/Cube device (crates/burn-dispatch/src/macros.rs:371).","commonSituations":"Multi-GPU/multi-backend setups that shuffle tensors between a GPU backend and another accelerator or CPU backend while autodiff (training) is active; deploying one model partition per backend.","solutions":["Detach first: call `<Dispatch as AutodiffBackend>::inner(tensor)` (or backward before moving) to get a non-autodiff tensor, move it with to_device, then re-wrap with from_inner on the destination.","Move the tensor to the target backend BEFORE enabling require_grad, or recreate the tensor on the target backend from its data (TensorData) and re-enable gradients there.","Restructure the pipeline so each autodiff subgraph stays on a single backend."],"exampleFix":"// before\nlet moved = autodiff_tensor.to_device(other_backend_device); // panics\n\n// after\nlet inner_t = <Dispatch as AutodiffBackend>::inner(autodiff_tensor);\nlet moved_inner = inner_t.to_device(other_backend_device);\nlet moved = <Dispatch as AutodiffBackend>::from_inner(moved_inner);","handlingStrategy":"validation","validationCode":"// Check the tensor's backend kind against the destination device before moving\nfn can_move_autodiff(tensor: &DispatchTensor, dst: &DispatchDevice) -> bool {\n    !matches!(tensor.kind, DispatchTensorKind::Autodiff(_))\n        || match (&tensor.kind, dst) {\n            (DispatchTensorKind::Autodiff(_), DispatchDevice::Wgpu(_)) => matches!(tensor.kind, DispatchTensorKind::Autodiff(_)),\n            _ => !matches!(tensor.kind, DispatchTensorKind::Autodiff(_)),\n        }\n}","typeGuard":"fn is_autodiff_tensor(t: &DispatchTensor) -> bool {\n    matches!(t.kind, DispatchTensorKind::Autodiff(_))\n}","tryCatchPattern":"// Panics cannot be caught; validate before calling to_device\nif is_autodiff_tensor(&t) && !same_backend(&t, &dst_device) {\n    let inner_t = <Dispatch as AutodiffBackend>::inner(t);\n    let moved = <Dispatch as AutodiffBackend>::from_inner(inner_t.to_device(dst_device));\n} else {\n    let moved = t.to_device(dst_device);\n}","preventionTips":["Detach via inner() before any cross-backend to_device on training tensors.","Design pipelines so each autodiff subgraph is confined to one backend/device type.","Enable gradients only after tensors are on their final backend.","Wrap cross-backend moves in a helper that always does the inner/from_inner round trip."],"tags":["rust","autodiff","multi-backend","to-device","unimplemented"],"backgroundTag":"cross-backend-tensor-move","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"}