use std::marker::PhantomData;
use crate::WgpuCompiler;
use crate::{
AutoCompiler, AutoGraphicsApi, GraphicsApi, WgpuBackend, WgpuDevice, WgpuDeviceKind, backend,
compute::WgpuServer, contiguous_strides,
};
use cubecl_common::device::{Device, DeviceService, ServiceId};
use cubecl_common::profile::TimingMethod;
use cubecl_core::WgpuCompilationOptions;
use cubecl_core::device::{DeviceId, ServerUtilitiesHandle};
use cubecl_core::ir::TargetProperties;
use cubecl_core::server::ServerUtilities;
use cubecl_core::zspace::{Shape, Strides};
use cubecl_environment::future;
use cubecl_ir::{DeviceIdentity, DeviceProperties, HardwareProperties, MemoryDeviceProperties};
use cubecl_monitoring::{DeviceUtilization, UtilizationUnavailable, gpu_utilization::CardCounters};
use cubecl_server::allocator::ContiguousMemoryLayoutPolicy;
#[cfg(not(feature = "vulkan-validate"))]
use cubecl_server::logging::ProfileLevel;
pub use cubecl_server::memory_management::MemoryConfiguration;
use cubecl_server::runtime::Runtime;
use cubecl_server::{client::Client, logging::ServerLogger};
use wgpu::{InstanceFlags, RequestAdapterOptions};
#[derive(Debug)]
pub struct WgpuRuntime<Compiler = AutoCompiler> {
_p: PhantomData<Compiler>,
}
impl<C> Clone for WgpuRuntime<C> {
fn clone(&self) -> Self {
Self { _p: self._p }
}
}
impl<C: WgpuCompiler> DeviceService for WgpuServer<C> {
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")
}
fn utilities(&self) -> ServerUtilitiesHandle {
self.utilities.clone() as ServerUtilitiesHandle
}
}
impl<C: WgpuCompiler> Runtime for WgpuRuntime<C> {
type Server = WgpuServer<C>;
type Device = WgpuDevice;
fn can_read_tensor(shape: &Shape, strides: &Strides) -> bool {
if shape.is_empty() {
return true;
}
for (&expected, &stride) in contiguous_strides(shape).iter().zip(strides.iter()) {
if expected != stride {
return false;
}
}
true
}
fn target_properties() -> TargetProperties {
TargetProperties {
mma: Default::default(),
}
}
fn enumerate_devices(type_id: u16) -> Vec<DeviceId> {
Self::enumerate_devices_like(DeviceId::new(type_id, 0))
}
fn is_available() -> bool {
let gpu = [
WgpuDeviceKind::DiscreteGpu(0),
WgpuDeviceKind::IntegratedGpu(0),
WgpuDeviceKind::VirtualGpu(0),
WgpuDeviceKind::Other(0),
]
.map(|kind| WgpuDevice::new(kind).to_id().type_id);
Self::enumerate_all_devices()
.iter()
.any(|device| gpu.contains(&device.type_id))
}
fn enumerate_all_devices() -> Vec<DeviceId> {
adapters_on(WgpuBackend::Auto)
}
fn enumerate_devices_like(device_id: DeviceId) -> Vec<DeviceId> {
let device = WgpuDevice::from_id(device_id);
let reachable = adapters_on(device.backend);
match device.kind {
WgpuDeviceKind::DefaultDevice if !reachable.is_empty() => alloc::vec![device_id],
_ => reachable
.into_iter()
.filter(|id| id.type_id == device_id.type_id)
.collect(),
}
}
#[cfg(target_family = "wasm")]
fn find_device(device_id: DeviceId) -> Result<(), usize> {
let device = WgpuDevice::from_id(device_id);
let peers = Self::enumerate_devices_like(device_id);
let low_power = device.kind == WgpuDeviceKind::IntegratedGpu(0)
&& !adapters_on(device.backend).is_empty();
match peers.contains(&device_id) || low_power {
true => Ok(()),
false => Err(peers.len()),
}
}
fn utilization(device: &Self::Device) -> Result<DeviceUtilization, UtilizationUnavailable> {
CardCounters::read(Self::client(device).properties().identity.physical.as_ref())
}
}
fn adapters_on(backend: WgpuBackend) -> Vec<DeviceId> {
#[cfg(target_family = "wasm")]
let ids = match backend {
WgpuBackend::Auto | WgpuBackend::WebGpu => vec![DeviceId::new(0, 0)],
_ => Vec::new(),
};
#[cfg(not(target_family = "wasm"))]
let ids = settle(backend)
.map(|(_, adapters)| adapter_device_ids(adapters))
.unwrap_or_default();
ids.into_iter()
.map(|id| WgpuDevice::from_id(id).on(backend).to_id())
.collect()
}
#[cfg(not(target_family = "wasm"))]
fn settle(backend: WgpuBackend) -> Option<(wgpu::Backend, Vec<wgpu::Adapter>)> {
let mut software_only = None;
for api in backend_candidates(backend) {
let adapters = enumerate_all_adapters(instance_for(api), api);
if adapters
.iter()
.any(|adapter| adapter.get_info().device_type != wgpu::DeviceType::Cpu)
{
return Some((api, adapters));
}
if software_only.is_none() && !adapters.is_empty() {
software_only = Some((api, adapters));
}
}
software_only
}
fn backend_candidates(backend: WgpuBackend) -> alloc::vec::Vec<wgpu::Backend> {
match backend {
WgpuBackend::Auto => AutoGraphicsApi::chain(),
WgpuBackend::Vulkan => alloc::vec![wgpu::Backend::Vulkan],
WgpuBackend::Metal => alloc::vec![wgpu::Backend::Metal],
WgpuBackend::Dx12 => alloc::vec![wgpu::Backend::Dx12],
WgpuBackend::Gl => alloc::vec![wgpu::Backend::Gl],
WgpuBackend::WebGpu => alloc::vec![wgpu::Backend::BrowserWebGpu],
}
}
#[cfg(not(target_family = "wasm"))]
fn instance_for(backend: wgpu::Backend) -> wgpu::Instance {
wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: backend.into(),
..wgpu::InstanceDescriptor::new_without_display_handle()
})
}
pub(crate) fn resolve_backend(backend: WgpuBackend) -> wgpu::Backend {
#[cfg(not(target_family = "wasm"))]
if let Some((api, _)) = settle(backend) {
return api;
}
backend_candidates(backend)[0]
}
#[cfg(not(target_family = "wasm"))]
fn adapter_device_ids(adapters: Vec<wgpu::Adapter>) -> Vec<DeviceId> {
let mut next = [0u16; 7];
adapters
.into_iter()
.map(|adapter| {
let type_id = match adapter.get_info().device_type {
wgpu::DeviceType::DiscreteGpu => 0,
wgpu::DeviceType::IntegratedGpu => 1,
wgpu::DeviceType::VirtualGpu => 2,
wgpu::DeviceType::Cpu => 3,
wgpu::DeviceType::Other => 6,
};
let index = match next.get_mut(type_id as usize).filter(|_| type_id != 3) {
Some(next) => {
let index = *next;
*next += 1;
index
}
None => 0,
};
DeviceId::new(type_id, index)
})
.collect()
}
#[cfg(not(target_family = "wasm"))]
fn enumerate_all_adapters(instance: wgpu::Instance, backend: wgpu::Backend) -> Vec<wgpu::Adapter> {
cubecl_environment::future::block_on(instance.enumerate_adapters(backend.into()))
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum WgpuInitError {
#[error("Invalid wgpu configuration: {message}")]
InvalidConfiguration {
message: String,
},
#[error("Graphics API {api:?} is unavailable in this build")]
UnsupportedGraphicsApi {
api: WgpuBackend,
},
#[error("No adapter available for {device:?}: {message}")]
AdapterUnavailable {
device: WgpuDevice,
message: String,
},
#[error("Unable to request a wgpu device: {message}")]
RequestDevice {
message: String,
},
#[error("Unable to register wgpu runtime: {message}")]
Registration {
message: String,
},
}
pub struct RuntimeOptions {
pub tasks_max: usize,
pub memory_config: MemoryConfiguration,
}
impl Default for RuntimeOptions {
fn default() -> Self {
Self::try_default().expect("Invalid wgpu runtime defaults")
}
}
impl RuntimeOptions {
pub fn try_default() -> Result<Self, WgpuInitError> {
Self::with_tasks_max(None)
}
pub fn with_tasks_max(tasks_max: Option<usize>) -> Result<Self, WgpuInitError> {
let tasks_max = match tasks_max {
Some(value) => value,
None => match std::env::var("CUBECL_WGPU_MAX_TASKS") {
Ok(value) => value
.parse()
.map_err(|_| WgpuInitError::InvalidConfiguration {
message: "CUBECL_WGPU_MAX_TASKS must be a positive integer".into(),
})?,
Err(std::env::VarError::NotPresent) => 32,
Err(err) => {
return Err(WgpuInitError::InvalidConfiguration {
message: err.to_string(),
});
}
},
};
let options = Self {
tasks_max,
memory_config: MemoryConfiguration::default(),
};
options.validate()?;
Ok(options)
}
fn validate(&self) -> Result<(), WgpuInitError> {
if self.tasks_max == 0 {
return Err(WgpuInitError::InvalidConfiguration {
message: "tasks_max must be greater than zero".into(),
});
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct WgpuSetup {
pub instance: wgpu::Instance,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub backend: wgpu::Backend,
}
pub fn init_device(setup: WgpuSetup, options: RuntimeOptions) -> WgpuDevice {
try_init_device(setup, options).expect("Unable to register existing wgpu setup")
}
pub fn init_device_with_api<G: GraphicsApi>(
setup: WgpuSetup,
options: RuntimeOptions,
) -> WgpuDevice {
try_init_device_with_api::<G>(setup, options).expect("Unable to register existing wgpu setup")
}
pub fn init_setup<G: GraphicsApi>(device: &WgpuDevice, options: RuntimeOptions) -> WgpuSetup {
cfg_if::cfg_if! {
if #[cfg(target_family = "wasm")] {
let _ = (device, options);
panic!("Creating a wgpu setup synchronously is unsupported on wasm. Use init_async instead");
} else {
future::block_on(init_setup_async::<G>(device, options))
}
}
}
pub async fn init_setup_async<G: GraphicsApi>(
device: &WgpuDevice,
options: RuntimeOptions,
) -> WgpuSetup {
try_init_setup_async::<G>(device, options)
.await
.expect("Unable to initialize wgpu setup")
}
pub fn try_init_device(
setup: WgpuSetup,
options: RuntimeOptions,
) -> Result<WgpuDevice, WgpuInitError> {
try_init_device_with_api::<AutoGraphicsApi>(setup, options)
}
pub fn try_init_device_with_api<G: GraphicsApi>(
setup: WgpuSetup,
options: RuntimeOptions,
) -> Result<WgpuDevice, WgpuInitError> {
use core::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
options.validate()?;
let requested_backend = G::backend_for(&WgpuDevice::default());
if requested_backend != WgpuBackend::Auto
&& !backend_candidates(requested_backend).contains(&setup.backend)
{
return Err(WgpuInitError::InvalidConfiguration {
message: format!(
"the imported setup uses {:?}, but {requested_backend:?} was requested",
setup.backend
),
});
}
if setup.backend != setup.adapter.get_info().backend
|| setup.backend != setup.device.adapter_info().backend
{
return Err(WgpuInitError::InvalidConfiguration {
message: "setup backend does not match its adapter and device".into(),
});
}
let limits = setup.device.limits();
if limits.max_compute_invocations_per_workgroup == 0
|| limits.max_storage_buffers_per_shader_stage < 2
{
return Err(WgpuInitError::InvalidConfiguration {
message: "compute support and at least two storage bindings are required".into(),
});
}
let id = COUNTER
.try_update(Ordering::Relaxed, Ordering::Relaxed, |id| {
(id <= u16::MAX as u32).then_some(id + 1)
})
.map_err(|_| WgpuInitError::Registration {
message: "external wgpu device IDs exhausted".into(),
})?;
let device = WgpuDevice::new(WgpuDeviceKind::Existing(id));
let server =
try_create_server::<AutoCompiler>(setup, options, device.to_id(), requested_backend)?;
Client::try_init(device.to_id(), server).map_err(|err| WgpuInitError::Registration {
message: format!("{device:?}: {err:?}"),
})?;
Ok(device)
}
#[cfg(not(target_family = "wasm"))]
pub fn try_init_setup<G: GraphicsApi>(
device: &WgpuDevice,
options: RuntimeOptions,
) -> Result<WgpuSetup, WgpuInitError> {
future::block_on(try_init_setup_async::<G>(device, options))
}
pub async fn try_init_setup_async<G: GraphicsApi>(
device: &WgpuDevice,
options: RuntimeOptions,
) -> Result<WgpuSetup, WgpuInitError> {
options.validate()?;
if matches!(device.kind, WgpuDeviceKind::Existing(_)) {
return Err(WgpuInitError::InvalidConfiguration {
message:
"an Existing ID is not an adapter selector; register a setup or reuse its device"
.into(),
});
}
let requested_backend = G::backend_for(device);
if device.backend != WgpuBackend::Auto && device.backend != requested_backend {
return Err(WgpuInitError::InvalidConfiguration {
message: format!("{device:?} cannot be initialized on {requested_backend:?}"),
});
}
let mut selected = device.clone();
selected.backend = requested_backend;
let setup = try_create_setup(&selected).await?;
let server = try_create_server::<AutoCompiler>(
setup.clone(),
options,
device.to_id(),
requested_backend,
)?;
Client::try_init(device.to_id(), server).map_err(|err| WgpuInitError::Registration {
message: format!("{device:?}: {err:?}"),
})?;
Ok(setup)
}
fn runtime_name(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> &'static str {
match backend {
wgpu::Backend::Vulkan if options.supports_vulkan_compiler => "wgpu<spirv>",
wgpu::Backend::Metal if options.supports_msl_compiler => "wgpu<msl>",
_ => "wgpu<wgsl>",
}
}
fn try_create_server<C: WgpuCompiler>(
setup: WgpuSetup,
options: RuntimeOptions,
device_id: DeviceId,
requested_backend: WgpuBackend,
) -> Result<WgpuServer<C>, WgpuInitError> {
let limits = setup.device.limits();
let adapter_limits = setup.adapter.limits();
let mut adapter_info = setup.adapter.get_info();
if adapter_info.subgroup_min_size == 0 && adapter_info.subgroup_max_size == 0 {
adapter_info.subgroup_min_size = 8;
adapter_info.subgroup_max_size = 128;
}
let mem_props = MemoryDeviceProperties::new(
limits
.max_storage_buffer_binding_size
.min(limits.max_buffer_size),
limits
.min_uniform_buffer_offset_alignment
.max(limits.min_storage_buffer_offset_alignment) as u64,
);
let max_count = adapter_limits.max_compute_workgroups_per_dimension;
let hardware_props = HardwareProperties {
load_width: 128,
vector_register_count: None,
#[cfg(apple_silicon)]
plane_size_min: 32,
#[cfg(not(apple_silicon))]
plane_size_min: adapter_info.subgroup_min_size,
#[cfg(apple_silicon)]
plane_size_max: 32,
#[cfg(not(apple_silicon))]
plane_size_max: adapter_info.subgroup_max_size,
max_bindings: limits
.max_storage_buffers_per_shader_stage
.saturating_sub(1),
max_shared_memory_size: limits.max_compute_workgroup_storage_size as usize,
max_cube_count: (max_count, max_count, max_count),
max_units_per_cube: adapter_limits.max_compute_invocations_per_workgroup,
max_cube_dim: (
adapter_limits.max_compute_workgroup_size_x,
adapter_limits.max_compute_workgroup_size_y,
adapter_limits.max_compute_workgroup_size_z,
),
num_streaming_multiprocessors: None,
num_tensor_cores: None,
min_tensor_cores_dim: None,
num_cpu_cores: None, last_level_cache_size: None,
max_vector_size: 4,
cube_mma_reserved_shared_memory: 0,
};
let mut compilation_options = Default::default();
let features = setup.adapter.features();
let time_measurement = if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
TimingMethod::Device
} else {
TimingMethod::System
};
let fingerprint = format!("spirv_{}_{}", adapter_info.vendor, adapter_info.device);
let mut device_props = DeviceProperties::new(
Default::default(),
mem_props,
hardware_props,
time_measurement,
DeviceIdentity {
name: adapter_info.name.clone(),
fingerprint,
physical: backend::physical_device(&setup.adapter, &adapter_info),
},
);
#[cfg(not(all(target_os = "macos", feature = "msl")))]
{
if features.contains(wgpu::Features::SUBGROUP)
&& setup.adapter.get_info().device_type != wgpu::DeviceType::Cpu
{
use cubecl_ir::features::Plane;
device_props.features.plane.insert(Plane::Ops);
}
}
backend::register_features(&setup.adapter, &mut device_props, &mut compilation_options);
#[cfg(any(feature = "spirv", feature = "msl"))]
if compilation_options.supports_vulkan_compiler || compilation_options.supports_msl_compiler {
device_props
.features
.plane
.insert(cubecl_ir::features::Plane::NonUniformControlFlow);
}
C::validate_runtime(setup.backend, &compilation_options, requested_backend).map_err(|err| {
WgpuInitError::InvalidConfiguration {
message: format!("failed to initialize the wgpu compiler: {err}"),
}
})?;
let logger = alloc::sync::Arc::new(ServerLogger::default());
let name = runtime_name(setup.backend, &compilation_options);
let allocator = ContiguousMemoryLayoutPolicy::new(device_props.memory.alignment as usize);
let memory_properties = device_props.memory.clone();
let (utilities, captures) = ServerUtilities::init(
ServiceId::of::<WgpuServer<C>>(device_id),
name,
device_props,
WgpuRuntime::<C>::target_properties(),
logger,
allocator,
);
Ok(WgpuServer::new(
memory_properties,
options.memory_config,
compilation_options,
setup.device.clone(),
setup.queue,
options.tasks_max,
setup.backend,
time_measurement,
utilities,
captures,
))
}
async fn try_create_setup(device: &WgpuDevice) -> Result<WgpuSetup, WgpuInitError> {
let override_device = match device.kind {
WgpuDeviceKind::DefaultDevice => try_device_override()?.map(|kind| WgpuDevice {
kind,
backend: device.backend,
}),
_ => None,
};
let device = override_device.as_ref().unwrap_or(device);
match device.kind {
WgpuDeviceKind::DiscreteGpu(index)
| WgpuDeviceKind::IntegratedGpu(index)
| WgpuDeviceKind::VirtualGpu(index)
| WgpuDeviceKind::Other(index)
if index > WgpuDeviceKind::MAX_INDEX =>
{
return Err(WgpuInitError::InvalidConfiguration {
message: format!(
"adapter index {index} exceeds {}",
WgpuDeviceKind::MAX_INDEX
),
});
}
WgpuDeviceKind::Existing(_) => {
return Err(WgpuInitError::InvalidConfiguration {
message: "an Existing ID cannot acquire an adapter".into(),
});
}
_ => {}
}
let candidates = backend_candidates(device.backend);
if !candidates
.iter()
.any(|api| wgpu::Instance::enabled_backend_features().contains((*api).into()))
{
return Err(WgpuInitError::UnsupportedGraphicsApi {
api: device.backend,
});
}
let mut backend = None;
#[cfg(not(target_family = "wasm"))]
for api in candidates {
if !wgpu::Instance::enabled_backend_features().contains(api.into()) {
continue;
}
let adapters = instance_for(api).enumerate_adapters(api.into()).await;
if adapters
.iter()
.any(|adapter| adapter.get_info().device_type != wgpu::DeviceType::Cpu)
{
backend = Some(api);
break;
}
if backend.is_none() && !adapters.is_empty() {
backend = Some(api);
}
}
#[cfg(target_family = "wasm")]
for api in candidates {
if api == wgpu::Backend::BrowserWebGpu
&& wgpu::Instance::enabled_backend_features().contains(api.into())
{
backend = Some(api);
break;
}
}
let backend = backend.ok_or_else(|| WgpuInitError::AdapterUnavailable {
device: device.clone(),
message: "no adapter found on the requested graphics API".into(),
})?;
let (instance, adapter) = request_adapter(device, backend).await?;
let (device, queue) = backend::try_request_device(&adapter).await?;
Ok(WgpuSetup {
instance,
adapter,
device,
queue,
backend,
})
}
async fn request_adapter(
device: &WgpuDevice,
backend: wgpu::Backend,
) -> Result<(wgpu::Instance, wgpu::Adapter), WgpuInitError> {
#[cfg(not(feature = "vulkan-validate"))]
let instance_flags = {
let debug = ServerLogger::default();
match (debug.profile_level(), debug.compilation_source_activated()) {
(Some(ProfileLevel::Full), _) => InstanceFlags::advanced_debugging(),
(_, true) => InstanceFlags::debugging(),
(_, false) => InstanceFlags::default(),
}
};
#[cfg(feature = "vulkan-validate")]
let instance_flags = InstanceFlags::advanced_debugging();
log::debug!("{instance_flags:?}");
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: backend.into(),
flags: instance_flags,
..wgpu::InstanceDescriptor::new_without_display_handle()
});
let adapter = match device.kind {
#[cfg(not(target_family = "wasm"))]
WgpuDeviceKind::DiscreteGpu(num) => {
select_from_adapter_list(
num,
"No Discrete GPU device found",
&instance,
device,
backend,
)
.await
}
#[cfg(not(target_family = "wasm"))]
WgpuDeviceKind::IntegratedGpu(num) => {
select_from_adapter_list(
num,
"No Integrated GPU device found",
&instance,
device,
backend,
)
.await
}
#[cfg(not(target_family = "wasm"))]
WgpuDeviceKind::VirtualGpu(num) => {
select_from_adapter_list(
num,
"No Virtual GPU device found",
&instance,
device,
backend,
)
.await
}
#[cfg(not(target_family = "wasm"))]
WgpuDeviceKind::Other(num) => {
select_from_adapter_list(num, "No Other device found", &instance, device, backend).await
}
#[cfg(not(target_family = "wasm"))]
WgpuDeviceKind::Cpu => {
select_from_adapter_list(0, "No CPU device found", &instance, device, backend).await
}
#[cfg(target_family = "wasm")]
WgpuDeviceKind::IntegratedGpu(_) => {
request_adapter_with_preference(&instance, wgpu::PowerPreference::LowPower).await
}
WgpuDeviceKind::Existing(_) => {
unreachable!("Cannot select an adapter for an existing device.")
}
_ => {
request_adapter_with_preference(&instance, wgpu::PowerPreference::HighPerformance).await
}
}
.map_err(|message| WgpuInitError::AdapterUnavailable {
device: device.clone(),
message,
})?;
Ok((instance, adapter))
}
async fn request_adapter_with_preference(
instance: &wgpu::Instance,
power_preference: wgpu::PowerPreference,
) -> Result<wgpu::Adapter, String> {
instance
.request_adapter(&RequestAdapterOptions {
power_preference,
force_fallback_adapter: false,
compatible_surface: None,
..RequestAdapterOptions::default()
})
.await
.map_err(|err| err.to_string())
}
#[cfg(not(target_family = "wasm"))]
async fn select_from_adapter_list(
num: usize,
error: &str,
instance: &wgpu::Instance,
device: &WgpuDevice,
backend: wgpu::Backend,
) -> Result<wgpu::Adapter, String> {
let adapters = instance.enumerate_adapters(backend.into()).await;
let found = adapters
.iter()
.map(|adapter| adapter.get_info())
.collect::<Vec<_>>();
let is_same_type = |adapter: &wgpu::Adapter| {
let device_type = adapter.get_info().device_type;
match device.kind {
WgpuDeviceKind::DiscreteGpu(_) => device_type == wgpu::DeviceType::DiscreteGpu,
WgpuDeviceKind::IntegratedGpu(_) => device_type == wgpu::DeviceType::IntegratedGpu,
WgpuDeviceKind::VirtualGpu(_) => device_type == wgpu::DeviceType::VirtualGpu,
WgpuDeviceKind::Cpu => device_type == wgpu::DeviceType::Cpu,
WgpuDeviceKind::Other(_) => device_type == wgpu::DeviceType::Other,
WgpuDeviceKind::DefaultDevice => true,
WgpuDeviceKind::Existing(_) => {
unreachable!("Cannot select an adapter for an existing device.")
}
}
};
adapters
.into_iter()
.filter(is_same_type)
.nth(num)
.ok_or_else(|| format!("{error}, adapters {found:?}"))
}
fn try_device_override() -> Result<Option<WgpuDeviceKind>, WgpuInitError> {
let value = match std::env::var("CUBECL_WGPU_DEFAULT_DEVICE") {
Ok(value) => value,
Err(std::env::VarError::NotPresent) => return Ok(None),
Err(err) => {
return Err(WgpuInitError::InvalidConfiguration {
message: err.to_string(),
});
}
};
let parsed = if value == "Cpu" {
Some(WgpuDeviceKind::Cpu)
} else {
[
(
"DiscreteGpu(",
WgpuDeviceKind::DiscreteGpu as fn(usize) -> WgpuDeviceKind,
),
("IntegratedGpu(", WgpuDeviceKind::IntegratedGpu),
("VirtualGpu(", WgpuDeviceKind::VirtualGpu),
("Other(", WgpuDeviceKind::Other),
]
.into_iter()
.find_map(|(prefix, kind)| {
value
.strip_prefix(prefix)?
.strip_suffix(')')?
.parse()
.ok()
.map(kind)
})
};
parsed
.map(Some)
.ok_or_else(|| WgpuInitError::InvalidConfiguration {
message: format!("Invalid CUBECL_WGPU_DEFAULT_DEVICE: {value}"),
})
}
#[cfg(all(test, not(target_family = "wasm")))]
mod device_tests {
use super::*;
#[test]
#[ignore = "requires a native Vulkan adapter (software adapters are supported)"]
fn fallible_import_preserves_api_selection_and_unique_ids() {
let selector = WgpuDevice::default().on(WgpuBackend::Vulkan);
let setup = future::block_on(try_create_setup(&selector)).unwrap();
assert!(matches!(
try_init_device_with_api::<crate::Metal>(setup.clone(), RuntimeOptions::default()),
Err(WgpuInitError::InvalidConfiguration { .. })
));
let automatic = try_init_device(setup, RuntimeOptions::default()).unwrap();
let setup = future::block_on(try_create_setup(&selector)).unwrap();
let explicit = init_device_with_api::<crate::Vulkan>(setup, RuntimeOptions::default());
assert_ne!(automatic.to_id(), explicit.to_id());
}
#[cfg(feature = "msl")]
#[test]
#[ignore = "requires a native Vulkan adapter (software adapters are supported)"]
fn compiler_validation_returns_an_initialization_error() {
let selector = WgpuDevice::default().on(WgpuBackend::Vulkan);
let setup = future::block_on(try_create_setup(&selector)).unwrap();
assert!(matches!(
try_create_server::<crate::MslCompiler>(
setup.clone(),
RuntimeOptions::default(),
selector.to_id(),
WgpuBackend::Auto,
),
Err(WgpuInitError::InvalidConfiguration { message })
if message.contains("MslCompiler") && message.contains("Metal")
));
assert!(matches!(
try_create_server::<AutoCompiler>(
setup,
RuntimeOptions::default(),
selector.to_id(),
WgpuBackend::Metal,
),
Err(WgpuInitError::InvalidConfiguration { .. })
));
let setup =
try_init_setup::<AutoGraphicsApi>(&selector, RuntimeOptions::default()).unwrap();
assert_eq!(setup.backend, wgpu::Backend::Vulkan);
assert!(matches!(
try_init_setup::<AutoGraphicsApi>(&selector, RuntimeOptions::default()),
Err(WgpuInitError::Registration { .. })
));
}
const PINNED: [WgpuBackend; 4] = [
WgpuBackend::Vulkan,
WgpuBackend::Metal,
WgpuBackend::Dx12,
WgpuBackend::Gl,
];
#[test]
fn each_adapter_is_listed_once() {
let ids = <WgpuRuntime>::enumerate_all_devices();
let adapters = settle(WgpuBackend::Auto).map_or(0, |(_, adapters)| adapters.len());
assert_eq!(ids.len(), adapters);
for id in ids {
assert_eq!(WgpuDevice::from_id(id).backend, WgpuBackend::Auto);
}
}
#[test]
fn a_device_is_found_on_the_api_it_names() {
for backend in PINNED {
let reachable = adapters_on(backend);
for id in reachable.iter() {
assert_eq!(
<WgpuRuntime>::find_device(*id),
Ok(()),
"{id} on {backend:?}"
);
}
let default = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(backend);
assert_eq!(
<WgpuRuntime>::find_device(default.to_id()).is_ok(),
!reachable.is_empty(),
"the default device on {backend:?}"
);
}
}
#[test]
fn the_default_device_is_its_own_only_peer() {
for backend in PINNED.into_iter().chain([WgpuBackend::Auto]) {
let default = WgpuDevice::new(WgpuDeviceKind::DefaultDevice)
.on(backend)
.to_id();
let expected = match adapters_on(backend).is_empty() {
true => Vec::new(),
false => alloc::vec![default],
};
assert_eq!(<WgpuRuntime>::enumerate_devices_like(default), expected);
}
}
#[test]
fn auto_defers_to_the_api_a_device_pins() {
for (backend, api) in [
(WgpuBackend::Vulkan, wgpu::Backend::Vulkan),
(WgpuBackend::Metal, wgpu::Backend::Metal),
(WgpuBackend::Dx12, wgpu::Backend::Dx12),
(WgpuBackend::Gl, wgpu::Backend::Gl),
] {
let device = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(backend);
assert_eq!(AutoGraphicsApi::backend_for(&device), backend);
assert_eq!(resolve_backend(AutoGraphicsApi::backend_for(&device)), api);
}
}
#[test]
fn a_setup_on_another_api_than_the_pinned_one_is_refused() {
let device = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(WgpuBackend::Gl);
assert!(matches!(
try_init_setup::<crate::Vulkan>(&device, RuntimeOptions::default()),
Err(WgpuInitError::InvalidConfiguration { .. })
));
}
#[test]
fn a_pinned_device_is_enumerated_with_its_own_api() {
for backend in PINNED {
for id in adapters_on(backend) {
let peers = <WgpuRuntime>::enumerate_devices_like(id);
assert!(peers.contains(&id), "{id} among {peers:?}");
for peer in peers {
assert_eq!(WgpuDevice::from_id(peer).backend, backend);
}
}
}
}
#[test]
fn an_index_past_the_end_is_not_found_on_any_api() {
for backend in PINNED.into_iter().chain([WgpuBackend::Auto]) {
let device = WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(4242)).on(backend);
let discrete = adapters_on(backend)
.into_iter()
.filter(|id| id.type_id == device.to_id().type_id)
.count();
assert_eq!(<WgpuRuntime>::find_device(device.to_id()), Err(discrete));
}
}
}