1use std::marker::PhantomData;
2
3use crate::WgpuCompiler;
4use crate::{
5 AutoCompiler, AutoGraphicsApi, GraphicsApi, WgpuDevice, backend, compute::WgpuServer,
6 contiguous_strides,
7};
8use cubecl_common::device::{Device, DeviceService};
9use cubecl_common::profile::TimingMethod;
10use cubecl_core::device::{DeviceId, ServerUtilitiesHandle};
11use cubecl_core::server::ServerUtilities;
12use cubecl_core::zspace::{Shape, Strides};
13use cubecl_core::{Runtime, ir::TargetProperties};
14use cubecl_environment::future;
15use cubecl_ir::{DeviceIdentity, DeviceProperties, HardwareProperties, MemoryDeviceProperties};
16use cubecl_runtime::allocator::ContiguousMemoryLayoutPolicy;
17#[cfg(not(feature = "vulkan-validate"))]
18use cubecl_runtime::logging::ProfileLevel;
19pub use cubecl_runtime::memory_management::MemoryConfiguration;
20use cubecl_runtime::{client::ComputeClient, logging::ServerLogger};
21use wgpu::{InstanceFlags, RequestAdapterOptions};
22
23#[derive(Debug)]
27pub struct WgpuRuntime<Compiler = AutoCompiler> {
28 _p: PhantomData<Compiler>,
29}
30
31impl<C> Clone for WgpuRuntime<C> {
32 fn clone(&self) -> Self {
33 Self { _p: self._p }
34 }
35}
36
37impl<C: WgpuCompiler> DeviceService for WgpuServer<C> {
38 fn init(device_id: cubecl_common::device::DeviceId) -> Self {
39 let device = WgpuDevice::from_id(device_id);
40 let setup = future::block_on(create_setup_for_device(&device, AutoGraphicsApi::backend()));
41 create_server(setup, RuntimeOptions::default())
42 }
43
44 fn utilities(&self) -> ServerUtilitiesHandle {
45 self.utilities.clone() as ServerUtilitiesHandle
46 }
47}
48
49impl<C: WgpuCompiler> Runtime for WgpuRuntime<C> {
50 type Compiler = C;
51 type Server = WgpuServer<C>;
52 type Device = WgpuDevice;
53
54 fn client(device: &Self::Device) -> ComputeClient<Self> {
55 ComputeClient::load(device)
56 }
57
58 fn name(client: &ComputeClient<Self>) -> &'static str {
59 match client.info() {
60 wgpu::Backend::Vulkan => {
61 #[cfg(feature = "spirv")]
62 return "wgpu<spirv>";
63
64 #[cfg(not(feature = "spirv"))]
65 return "wgpu<wgsl>";
66 }
67 wgpu::Backend::Metal => {
68 #[cfg(feature = "msl")]
69 return "wgpu<msl>";
70
71 #[cfg(not(feature = "msl"))]
72 return "wgpu<wgsl>";
73 }
74 _ => "wgpu<wgsl>",
75 }
76 }
77
78 fn max_cube_count() -> (u32, u32, u32) {
79 let max_dim = u16::MAX as u32;
80 (max_dim, max_dim, max_dim)
81 }
82
83 fn can_read_tensor(shape: &Shape, strides: &Strides) -> bool {
84 if shape.is_empty() {
85 return true;
86 }
87
88 for (&expected, &stride) in contiguous_strides(shape).iter().zip(strides.iter()) {
89 if expected != stride {
90 return false;
91 }
92 }
93
94 true
95 }
96
97 fn target_properties() -> TargetProperties {
98 TargetProperties {
99 mma: Default::default(),
101 }
102 }
103
104 fn enumerate_devices(type_id: u16, info: &wgpu::Backend) -> Vec<DeviceId> {
105 #[cfg(target_family = "wasm")]
106 {
107 let _ = type_id;
108 let _ = info;
109 vec![DeviceId::new(0, 0)]
111 }
112
113 #[cfg(not(target_family = "wasm"))]
114 {
115 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
116 backends: wgpu::Backends::all(),
117 ..wgpu::InstanceDescriptor::new_without_display_handle()
118 });
119
120 let adapters = enumerate_all_adapters(instance, *info);
121 adapters
122 .into_iter()
123 .filter(|adapter| {
124 if type_id == 4 {
126 return true;
127 }
128
129 let device_type = adapter.get_info().device_type;
130
131 let adapter_type_id = match device_type {
132 wgpu::DeviceType::Other => 4,
133 wgpu::DeviceType::IntegratedGpu => 1,
134 wgpu::DeviceType::DiscreteGpu => 0,
135 wgpu::DeviceType::VirtualGpu => 2,
136 wgpu::DeviceType::Cpu => 3,
137 };
138
139 adapter_type_id == type_id
140 })
141 .enumerate()
142 .map(|(index, adapter)| match adapter.get_info().device_type {
143 wgpu::DeviceType::DiscreteGpu => DeviceId::new(0, index as u16),
144 wgpu::DeviceType::IntegratedGpu => DeviceId::new(1, index as u16),
145 wgpu::DeviceType::VirtualGpu => DeviceId::new(2, index as u16),
146 wgpu::DeviceType::Cpu => DeviceId::new(3, 0),
147 wgpu::DeviceType::Other => DeviceId::new(4, 0),
148 })
149 .collect()
150 }
151 }
152
153 fn enumerate_all_devices(info: &wgpu::Backend) -> Vec<DeviceId> {
154 #[cfg(target_family = "wasm")]
155 {
156 let _ = info;
157 vec![DeviceId::new(0, 0)]
159 }
160
161 #[cfg(not(target_family = "wasm"))]
162 {
163 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
164 backends: wgpu::Backends::all(),
165 ..wgpu::InstanceDescriptor::new_without_display_handle()
166 });
167 let adapters = enumerate_all_adapters(instance, *info);
168 adapters
169 .into_iter()
170 .enumerate()
171 .map(|(index, adapter)| match adapter.get_info().device_type {
172 wgpu::DeviceType::DiscreteGpu => DeviceId::new(0, index as u16),
173 wgpu::DeviceType::IntegratedGpu => DeviceId::new(1, index as u16),
174 wgpu::DeviceType::VirtualGpu => DeviceId::new(2, index as u16),
175 wgpu::DeviceType::Cpu => DeviceId::new(3, 0),
176 wgpu::DeviceType::Other => DeviceId::new(4, 0),
177 })
178 .collect()
179 }
180 }
181}
182
183#[cfg(not(target_family = "wasm"))]
184fn enumerate_all_adapters(instance: wgpu::Instance, backend: wgpu::Backend) -> Vec<wgpu::Adapter> {
185 cubecl_environment::future::block_on(instance.enumerate_adapters(backend.into()))
187}
188
189pub struct RuntimeOptions {
191 pub tasks_max: usize,
193 pub memory_config: MemoryConfiguration,
195}
196
197impl Default for RuntimeOptions {
198 fn default() -> Self {
199 #[cfg(test)]
200 const DEFAULT_MAX_TASKS: usize = 32;
201 #[cfg(not(test))]
202 const DEFAULT_MAX_TASKS: usize = 32;
203
204 let tasks_max = match std::env::var("CUBECL_WGPU_MAX_TASKS") {
205 Ok(value) => value
206 .parse::<usize>()
207 .expect("CUBECL_WGPU_MAX_TASKS should be a positive integer."),
208 Err(_) => DEFAULT_MAX_TASKS,
209 };
210
211 Self {
212 tasks_max,
213 memory_config: MemoryConfiguration::default(),
214 }
215 }
216}
217
218#[derive(Clone, Debug)]
222pub struct WgpuSetup {
223 pub instance: wgpu::Instance,
225 pub adapter: wgpu::Adapter,
227 pub device: wgpu::Device,
229 pub queue: wgpu::Queue,
231 pub backend: wgpu::Backend,
233}
234
235pub fn init_device(setup: WgpuSetup, options: RuntimeOptions) -> WgpuDevice {
245 use core::sync::atomic::{AtomicU32, Ordering};
246
247 static COUNTER: AtomicU32 = AtomicU32::new(0);
248
249 let device_id = COUNTER.fetch_add(1, Ordering::Relaxed);
250 if device_id == u32::MAX {
251 core::panic!("Memory ID overflowed");
252 }
253
254 let device_id = WgpuDevice::Existing(device_id);
255 let server = create_server(setup, options);
256 let _ = ComputeClient::<WgpuRuntime>::init(&device_id, server);
257 device_id
258}
259
260pub fn init_setup<G: GraphicsApi>(device: &WgpuDevice, options: RuntimeOptions) -> WgpuSetup {
263 cfg_if::cfg_if! {
264 if #[cfg(target_family = "wasm")] {
265 let _ = (device, options);
266 panic!("Creating a wgpu setup synchronously is unsupported on wasm. Use init_async instead");
267 } else {
268 future::block_on(init_setup_async::<G>(device, options))
269 }
270 }
271}
272
273pub async fn init_setup_async<G: GraphicsApi>(
277 device: &WgpuDevice,
278 options: RuntimeOptions,
279) -> WgpuSetup {
280 let setup = create_setup_for_device(device, G::backend()).await;
281 let return_setup = setup.clone();
282 let server = create_server(setup, options);
283 let _ = ComputeClient::<WgpuRuntime>::init(device, server);
284 return_setup
285}
286
287pub(crate) fn create_server<C: WgpuCompiler>(
288 setup: WgpuSetup,
289 options: RuntimeOptions,
290) -> WgpuServer<C> {
291 let limits = setup.device.limits();
292 let adapter_limits = setup.adapter.limits();
293 let mut adapter_info = setup.adapter.get_info();
294
295 if adapter_info.subgroup_min_size == 0 && adapter_info.subgroup_max_size == 0 {
299 adapter_info.subgroup_min_size = 8;
301 adapter_info.subgroup_max_size = 128;
303 }
304
305 let mem_props = MemoryDeviceProperties {
306 max_page_size: limits.max_storage_buffer_binding_size,
307 alignment: limits.min_uniform_buffer_offset_alignment as u64,
308 };
309 let max_count = adapter_limits.max_compute_workgroups_per_dimension;
310 let hardware_props = HardwareProperties {
311 load_width: 128,
312 #[cfg(apple_silicon)]
316 plane_size_min: 32,
317 #[cfg(not(apple_silicon))]
318 plane_size_min: adapter_info.subgroup_min_size,
319 #[cfg(apple_silicon)]
320 plane_size_max: 32,
321 #[cfg(not(apple_silicon))]
322 plane_size_max: adapter_info.subgroup_max_size,
323 max_bindings: limits
327 .max_storage_buffers_per_shader_stage
328 .saturating_sub(1),
329 max_shared_memory_size: limits.max_compute_workgroup_storage_size as usize,
330 max_cube_count: (max_count, max_count, max_count),
331 max_units_per_cube: adapter_limits.max_compute_invocations_per_workgroup,
332 max_cube_dim: (
333 adapter_limits.max_compute_workgroup_size_x,
334 adapter_limits.max_compute_workgroup_size_y,
335 adapter_limits.max_compute_workgroup_size_z,
336 ),
337 num_streaming_multiprocessors: None,
338 num_tensor_cores: None,
339 min_tensor_cores_dim: None,
340 num_cpu_cores: None, max_vector_size: 4,
342 cube_mma_reserved_shared_memory: 0,
344 };
345
346 let mut compilation_options = Default::default();
347
348 let features = setup.adapter.features();
349
350 let time_measurement = if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
351 TimingMethod::Device
352 } else {
353 TimingMethod::System
354 };
355
356 let fingerprint = format!("spirv_{}_{}", adapter_info.vendor, adapter_info.device);
362
363 let mut device_props = DeviceProperties::new(
364 Default::default(),
365 mem_props,
366 hardware_props,
367 time_measurement,
368 DeviceIdentity {
369 name: adapter_info.name.clone(),
370 fingerprint,
371 },
372 );
373
374 #[cfg(not(all(target_os = "macos", feature = "msl")))]
375 {
376 if features.contains(wgpu::Features::SUBGROUP)
377 && setup.adapter.get_info().device_type != wgpu::DeviceType::Cpu
378 {
379 use cubecl_ir::features::Plane;
380
381 device_props.features.plane.insert(Plane::Ops);
382 }
383 }
384
385 #[cfg(any(feature = "spirv", feature = "msl"))]
386 device_props
387 .features
388 .plane
389 .insert(cubecl_ir::features::Plane::NonUniformControlFlow);
390
391 backend::register_features(
392 &setup.adapter,
393 &mut device_props,
394 &mut compilation_options,
395 &options.memory_config,
396 );
397
398 let logger = alloc::sync::Arc::new(ServerLogger::default());
399
400 let allocator = ContiguousMemoryLayoutPolicy::new(device_props.memory.alignment as usize);
401 WgpuServer::new(
402 device_props.memory.clone(),
403 options.memory_config,
404 compilation_options,
405 setup.device.clone(),
406 setup.queue,
407 options.tasks_max,
408 setup.backend,
409 time_measurement,
410 ServerUtilities::new(device_props, logger, setup.backend, allocator),
411 )
412}
413
414pub(crate) async fn create_setup_for_device(
417 device: &WgpuDevice,
418 backend: wgpu::Backend,
419) -> WgpuSetup {
420 let (instance, adapter) = request_adapter(device, backend).await;
421 let (device, queue) = backend::request_device(&adapter).await;
422
423 log::info!(
424 "Created wgpu compute server on device {:?}",
425 adapter.get_info()
426 );
427
428 WgpuSetup {
429 instance,
430 adapter,
431 device,
432 queue,
433 backend,
434 }
435}
436
437async fn request_adapter(
438 device: &WgpuDevice,
439 backend: wgpu::Backend,
440) -> (wgpu::Instance, wgpu::Adapter) {
441 #[cfg(not(feature = "vulkan-validate"))]
442 let instance_flags = {
443 let debug = ServerLogger::default();
444 match (debug.profile_level(), debug.compilation_source_activated()) {
448 (Some(ProfileLevel::Full), _) => InstanceFlags::advanced_debugging(),
449 (_, true) => InstanceFlags::debugging(),
450 (_, false) => InstanceFlags::default(),
451 }
452 };
453 #[cfg(feature = "vulkan-validate")]
454 let instance_flags = InstanceFlags::advanced_debugging();
455 log::debug!("{instance_flags:?}");
456 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
457 backends: backend.into(),
458 flags: instance_flags,
459 ..wgpu::InstanceDescriptor::new_without_display_handle()
460 });
461
462 #[allow(deprecated)]
463 let override_device = if matches!(
464 device,
465 WgpuDevice::DefaultDevice | WgpuDevice::BestAvailable
466 ) {
467 get_device_override()
468 } else {
469 None
470 };
471
472 let device = override_device.unwrap_or_else(|| device.clone());
473
474 let adapter = match device {
475 #[cfg(not(target_family = "wasm"))]
476 WgpuDevice::DiscreteGpu(num) => {
477 select_from_adapter_list(
478 num,
479 "No Discrete GPU device found",
480 &instance,
481 &device,
482 backend,
483 )
484 .await
485 }
486 #[cfg(not(target_family = "wasm"))]
487 WgpuDevice::IntegratedGpu(num) => {
488 select_from_adapter_list(
489 num,
490 "No Integrated GPU device found",
491 &instance,
492 &device,
493 backend,
494 )
495 .await
496 }
497 #[cfg(not(target_family = "wasm"))]
498 WgpuDevice::VirtualGpu(num) => {
499 select_from_adapter_list(
500 num,
501 "No Virtual GPU device found",
502 &instance,
503 &device,
504 backend,
505 )
506 .await
507 }
508 #[cfg(not(target_family = "wasm"))]
509 WgpuDevice::Cpu => {
510 select_from_adapter_list(0, "No CPU device found", &instance, &device, backend).await
511 }
512 #[cfg(target_family = "wasm")]
513 WgpuDevice::IntegratedGpu(_) => {
514 request_adapter_with_preference(&instance, wgpu::PowerPreference::LowPower).await
515 }
516 WgpuDevice::Existing(_) => {
517 unreachable!("Cannot select an adapter for an existing device.")
518 }
519 _ => {
520 request_adapter_with_preference(&instance, wgpu::PowerPreference::HighPerformance).await
521 }
522 };
523
524 (instance, adapter)
525}
526
527async fn request_adapter_with_preference(
528 instance: &wgpu::Instance,
529 power_preference: wgpu::PowerPreference,
530) -> wgpu::Adapter {
531 instance
532 .request_adapter(&RequestAdapterOptions {
533 power_preference,
534 force_fallback_adapter: false,
535 compatible_surface: None,
536 ..RequestAdapterOptions::default()
537 })
538 .await
539 .expect("No possible adapter available for backend. Falling back to first available.")
540}
541
542#[cfg(not(target_family = "wasm"))]
543async fn select_from_adapter_list(
544 num: usize,
545 error: &str,
546 instance: &wgpu::Instance,
547 device: &WgpuDevice,
548 backend: wgpu::Backend,
549) -> wgpu::Adapter {
550 let mut adapters_other = Vec::new();
551 let mut adapters = Vec::new();
552
553 instance
554 .enumerate_adapters(backend.into())
555 .await
556 .into_iter()
557 .for_each(|adapter| {
558 let device_type = adapter.get_info().device_type;
559
560 if let wgpu::DeviceType::Other = device_type {
561 adapters_other.push(adapter);
562 return;
563 }
564
565 let is_same_type = match device {
566 WgpuDevice::DiscreteGpu(_) => device_type == wgpu::DeviceType::DiscreteGpu,
567 WgpuDevice::IntegratedGpu(_) => device_type == wgpu::DeviceType::IntegratedGpu,
568 WgpuDevice::VirtualGpu(_) => device_type == wgpu::DeviceType::VirtualGpu,
569 WgpuDevice::Cpu => device_type == wgpu::DeviceType::Cpu,
570 #[allow(deprecated)]
571 WgpuDevice::DefaultDevice | WgpuDevice::BestAvailable => true,
572 WgpuDevice::Existing(_) => {
573 unreachable!("Cannot select an adapter for an existing device.")
574 }
575 };
576
577 if is_same_type {
578 adapters.push(adapter);
579 }
580 });
581
582 if adapters.len() <= num {
583 if adapters_other.len() <= num {
584 panic!(
585 "{}, adapters {:?}, other adapters {:?}",
586 error,
587 adapters
588 .into_iter()
589 .map(|adapter| adapter.get_info())
590 .collect::<Vec<_>>(),
591 adapters_other
592 .into_iter()
593 .map(|adapter| adapter.get_info())
594 .collect::<Vec<_>>(),
595 );
596 }
597
598 return adapters_other.remove(num);
599 }
600
601 adapters.remove(num)
602}
603
604fn get_device_override() -> Option<WgpuDevice> {
605 std::env::var("CUBECL_WGPU_DEFAULT_DEVICE")
608 .ok()
609 .and_then(|var| {
610 let override_device = if let Some(inner) = var.strip_prefix("DiscreteGpu(") {
611 inner
612 .strip_suffix(")")
613 .and_then(|s| s.parse().ok())
614 .map(WgpuDevice::DiscreteGpu)
615 } else if let Some(inner) = var.strip_prefix("IntegratedGpu(") {
616 inner
617 .strip_suffix(")")
618 .and_then(|s| s.parse().ok())
619 .map(WgpuDevice::IntegratedGpu)
620 } else if let Some(inner) = var.strip_prefix("VirtualGpu(") {
621 inner
622 .strip_suffix(")")
623 .and_then(|s| s.parse().ok())
624 .map(WgpuDevice::VirtualGpu)
625 } else if var == "Cpu" {
626 Some(WgpuDevice::Cpu)
627 } else {
628 None
629 };
630
631 if override_device.is_none() {
632 log::warn!("Unknown CUBECL_WGPU_DEVICE override {var}");
633 }
634 override_device
635 })
636}