use cubecl_core::client::Client;
#[cfg(any_runtime)]
use cubecl_core::device::Device as DeviceIdentity;
use cubecl_core::zspace::{Shape, Strides};
#[cfg(any_runtime)]
use cubecl_runtime::runtime::Runtime;
pub use cubecl_core::device::DeviceId;
pub use cubecl_runtime::device::{
AmdDevice, CpuDevice, CudaDevice, MetalDevice, WgpuBackend, WgpuDevice, WgpuDeviceKind,
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Device {
Cuda(CudaDevice),
Hip(AmdDevice),
Metal(MetalDevice),
Wgpu(WgpuDevice),
Cpu(CpuDevice),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum RuntimeId {
Cuda = 0,
Hip = 1,
Metal = 2,
Wgpu = 3,
Cpu = 4,
}
const RUNTIME_TYPE_ID_MASK: u16 = 0x001F;
const RUNTIME_TYPE_ID_SHIFT: u32 = 5;
const RUNTIME_MASK: u16 = 0x0007;
impl TryFrom<u16> for RuntimeId {
type Error = u16;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Cuda),
1 => Ok(Self::Hip),
2 => Ok(Self::Metal),
3 => Ok(Self::Wgpu),
4 => Ok(Self::Cpu),
other => Err(other),
}
}
}
impl RuntimeId {
pub const OUTER_MASK: u16 = !(RUNTIME_TYPE_ID_MASK | (RUNTIME_MASK << RUNTIME_TYPE_ID_SHIFT));
pub fn of_device_id(device_id: DeviceId) -> Result<Self, u16> {
Self::try_from((device_id.type_id >> RUNTIME_TYPE_ID_SHIFT) & RUNTIME_MASK)
}
pub fn strip(device_id: DeviceId) -> DeviceId {
DeviceId::new(device_id.type_id & RUNTIME_TYPE_ID_MASK, device_id.index_id)
}
pub fn stamp(self, device_id: DeviceId) -> DeviceId {
let tag = ((self as u16) & RUNTIME_MASK) << RUNTIME_TYPE_ID_SHIFT;
DeviceId::new(
(device_id.type_id & !(RUNTIME_MASK << RUNTIME_TYPE_ID_SHIFT)) | tag,
device_id.index_id,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DeviceUnavailable {
NotLinked(RuntimeId),
NoSuchDevice {
runtime: RuntimeId,
available: usize,
},
}
impl core::fmt::Display for DeviceUnavailable {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::NotLinked(runtime) => write!(
f,
"this build does not link the {runtime:?} runtime: turn on its `cubecl` feature"
),
Self::NoSuchDevice { runtime, available } => write!(
f,
"the {runtime:?} runtime has no such device on this machine, \
which has {available} of that kind"
),
}
}
}
impl core::error::Error for DeviceUnavailable {}
impl Device {
pub fn cpu() -> Result<Self, DeviceUnavailable> {
Self::named(RuntimeId::Cpu, Self::Cpu(CpuDevice))
}
pub fn cuda(index: usize) -> Result<Self, DeviceUnavailable> {
Self::named(RuntimeId::Cuda, Self::Cuda(CudaDevice::new(index)))
}
pub fn rocm(index: usize) -> Result<Self, DeviceUnavailable> {
Self::named(RuntimeId::Hip, Self::Hip(AmdDevice::new(index)))
}
pub fn metal(kind: MetalDevice) -> Result<Self, DeviceUnavailable> {
match kind {
MetalDevice::Existing(_) => Self::linked(RuntimeId::Metal, Self::Metal(kind)),
kind => Self::named(RuntimeId::Metal, Self::Metal(kind)),
}
}
pub fn wgpu(kind: WgpuDeviceKind) -> Result<Self, DeviceUnavailable> {
Self::wgpu_device(WgpuDevice::new(kind))
}
pub fn vulkan(kind: WgpuDeviceKind) -> Result<Self, DeviceUnavailable> {
Self::wgpu_device(WgpuDevice::new(kind).on(WgpuBackend::Vulkan))
}
pub fn dx12(kind: WgpuDeviceKind) -> Result<Self, DeviceUnavailable> {
Self::wgpu_device(WgpuDevice::new(kind).on(WgpuBackend::Dx12))
}
pub fn gl(kind: WgpuDeviceKind) -> Result<Self, DeviceUnavailable> {
Self::wgpu_device(WgpuDevice::new(kind).on(WgpuBackend::Gl))
}
pub fn webgpu(kind: WgpuDeviceKind) -> Result<Self, DeviceUnavailable> {
Self::wgpu_device(WgpuDevice::new(kind).on(WgpuBackend::WebGpu))
}
pub fn metal_msl(kind: WgpuDeviceKind) -> Result<Self, DeviceUnavailable> {
Self::wgpu_device(WgpuDevice::new(kind).on(WgpuBackend::Metal))
}
pub fn on(self, backend: WgpuBackend) -> Result<Self, DeviceUnavailable> {
match self {
Self::Wgpu(device) => Self::wgpu_device(device.on(backend)),
other => Err(DeviceUnavailable::NoSuchDevice {
runtime: other.runtime(),
available: 0,
}),
}
}
fn wgpu_device(device: WgpuDevice) -> Result<Self, DeviceUnavailable> {
match device.kind {
WgpuDeviceKind::Existing(_) => Self::linked(RuntimeId::Wgpu, Self::Wgpu(device)),
_ => Self::named(RuntimeId::Wgpu, Self::Wgpu(device)),
}
}
fn named(runtime: RuntimeId, device: Self) -> Result<Self, DeviceUnavailable> {
let device = Self::linked(runtime, device)?;
match runtime.find_device(RuntimeId::strip(device.to_id())) {
Ok(()) => Ok(device),
Err(available) => Err(DeviceUnavailable::NoSuchDevice { runtime, available }),
}
}
fn linked(runtime: RuntimeId, device: Self) -> Result<Self, DeviceUnavailable> {
match runtime.is_linked() {
true => Ok(device),
false => Err(DeviceUnavailable::NotLinked(runtime)),
}
}
pub fn client(&self) -> Client {
match *self {
#[cfg(feature = "cuda")]
Self::Cuda(ref device) => cubecl_cuda::CudaRuntime::client(device),
#[cfg(feature = "hip")]
Self::Hip(ref device) => cubecl_hip::HipRuntime::client(device),
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
Self::Metal(ref device) => cubecl_metal::MetalRuntime::client(device),
#[cfg(feature = "wgpu")]
Self::Wgpu(ref device) => <cubecl_wgpu::WgpuRuntime>::client(device),
#[cfg(feature = "cpu")]
Self::Cpu(ref device) => cubecl_cpu::CpuRuntime::client(device),
#[allow(unreachable_patterns)]
ref other => panic!("{other:?} belongs to a runtime this build does not link"),
}
}
#[cfg_attr(not(any_runtime), allow(unused_variables))]
pub fn can_read_tensor(&self, shape: &Shape, strides: &Strides) -> bool {
match *self {
#[cfg(feature = "cuda")]
Self::Cuda(_) => cubecl_cuda::CudaRuntime::can_read_tensor(shape, strides),
#[cfg(feature = "hip")]
Self::Hip(_) => cubecl_hip::HipRuntime::can_read_tensor(shape, strides),
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
Self::Metal(_) => cubecl_metal::MetalRuntime::can_read_tensor(shape, strides),
#[cfg(feature = "wgpu")]
Self::Wgpu(_) => <cubecl_wgpu::WgpuRuntime>::can_read_tensor(shape, strides),
#[cfg(feature = "cpu")]
Self::Cpu(_) => cubecl_cpu::CpuRuntime::can_read_tensor(shape, strides),
#[allow(unreachable_patterns)]
ref other => panic!("{other:?} belongs to a runtime this build does not link"),
}
}
pub fn enumerate(device_id: DeviceId) -> alloc::vec::Vec<DeviceId> {
let Ok(runtime) = RuntimeId::of_device_id(device_id) else {
return alloc::vec::Vec::new();
};
let outer = device_id.type_id & RuntimeId::OUTER_MASK;
runtime
.enumerate_devices_like(RuntimeId::strip(device_id))
.into_iter()
.map(|id| runtime.stamp(DeviceId::new(id.type_id | outer, id.index_id)))
.collect()
}
pub fn enumerate_all() -> alloc::vec::Vec<Self> {
#[allow(unused_mut)]
let mut devices = alloc::vec::Vec::new();
#[cfg(feature = "cuda")]
devices.extend(
cubecl_cuda::CudaRuntime::enumerate_all_devices()
.into_iter()
.map(|id| Self::Cuda(DeviceIdentity::from_id(id))),
);
#[cfg(feature = "hip")]
devices.extend(
cubecl_hip::HipRuntime::enumerate_all_devices()
.into_iter()
.map(|id| Self::Hip(DeviceIdentity::from_id(id))),
);
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
devices.extend(
cubecl_metal::MetalRuntime::enumerate_all_devices()
.into_iter()
.map(|id| Self::Metal(DeviceIdentity::from_id(id))),
);
#[cfg(feature = "wgpu")]
devices.extend(
<cubecl_wgpu::WgpuRuntime>::enumerate_all_devices()
.into_iter()
.map(|id| Self::Wgpu(DeviceIdentity::from_id(id))),
);
#[cfg(feature = "cpu")]
devices.extend(
cubecl_cpu::CpuRuntime::enumerate_all_devices()
.into_iter()
.map(|id| Self::Cpu(DeviceIdentity::from_id(id))),
);
devices
}
pub fn runtime(&self) -> RuntimeId {
match *self {
Self::Cuda(_) => RuntimeId::Cuda,
Self::Hip(_) => RuntimeId::Hip,
Self::Metal(_) => RuntimeId::Metal,
Self::Wgpu(_) => RuntimeId::Wgpu,
Self::Cpu(_) => RuntimeId::Cpu,
}
}
pub fn from_id(device_id: DeviceId) -> Self {
let runtime = RuntimeId::of_device_id(device_id);
let inner = RuntimeId::strip(device_id);
match runtime {
Ok(RuntimeId::Cuda) => Self::Cuda(cubecl_core::device::Device::from_id(inner)),
Ok(RuntimeId::Hip) => Self::Hip(cubecl_core::device::Device::from_id(inner)),
Ok(RuntimeId::Metal) => Self::Metal(cubecl_core::device::Device::from_id(inner)),
Ok(RuntimeId::Wgpu) => Self::Wgpu(cubecl_core::device::Device::from_id(inner)),
Ok(RuntimeId::Cpu) => Self::Cpu(cubecl_core::device::Device::from_id(inner)),
Err(other) => {
panic!("device id {device_id} names the runtime tag {other}, which no runtime has")
}
}
}
pub fn to_id(&self) -> DeviceId {
use cubecl_core::device::Device as _;
let inner = match *self {
Self::Cuda(ref device) => device.to_id(),
Self::Hip(ref device) => device.to_id(),
Self::Metal(ref device) => device.to_id(),
Self::Wgpu(ref device) => device.to_id(),
Self::Cpu(ref device) => device.to_id(),
};
self.runtime().stamp(inner)
}
}
#[cfg(any_runtime)]
const LINKED: &[RuntimeId] = &[
#[cfg(feature = "cuda")]
RuntimeId::Cuda,
#[cfg(feature = "hip")]
RuntimeId::Hip,
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
RuntimeId::Metal,
#[cfg(feature = "wgpu")]
RuntimeId::Wgpu,
#[cfg(feature = "cpu")]
RuntimeId::Cpu,
];
impl RuntimeId {
fn is_linked(self) -> bool {
match self {
#[cfg(feature = "cuda")]
Self::Cuda => true,
#[cfg(feature = "hip")]
Self::Hip => true,
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
Self::Metal => true,
#[cfg(feature = "wgpu")]
Self::Wgpu => true,
#[cfg(feature = "cpu")]
Self::Cpu => true,
#[allow(unreachable_patterns)]
_ => false,
}
}
#[cfg_attr(not(any_runtime), allow(unused_variables))]
fn enumerate_devices_like(self, device_id: DeviceId) -> alloc::vec::Vec<DeviceId> {
match self {
#[cfg(feature = "cuda")]
Self::Cuda => cubecl_cuda::CudaRuntime::enumerate_devices_like(device_id),
#[cfg(feature = "hip")]
Self::Hip => cubecl_hip::HipRuntime::enumerate_devices_like(device_id),
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
Self::Metal => cubecl_metal::MetalRuntime::enumerate_devices_like(device_id),
#[cfg(feature = "wgpu")]
Self::Wgpu => <cubecl_wgpu::WgpuRuntime>::enumerate_devices_like(device_id),
#[cfg(feature = "cpu")]
Self::Cpu => cubecl_cpu::CpuRuntime::enumerate_devices_like(device_id),
#[allow(unreachable_patterns)]
_ => alloc::vec::Vec::new(),
}
}
#[cfg_attr(not(any_runtime), allow(unused_variables))]
fn find_device(self, device_id: DeviceId) -> Result<(), usize> {
match self {
#[cfg(feature = "cuda")]
Self::Cuda => cubecl_cuda::CudaRuntime::find_device(device_id),
#[cfg(feature = "hip")]
Self::Hip => cubecl_hip::HipRuntime::find_device(device_id),
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
Self::Metal => cubecl_metal::MetalRuntime::find_device(device_id),
#[cfg(feature = "wgpu")]
Self::Wgpu => <cubecl_wgpu::WgpuRuntime>::find_device(device_id),
#[cfg(feature = "cpu")]
Self::Cpu => cubecl_cpu::CpuRuntime::find_device(device_id),
#[allow(unreachable_patterns)]
_ => Err(0),
}
}
#[cfg(any_runtime)]
fn is_available(self) -> bool {
match self {
#[cfg(feature = "cuda")]
Self::Cuda => cubecl_cuda::CudaRuntime::is_available(),
#[cfg(feature = "hip")]
Self::Hip => cubecl_hip::HipRuntime::is_available(),
#[cfg(all(feature = "metal-native", target_vendor = "apple"))]
Self::Metal => cubecl_metal::MetalRuntime::is_available(),
#[cfg(feature = "wgpu")]
Self::Wgpu => <cubecl_wgpu::WgpuRuntime>::is_available(),
#[cfg(feature = "cpu")]
Self::Cpu => cubecl_cpu::CpuRuntime::is_available(),
#[allow(unreachable_patterns)]
_ => false,
}
}
#[cfg(any_runtime)]
fn default_device(self) -> Device {
match self {
Self::Cuda => Device::Cuda(Default::default()),
Self::Hip => Device::Hip(Default::default()),
Self::Metal => Device::Metal(Default::default()),
Self::Wgpu => Device::Wgpu(Default::default()),
Self::Cpu => Device::Cpu(Default::default()),
}
}
}
#[cfg(any_runtime)]
const UNPROBED: u32 = u32::MAX;
#[cfg(any_runtime)]
static DEFAULT_DEVICE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(UNPROBED);
#[cfg(any_runtime)]
impl Default for Device {
fn default() -> Self {
use core::sync::atomic::Ordering;
let cached = DEFAULT_DEVICE.load(Ordering::Relaxed);
if cached != UNPROBED {
return Self::from_id(DeviceId::new((cached >> 16) as u16, cached as u16));
}
let device = Self::probe_default();
let id = device.to_id();
DEFAULT_DEVICE.store(
((id.type_id as u32) << 16) | id.index_id as u32,
Ordering::Relaxed,
);
device
}
}
#[cfg(any_runtime)]
impl Device {
fn probe_default() -> Self {
let (last, rest) = LINKED
.split_last()
.expect("`any_runtime` is set, so this build links at least one runtime");
for runtime in rest {
if runtime.is_available() {
return runtime.default_device();
}
}
last.default_device()
}
}
#[cfg(any_runtime)]
impl DeviceIdentity for Device {
fn from_id(device_id: DeviceId) -> Self {
Self::from_id(device_id)
}
fn to_id(&self) -> DeviceId {
Self::to_id(self)
}
}
impl From<CudaDevice> for Device {
fn from(device: CudaDevice) -> Self {
Self::Cuda(device)
}
}
impl From<AmdDevice> for Device {
fn from(device: AmdDevice) -> Self {
Self::Hip(device)
}
}
impl From<MetalDevice> for Device {
fn from(device: MetalDevice) -> Self {
Self::Metal(device)
}
}
impl From<WgpuDevice> for Device {
fn from(device: WgpuDevice) -> Self {
Self::Wgpu(device)
}
}
impl From<CpuDevice> for Device {
fn from(device: CpuDevice) -> Self {
Self::Cpu(device)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_device_id_round_trips_through_its_runtime() {
let device = Device::Wgpu(WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(1)));
let restored = Device::from_id(device.to_id());
assert_eq!(device, restored);
}
#[test]
fn a_device_id_names_its_runtime() {
let device = Device::Wgpu(WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(1)));
let id = device.to_id();
assert_eq!(RuntimeId::of_device_id(id), Ok(device.runtime()));
}
#[test]
fn a_nested_high_byte_does_not_change_what_an_id_names() {
let device = Device::Wgpu(WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(1)));
let id = device.to_id();
let nested = DeviceId::new(id.type_id | 0xAB00, id.index_id);
assert_eq!(RuntimeId::of_device_id(nested), Ok(device.runtime()));
assert_eq!(RuntimeId::strip(nested), RuntimeId::strip(id));
assert_eq!(Device::from_id(nested), device);
}
#[test]
fn stamping_a_runtime_leaves_the_high_byte_alone() {
let inner = DeviceId::new(0x03, 7);
let stamped = RuntimeId::Cpu.stamp(DeviceId::new(inner.type_id | 0xAB00, inner.index_id));
assert_eq!(stamped.type_id & RuntimeId::OUTER_MASK, 0xAB00);
assert_eq!(RuntimeId::of_device_id(stamped), Ok(RuntimeId::Cpu));
assert_eq!(RuntimeId::strip(stamped), inner);
}
}
#[cfg(all(test, any_runtime))]
mod default_tests {
use super::*;
#[test]
fn the_default_device_is_one_this_machine_has() {
let device = Device::default();
if LINKED.iter().any(|runtime| runtime.is_available()) {
assert!(
device.runtime().is_available(),
"{device:?} was chosen over a runtime this machine can actually run"
);
}
}
#[test]
fn the_device_trait_agrees_with_the_inherent_methods() {
fn round_trip<D: DeviceIdentity>(device: &D) -> D {
D::from_id(device.to_id())
}
let device = Device::Wgpu(WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(1)));
assert_eq!(DeviceIdentity::to_id(&device), device.to_id());
assert_eq!(round_trip(&device), device);
}
#[test]
fn the_cached_default_decodes_back_to_the_same_device() {
let first = Device::default();
let second = Device::default();
assert_eq!(first, second);
assert_eq!(first.to_id(), second.to_id());
}
#[test]
fn no_device_encodes_to_the_unprobed_sentinel() {
for runtime in LINKED {
let id = runtime.default_device().to_id();
let encoded = ((id.type_id as u32) << 16) | id.index_id as u32;
assert_ne!(encoded, UNPROBED);
}
}
}
#[cfg(test)]
mod named_tests {
use super::*;
#[test]
fn an_unlinked_runtime_says_so() {
for runtime in [
RuntimeId::Cuda,
RuntimeId::Hip,
RuntimeId::Metal,
RuntimeId::Wgpu,
RuntimeId::Cpu,
] {
if runtime.is_linked() {
continue;
}
let named = match runtime {
RuntimeId::Cuda => Device::cuda(0),
RuntimeId::Hip => Device::rocm(0),
RuntimeId::Metal => Device::metal(Default::default()),
RuntimeId::Wgpu => Device::wgpu(Default::default()),
RuntimeId::Cpu => Device::cpu(),
};
assert_eq!(named, Err(DeviceUnavailable::NotLinked(runtime)));
}
}
#[test]
fn an_index_past_the_end_is_an_error() {
let far_past_any_machine = 4242;
let wider_than_the_id = u16::MAX as usize + 1;
let wider_than_a_wgpu_id = WgpuDeviceKind::MAX_INDEX + 1;
for named in [
Device::cuda(far_past_any_machine),
Device::rocm(far_past_any_machine),
Device::wgpu(WgpuDeviceKind::DiscreteGpu(far_past_any_machine)),
Device::cuda(wider_than_the_id),
Device::rocm(wider_than_the_id),
Device::wgpu(WgpuDeviceKind::DiscreteGpu(wider_than_a_wgpu_id)),
] {
assert!(
matches!(
named,
Err(DeviceUnavailable::NotLinked(_) | DeviceUnavailable::NoSuchDevice { .. })
),
"{named:?} was accepted for a device no machine has"
);
}
}
#[cfg(any_runtime)]
#[test]
fn the_default_device_can_be_named() {
if !LINKED.iter().any(|runtime| runtime.is_available()) {
return;
}
let named = match Device::default() {
Device::Cuda(device) => Device::cuda(device.index),
Device::Hip(device) => Device::rocm(device.index),
Device::Metal(kind) => Device::metal(kind),
Device::Wgpu(device) => {
Device::wgpu(device.kind).and_then(|named| named.on(device.backend))
}
Device::Cpu(_) => Device::cpu(),
};
assert_eq!(named, Ok(Device::default()));
}
#[test]
fn a_device_is_among_those_enumerated_with_it() {
let named = [
Device::cuda(0),
Device::cpu(),
Device::wgpu(WgpuDeviceKind::DiscreteGpu(0)),
Device::vulkan(WgpuDeviceKind::DiscreteGpu(0)),
Device::gl(WgpuDeviceKind::Other(0)),
Device::wgpu(WgpuDeviceKind::DefaultDevice),
Device::gl(WgpuDeviceKind::DefaultDevice),
];
for device in named.into_iter().flatten() {
let peers = Device::enumerate(device.to_id());
assert!(
peers.contains(&device.to_id()),
"{device:?} among {peers:?}"
);
}
}
#[test]
fn only_a_wgpu_device_can_be_pinned_to_a_backend() {
let pinned = Device::Cuda(CudaDevice::new(0)).on(WgpuBackend::Vulkan);
assert!(matches!(
pinned,
Err(DeviceUnavailable::NoSuchDevice {
runtime: RuntimeId::Cuda,
..
})
));
}
#[test]
fn a_pinned_device_is_a_device_of_its_own() {
let Ok(auto) = Device::wgpu(WgpuDeviceKind::default()) else {
return;
};
let Ok(vulkan) = Device::vulkan(WgpuDeviceKind::default()) else {
return;
};
assert_ne!(auto, vulkan);
assert_ne!(auto.to_id(), vulkan.to_id());
assert_eq!(Device::from_id(vulkan.to_id()), vulkan);
}
#[test]
fn an_existing_device_is_not_looked_for() {
let named = Device::wgpu(WgpuDeviceKind::Existing(7));
match RuntimeId::Wgpu.is_linked() {
true => assert_eq!(
named,
Ok(Device::Wgpu(WgpuDevice::new(WgpuDeviceKind::Existing(7))))
),
false => assert_eq!(named, Err(DeviceUnavailable::NotLinked(RuntimeId::Wgpu))),
}
}
#[test]
fn an_existing_device_can_be_pinned() {
let existing = Device::Wgpu(WgpuDevice::new(WgpuDeviceKind::Existing(7)));
let expected = match RuntimeId::Wgpu.is_linked() {
true => Ok(existing.clone()),
false => Err(DeviceUnavailable::NotLinked(RuntimeId::Wgpu)),
};
assert_eq!(existing.on(WgpuBackend::Vulkan), expected);
assert_eq!(Device::gl(WgpuDeviceKind::Existing(7)), expected);
}
}