The provided tensors are not on the same device
主线位置:算子主线第一步。
a + b先由TensorCheck::binary_ops_ew比较两个张量的设备(检查与报错),不等就 panic,调用到不了Dispatch。本页的情况是:两个设备在这台 Mac 上指向同一块 GPU,Device的相等却判它们不同。
flowchart TB
def["Device::default"] --> auto["Wgpu backend Auto"]
from["Tensor::from"] --> def
met["Device::metal DefaultDevice"] --> mtl["Wgpu backend Metal"]
auto --> cmp{"设备相等吗"}
mtl --> cmp
cmp -->|"Auto 不等于 Metal"| pnc["not on the same device"]
env["BURN_DEVICE=metal"] --> def2["default 变成 Metal"]
def2 --> ok["相等,能运算"]
本页实验 crate pa-dev(模板的 feature,含 flex、ndarray、wgpu、metal;debug 构建),每个用例单独起一个进程。平台 M2 Max、macOS 27、rustc 1.99.0。
现象
一部分代码用默认设备,另一部分明确写了 Metal:
let metal = Device::metal(DeviceKind::DefaultDevice);
let a = Tensor::<1>::from_floats([1.0, 2.0], &Device::default());
let b = Tensor::<1>::from_floats([3.0, 4.0], &metal);
let _ = a + b;
thread 'main' (3523454) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/burn-tensor-0.22.1/src/tensor/api/numeric.rs:40:9:
=== Tensor Operation Error ===
Operation: 'Add'
Reason:
1. The provided tensors are not on the same device. Lhs tensor device Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Auto }))>, Rhs tensor device Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Metal }))>.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
两个设备只差 backend: Auto 和 backend: Metal。更隐蔽的是没写设备的那种创建方式,Tensor::from 用的也是默认设备:
let a: Tensor<1> = Tensor::from([1.0f32, 2.0]);
let b = Tensor::<1>::from_floats([3.0, 4.0], &metal);
println!("a on {:?}", a.device());
let _ = a + b;
a on Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Auto }))>
thread 'main' (3523482) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/burn-tensor-0.22.1/src/tensor/api/numeric.rs:40:9:
=== Tensor Operation Error ===
Operation: 'Add'
Reason:
1. The provided tensors are not on the same device. Lhs tensor device Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Auto }))>, Rhs tensor device Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Metal }))>.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
出处
From<T> for Tensor 就是 Tensor::from_data(value.into(), &Default::default())。DispatchDevice::default 在开了 metal 且没有 BURN_DEVICE 时返回 CubeDevice::Wgpu(Default::default()),而 cubecl 的 WgpuDevice 默认的图形 API 是 WgpuBackend::Auto;Device::metal 则把它固定成 Metal。WgpuDevice 派生的 PartialEq 同时比较 kind 和 backend,于是 binary_ops_device 的 lhs != rhs 成立:
match lhs != rhs {
true => self.register(
ops,
TensorError::new("The provided tensors are not on the same device.").details(
format!("Lhs tensor device {lhs:?}, Rhs tensor device {rhs:?}.",),
),
),
false => self,
}
默认设备的选择顺序、BURN_DEVICE 的写法见 Device;Auto 在 macOS 上为什么最后也落到 Metal,见 wgpu 与 Metal。
问: 两个设备用的是同一块 GPU,burn 为什么不干脆把它们当成一个?
答: 因为在 cubecl 那一层它们确实是两个运行时。图形 API 编在 DeviceId 里,Device::metal(..) 和默认设备各有一套设备服务线程、内存池、内核管线表和 autotune 结果(wgpu 与 Metal 实测过第二个设备要重新编译同一个内核)。一个设备上的张量句柄在另一个设备的内存池里不存在。TensorCheck 在这里拦下,是比放过去更好的结局:不经过这条检查的 cat 会走到执行阶段才出错,见 input tensor is on the wrong backend。
修法
全程只用一个设备值,创建一次、到处传 &device,不在半路调 Device::default() 或 Tensor::from:
let device = Device::metal(DeviceKind::DefaultDevice);
let a = Tensor::<1>::from_data([1.0f32, 2.0], &device);
let b = Tensor::<1>::from_floats([3.0, 4.0], &device);
println!("{:?}", (a + b).into_data().to_vec::<f32>().unwrap());
[4.0, 6.0]
case one-device: done
exit=0
如果代码里(或依赖的库里)有用默认设备的地方改不掉,用环境变量把默认设备固定成 Metal。同一个程序,打印 Device::default()、和 metal 比较、再用 Tensor::from 建一个张量和 Metal 上的张量相加:
### env-default BURN_DEVICE=metal
Device::default() = Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Metal }))>
== metal: true
[4.0, 6.0]
case env-default: done
exit=0
### env-default (no BURN_DEVICE)
Device::default() = Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Auto }))>
== metal: false
thread 'main' (3523885) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/burn-tensor-0.22.1/src/tensor/api/numeric.rs:40:9:
...
exit=101
反过来,统一用 Device::wgpu(DeviceKind::DefaultDevice) 也行,它和默认设备相等(Device 的相等表)。张量已经建在两个设备上了,就用 to_device 搬到同一个设备再算。
怎么确认没中招
- 在代码里搜
Device::default()、Default::default()、Tensor::from(,看它们和显式的Device::metal(..)/Device::wgpu(..)有没有混用。 - 启动时打印一次实际用的设备,
backend: Auto和backend: Metal同时出现就是信号。 - 报错里把两个设备都打印出来了,逐项对:
kind、backend只要一项不同就不是同一个设备。
小结
- 开了
metal时,Device::default()是backend: Auto的 wgpu 设备,Device::metal(DeviceKind::DefaultDevice)是backend: Metal,两者不相等。 Tensor::from和其他不传设备的创建方式都用默认设备,和Device::metal(..)上的张量混用就报「not on the same device」。- 修法:一个设备值传到底;或者
BURN_DEVICE=metal;或者统一用Device::wgpu(..)。