1use std::marker::PhantomData;
2
3use crate::WgpuCompiler;
4use crate::{
5 AutoCompiler, AutoGraphicsApi, GraphicsApi, WgpuBackend, WgpuDevice, WgpuDeviceKind, backend,
6 compute::WgpuServer, contiguous_strides,
7};
8use cubecl_common::device::{Device, DeviceService, ServiceId};
9use cubecl_common::profile::TimingMethod;
10use cubecl_core::WgpuCompilationOptions;
11use cubecl_core::device::{DeviceId, ServerUtilitiesHandle};
12use cubecl_core::ir::TargetProperties;
13use cubecl_core::server::ServerUtilities;
14use cubecl_core::zspace::{Shape, Strides};
15use cubecl_environment::{future, sync::Mutex};
16use cubecl_ir::{DeviceIdentity, DeviceProperties, HardwareProperties, MemoryDeviceProperties};
17use cubecl_monitoring::{DeviceUtilization, UtilizationUnavailable, gpu_utilization::CardCounters};
18use cubecl_server::allocator::ContiguousMemoryLayoutPolicy;
19#[cfg(not(feature = "vulkan-validate"))]
20use cubecl_server::logging::ProfileLevel;
21pub use cubecl_server::memory_management::MemoryConfiguration;
22use cubecl_server::runtime::Runtime;
23use cubecl_server::{client::Client, logging::ServerLogger};
24use wgpu::{InstanceFlags, RequestAdapterOptions};
25
26#[derive(Debug)]
37pub struct WgpuRuntime<Compiler = AutoCompiler> {
38 _p: PhantomData<Compiler>,
39}
40
41impl<C> Clone for WgpuRuntime<C> {
42 fn clone(&self) -> Self {
43 Self { _p: self._p }
44 }
45}
46
47impl<C: WgpuCompiler> DeviceService for WgpuServer<C> {
48 fn init(device_id: cubecl_common::device::DeviceId) -> Self {
49 let device = WgpuDevice::from_id(device_id);
50 let setup =
51 future::block_on(try_create_setup(&device)).expect("Unable to acquire wgpu device");
52 try_create_server(setup, RuntimeOptions::default(), device_id, device.backend)
53 .expect("Unable to initialize wgpu runtime")
54 }
55
56 fn utilities(&self) -> ServerUtilitiesHandle {
57 self.utilities.clone() as ServerUtilitiesHandle
58 }
59}
60
61impl<C: WgpuCompiler> Runtime for WgpuRuntime<C> {
62 type Server = WgpuServer<C>;
63 type Device = WgpuDevice;
64
65 fn can_read_tensor(shape: &Shape, strides: &Strides) -> bool {
66 if shape.is_empty() {
67 return true;
68 }
69
70 for (&expected, &stride) in contiguous_strides(shape).iter().zip(strides.iter()) {
71 if expected != stride {
72 return false;
73 }
74 }
75
76 true
77 }
78
79 fn target_properties() -> TargetProperties {
80 TargetProperties {
81 mma: Default::default(),
83 }
84 }
85
86 fn enumerate_devices(type_id: u16) -> Vec<DeviceId> {
87 Self::enumerate_devices_like(DeviceId::new(type_id, 0))
90 }
91
92 fn is_available() -> bool {
93 let gpu = [
98 WgpuDeviceKind::DiscreteGpu(0),
99 WgpuDeviceKind::IntegratedGpu(0),
100 WgpuDeviceKind::VirtualGpu(0),
101 WgpuDeviceKind::Other(0),
102 ]
103 .map(|kind| WgpuDevice::new(kind).to_id().type_id);
104
105 Self::enumerate_all_devices()
106 .iter()
107 .any(|device| gpu.contains(&device.type_id))
108 }
109
110 fn enumerate_all_devices() -> Vec<DeviceId> {
115 adapters_on(WgpuBackend::Auto)
116 }
117
118 fn enumerate_devices_like(device_id: DeviceId) -> Vec<DeviceId> {
119 let device = WgpuDevice::from_id(device_id);
120 let reachable = adapters_on(device.backend);
121
122 match device.kind {
123 WgpuDeviceKind::DefaultDevice if !reachable.is_empty() => alloc::vec![device_id],
128 _ => reachable
129 .into_iter()
130 .filter(|id| id.type_id == device_id.type_id)
131 .collect(),
132 }
133 }
134
135 #[cfg(target_family = "wasm")]
139 fn find_device(device_id: DeviceId) -> Result<(), usize> {
140 let device = WgpuDevice::from_id(device_id);
141 let peers = Self::enumerate_devices_like(device_id);
142
143 let low_power = device.kind == WgpuDeviceKind::IntegratedGpu(0)
144 && !adapters_on(device.backend).is_empty();
145
146 match peers.contains(&device_id) || low_power {
147 true => Ok(()),
148 false => Err(peers.len()),
149 }
150 }
151
152 fn utilization(device: &Self::Device) -> Result<DeviceUtilization, UtilizationUnavailable> {
153 CardCounters::read(Self::client(device).properties().identity.physical.as_ref())
154 }
155}
156
157fn adapters_on(backend: WgpuBackend) -> Vec<DeviceId> {
164 #[cfg(target_family = "wasm")]
167 let ids = match backend {
168 WgpuBackend::Auto | WgpuBackend::WebGpu => vec![DeviceId::new(0, 0)],
169 _ => Vec::new(),
170 };
171
172 #[cfg(not(target_family = "wasm"))]
173 let ids = settle(backend)
174 .map(|(_, adapters)| adapter_device_ids(adapters))
175 .unwrap_or_default();
176
177 ids.into_iter()
178 .map(|id| WgpuDevice::from_id(id).on(backend).to_id())
179 .collect()
180}
181
182#[cfg(not(target_family = "wasm"))]
191fn settle(backend: WgpuBackend) -> Option<(wgpu::Backend, Vec<wgpu::AdapterInfo>)> {
192 let mut software_only = None;
193
194 for api in backend_candidates(backend) {
195 let adapters = probe(api);
196
197 if adapters
198 .iter()
199 .any(|adapter| adapter.device_type != wgpu::DeviceType::Cpu)
200 {
201 return Some((api, adapters));
202 }
203
204 if software_only.is_none() && !adapters.is_empty() {
205 software_only = Some((api, adapters));
206 }
207 }
208
209 software_only
210}
211
212fn backend_candidates(backend: WgpuBackend) -> alloc::vec::Vec<wgpu::Backend> {
216 match backend {
217 WgpuBackend::Auto => AutoGraphicsApi::chain(),
218 WgpuBackend::Vulkan => alloc::vec![wgpu::Backend::Vulkan],
219 WgpuBackend::Metal => alloc::vec![wgpu::Backend::Metal],
220 WgpuBackend::Dx12 => alloc::vec![wgpu::Backend::Dx12],
221 WgpuBackend::Gl => alloc::vec![wgpu::Backend::Gl],
222 WgpuBackend::WebGpu => alloc::vec![wgpu::Backend::BrowserWebGpu],
223 }
224}
225
226static INSTANCES: Mutex<Vec<(wgpu::Backend, wgpu::Instance)>> = Mutex::new(Vec::new());
230
231#[cfg(not(target_family = "wasm"))]
233fn probe(backend: wgpu::Backend) -> Vec<wgpu::AdapterInfo> {
234 let _instances = INSTANCES.lock();
235 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
236 backends: backend.into(),
237 ..wgpu::InstanceDescriptor::new_without_display_handle()
238 });
239 future::block_on(instance.enumerate_adapters(backend.into()))
240 .iter()
241 .map(wgpu::Adapter::get_info)
242 .collect()
243}
244
245pub(crate) fn resolve_backend(backend: WgpuBackend) -> wgpu::Backend {
251 #[cfg(not(target_family = "wasm"))]
252 if let Some((api, _)) = settle(backend) {
253 return api;
254 }
255
256 backend_candidates(backend)[0]
259}
260
261#[cfg(not(target_family = "wasm"))]
268fn adapter_device_ids(adapters: Vec<wgpu::AdapterInfo>) -> Vec<DeviceId> {
269 let mut next = [0u16; 7];
270
271 adapters
272 .into_iter()
273 .map(|adapter| {
274 let type_id = match adapter.device_type {
275 wgpu::DeviceType::DiscreteGpu => 0,
276 wgpu::DeviceType::IntegratedGpu => 1,
277 wgpu::DeviceType::VirtualGpu => 2,
278 wgpu::DeviceType::Cpu => 3,
279 wgpu::DeviceType::Other => 6,
280 };
281
282 let index = match next.get_mut(type_id as usize).filter(|_| type_id != 3) {
284 Some(next) => {
285 let index = *next;
286 *next += 1;
287 index
288 }
289 None => 0,
290 };
291
292 DeviceId::new(type_id, index)
293 })
294 .collect()
295}
296
297#[derive(Debug, thiserror::Error)]
299#[non_exhaustive]
300pub enum WgpuInitError {
301 #[error("Invalid wgpu configuration: {message}")]
303 InvalidConfiguration {
304 message: String,
306 },
307 #[error("Graphics API {api:?} is unavailable in this build")]
309 UnsupportedGraphicsApi {
310 api: WgpuBackend,
312 },
313 #[error("No adapter available for {device:?}: {message}")]
315 AdapterUnavailable {
316 device: WgpuDevice,
318 message: String,
320 },
321 #[error("Unable to request a wgpu device: {message}")]
323 RequestDevice {
324 message: String,
326 },
327 #[error("Unable to register wgpu runtime: {message}")]
329 Registration {
330 message: String,
332 },
333}
334
335pub struct RuntimeOptions {
337 pub tasks_max: usize,
339 pub memory_config: MemoryConfiguration,
341}
342
343impl Default for RuntimeOptions {
344 fn default() -> Self {
345 Self::try_default().expect("Invalid wgpu runtime defaults")
346 }
347}
348
349impl RuntimeOptions {
350 pub fn try_default() -> Result<Self, WgpuInitError> {
352 Self::with_tasks_max(None)
353 }
354
355 pub fn with_tasks_max(tasks_max: Option<usize>) -> Result<Self, WgpuInitError> {
357 let tasks_max = match tasks_max {
358 Some(value) => value,
359 None => match std::env::var("CUBECL_WGPU_MAX_TASKS") {
360 Ok(value) => value
361 .parse()
362 .map_err(|_| WgpuInitError::InvalidConfiguration {
363 message: "CUBECL_WGPU_MAX_TASKS must be a positive integer".into(),
364 })?,
365 Err(std::env::VarError::NotPresent) => 32,
366 Err(err) => {
367 return Err(WgpuInitError::InvalidConfiguration {
368 message: err.to_string(),
369 });
370 }
371 },
372 };
373 let options = Self {
374 tasks_max,
375 memory_config: MemoryConfiguration::default(),
376 };
377 options.validate()?;
378 Ok(options)
379 }
380
381 fn validate(&self) -> Result<(), WgpuInitError> {
382 if self.tasks_max == 0 {
383 return Err(WgpuInitError::InvalidConfiguration {
384 message: "tasks_max must be greater than zero".into(),
385 });
386 }
387 Ok(())
388 }
389}
390
391#[derive(Clone, Debug)]
395pub struct WgpuSetup {
396 pub instance: wgpu::Instance,
398 pub adapter: wgpu::Adapter,
400 pub device: wgpu::Device,
402 pub queue: wgpu::Queue,
404 pub backend: wgpu::Backend,
406}
407
408pub fn init_device(setup: WgpuSetup, options: RuntimeOptions) -> WgpuDevice {
420 try_init_device(setup, options).expect("Unable to register existing wgpu setup")
421}
422
423pub fn init_device_with_api<G: GraphicsApi>(
436 setup: WgpuSetup,
437 options: RuntimeOptions,
438) -> WgpuDevice {
439 try_init_device_with_api::<G>(setup, options).expect("Unable to register existing wgpu setup")
440}
441
442pub fn init_setup<G: GraphicsApi>(device: &WgpuDevice, options: RuntimeOptions) -> WgpuSetup {
456 cfg_if::cfg_if! {
457 if #[cfg(target_family = "wasm")] {
458 let _ = (device, options);
459 panic!("Creating a wgpu setup synchronously is unsupported on wasm. Use init_async instead");
460 } else {
461 future::block_on(init_setup_async::<G>(device, options))
462 }
463 }
464}
465
466pub async fn init_setup_async<G: GraphicsApi>(
480 device: &WgpuDevice,
481 options: RuntimeOptions,
482) -> WgpuSetup {
483 try_init_setup_async::<G>(device, options)
484 .await
485 .expect("Unable to initialize wgpu setup")
486}
487
488pub fn try_init_device(
498 setup: WgpuSetup,
499 options: RuntimeOptions,
500) -> Result<WgpuDevice, WgpuInitError> {
501 try_init_device_with_api::<AutoGraphicsApi>(setup, options)
502}
503
504pub fn try_init_device_with_api<G: GraphicsApi>(
511 setup: WgpuSetup,
512 options: RuntimeOptions,
513) -> Result<WgpuDevice, WgpuInitError> {
514 use core::sync::atomic::{AtomicU32, Ordering};
515 static COUNTER: AtomicU32 = AtomicU32::new(0);
516 options.validate()?;
517 let requested_backend = G::backend_for(&WgpuDevice::default());
518 if requested_backend != WgpuBackend::Auto
519 && !backend_candidates(requested_backend).contains(&setup.backend)
520 {
521 return Err(WgpuInitError::InvalidConfiguration {
522 message: format!(
523 "the imported setup uses {:?}, but {requested_backend:?} was requested",
524 setup.backend
525 ),
526 });
527 }
528 if setup.backend != setup.adapter.get_info().backend
529 || setup.backend != setup.device.adapter_info().backend
530 {
531 return Err(WgpuInitError::InvalidConfiguration {
532 message: "setup backend does not match its adapter and device".into(),
533 });
534 }
535 let limits = setup.device.limits();
536 if limits.max_compute_invocations_per_workgroup == 0
537 || limits.max_storage_buffers_per_shader_stage < 2
538 {
539 return Err(WgpuInitError::InvalidConfiguration {
540 message: "compute support and at least two storage bindings are required".into(),
541 });
542 }
543 let id = COUNTER
545 .try_update(Ordering::Relaxed, Ordering::Relaxed, |id| {
546 (id <= u16::MAX as u32).then_some(id + 1)
547 })
548 .map_err(|_| WgpuInitError::Registration {
549 message: "external wgpu device IDs exhausted".into(),
550 })?;
551 let device = WgpuDevice::new(WgpuDeviceKind::Existing(id));
552 let server =
553 try_create_server::<AutoCompiler>(setup, options, device.to_id(), requested_backend)?;
554 Client::try_init(device.to_id(), server).map_err(|err| WgpuInitError::Registration {
555 message: format!("{device:?}: {err:?}"),
556 })?;
557 Ok(device)
558}
559
560#[cfg(not(target_family = "wasm"))]
562pub fn try_init_setup<G: GraphicsApi>(
563 device: &WgpuDevice,
564 options: RuntimeOptions,
565) -> Result<WgpuSetup, WgpuInitError> {
566 future::block_on(try_init_setup_async::<G>(device, options))
567}
568
569pub async fn try_init_setup_async<G: GraphicsApi>(
578 device: &WgpuDevice,
579 options: RuntimeOptions,
580) -> Result<WgpuSetup, WgpuInitError> {
581 options.validate()?;
582 if matches!(device.kind, WgpuDeviceKind::Existing(_)) {
583 return Err(WgpuInitError::InvalidConfiguration {
584 message:
585 "an Existing ID is not an adapter selector; register a setup or reuse its device"
586 .into(),
587 });
588 }
589 let requested_backend = G::backend_for(device);
592 if device.backend != WgpuBackend::Auto && device.backend != requested_backend {
593 return Err(WgpuInitError::InvalidConfiguration {
594 message: format!("{device:?} cannot be initialized on {requested_backend:?}"),
595 });
596 }
597 let mut selected = device.clone();
598 selected.backend = requested_backend;
599 let setup = try_create_setup(&selected).await?;
600 let server = try_create_server::<AutoCompiler>(
601 setup.clone(),
602 options,
603 device.to_id(),
604 requested_backend,
605 )?;
606 Client::try_init(device.to_id(), server).map_err(|err| WgpuInitError::Registration {
607 message: format!("{device:?}: {err:?}"),
608 })?;
609 Ok(setup)
610}
611
612fn runtime_name(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> &'static str {
617 match backend {
618 wgpu::Backend::Vulkan if options.supports_vulkan_compiler => "wgpu<spirv>",
619 wgpu::Backend::Metal if options.supports_msl_compiler => "wgpu<msl>",
620 _ => "wgpu<wgsl>",
621 }
622}
623
624fn try_create_server<C: WgpuCompiler>(
625 setup: WgpuSetup,
626 options: RuntimeOptions,
627 device_id: DeviceId,
628 requested_backend: WgpuBackend,
629) -> Result<WgpuServer<C>, WgpuInitError> {
630 let limits = setup.device.limits();
631 let adapter_limits = setup.adapter.limits();
632 let mut adapter_info = setup.adapter.get_info();
633
634 if adapter_info.subgroup_min_size == 0 && adapter_info.subgroup_max_size == 0 {
638 adapter_info.subgroup_min_size = 8;
640 adapter_info.subgroup_max_size = 128;
642 }
643
644 let mem_props = MemoryDeviceProperties::new(
648 limits
649 .max_storage_buffer_binding_size
650 .min(limits.max_buffer_size),
651 limits
652 .min_uniform_buffer_offset_alignment
653 .max(limits.min_storage_buffer_offset_alignment) as u64,
654 );
655 let max_count = adapter_limits.max_compute_workgroups_per_dimension;
656 let hardware_props = HardwareProperties {
657 load_width: 128,
658 vector_register_count: None,
659 #[cfg(apple_silicon)]
663 plane_size_min: 32,
664 #[cfg(not(apple_silicon))]
665 plane_size_min: adapter_info.subgroup_min_size,
666 #[cfg(apple_silicon)]
667 plane_size_max: 32,
668 #[cfg(not(apple_silicon))]
669 plane_size_max: adapter_info.subgroup_max_size,
670 max_bindings: limits
674 .max_storage_buffers_per_shader_stage
675 .saturating_sub(1),
676 max_shared_memory_size: limits.max_compute_workgroup_storage_size as usize,
677 max_cube_count: (max_count, max_count, max_count),
678 max_units_per_cube: adapter_limits.max_compute_invocations_per_workgroup,
679 max_cube_dim: (
680 adapter_limits.max_compute_workgroup_size_x,
681 adapter_limits.max_compute_workgroup_size_y,
682 adapter_limits.max_compute_workgroup_size_z,
683 ),
684 num_streaming_multiprocessors: None,
685 num_tensor_cores: None,
686 min_tensor_cores_dim: None,
687 num_cpu_cores: None, last_level_cache_size: None,
689 max_vector_size: 4,
690 cube_mma_reserved_shared_memory: 0,
692 };
693
694 let mut compilation_options = Default::default();
695
696 let features = setup.adapter.features();
697
698 let time_measurement = if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
699 TimingMethod::Device
700 } else {
701 TimingMethod::System
702 };
703
704 let fingerprint = format!("spirv_{}_{}", adapter_info.vendor, adapter_info.device);
710
711 let mut device_props = DeviceProperties::new(
712 Default::default(),
713 mem_props,
714 hardware_props,
715 time_measurement,
716 DeviceIdentity {
717 name: adapter_info.name.clone(),
718 fingerprint,
719 physical: backend::physical_device(&setup.adapter, &adapter_info),
720 },
721 );
722
723 #[cfg(not(all(target_os = "macos", feature = "msl")))]
724 {
725 if features.contains(wgpu::Features::SUBGROUP)
726 && setup.adapter.get_info().device_type != wgpu::DeviceType::Cpu
727 {
728 use cubecl_ir::features::Plane;
729
730 device_props.features.plane.insert(Plane::Ops);
731 }
732 }
733
734 backend::register_features(&setup.adapter, &mut device_props, &mut compilation_options);
735
736 #[cfg(any(feature = "spirv", feature = "msl"))]
737 if compilation_options.supports_vulkan_compiler || compilation_options.supports_msl_compiler {
738 device_props
739 .features
740 .plane
741 .insert(cubecl_ir::features::Plane::NonUniformControlFlow);
742 }
743
744 C::validate_runtime(setup.backend, &compilation_options, requested_backend).map_err(|err| {
745 WgpuInitError::InvalidConfiguration {
746 message: format!("failed to initialize the wgpu compiler: {err}"),
747 }
748 })?;
749
750 let logger = alloc::sync::Arc::new(ServerLogger::default());
751 let name = runtime_name(setup.backend, &compilation_options);
752
753 let allocator = ContiguousMemoryLayoutPolicy::new(device_props.memory.alignment as usize);
754 let memory_properties = device_props.memory.clone();
755 let (utilities, captures) = ServerUtilities::init(
756 ServiceId::of::<WgpuServer<C>>(device_id),
757 name,
758 device_props,
759 WgpuRuntime::<C>::target_properties(),
760 logger,
761 allocator,
762 );
763 Ok(WgpuServer::new(
764 memory_properties,
765 options.memory_config,
766 compilation_options,
767 setup.device.clone(),
768 setup.queue,
769 options.tasks_max,
770 setup.backend,
771 time_measurement,
772 utilities,
773 captures,
774 ))
775}
776
777async fn try_create_setup(device: &WgpuDevice) -> Result<WgpuSetup, WgpuInitError> {
779 let override_device = match device.kind {
782 WgpuDeviceKind::DefaultDevice => try_device_override()?.map(|kind| WgpuDevice {
783 kind,
784 backend: device.backend,
785 }),
786 _ => None,
787 };
788 let device = override_device.as_ref().unwrap_or(device);
789 match device.kind {
790 WgpuDeviceKind::DiscreteGpu(index)
791 | WgpuDeviceKind::IntegratedGpu(index)
792 | WgpuDeviceKind::VirtualGpu(index)
793 | WgpuDeviceKind::Other(index)
794 if index > WgpuDeviceKind::MAX_INDEX =>
795 {
796 return Err(WgpuInitError::InvalidConfiguration {
797 message: format!(
798 "adapter index {index} exceeds {}",
799 WgpuDeviceKind::MAX_INDEX
800 ),
801 });
802 }
803 WgpuDeviceKind::Existing(_) => {
804 return Err(WgpuInitError::InvalidConfiguration {
805 message: "an Existing ID cannot acquire an adapter".into(),
806 });
807 }
808 _ => {}
809 }
810 let candidates = backend_candidates(device.backend);
811 if !candidates
812 .iter()
813 .any(|api| wgpu::Instance::enabled_backend_features().contains((*api).into()))
814 {
815 return Err(WgpuInitError::UnsupportedGraphicsApi {
816 api: device.backend,
817 });
818 }
819 let mut backend = None;
820 #[cfg(not(target_family = "wasm"))]
821 for api in candidates {
822 if !wgpu::Instance::enabled_backend_features().contains(api.into()) {
823 continue;
824 }
825 let adapters = probe(api);
826 if adapters
827 .iter()
828 .any(|adapter| adapter.device_type != wgpu::DeviceType::Cpu)
829 {
830 backend = Some(api);
831 break;
832 }
833 if backend.is_none() && !adapters.is_empty() {
834 backend = Some(api);
835 }
836 }
837 #[cfg(target_family = "wasm")]
838 for api in candidates {
839 if api == wgpu::Backend::BrowserWebGpu
840 && wgpu::Instance::enabled_backend_features().contains(api.into())
841 {
842 backend = Some(api);
843 break;
844 }
845 }
846 let backend = backend.ok_or_else(|| WgpuInitError::AdapterUnavailable {
847 device: device.clone(),
848 message: "no adapter found on the requested graphics API".into(),
849 })?;
850 let (instance, adapter) = request_adapter(device, backend).await?;
851 let (device, queue) = backend::try_request_device(&adapter).await?;
852 Ok(WgpuSetup {
853 instance,
854 adapter,
855 device,
856 queue,
857 backend,
858 })
859}
860
861async fn request_adapter(
862 device: &WgpuDevice,
863 backend: wgpu::Backend,
864) -> Result<(wgpu::Instance, wgpu::Adapter), WgpuInitError> {
865 #[cfg(not(feature = "vulkan-validate"))]
866 let instance_flags = {
867 let debug = ServerLogger::default();
868 match (debug.profile_level(), debug.compilation_source_activated()) {
872 (Some(ProfileLevel::Full), _) => InstanceFlags::advanced_debugging(),
873 (_, true) => InstanceFlags::debugging(),
874 (_, false) => InstanceFlags::default(),
875 }
876 };
877 #[cfg(feature = "vulkan-validate")]
878 let instance_flags = InstanceFlags::advanced_debugging();
879 log::debug!("{instance_flags:?}");
880 let instance = device_instance(backend, instance_flags);
881
882 let adapter = match device.kind {
883 #[cfg(not(target_family = "wasm"))]
884 WgpuDeviceKind::DiscreteGpu(num) => {
885 select_from_adapter_list(
886 num,
887 "No Discrete GPU device found",
888 &instance,
889 device,
890 backend,
891 )
892 .await
893 }
894 #[cfg(not(target_family = "wasm"))]
895 WgpuDeviceKind::IntegratedGpu(num) => {
896 select_from_adapter_list(
897 num,
898 "No Integrated GPU device found",
899 &instance,
900 device,
901 backend,
902 )
903 .await
904 }
905 #[cfg(not(target_family = "wasm"))]
906 WgpuDeviceKind::VirtualGpu(num) => {
907 select_from_adapter_list(
908 num,
909 "No Virtual GPU device found",
910 &instance,
911 device,
912 backend,
913 )
914 .await
915 }
916 #[cfg(not(target_family = "wasm"))]
917 WgpuDeviceKind::Other(num) => {
918 select_from_adapter_list(num, "No Other device found", &instance, device, backend).await
919 }
920 #[cfg(not(target_family = "wasm"))]
921 WgpuDeviceKind::Cpu => {
922 select_from_adapter_list(0, "No CPU device found", &instance, device, backend).await
923 }
924 #[cfg(target_family = "wasm")]
925 WgpuDeviceKind::IntegratedGpu(_) => {
926 request_adapter_with_preference(&instance, wgpu::PowerPreference::LowPower).await
927 }
928 WgpuDeviceKind::Existing(_) => {
929 unreachable!("Cannot select an adapter for an existing device.")
930 }
931 _ => {
932 request_adapter_with_preference(&instance, wgpu::PowerPreference::HighPerformance).await
933 }
934 }
935 .map_err(|message| WgpuInitError::AdapterUnavailable {
936 device: device.clone(),
937 message,
938 })?;
939
940 Ok((instance, adapter))
941}
942
943fn device_instance(backend: wgpu::Backend, flags: InstanceFlags) -> wgpu::Instance {
945 let mut instances = INSTANCES.lock();
946 if let Some((_, instance)) = instances.iter().find(|(api, _)| *api == backend) {
947 return instance.clone();
948 }
949 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
950 backends: backend.into(),
951 flags,
952 ..wgpu::InstanceDescriptor::new_without_display_handle()
953 });
954 instances.push((backend, instance.clone()));
955 instance
956}
957
958async fn request_adapter_with_preference(
959 instance: &wgpu::Instance,
960 power_preference: wgpu::PowerPreference,
961) -> Result<wgpu::Adapter, String> {
962 instance
963 .request_adapter(&RequestAdapterOptions {
964 power_preference,
965 force_fallback_adapter: false,
966 compatible_surface: None,
967 ..RequestAdapterOptions::default()
968 })
969 .await
970 .map_err(|err| err.to_string())
971}
972
973#[cfg(not(target_family = "wasm"))]
974async fn select_from_adapter_list(
975 num: usize,
976 error: &str,
977 instance: &wgpu::Instance,
978 device: &WgpuDevice,
979 backend: wgpu::Backend,
980) -> Result<wgpu::Adapter, String> {
981 let adapters = instance.enumerate_adapters(backend.into()).await;
986 let found = adapters
987 .iter()
988 .map(|adapter| adapter.get_info())
989 .collect::<Vec<_>>();
990
991 let is_same_type = |adapter: &wgpu::Adapter| {
992 let device_type = adapter.get_info().device_type;
993
994 match device.kind {
995 WgpuDeviceKind::DiscreteGpu(_) => device_type == wgpu::DeviceType::DiscreteGpu,
996 WgpuDeviceKind::IntegratedGpu(_) => device_type == wgpu::DeviceType::IntegratedGpu,
997 WgpuDeviceKind::VirtualGpu(_) => device_type == wgpu::DeviceType::VirtualGpu,
998 WgpuDeviceKind::Cpu => device_type == wgpu::DeviceType::Cpu,
999 WgpuDeviceKind::Other(_) => device_type == wgpu::DeviceType::Other,
1000 WgpuDeviceKind::DefaultDevice => true,
1001 WgpuDeviceKind::Existing(_) => {
1002 unreachable!("Cannot select an adapter for an existing device.")
1003 }
1004 }
1005 };
1006
1007 adapters
1008 .into_iter()
1009 .filter(is_same_type)
1010 .nth(num)
1011 .ok_or_else(|| format!("{error}, adapters {found:?}"))
1012}
1013
1014fn try_device_override() -> Result<Option<WgpuDeviceKind>, WgpuInitError> {
1015 let value = match std::env::var("CUBECL_WGPU_DEFAULT_DEVICE") {
1016 Ok(value) => value,
1017 Err(std::env::VarError::NotPresent) => return Ok(None),
1018 Err(err) => {
1019 return Err(WgpuInitError::InvalidConfiguration {
1020 message: err.to_string(),
1021 });
1022 }
1023 };
1024 let parsed = if value == "Cpu" {
1025 Some(WgpuDeviceKind::Cpu)
1026 } else {
1027 [
1028 (
1029 "DiscreteGpu(",
1030 WgpuDeviceKind::DiscreteGpu as fn(usize) -> WgpuDeviceKind,
1031 ),
1032 ("IntegratedGpu(", WgpuDeviceKind::IntegratedGpu),
1033 ("VirtualGpu(", WgpuDeviceKind::VirtualGpu),
1034 ("Other(", WgpuDeviceKind::Other),
1035 ]
1036 .into_iter()
1037 .find_map(|(prefix, kind)| {
1038 value
1039 .strip_prefix(prefix)?
1040 .strip_suffix(')')?
1041 .parse()
1042 .ok()
1043 .map(kind)
1044 })
1045 };
1046 parsed
1047 .map(Some)
1048 .ok_or_else(|| WgpuInitError::InvalidConfiguration {
1049 message: format!("Invalid CUBECL_WGPU_DEFAULT_DEVICE: {value}"),
1050 })
1051}
1052
1053#[cfg(all(test, not(target_family = "wasm")))]
1054mod device_tests {
1055 use super::*;
1056
1057 #[test]
1058 #[ignore = "requires a native Vulkan adapter (software adapters are supported)"]
1059 fn fallible_import_preserves_api_selection_and_unique_ids() {
1060 let selector = WgpuDevice::default().on(WgpuBackend::Vulkan);
1061 let setup = future::block_on(try_create_setup(&selector)).unwrap();
1062
1063 assert!(matches!(
1064 try_init_device_with_api::<crate::Metal>(setup.clone(), RuntimeOptions::default()),
1065 Err(WgpuInitError::InvalidConfiguration { .. })
1066 ));
1067
1068 let automatic = try_init_device(setup, RuntimeOptions::default()).unwrap();
1069 let setup = future::block_on(try_create_setup(&selector)).unwrap();
1070 let explicit = init_device_with_api::<crate::Vulkan>(setup, RuntimeOptions::default());
1071 assert_ne!(automatic.to_id(), explicit.to_id());
1072 }
1073
1074 #[cfg(feature = "msl")]
1075 #[test]
1076 #[ignore = "requires a native Vulkan adapter (software adapters are supported)"]
1077 fn compiler_validation_returns_an_initialization_error() {
1078 let selector = WgpuDevice::default().on(WgpuBackend::Vulkan);
1079 let setup = future::block_on(try_create_setup(&selector)).unwrap();
1080
1081 assert!(matches!(
1083 try_create_server::<crate::MslCompiler>(
1084 setup.clone(),
1085 RuntimeOptions::default(),
1086 selector.to_id(),
1087 WgpuBackend::Auto,
1088 ),
1089 Err(WgpuInitError::InvalidConfiguration { message })
1090 if message.contains("MslCompiler") && message.contains("Metal")
1091 ));
1092
1093 assert!(matches!(
1095 try_create_server::<AutoCompiler>(
1096 setup,
1097 RuntimeOptions::default(),
1098 selector.to_id(),
1099 WgpuBackend::Metal,
1100 ),
1101 Err(WgpuInitError::InvalidConfiguration { .. })
1102 ));
1103
1104 let setup =
1106 try_init_setup::<AutoGraphicsApi>(&selector, RuntimeOptions::default()).unwrap();
1107 assert_eq!(setup.backend, wgpu::Backend::Vulkan);
1108 assert!(matches!(
1109 try_init_setup::<AutoGraphicsApi>(&selector, RuntimeOptions::default()),
1110 Err(WgpuInitError::Registration { .. })
1111 ));
1112 }
1113
1114 const PINNED: [WgpuBackend; 4] = [
1115 WgpuBackend::Vulkan,
1116 WgpuBackend::Metal,
1117 WgpuBackend::Dx12,
1118 WgpuBackend::Gl,
1119 ];
1120
1121 #[test]
1126 fn each_adapter_is_listed_once() {
1127 let ids = <WgpuRuntime>::enumerate_all_devices();
1128
1129 let adapters = settle(WgpuBackend::Auto).map_or(0, |(_, adapters)| adapters.len());
1130
1131 assert_eq!(ids.len(), adapters);
1132 for id in ids {
1133 assert_eq!(WgpuDevice::from_id(id).backend, WgpuBackend::Auto);
1134 }
1135 }
1136
1137 #[test]
1141 fn a_device_is_found_on_the_api_it_names() {
1142 for backend in PINNED {
1143 let reachable = adapters_on(backend);
1144
1145 for id in reachable.iter() {
1146 assert_eq!(
1147 <WgpuRuntime>::find_device(*id),
1148 Ok(()),
1149 "{id} on {backend:?}"
1150 );
1151 }
1152
1153 let default = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(backend);
1154 assert_eq!(
1155 <WgpuRuntime>::find_device(default.to_id()).is_ok(),
1156 !reachable.is_empty(),
1157 "the default device on {backend:?}"
1158 );
1159 }
1160 }
1161
1162 #[test]
1166 fn the_default_device_is_its_own_only_peer() {
1167 for backend in PINNED.into_iter().chain([WgpuBackend::Auto]) {
1168 let default = WgpuDevice::new(WgpuDeviceKind::DefaultDevice)
1169 .on(backend)
1170 .to_id();
1171
1172 let expected = match adapters_on(backend).is_empty() {
1173 true => Vec::new(),
1174 false => alloc::vec![default],
1175 };
1176
1177 assert_eq!(<WgpuRuntime>::enumerate_devices_like(default), expected);
1178 }
1179 }
1180
1181 #[test]
1184 fn auto_defers_to_the_api_a_device_pins() {
1185 for (backend, api) in [
1186 (WgpuBackend::Vulkan, wgpu::Backend::Vulkan),
1187 (WgpuBackend::Metal, wgpu::Backend::Metal),
1188 (WgpuBackend::Dx12, wgpu::Backend::Dx12),
1189 (WgpuBackend::Gl, wgpu::Backend::Gl),
1190 ] {
1191 let device = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(backend);
1192
1193 assert_eq!(AutoGraphicsApi::backend_for(&device), backend);
1194 assert_eq!(resolve_backend(AutoGraphicsApi::backend_for(&device)), api);
1195 }
1196 }
1197
1198 #[test]
1202 fn a_setup_on_another_api_than_the_pinned_one_is_refused() {
1203 let device = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(WgpuBackend::Gl);
1204
1205 assert!(matches!(
1206 try_init_setup::<crate::Vulkan>(&device, RuntimeOptions::default()),
1207 Err(WgpuInitError::InvalidConfiguration { .. })
1208 ));
1209 }
1210
1211 #[test]
1214 fn a_pinned_device_is_enumerated_with_its_own_api() {
1215 for backend in PINNED {
1216 for id in adapters_on(backend) {
1217 let peers = <WgpuRuntime>::enumerate_devices_like(id);
1218
1219 assert!(peers.contains(&id), "{id} among {peers:?}");
1220 for peer in peers {
1221 assert_eq!(WgpuDevice::from_id(peer).backend, backend);
1222 }
1223 }
1224 }
1225 }
1226
1227 #[test]
1230 fn an_index_past_the_end_is_not_found_on_any_api() {
1231 for backend in PINNED.into_iter().chain([WgpuBackend::Auto]) {
1232 let device = WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(4242)).on(backend);
1233
1234 let discrete = adapters_on(backend)
1235 .into_iter()
1236 .filter(|id| id.type_id == device.to_id().type_id)
1237 .count();
1238
1239 assert_eq!(<WgpuRuntime>::find_device(device.to_id()), Err(discrete));
1240 }
1241 }
1242}