1use cubecl_core::server::{DeviceCaptures, ServerStorage};
2use cubecl_server::kernel::BufferIOAttr;
3use std::collections::HashMap;
4use std::marker::PhantomData;
5
6use super::graph::WgpuGraph;
7use super::storage::{WgpuResource, WgpuStorage};
8use crate::WgpuCompiler;
9use crate::compute::artifact::WgpuArtifactCompiler;
10use crate::compute::pipelines::{MetadataLayout, WgpuPipelines};
11use crate::schedule::{BindingsResource, ScheduleTask, ScheduledWgpuBackend};
12use alloc::sync::Arc;
13use cubecl_common::pool::LeasePool;
14use cubecl_common::{
15 bytes::Bytes,
16 profile::{ProfileDuration, TimingMethod},
17};
18use cubecl_core::server::{BufferBinding, KernelResource};
19use cubecl_core::zspace::Shape;
20use cubecl_core::{
21 MemoryConfiguration, WgpuCompilationOptions,
22 prelude::*,
23 server::{
24 CopyDescriptor, IoError, KernelArguments, ProfileError, ProfilingToken,
25 ServerCommunication, ServerError, ServerUtilities,
26 },
27 zspace::{Strides, strides},
28};
29use cubecl_environment::backtrace::BackTrace;
30use cubecl_environment::future::DynFut;
31use cubecl_environment::stream::StreamId;
32use cubecl_ir::MemoryDeviceProperties;
33#[cfg(feature = "spirv")]
34use cubecl_server::compiler::compilation_store;
35use cubecl_server::compiler::{ArtifactId, KernelLoader};
36use cubecl_server::memory_management::{
37 ManagedMemoryHandle, SharedMemoryBindings, StreamMemoryReport,
38 relocation::{RelocatingStreams, RelocationReason},
39};
40use cubecl_server::{
41 config::{CubeClRuntimeConfig, RuntimeConfig},
42 execution::LaunchMode,
43 id::GraphId,
44 kernel::CubeKernel,
45 logging::ServerLogger,
46 memory_management::MemoryAllocationMode,
47 server::Server,
48 storage::ManagedResource,
49 stream::scheduler::{SchedulerMultiStream, SchedulerMultiStreamOptions, SchedulerStrategy},
50 stream::{ExecuteScope, FailureStore, StreamCapture, WriteScoped, failed_writing},
51};
52use wgpu::ComputePipeline;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum ParamsTransfer {
56 Immediate,
57 Uniform,
58}
59
60#[derive(Debug, Clone)]
62pub struct WgpuCompiledKernel {
63 pub pipeline: Arc<ComputePipeline>,
65 pub compiler_info: CompilerInfo,
67 pub io: Option<Arc<[BufferIOAttr]>>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum CompilerInfo {
76 Vulkan { params_transfer: ParamsTransfer },
77 Metal,
78 WGSL,
79 None,
80}
81
82#[derive(Debug)]
84pub struct WgpuServer<C: WgpuCompiler> {
85 streams_pool: Vec<StreamId>,
87 pipelines: KernelLoader<WgpuPipelines<C>>,
89 scheduler: SchedulerMultiStream<ScheduledWgpuBackend>,
90 pub(crate) utilities: Arc<ServerUtilities>,
91 shared_bindings_pool: LeasePool<SharedMemoryBindings>,
93 graphs: HashMap<GraphId, WgpuGraph>,
97 _compiler: PhantomData<C>,
98}
99
100impl<C: WgpuCompiler> ServerCommunication for WgpuServer<C> {}
101
102impl<C: WgpuCompiler> WriteScoped for WgpuServer<C> {
103 type Streams = SchedulerMultiStream<ScheduledWgpuBackend>;
104
105 fn write_streams(&mut self) -> &mut Self::Streams {
106 &mut self.scheduler
107 }
108
109 fn on_failure(&mut self, stream: StreamId, error: &ServerError) {
110 self.scheduler.stream(&stream).profile_failure(error);
113 }
114
115 fn capturing(&mut self, stream: StreamId) -> Option<&mut StreamCapture> {
116 Some(&mut self.scheduler.stream(&stream).capturing)
117 }
118}
119
120impl<C: WgpuCompiler> WgpuServer<C> {
121 #[allow(clippy::too_many_arguments)]
123 pub fn new(
124 memory_properties: MemoryDeviceProperties,
125 memory_config: MemoryConfiguration,
126 compilation_options: WgpuCompilationOptions,
127 device: wgpu::Device,
128 queue: wgpu::Queue,
129 tasks_max: usize,
130 backend: wgpu::Backend,
131 timing_method: TimingMethod,
132 utilities: ServerUtilities,
133 captures: DeviceCaptures,
134 ) -> Self {
135 #[cfg(feature = "spirv")]
136 let adapter_info = device.adapter_info();
137 let backend_scheduler = ScheduledWgpuBackend::new(
138 device.clone(),
139 queue.clone(),
140 memory_properties,
141 memory_config,
142 timing_method,
143 backend,
144 tasks_max,
145 utilities.logger.clone(),
146 compilation_options.supports_vulkan_compiler,
147 captures,
148 );
149
150 let config = CubeClRuntimeConfig::get();
151 let max_streams = config.streaming.max_streams;
152
153 let compiler = WgpuArtifactCompiler::new(
154 device,
155 (*utilities.properties).clone(),
156 compilation_options,
157 backend,
158 );
159 let pipelines = WgpuPipelines::new(
160 compiler,
161 #[cfg(feature = "spirv")]
162 compilation_store(
163 "vulkan",
164 format!("spirv_{}_{}", adapter_info.vendor, adapter_info.device),
165 ),
166 #[cfg(feature = "spirv")]
167 utilities.properties_hash,
168 );
169
170 Self {
171 streams_pool: Vec::new(),
172 pipelines: KernelLoader::new(pipelines),
173 scheduler: SchedulerMultiStream::new(
174 utilities.logger.clone(),
175 backend_scheduler,
176 SchedulerMultiStreamOptions {
177 max_streams,
178 max_tasks: tasks_max,
179 strategy: SchedulerStrategy::Interleave,
180 },
181 ),
182 utilities: Arc::new(utilities),
183 shared_bindings_pool: LeasePool::with_capacity(tasks_max * max_streams as usize),
184 graphs: HashMap::new(),
185 _compiler: PhantomData,
186 }
187 }
188
189 fn prepare_bindings(
190 &mut self,
191 stream_id: StreamId,
192 bindings: KernelArguments,
193 compiler_info: CompilerInfo,
194 ) -> Result<BindingsResource, IoError> {
195 let mut resources = Vec::with_capacity(bindings.resources.len());
198
199 for resource in bindings.resources.into_iter() {
200 match resource {
201 KernelResource::Buffer(b) => {
202 self.scheduler
204 .stream(&stream_id)
205 .capturing
206 .touch(b.stream, b.memory.descriptor().location());
207 let stream = self.scheduler.stream(&b.stream);
208 let resource = stream.resource(b)?;
209 resources.push(resource);
210 }
211 KernelResource::TensorMap(_) => panic!("Tensor map not supported in wgpu"),
212 }
213 }
214
215 Ok(BindingsResource {
216 resources,
217 info: bindings.info,
218 compiler_info,
219 })
220 }
221}
222
223impl<C: WgpuCompiler> Server for WgpuServer<C> {
224 fn logger(&self) -> Arc<ServerLogger> {
225 self.scheduler.logger.clone()
226 }
227
228 fn utilities(&self) -> Arc<ServerUtilities> {
229 self.utilities.clone()
230 }
231
232 fn staging(
233 &mut self,
234 _sizes: &[usize],
235 _stream_id: StreamId,
236 ) -> Result<Vec<Bytes>, ServerError> {
237 Err(IoError::UnsupportedIoOperation {
239 backtrace: BackTrace::capture(),
240 }
241 .into())
242 }
243
244 fn initialize_memory(
245 &mut self,
246 memory: ManagedMemoryHandle,
247 size: u64,
248 stream_id: StreamId,
249 ) -> Result<(), ServerError> {
250 let mut relocating = self.scheduler.relocating(stream_id);
251 relocating.relocate_when_wanted();
252 let (stream, failures) = self.scheduler.stream_and_failures(&stream_id);
253 let update = stream.capturing.page_update();
254 let reserved = match stream.memory.reserve(size, update, failures) {
255 Ok(reserved) => reserved,
256 Err(err @ IoError::PageUpdateForbidden { .. }) => {
260 stream.capturing.fail(err.into());
261 return Ok(());
262 }
263 Err(err) => {
264 let err = ServerError::from(err);
266 failures.fail_unallocated(&memory, err.clone());
267 return Err(err);
268 }
269 };
270 if let Err(err) = stream.memory.bind(reserved, memory.clone(), 0, failures) {
271 let err = ServerError::from(err);
272 failures.fail_unallocated(&memory, err.clone());
273 return Err(err);
274 }
275 Ok(())
276 }
277
278 fn read(
279 &mut self,
280 descriptors: Vec<CopyDescriptor>,
281 stream_id: StreamId,
282 ) -> DynFut<Result<Vec<Bytes>, ServerError>> {
283 if let Err(err) = self
286 .scheduler
287 .stream(&stream_id)
288 .reject_while_recording("read")
289 {
290 return Box::pin(async move { Err(err) });
291 }
292
293 if let Err(err) = self
297 .scheduler
298 .ensure_written(descriptors.iter().map(|d| &d.handle))
299 {
300 return Box::pin(async move { Err(err) });
301 }
302
303 let mut streams = vec![stream_id];
304 let mut resources = Vec::with_capacity(descriptors.len());
305 for desc in descriptors {
306 if contiguous_strides(&desc.shape) != desc.strides {
307 return Box::pin(async {
308 Err(IoError::UnsupportedStrides {
309 backtrace: BackTrace::capture(),
310 }
311 .into())
312 });
313 }
314 if !streams.contains(&desc.handle.stream) {
315 streams.push(desc.handle.stream);
316 }
317 let stream = self.scheduler.stream(&desc.handle.stream);
318 let resource = match stream.resource(desc.handle) {
319 Ok(val) => val,
320 Err(err) => return Box::pin(async move { Err(err.into()) }),
321 };
322 resources.push((resource, desc.shape, desc.elem_size));
323 }
324
325 self.scheduler.execute_streams(streams);
326
327 let stream = self.scheduler.stream(&stream_id);
328 stream.read_resources(resources, stream_id)
329 }
330
331 fn write(&mut self, descriptors: Vec<(CopyDescriptor, Bytes)>, stream_id: StreamId) {
332 {
342 let recording = self
343 .scheduler
344 .stream(&stream_id)
345 .reject_while_recording("write");
346 if let Err(err) = recording {
347 self.scheduler.taint(
352 err.clone(),
353 descriptors.iter().map(|(desc, _)| &desc.handle),
354 );
355 let stream = self.scheduler.stream(&stream_id);
360 if stream.capturing.owner() == Some(stream_id) {
361 stream.capturing.fail(err);
362 }
363 return;
364 }
365 }
366 for (desc, data) in descriptors {
367 let mut written = self.write_set();
376 written.push(desc.handle.clone());
377 ExecuteScope::over(self, stream_id, written).execute(|server| {
378 if contiguous_strides(&desc.shape) != desc.strides {
379 return Err(ServerError::Io(IoError::UnsupportedStrides {
380 backtrace: BackTrace::capture(),
381 }));
382 }
383
384 let owner = desc.handle.stream;
389 let handle = desc.handle.clone();
390 let stream = server.scheduler.stream(&owner);
391 let resource = stream.resource(desc.handle).map_err(ServerError::Io)?;
392 let task = ScheduleTask::Write {
393 data,
394 buffer: resource,
395 handle,
396 };
397
398 server.scheduler.register(stream_id, task, &[owner]);
399 Ok(())
400 });
401 }
402 }
403
404 fn check(
405 &mut self,
406 handles: Vec<BufferBinding>,
407 _stream_id: StreamId,
408 ) -> Result<(), ServerError> {
409 self.scheduler.ensure_written(handles.iter())
410 }
411
412 unsafe fn launch(
413 &mut self,
414 kernel: Box<dyn CubeKernel>,
415 count: CubeCount,
416 args: KernelArguments,
417 stream_id: StreamId,
418 launch_mode: LaunchMode,
419 ) {
420 let layout = MetadataLayout::from(&args.info);
433 if launch_mode == LaunchMode::Queue {
434 self.pipelines.enqueue(kernel, layout);
435 return;
436 }
437 let id = ArtifactId {
438 kernel: kernel.id(),
439 variant: layout,
440 };
441 let loaded = self.pipelines.load(&*kernel, &id, &self.scheduler.logger);
442 let WgpuCompiledKernel {
443 pipeline,
444 compiler_info,
445 io,
446 } = match loaded {
447 Ok(entry) => entry,
448 Err(err) => {
449 let error = ServerError::Launch(err);
450 self.scheduler.stream(&stream_id).profile_failure(&error);
451 if !launch_mode.discards_launch() {
452 let mut written = self.write_set();
453 written.extend(args.buffers_written(None).cloned());
454 failed_writing(self, stream_id, written, error);
455 }
456 return;
457 }
458 };
459 if launch_mode.discards_launch() {
460 return;
461 }
462 let kernel_id = id.kernel;
463
464 let mut written = self.write_set();
482 written.extend(args.buffers_written(io.as_deref()).cloned());
483 let count_read = match &count {
488 CubeCount::Dynamic(binding) => Some(binding),
489 CubeCount::Static(..) => None,
490 };
491 ExecuteScope::launching(
492 self,
493 kernel_id,
494 stream_id,
495 args.buffers_read(io.as_deref()).chain(count_read),
496 written,
497 )
498 .execute(|server| {
499 server.streams_pool.clear();
500 let mut shared_inputs = server.shared_bindings_pool.acquire();
503 args.resources.iter().for_each(|resource| match resource {
505 KernelResource::Buffer(b) => {
506 server.streams_pool.push(b.stream);
507 if b.stream != stream_id {
508 shared_inputs.push(b.memory.clone());
509 }
510 }
511 KernelResource::TensorMap(_) => {
512 panic!("Tensor maps not supported in WGPU")
513 }
514 });
515
516 let resources = server
517 .prepare_bindings(stream_id, args, compiler_info)
518 .map_err(ServerError::Io)?;
519
520 let task = ScheduleTask::Execute {
521 pipeline,
522 count,
523 resources,
524 shared_inputs,
525 };
526
527 server
528 .scheduler
529 .register(stream_id, task, &server.streams_pool);
530 Ok(())
531 });
532 }
533
534 fn compile_queued(&mut self) {
535 self.pipelines.compile_queued(&self.scheduler.logger);
536 }
537
538 fn flush(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
539 self.scheduler.execute_streams(vec![stream_id]);
540
541 let stream = self.scheduler.stream(&stream_id);
542
543 stream.flush(stream_id)
544 }
545
546 fn sync(
548 &mut self,
549 handles: Vec<BufferBinding>,
550 stream_id: StreamId,
551 ) -> DynFut<Result<(), ServerError>> {
552 if let Err(err) = self
553 .scheduler
554 .stream(&stream_id)
555 .reject_while_recording("sync")
556 {
557 return Box::pin(async move { Err(err) });
558 }
559 if let Err(err) = self.scheduler.ensure_written(handles.iter()) {
562 return Box::pin(async move { Err(err) });
563 }
564 self.scheduler.execute_streams(vec![stream_id]);
565 let stream = self.scheduler.stream(&stream_id);
566
567 stream.sync(stream_id)
568 }
569
570 fn start_profile(&mut self, stream_id: StreamId) -> Result<ProfilingToken, ServerError> {
571 self.scheduler
574 .stream(&stream_id)
575 .reject_while_recording("start_profile")?;
576 self.scheduler.execute_streams(vec![stream_id]);
577 let stream = self.scheduler.stream(&stream_id);
578 stream.start_profile(stream_id)
579 }
580
581 fn end_profile(
582 &mut self,
583 stream_id: StreamId,
584 token: ProfilingToken,
585 ) -> Result<ProfileDuration, ProfileError> {
586 self.scheduler.execute_streams(vec![stream_id]);
587 let stream = self.scheduler.stream(&stream_id);
588
589 stream.end_profile(token, stream_id)
590 }
591
592 fn abandon_profile(&mut self, stream_id: StreamId, token: ProfilingToken) {
593 self.scheduler.stream(&stream_id).abandon_profile(token);
597 }
598
599 fn memory_report(&mut self, stream_id: StreamId) -> Option<StreamMemoryReport> {
600 self.scheduler.execute_streams(vec![stream_id]);
601 let stream = self.scheduler.stream(&stream_id);
602 Some(StreamMemoryReport {
603 stream: stream_id,
604 pools: stream.memory.memory_report(),
605 auxiliary: stream.auxiliary.report(),
606 })
607 }
608
609 fn stream_ids(&self) -> Vec<StreamId> {
610 self.scheduler.stream_ids().collect()
611 }
612
613 fn memory_cleanup(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
614 self.scheduler.execute_streams(vec![stream_id]);
615 self.scheduler
616 .relocating(stream_id)
617 .refuse_while_recording()?;
618 let stream = self.scheduler.stream(&stream_id);
619 stream.info_cache.clear_unpinned();
624 self.scheduler.relocating(stream_id).reclaim()
625 }
626
627 fn allocation_mode(&mut self, mode: MemoryAllocationMode, stream_id: StreamId) {
628 self.scheduler.execute_streams(vec![stream_id]);
629 let stream = self.scheduler.stream(&stream_id);
630 stream.memory.mode(mode);
631 }
632
633 fn graph_prepare(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
634 self.scheduler.execute_streams(vec![stream_id]);
637 let stream = self.scheduler.stream(&stream_id);
638
639 stream.capturing.prepare(stream_id)?;
642
643 self.scheduler
647 .relocating(stream_id)
648 .relocate_device(RelocationReason::Capture);
649 Ok(())
650 }
651
652 fn begin_capture(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
653 self.scheduler.execute_streams(vec![stream_id]);
656 let stream = self.scheduler.stream(&stream_id);
657
658 stream.capturing.begin()?;
659
660 let stream = self.scheduler.stream(&stream_id);
664 if let Err(err) = stream.flush(stream_id) {
665 stream.info_cache.capture_discard();
669 stream.capturing.abort();
670 return Err(err);
671 }
672 Ok(())
673 }
674
675 fn end_capture(&mut self, stream_id: StreamId) -> Result<GraphId, ServerError> {
676 self.scheduler.execute_streams(vec![stream_id]);
678 let stream = self.scheduler.stream(&stream_id);
679
680 let outcome = match stream.capturing.end(stream_id) {
687 Ok(outcome) => outcome,
688 Err(err) => {
689 if stream.capturing.is_active() {
696 stream.info_cache.capture_discard();
697 stream.capturing.abort();
698 }
699 return Err(err);
700 }
701 };
702 let recording = stream.take_recording();
703 let written = stream.capturing.take_recorded();
707 let touched = stream.capturing.take_touched();
708
709 let doomed = stream.capturing.take_failure().map(|reason| {
715 ServerError::graph_state(format!(
716 "an operation inside the capture window failed, so the recording is missing \
717 an operation and cannot seal: {reason}"
718 ))
719 });
720 let discarded = match outcome.is_abandoned() {
721 true => Some(outcome.abandoned_error(stream_id, doomed)),
722 false => doomed,
723 };
724 if let Some(err) = discarded {
725 stream.info_cache.capture_discard();
726 self.scheduler.taint(err.clone(), written.iter());
731 return Err(err);
732 }
733
734 let id = GraphId::new();
735 stream.info_cache.capture_commit(id);
738 let guards = touched
741 .iter()
742 .filter_map(|page| {
743 self.scheduler
744 .stream(&page.stream)
745 .memory
746 .guard(page.location)
747 })
748 .collect();
749 self.graphs.insert(
750 id,
751 WgpuGraph {
752 tasks: recording.tasks,
753 _guards: guards,
754 _uniforms: recording.uniform_pins,
755 _shared: recording.shared,
756 written,
757 },
758 );
759 Ok(id)
760 }
761
762 fn replay(&mut self, graph: GraphId, stream_id: StreamId) -> Result<(), ServerError> {
763 self.scheduler.execute_streams(vec![stream_id]);
765
766 let Some(wgpu_graph) = self.graphs.get(&graph) else {
771 return Err(ServerError::graph_state(
772 "replay was given an unknown or already-destroyed graph",
773 ));
774 };
775
776 let recorded = wgpu_graph.written.clone();
784 let mut written = self.write_set();
785 written.extend(recorded);
786 ExecuteScope::over(self, stream_id, written)
787 .execute(|server| {
788 server
789 .scheduler
790 .stream(&stream_id)
791 .reject_while_recording("replay")?;
792 let wgpu_graph = server
793 .graphs
794 .get(&graph)
795 .expect("checked above; nothing in the scope removes graphs");
796 let stream = server.scheduler.stream(&stream_id);
797 stream.replay_graph(wgpu_graph);
798 Ok(())
799 })
800 .into_result()
801 }
802
803 fn graph_destroy(&mut self, graph: GraphId, stream_id: StreamId) {
804 let Some(wgpu_graph) = self.graphs.remove(&graph) else {
807 return;
808 };
809 let stream = self.scheduler.stream(&stream_id);
810 stream.submit();
819 stream.info_cache.graph_release(graph);
822 drop(wgpu_graph);
823 }
824}
825
826pub(crate) fn contiguous_strides(shape: &Shape) -> Strides {
827 let rank = shape.len();
828 let mut strides = strides![1; rank];
829 for i in (0..rank - 1).rev() {
830 strides[i] = strides[i + 1] * shape[i + 1];
831 }
832 strides
833}
834
835impl<C: WgpuCompiler> ServerStorage for WgpuServer<C> {
836 type Storage = WgpuStorage;
837
838 fn get_resource(
839 &mut self,
840 binding: BufferBinding,
841 stream_id: StreamId,
842 ) -> Result<ManagedResource<WgpuResource>, ServerError> {
843 self.scheduler.ensure_written([&binding].into_iter())?;
847 let mut streams = vec![stream_id];
848 if binding.stream != stream_id {
849 streams.push(binding.stream);
850 }
851 self.scheduler.execute_streams(streams);
852 let stream = self.scheduler.stream(&binding.stream);
853 Ok(stream.memory.managed_resource(
854 binding.memory,
855 binding.offset_start,
856 binding.offset_end,
857 )?)
858 }
859}