Rust in Everything
基于 burn 0.22.1

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。

💬 关联讨论

发起讨论
加载中...