No Discrete GPU device found
主线位置:算子主线的最底层第一次被用到时。
Device::metal(kind)只是一个值,真正去找适配器是第一个 op 到达 cubecl 时,在设备服务线程上做(wgpu 与 Metal)。找不到适配器,那个线程 panic,调用方的线程拿到的只是一个断开的通道。
sequenceDiagram participant M as main 线程 participant D as DSD 服务线程 M->>M: Device::metal DiscreteGpu 0 M->>D: 第一个 op,初始化服务 D->>D: 枚举适配器,只有 IntegratedGpu D--xD: panic Unable to acquire wgpu device M->>M: recv 失败 RecvError panic
本页实验 crate pa-dev(模板的 feature,debug 构建)。平台 M2 Max、macOS 27、rustc 1.99.0;Metal 是实测,其他平台没有测。
现象
照 Device 的文档注释(device.rs:72 写着 Device::wgpu(DeviceKind::DiscreteGpu(0)))或 burn book 的 backend 一节去要「独立显卡」:
fn add(device: &Device) {
let x = Tensor::<1>::from_floats([1.0, 2.0], device);
println!("{:?} -> {:?}", device, (x.clone() + x).into_data().to_vec::<f32>().unwrap());
}
let device = Device::metal(DeviceKind::DiscreteGpu(0));
println!("constructed {device:?}");
add(&device);
constructed Device<Cube(Wgpu(WgpuDevice { kind: DiscreteGpu(0), backend: Metal }))>
thread 'DSD-0-16384' (3523633) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cubecl-wgpu-0.11.3/src/runtime.rs:51:57:
Unable to acquire wgpu device: AdapterUnavailable { device: WgpuDevice { kind: DiscreteGpu(0), backend: Metal }, message: "No Discrete GPU device found, adapters [AdapterInfo { name: \"Apple M2 Max\", vendor: 0, device: 0, device_type: IntegratedGpu, device_pci_bus_id: \"\", driver: \"\", driver_info: \"\", backend: Metal, subgroup_min_size: 4, subgroup_max_size: 64, transient_saves_memory: Some(true), limit_bucket: None }]" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' (3523632) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cubecl-common-0.11.3/src/device/handle/channel.rs:409:35:
called `Result::unwrap()` on an `Err` value: RecvError
exit=101
构造设备没有出错,第一行照常打印。错误分成两段:有用的信息在服务线程 DSD-0-16384 那一段(这台机器只有一个适配器,类型是 IntegratedGpu),主线程那一段只有 RecvError。日志被截断、或者 stderr 只留了最后几行时,看到的就只有后者。
出处
适配器在 cubecl 的 DeviceService::init 里找,它跑在设备服务线程上:
fn init(device_id: cubecl_common::device::DeviceId) -> Self {
let device = WgpuDevice::from_id(device_id);
let setup =
future::block_on(try_create_setup(&device)).expect("Unable to acquire wgpu device");
try_create_server(setup, RuntimeOptions::default(), device_id, device.backend)
.expect("Unable to initialize wgpu runtime")
}
try_create_setup 按 kind 选适配器,DiscreteGpu(num) 交给 select_from_adapter_list:只留 wgpu 报告为同一类型的适配器、取第 num 个,没有就返回带适配器清单的 AdapterUnavailable。init 用 expect 把它变成 panic。
调用方这边是 cubecl 的通道。init 把「初始化服务」的闭包发给服务线程,闭包里 S::init(device_id) 成功后才 callback.send(..);S::init panic 了,回调的发送端随闭包一起被丢掉,调用方在第 409 行等结果:
let service = recv.recv().unwrap();
于是主线程 panic 成 RecvError。同一个函数在「调用方本身就是服务线程」的分支里用 catch_unwind 把初始化的 panic 转成 ServiceCreationError,从别的线程调用的这条路径没有,原因被留在了另一个线程的输出里。
DeviceKind 每个变体怎么映射到 wgpu 的选择方式,见 wgpu 与 Metal 的「选设备」一节。IntegratedGpu(1) 和 Cpu 在这台机器上也是同样的两段 panic(那一页实测过)。
修法
Apple 芯片上用 DeviceKind::DefaultDevice(它带 PowerPreference::HighPerformance 去请求适配器)或 IntegratedGpu(0):
Device<Cube(Wgpu(WgpuDevice { kind: IntegratedGpu(0), backend: Metal }))> -> [2.0, 4.0]
case integrated: done
exit=0
代码要在不同机器上跑、想「有独显用独显,没有就退回」,不要用会 panic 的惰性构造,用 Device::wgpu_options 立即初始化,它返回 Result:
use burn::tensor::wgpu::WgpuBackend;
let device = match Device::wgpu_options()
.device_kind(DeviceKind::DiscreteGpu(0))
.graphics_api(WgpuBackend::Metal)
.init()
{
Ok(device) => device,
Err(e) => {
println!("no discrete GPU: {e}");
Device::metal(DeviceKind::DefaultDevice)
}
};
add(&device);
no discrete GPU: No adapter available for WgpuDevice { kind: DiscreteGpu(0), backend: Metal }: No Discrete GPU device found, adapters [AdapterInfo { name: "Apple M2 Max", vendor: 0, device: 0, device_type: IntegratedGpu, device_pci_bus_id: "", driver: "", driver_info: "", backend: Metal, subgroup_min_size: 4, subgroup_max_size: 64, transient_saves_memory: Some(true), limit_bucket: None }]
Device<Cube(Wgpu(WgpuDevice { kind: DefaultDevice, backend: Metal }))> -> [2.0, 4.0]
case discrete-try: done
exit=0
错误是 WgpuInitError,Display 里带着同样的适配器清单,而且在主线程上,可以处理。文档说用 wgpu_options 初始化的运行时会被登记下来复用,同一选择器之后不能再改配置;要在建张量之前调用。
怎么确认没中招
- 搜代码里的
DiscreteGpu(、VirtualGpu(、IntegratedGpu(后面跟非 0 的下标:这些都依赖具体机器。 - 看到主线程
RecvError、位置在cubecl-common/.../channel.rs,往上翻找DSD-线程的 panic,那里才是原因。 - 要换设备又不想改代码,
DefaultDevice可以用环境变量CUBECL_WGPU_DEFAULT_DEVICE改写(例如IntegratedGpu(1)、Cpu,见DeviceKind::DefaultDevice的文档注释)。
小结
- wgpu 在 Apple 芯片上把 GPU 报成
IntegratedGpu,DiscreteGpu(0)找不到适配器;burn 自己的文档示例就写着DiscreteGpu(0)。 - 设备是惰性初始化的:构造不报错,第一个 op 时服务线程 panic,主线程只看到
RecvError。 - 修法:用
DefaultDevice;需要回退逻辑时用Device::wgpu_options().device_kind(..).init()拿Result。