1use std::marker::PhantomData;
2
3use super::storage::{WgpuResource, WgpuStorage};
4use crate::WgpuCompiler;
5use crate::schedule::{BindingsResource, ScheduleTask, ScheduledWgpuBackend};
6use alloc::sync::Arc;
7use cubecl_common::pool::LeasePool;
8use cubecl_common::{
9 bytes::Bytes,
10 profile::{ProfileDuration, TimingMethod},
11};
12use cubecl_core::server::{Binding, StreamErrorMode};
13use cubecl_core::zspace::Shape;
14use cubecl_core::{
15 MemoryConfiguration, WgpuCompilationOptions,
16 prelude::*,
17 server::{
18 CopyDescriptor, IoError, KernelArguments, LaunchError, ProfileError, ProfilingToken,
19 ServerCommunication, ServerError, ServerUtilities,
20 },
21 zspace::{Strides, strides},
22};
23use cubecl_environment::backtrace::BackTrace;
24use cubecl_environment::future::DynFut;
25#[cfg(feature = "spirv")]
26use cubecl_environment::persistence::Store;
27use cubecl_environment::stream::StreamId;
28use cubecl_ir::MemoryDeviceProperties;
29use cubecl_runtime::allocator::ContiguousMemoryLayoutPolicy;
30#[cfg(feature = "spirv")]
31use cubecl_runtime::compiler::{KernelCacheKey, compilation_store, store_compiled};
32use cubecl_runtime::memory_management::{ManagedMemoryHandle, MemoryUsage, SharedMemoryBindings};
33use cubecl_runtime::{
34 compiler::{CompilationCache, CubeTask},
35 config::{CubeClRuntimeConfig, RuntimeConfig},
36 dry_run::LaunchMode,
37 logging::ServerLogger,
38 memory_management::MemoryAllocationMode,
39 server::ComputeServer,
40 storage::ManagedResource,
41 stream::scheduler::{
42 SchedulerMultiStream, SchedulerMultiStreamOptions, SchedulerStrategy,
43 SchedulerStreamBackend,
44 },
45 validation::{validate_cube_dim, validate_units},
46};
47use wgpu::ComputePipeline;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ParamsTransfer {
51 Immediate,
52 Uniform,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum CompilerInfo {
58 Vulkan { params_transfer: ParamsTransfer },
59 Metal,
60 WGSL,
61 None,
62}
63
64#[derive(Debug)]
66pub struct WgpuServer<C: WgpuCompiler> {
67 pub(crate) device: wgpu::Device,
68 streams_pool: Vec<StreamId>,
70 pipelines: CompilationCache<KernelId, (Arc<ComputePipeline>, CompilerInfo)>,
73 scheduler: SchedulerMultiStream<ScheduledWgpuBackend>,
74 #[cfg(feature = "spirv")]
75 pub(crate) spirv_cache: Option<Store<(u64, KernelCacheKey), cubecl_spirv::SpirvCacheEntry>>,
76 pub compilation_options: WgpuCompilationOptions,
77 pub(crate) backend: wgpu::Backend,
78 pub(crate) utilities: Arc<ServerUtilities<Self>>,
79 shared_bindings_pool: LeasePool<SharedMemoryBindings>,
81 _compiler: PhantomData<C>,
82}
83
84impl<C: WgpuCompiler> ServerCommunication for WgpuServer<C> {
85 const SERVER_COMM_ENABLED: bool = false;
86}
87
88impl<C: WgpuCompiler> WgpuServer<C> {
89 #[allow(clippy::too_many_arguments)]
91 pub fn new(
92 memory_properties: MemoryDeviceProperties,
93 memory_config: MemoryConfiguration,
94 compilation_options: WgpuCompilationOptions,
95 device: wgpu::Device,
96 queue: wgpu::Queue,
97 tasks_max: usize,
98 backend: wgpu::Backend,
99 timing_method: TimingMethod,
100 utilities: ServerUtilities<Self>,
101 ) -> Self {
102 #[cfg(feature = "spirv")]
103 let adapter_info = device.adapter_info();
104 let backend_scheduler = ScheduledWgpuBackend::new(
105 device.clone(),
106 queue.clone(),
107 memory_properties,
108 memory_config,
109 timing_method,
110 backend,
111 tasks_max,
112 utilities.logger.clone(),
113 compilation_options.supports_vulkan_compiler,
114 );
115
116 let config = CubeClRuntimeConfig::get();
117 let max_streams = config.streaming.max_streams;
118
119 #[cfg(feature = "spirv")]
120 let spirv_cache = compilation_store(
121 "vulkan",
122 format!("spirv_{}_{}", adapter_info.vendor, adapter_info.device),
123 );
124
125 #[cfg(feature = "spirv")]
128 let pipelines = CompilationCache::mirroring(&spirv_cache);
129 #[cfg(not(feature = "spirv"))]
130 let pipelines = CompilationCache::unbound();
131
132 Self {
133 compilation_options,
134 streams_pool: Vec::new(),
135 device,
136 pipelines,
137 scheduler: SchedulerMultiStream::new(
138 utilities.logger.clone(),
139 backend_scheduler,
140 SchedulerMultiStreamOptions {
141 max_streams,
142 max_tasks: tasks_max,
143 strategy: SchedulerStrategy::Interleave,
144 },
145 ),
146 #[cfg(feature = "spirv")]
147 spirv_cache,
148 backend,
149 utilities: Arc::new(utilities),
150 shared_bindings_pool: LeasePool::with_capacity(tasks_max * max_streams as usize),
151 _compiler: PhantomData,
152 }
153 }
154
155 fn prepare_bindings(
156 &mut self,
157 bindings: KernelArguments,
158 compiler_info: CompilerInfo,
159 ) -> Result<BindingsResource, IoError> {
160 let mut resources = Vec::with_capacity(bindings.buffers.len());
163
164 for b in bindings.buffers.into_iter() {
165 let stream = self.scheduler.stream(&b.stream);
166 let resource = stream.mem_manage.get_resource(b)?;
167 resources.push(resource);
168 }
169
170 Ok(BindingsResource {
171 resources,
172 info: bindings.info,
173 compiler_info,
174 })
175 }
176
177 fn pipeline(
178 &mut self,
179 kernel: <Self as ComputeServer>::Kernel,
180 bindings: &KernelArguments,
181 mode: ExecutionMode,
182 ) -> Result<(Arc<ComputePipeline>, CompilerInfo), LaunchError> {
183 let mut kernel_id = kernel.id();
184 kernel_id.mode(mode);
185
186 if let Some(pipeline) = self.pipelines.get(&kernel_id) {
187 return Ok(pipeline.clone());
188 }
189
190 let definition = kernel.define();
191 let cached = self.load_cached_pipeline(&kernel_id, &definition, bindings, mode)?;
192
193 if let Some(Ok(pipeline)) = cached {
194 self.pipelines.insert(kernel_id, pipeline.clone());
195 return Ok(pipeline);
196 }
197
198 validate_cube_dim(&self.utilities.properties, &kernel_id)?;
199 validate_units(&self.utilities.properties, &kernel_id)?;
200
201 let mut compiler = C::init(self.backend, &self.compilation_options);
202 let mut compiled = compiler.compile_kernel(self, kernel, definition, mode)?;
203
204 if self.scheduler.logger.compilation_source_activated() {
205 compiled.debug_info = Some(DebugInformation::new(
206 compiler.lang_tag(),
207 kernel_id.clone(),
208 ));
209 }
210 self.scheduler.logger.log_compilation(&compiled);
211
212 compiler.validate_ir(&compiled.repr, &self.utilities.properties)?;
213 let (compiler_info, auto_repr) = compiler.normalize_repr(compiled.repr);
214 let repr = auto_repr.as_ref().map(|r| r.as_ref());
215
216 let module = self.create_module(
243 &compiled.entrypoint_name,
244 kernel_id.cube_dim,
245 repr,
246 &compiled.source,
247 mode,
248 )?;
249 let pipeline = self.create_pipeline(&compiled.entrypoint_name, repr, module, bindings);
250 self.pipelines
251 .insert(kernel_id.clone(), (pipeline.clone(), compiler_info));
252
253 #[cfg(feature = "spirv")]
254 if let Some(Err(key)) = cached
255 && let Some(crate::AutoRepresentation::SpirV(kernel)) = auto_repr
256 {
257 let cache = self.spirv_cache.as_mut().unwrap();
258 store_compiled(
259 cache,
260 key,
261 cubecl_spirv::SpirvCacheEntry::new(compiled.entrypoint_name, kernel),
262 );
263 }
264
265 Ok((pipeline, compiler_info))
266 }
267}
268
269impl<C: WgpuCompiler> ComputeServer for WgpuServer<C> {
270 type Kernel = Box<dyn CubeTask<C>>;
271 type Storage = WgpuStorage;
272 type MemoryLayoutPolicy = ContiguousMemoryLayoutPolicy;
273 type Info = wgpu::Backend;
274
275 fn logger(&self) -> Arc<ServerLogger> {
276 self.scheduler.logger.clone()
277 }
278
279 fn utilities(&self) -> Arc<ServerUtilities<Self>> {
280 self.utilities.clone()
281 }
282
283 fn staging(
284 &mut self,
285 _sizes: &[usize],
286 _stream_id: StreamId,
287 ) -> Result<Vec<Bytes>, ServerError> {
288 Err(IoError::UnsupportedIoOperation {
290 backtrace: BackTrace::capture(),
291 }
292 .into())
293 }
294
295 fn initialize_memory(&mut self, memory: ManagedMemoryHandle, size: u64, stream_id: StreamId) {
296 let stream = self.scheduler.stream(&stream_id);
297 let reserved = stream
298 .empty(size)
299 .unwrap_or_else(|err| panic!("failed to reserve {size} bytes of device memory: {err}"));
300 stream.mem_manage.bind(reserved, memory);
301 }
302
303 fn read(
304 &mut self,
305 descriptors: Vec<CopyDescriptor>,
306 stream_id: StreamId,
307 ) -> DynFut<Result<Vec<Bytes>, ServerError>> {
308 let mut streams = vec![stream_id];
309 let mut resources = Vec::with_capacity(descriptors.len());
310 for desc in descriptors {
311 if contiguous_strides(&desc.shape) != desc.strides {
312 return Box::pin(async {
313 Err(IoError::UnsupportedStrides {
314 backtrace: BackTrace::capture(),
315 }
316 .into())
317 });
318 }
319 if !streams.contains(&desc.handle.stream) {
320 streams.push(desc.handle.stream);
321 }
322 let stream = self.scheduler.stream(&desc.handle.stream);
323 let resource = match stream.mem_manage.get_resource(desc.handle) {
324 Ok(val) => val,
325 Err(err) => return Box::pin(async move { Err(err.into()) }),
326 };
327 resources.push((resource, desc.shape, desc.elem_size));
328 }
329
330 self.scheduler.execute_streams(streams);
331
332 let stream = self.scheduler.stream(&stream_id);
333 stream.read_resources(resources)
334 }
335
336 fn write(&mut self, descriptors: Vec<(CopyDescriptor, Bytes)>, stream_id: StreamId) {
337 for (desc, data) in descriptors {
338 let stream = self.scheduler.stream(&desc.handle.stream);
339
340 if contiguous_strides(&desc.shape) != desc.strides {
341 stream.error(ServerError::Io(IoError::UnsupportedStrides {
342 backtrace: BackTrace::capture(),
343 }));
344 return;
345 }
346
347 let resource = match stream.mem_manage.get_resource(desc.handle) {
348 Ok(r) => r,
349 Err(err) => {
350 stream.error(ServerError::Io(err));
351 return;
352 }
353 };
354 let task = ScheduleTask::Write {
355 data,
356 buffer: resource,
357 };
358
359 self.scheduler.register(stream_id, task, &[]);
360 }
361 }
362
363 fn get_resource(
364 &mut self,
365 binding: Binding,
366 stream_id: StreamId,
367 ) -> Result<ManagedResource<WgpuResource>, ServerError> {
368 let mut streams = vec![stream_id];
369 if binding.stream != stream_id {
370 streams.push(binding.stream);
371 }
372 self.scheduler.execute_streams(streams);
373 let stream = self.scheduler.stream(&binding.stream);
374 let memory = binding.memory.clone();
375 let resource = stream.mem_manage.get_resource(binding)?;
376
377 Ok(ManagedResource::new(memory, resource))
378 }
379
380 unsafe fn launch(
381 &mut self,
382 kernel: Self::Kernel,
383 count: CubeCount,
384 args: KernelArguments,
385 mode: ExecutionMode,
386 stream_id: StreamId,
387 launch_mode: LaunchMode,
388 ) {
389 let (pipeline, compiler_info) = match self.pipeline(kernel, &args, mode) {
390 Ok(val) => val,
391 Err(err) => {
392 let stream = self.scheduler.stream(&stream_id);
394 stream.errors.push(ServerError::Launch(err));
395 return;
396 }
397 };
398
399 if launch_mode.is_skipped() {
400 return;
401 }
402
403 self.streams_pool.clear();
404 let mut shared_inputs = self.shared_bindings_pool.acquire();
407 args.buffers.iter().for_each(|b| {
409 self.streams_pool.push(b.stream);
410 if b.stream != stream_id {
411 shared_inputs.push(b.memory.clone());
412 }
413 });
414
415 let resources = match self.prepare_bindings(args, compiler_info) {
416 Ok(val) => val,
417 Err(err) => {
418 let stream = self.scheduler.stream(&stream_id);
420 stream.errors.push(ServerError::Io(err));
421 return;
422 }
423 };
424 let task = ScheduleTask::Execute {
425 pipeline,
426 count,
427 resources,
428 shared_inputs,
429 };
430
431 self.scheduler.register(stream_id, task, &self.streams_pool);
432 }
433
434 fn flush(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
435 self.scheduler.execute_streams(vec![stream_id]);
436
437 let stream = self.scheduler.stream(&stream_id);
438
439 stream.flush(StreamErrorMode {
440 ignore: false,
441 flush: true,
442 })
443 }
444
445 fn sync(&mut self, stream_id: StreamId) -> DynFut<Result<(), ServerError>> {
447 self.scheduler.execute_streams(vec![stream_id]);
448 let stream = self.scheduler.stream(&stream_id);
449
450 stream.sync()
451 }
452
453 fn start_profile(&mut self, stream_id: StreamId) -> Result<ProfilingToken, ServerError> {
454 self.scheduler.execute_streams(vec![stream_id]);
455 let stream = self.scheduler.stream(&stream_id);
456 stream.start_profile()
457 }
458
459 fn end_profile(
460 &mut self,
461 stream_id: StreamId,
462 token: ProfilingToken,
463 ) -> Result<ProfileDuration, ProfileError> {
464 self.scheduler.execute_streams(vec![stream_id]);
465 let stream = self.scheduler.stream(&stream_id);
466
467 stream.end_profile(token)
468 }
469
470 fn memory_usage(&mut self, stream_id: StreamId) -> Result<MemoryUsage, ServerError> {
471 self.scheduler.execute_streams(vec![stream_id]);
472 let stream = self.scheduler.stream(&stream_id);
473 Ok(stream.mem_manage.memory_usage())
474 }
475
476 fn stream_ids(&self) -> Vec<StreamId> {
477 self.scheduler.stream_ids().collect()
478 }
479
480 fn memory_cleanup(&mut self, stream_id: StreamId) {
481 self.scheduler.execute_streams(vec![stream_id]);
482 let stream = self.scheduler.stream(&stream_id);
483 stream.mem_manage.memory_cleanup(true);
484 }
485
486 fn allocation_mode(&mut self, mode: MemoryAllocationMode, stream_id: StreamId) {
487 self.scheduler.execute_streams(vec![stream_id]);
488 let stream = self.scheduler.stream(&stream_id);
489 stream.mem_manage.mode(mode);
490 }
491
492 fn configure_memory_pools(&mut self, config: MemoryConfiguration, stream_id: StreamId) -> bool {
493 self.scheduler
496 .backend_mut()
497 .factory()
498 .set_gpu_pools(config.clone());
499 let (_, props) = self.scheduler.backend_mut().factory().gpu_pools();
500
501 self.scheduler.execute_streams(vec![stream_id]);
504 let stream = self.scheduler.stream(&stream_id);
505 stream.mem_manage.configure_memory_pools(config, &props)
506 }
507}
508
509pub(crate) fn contiguous_strides(shape: &Shape) -> Strides {
510 let rank = shape.len();
511 let mut strides = strides![1; rank];
512 for i in (0..rank - 1).rev() {
513 strides[i] = strides[i + 1] * shape[i + 1];
514 }
515 strides
516}