1use super::wgsl;
2use crate::{AutoRepresentationRef, WgpuCompiler, WgpuServer};
3use cubecl_core::{
4 CubeDim, ExecutionMode, MemoryConfiguration, WgpuCompilationOptions, prelude::Visibility,
5 server::KernelArguments,
6};
7use cubecl_ir::{DeviceProperties, PhysicalDevice};
8use cubecl_server::{
9 compiler::{CompilationError, KernelCacheKey},
10 id::KernelId,
11};
12use std::{borrow::Cow, sync::Arc};
13use wgpu::{
14 Adapter, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
15 ComputePipeline, Device, PipelineLayoutDescriptor, Queue, ShaderModule, ShaderModuleDescriptor,
16 ShaderStages,
17};
18
19#[cfg(feature = "spirv")]
20use super::vulkan;
21
22#[cfg(all(feature = "msl", target_os = "macos"))]
23use super::metal;
24
25#[cfg(windows)]
26use super::dx12;
27
28pub enum ModuleSource<'a> {
35 #[cfg(feature = "spirv")]
37 SpirV(&'a cubecl_spirv::SpirvKernel),
38 #[cfg(all(feature = "msl", target_os = "macos"))]
40 Msl(&'a str),
41 Wgsl(&'a str),
43}
44
45impl<'a> ModuleSource<'a> {
46 pub fn resolve(
50 repr: Option<AutoRepresentationRef<'a>>,
51 lang: &str,
52 source: &'a str,
53 ) -> Result<Self, CompilationError> {
54 match repr {
55 #[cfg(feature = "spirv")]
56 Some(AutoRepresentationRef::SpirV(repr)) => Ok(Self::SpirV(repr)),
57 Some(AutoRepresentationRef::Wgsl(_)) => Ok(Self::Wgsl(source)),
58 #[cfg(feature = "msl")]
59 Some(AutoRepresentationRef::Msl(_)) => Self::msl(source),
60 None => match lang {
61 "wgsl" => Ok(Self::Wgsl(source)),
62 "msl" => Self::msl(source),
63 other => Err(CompilationError::Generic {
64 reason: format!(
65 "wgpu has no text passthrough for a precompiled `{other}` kernel"
66 ),
67 backtrace: cubecl_environment::backtrace::BackTrace::capture(),
68 }),
69 },
70 }
71 }
72
73 #[cfg(all(feature = "msl", target_os = "macos"))]
74 fn msl(source: &'a str) -> Result<Self, CompilationError> {
75 Ok(Self::Msl(source))
76 }
77
78 #[cfg(not(all(feature = "msl", target_os = "macos")))]
79 fn msl(_source: &'a str) -> Result<Self, CompilationError> {
80 Err(CompilationError::Generic {
81 reason: "MSL passthrough is only available on macOS".to_string(),
82 backtrace: cubecl_environment::backtrace::BackTrace::capture(),
83 })
84 }
85}
86
87impl<C: WgpuCompiler> WgpuServer<C> {
88 #[allow(
92 clippy::type_complexity,
93 reason = "required because of error propagation"
94 )]
95 #[allow(unused_variables)]
96 pub fn load_cached_pipeline(
97 &mut self,
98 kernel_id: &KernelId,
99 bindings: &KernelArguments,
100 mode: ExecutionMode,
101 ) -> Result<
102 Option<Result<crate::compute::PipelineEntry, (u64, KernelCacheKey)>>,
103 CompilationError,
104 > {
105 #[cfg(not(feature = "spirv"))]
106 let res = Ok(None);
107 #[cfg(feature = "spirv")]
108 let res = if let Some(cache) = self.spirv_cache.as_mut() {
109 let key = (
110 self.utilities.properties_hash,
111 KernelCacheKey::new(kernel_id, self.build_id),
112 );
113 if let Some(entry) = cache.remove(&key) {
114 use crate::ParamsTransfer;
115
116 log::trace!("Using SPIR-V cache");
117
118 let params_transfer = match entry.kernel.immediate_size {
119 Some(_) => ParamsTransfer::Immediate,
120 None => ParamsTransfer::Uniform,
121 };
122 let repr = AutoRepresentationRef::SpirV(&entry.kernel);
123 let module = self.create_module(
124 &entry.entrypoint_name,
125 kernel_id.cube_dim.into(),
126 ModuleSource::SpirV(&entry.kernel),
127 mode,
128 )?;
129 let pipeline =
130 self.create_pipeline(&entry.entrypoint_name, Some(repr), module, bindings);
131 let io = entry.kernel.io.clone().map(std::sync::Arc::from);
132 Ok(Some(Ok((
133 pipeline,
134 crate::compute::CompilerInfo::Vulkan { params_transfer },
135 io,
136 ))))
137 } else {
138 Ok(Some(Err(key)))
139 }
140 } else {
141 Ok(None)
142 };
143
144 res
145 }
146
147 pub fn create_module(
148 &self,
149 entrypoint_name: &str,
150 cube_dim: CubeDim,
151 source: ModuleSource<'_>,
152 mode: ExecutionMode,
153 ) -> Result<ShaderModule, CompilationError> {
154 match source {
155 #[cfg(feature = "spirv")]
156 ModuleSource::SpirV(repr) => unsafe {
157 Ok(self.device.create_shader_module_passthrough(
158 wgpu::ShaderModuleDescriptorPassthrough {
159 label: Some(entrypoint_name),
160 spirv: Some(Cow::Borrowed(&repr.assembled_module)),
161 entry_points: Cow::Borrowed(&[wgpu::PassthroughShaderEntryPoint {
162 name: entrypoint_name.into(),
163 workgroup_size: cube_dim.into(),
164 }]),
165 ..Default::default()
166 },
167 ))
168 },
169 #[cfg(all(feature = "msl", target_os = "macos"))]
170 ModuleSource::Msl(source) => unsafe {
171 Ok(self.device.create_shader_module_passthrough(
172 wgpu::ShaderModuleDescriptorPassthrough {
173 label: Some(entrypoint_name),
174 msl: Some(Cow::Borrowed(source)),
175 entry_points: Cow::Borrowed(&[wgpu::PassthroughShaderEntryPoint {
176 name: entrypoint_name.into(),
177 workgroup_size: cube_dim.into(),
178 }]),
179 ..Default::default()
180 },
181 ))
182 },
183 ModuleSource::Wgsl(source) => {
184 let _ = cube_dim;
185 let checks = wgpu::ShaderRuntimeChecks {
186 bounds_checks: false,
190 force_loop_bounding: mode == ExecutionMode::Checked,
192 ..wgpu::ShaderRuntimeChecks::unchecked()
193 };
194
195 log::trace!("[cubecl-wgpu] compiling WGSL module `{entrypoint_name}`\n{source}");
196
197 let error_scope = self.device.push_error_scope(wgpu::ErrorFilter::Validation);
198
199 let module = unsafe {
202 self.device.create_shader_module_trusted(
203 ShaderModuleDescriptor {
204 label: Some(entrypoint_name),
205 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(source)),
206 },
207 checks,
208 )
209 };
210
211 let err_future = error_scope.pop();
214
215 #[cfg(not(target_family = "wasm"))]
216 if let Some(err) = cubecl_environment::future::block_on(err_future) {
217 log::error!(
218 "[cubecl-wgpu] WGSL compilation failed for kernel `{entrypoint_name}`:\n{err}\n--- shader source ({} bytes) ---\n{source}\n--- end shader ---",
219 source.len()
220 );
221 return Err(CompilationError::Generic {
222 reason: format!(
223 "WGSL compilation failed for kernel `{entrypoint_name}`: {err}"
224 ),
225 backtrace: cubecl_environment::backtrace::BackTrace::capture(),
226 });
227 }
228
229 #[cfg(target_family = "wasm")]
232 {
233 let entrypoint_name = entrypoint_name.to_string();
234 let source = source.to_string();
235 wasm_bindgen_futures::spawn_local(async move {
236 if let Some(err) = err_future.await {
237 log::error!(
238 "[cubecl-wgpu] WGSL compilation failed for kernel `{entrypoint_name}`:\n{err}\n--- shader source ({} bytes) ---\n{source}\n--- end shader ---",
239 source.len()
240 );
241 }
242 });
243 }
244
245 Ok(module)
246 }
247 }
248 }
249
250 #[allow(unused_variables)]
251 pub fn create_pipeline(
252 &self,
253 entrypoint_name: &str,
254 repr: Option<AutoRepresentationRef<'_>>,
255 module: ShaderModule,
256 bindings: &KernelArguments,
257 ) -> Arc<ComputePipeline> {
258 let bindings_info = match repr {
259 Some(AutoRepresentationRef::Wgsl(repr)) => Some(wgsl::bindings(repr, bindings)),
260 #[cfg(all(feature = "msl", target_os = "macos"))]
261 Some(AutoRepresentationRef::Msl(repr)) => Some(metal::bindings(repr, bindings)),
262 #[cfg(feature = "spirv")]
263 Some(AutoRepresentationRef::SpirV(repr)) => Some(vulkan::bindings(repr, bindings)),
264 _ => None,
265 };
266
267 let layout = bindings_info.map(|(bindings, immediate_size)| {
268 if !bindings.is_empty() {
269 let bindings = bindings
270 .into_iter()
271 .map(|visibility| match visibility {
272 Visibility::Uniform => BufferBindingType::Uniform,
273 Visibility::Read => BufferBindingType::Storage { read_only: true },
274 Visibility::ReadWrite => BufferBindingType::Storage { read_only: false },
275 })
276 .enumerate()
277 .map(|(i, ty)| BindGroupLayoutEntry {
278 binding: i as u32,
279 visibility: ShaderStages::COMPUTE,
280 ty: BindingType::Buffer {
281 ty,
282 has_dynamic_offset: false,
283 min_binding_size: None,
284 },
285 count: None,
286 })
287 .collect::<Vec<_>>();
288 let layout = self
289 .device
290 .create_bind_group_layout(&BindGroupLayoutDescriptor {
291 label: None,
292 entries: &bindings,
293 });
294 self.device
295 .create_pipeline_layout(&PipelineLayoutDescriptor {
296 label: None,
297 bind_group_layouts: &[Some(&layout)],
298 immediate_size: immediate_size as u32,
299 })
300 } else {
301 self.device
302 .create_pipeline_layout(&PipelineLayoutDescriptor {
303 label: None,
304 bind_group_layouts: &[],
305 immediate_size: immediate_size as u32,
306 })
307 }
308 });
309
310 let pipeline = self
311 .device
312 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
313 label: Some(entrypoint_name),
314 layout: layout.as_ref(),
315 module: &module,
316 entry_point: Some(entrypoint_name),
317 compilation_options: wgpu::PipelineCompilationOptions {
318 zero_initialize_workgroup_memory: false,
319 ..Default::default()
320 },
321 cache: None,
322 });
323 Arc::new(pipeline)
324 }
325}
326
327pub async fn request_device(adapter: &Adapter) -> (Device, Queue) {
328 if let Some(result) = request_vulkan_device(adapter).await {
329 return result;
330 }
331 if let Some(result) = request_metal_device(adapter).await {
332 return result;
333 }
334 wgsl::request_device(adapter).await
335}
336
337#[cfg(feature = "spirv")]
338async fn request_vulkan_device(adapter: &Adapter) -> Option<(Device, Queue)> {
339 if is_vulkan(adapter) {
340 vulkan::request_vulkan_device(adapter).await
341 } else {
342 None
343 }
344}
345
346#[cfg(not(feature = "spirv"))]
347async fn request_vulkan_device(_adapter: &Adapter) -> Option<(Device, Queue)> {
348 None
349}
350
351#[cfg(all(feature = "msl", target_os = "macos"))]
352async fn request_metal_device(adapter: &Adapter) -> Option<(Device, Queue)> {
353 if is_metal(adapter) {
354 Some(metal::request_metal_device(adapter).await)
355 } else {
356 None
357 }
358}
359
360#[cfg(not(all(feature = "msl", target_os = "macos")))]
361async fn request_metal_device(_adapter: &Adapter) -> Option<(Device, Queue)> {
362 None
363}
364
365pub fn register_features(
366 adapter: &Adapter,
367 props: &mut DeviceProperties,
368 comp_options: &mut WgpuCompilationOptions,
369 memory_config: &MemoryConfiguration,
370) {
371 if register_vulkan_features(adapter, props, comp_options, memory_config) {
372 return;
373 }
374 if register_metal_features(adapter, props, comp_options, memory_config) {
375 return;
376 }
377 wgsl::register_wgsl_features(adapter, props, comp_options);
378}
379
380#[cfg(feature = "spirv")]
381pub fn register_vulkan_features(
382 adapter: &Adapter,
383 props: &mut DeviceProperties,
384 comp_options: &mut WgpuCompilationOptions,
385 memory_config: &MemoryConfiguration,
386) -> bool {
387 if is_vulkan(adapter) {
388 vulkan::register_vulkan_features(adapter, props, comp_options, memory_config)
389 } else {
390 false
391 }
392}
393
394#[cfg(not(feature = "spirv"))]
395pub fn register_vulkan_features(
396 _adapter: &Adapter,
397 _props: &mut DeviceProperties,
398 _comp_options: &mut WgpuCompilationOptions,
399 _memory_config: &MemoryConfiguration,
400) -> bool {
401 false
402}
403
404#[cfg(all(feature = "msl", target_os = "macos"))]
405pub fn register_metal_features(
406 adapter: &Adapter,
407 props: &mut DeviceProperties,
408 comp_options: &mut WgpuCompilationOptions,
409 _memory_config: &MemoryConfiguration,
410) -> bool {
411 if is_metal(adapter) {
412 metal::register_metal_features(adapter, props, comp_options)
413 } else {
414 false
415 }
416}
417
418#[cfg(not(all(feature = "msl", target_os = "macos")))]
419pub fn register_metal_features(
420 _adapter: &Adapter,
421 _props: &mut DeviceProperties,
422 _comp_options: &mut WgpuCompilationOptions,
423 _memory_config: &MemoryConfiguration,
424) -> bool {
425 false
426}
427
428#[cfg_attr(not(any(feature = "spirv", windows)), expect(unused_variables))]
430pub fn physical_device(adapter: &Adapter, info: &wgpu::AdapterInfo) -> Option<PhysicalDevice> {
431 if info.device_type == wgpu::DeviceType::Cpu {
432 return None;
433 }
434 let mut physical = PhysicalDevice::default();
435 physical.vendor = (info.vendor != 0).then(|| info.vendor.into());
437 if info.backend == wgpu::Backend::Vulkan {
440 physical.pci_address = info.device_pci_bus_id.parse().ok();
441 }
442 #[cfg(feature = "spirv")]
443 if is_vulkan(adapter) {
444 vulkan::describe_card(adapter, &mut physical);
445 }
446 #[cfg(windows)]
447 dx12::describe_card(adapter, &mut physical);
448 Some(physical)
449}
450
451#[cfg(feature = "spirv")]
452fn is_vulkan(adapter: &Adapter) -> bool {
453 unsafe { adapter.as_hal::<wgpu::hal::api::Vulkan>().is_some() }
454}
455
456#[cfg(all(feature = "msl", target_os = "macos"))]
457fn is_metal(adapter: &Adapter) -> bool {
458 unsafe { adapter.as_hal::<wgpu::hal::api::Metal>().is_some() }
459}