1use crate::{
2 config::memory::MemoryPoolsConfig,
3 config::{TypeNameFormatLevel, type_name_format},
4 id::GraphId,
5 kernel::KernelMetadata,
6 logging::ProfileLevel,
7 memory_management::{MemoryAllocationMode, MemoryConfiguration, MemoryUsage},
8 runtime::Runtime,
9 server::{
10 CommunicationId, ComputeServer, CopyDescriptor, CubeCount, ExecutionMode, Handle, IoError,
11 KernelArguments, MemoryLayout, MemoryLayoutDescriptor, MemoryLayoutPolicy,
12 MemoryLayoutStrategy, ProfileError, ReduceOperation, ServerCommunication, ServerError,
13 ServerUtilities,
14 },
15 storage::{ComputeStorage, ManagedResource},
16 throughput::{
17 KernelConfig, ThroughputBenchmarker, ThroughputCache, ThroughputKey, ThroughputValue,
18 },
19};
20use alloc::{format, string::String, sync::Arc, vec, vec::Vec};
21
22#[cfg(not(target_family = "wasm"))]
23mod lazy;
24use cubecl_common::{
25 backtrace::BackTrace,
26 bytes::{AllocationProperty, Bytes},
27 device::{Device, DeviceId},
28 device_handle::{CallResultExt, DeviceHandle},
29 future::DynFut,
30 profile::ProfileDuration,
31};
32use cubecl_ir::{DeviceProperties, ElemType, VectorSize, features::Features};
33use cubecl_zspace::Shape;
34
35#[allow(unused)]
36use cubecl_common::profile::TimingMethod;
37use cubecl_common::stream_id::StreamId;
38
39pub struct ComputeClient<R: Runtime> {
42 device: DeviceHandle<R::Server>,
43 utilities: Arc<ServerUtilities<R::Server>>,
44 stream_id: Option<StreamId>,
45}
46
47pub struct Graph<R: Runtime> {
68 inner: Arc<GraphHandle<R>>,
69}
70
71impl<R: Runtime> core::fmt::Debug for Graph<R> {
72 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
73 f.debug_struct("Graph")
74 .field("id", &self.inner.id)
75 .field("stream_id", &self.inner.stream_id)
76 .finish()
77 }
78}
79
80struct GraphHandle<R: Runtime> {
84 id: GraphId,
85 device: DeviceHandle<R::Server>,
86 stream_id: StreamId,
87}
88
89impl<R: Runtime> Graph<R> {
90 pub unsafe fn replay(&self) {
120 let id = self.inner.id;
121 let stream_id = self.inner.stream_id;
122 self.inner
123 .device
124 .submit(move |server| server.replay(id, stream_id));
125 }
126}
127
128impl<R: Runtime> Clone for Graph<R> {
129 fn clone(&self) -> Self {
130 Self {
131 inner: self.inner.clone(),
132 }
133 }
134}
135
136impl<R: Runtime> Drop for GraphHandle<R> {
137 fn drop(&mut self) {
138 let id = self.id;
139 let stream_id = self.stream_id;
140 self.device
145 .submit(move |server| server.graph_destroy(id, stream_id));
146 }
147}
148
149impl<R: Runtime> Clone for ComputeClient<R> {
150 fn clone(&self) -> Self {
151 Self {
152 device: self.device.clone(),
153 utilities: self.utilities.clone(),
154 stream_id: self.stream_id,
155 }
156 }
157}
158
159impl<R: Runtime> ComputeClient<R> {
160 pub fn info(&self) -> &<R::Server as ComputeServer>::Info {
162 &self.utilities.info
163 }
164
165 pub fn init<D: Device>(device: &D, server: R::Server) -> Self {
167 let utilities = server.utilities();
168 let context = DeviceHandle::<R::Server>::insert(device.to_id(), server)
169 .expect("Can't create a new client on an already registered server");
170
171 Self {
172 device: context,
173 utilities,
174 stream_id: None,
175 }
176 }
177
178 pub fn load<D: Device>(device: &D) -> Self {
180 let context = DeviceHandle::<R::Server>::new(device.to_id());
181
182 let utilities = context
184 .utilities()
185 .downcast::<ServerUtilities<R::Server>>()
186 .expect("Can downcast to `ServerUtilities`");
187
188 Self {
189 device: context,
190 utilities,
191 stream_id: None,
192 }
193 }
194
195 fn stream_id(&self) -> StreamId {
196 match self.stream_id {
197 Some(val) => val,
198 None => StreamId::current(),
199 }
200 }
201
202 pub unsafe fn set_stream(&mut self, stream_id: StreamId) {
208 self.stream_id = Some(stream_id);
209 }
210
211 fn do_read(&self, descriptors: Vec<CopyDescriptor>) -> DynFut<Result<Vec<Bytes>, ServerError>> {
212 let stream_id = self.stream_id();
213 self.device
214 .submit_blocking(move |server| server.read(descriptors, stream_id))
215 .unwrap_or_resume()
216 }
217
218 pub fn read_async(
220 &self,
221 handles: Vec<Handle>,
222 ) -> impl Future<Output = Result<Vec<Bytes>, ServerError>> + Send {
223 let shapes = handles
224 .iter()
225 .map(|it| [it.size_in_used() as usize].into())
226 .collect::<Vec<Shape>>();
227 let descriptors = handles
228 .into_iter()
229 .zip(shapes)
230 .map(|(handle, shape)| CopyDescriptor::new(handle.binding(), shape, [1].into(), 1))
231 .collect();
232
233 self.do_read(descriptors)
234 }
235
236 pub fn read(&self, handles: Vec<Handle>) -> Vec<Bytes> {
242 cubecl_common::reader::read_sync(self.read_async(handles)).expect("TODO")
243 }
244
245 pub fn read_one(&self, handle: Handle) -> Result<Bytes, ServerError> {
247 Ok(cubecl_common::reader::read_sync(self.read_async(vec![handle]))?.remove(0))
248 }
249
250 pub fn read_one_unchecked(&self, handle: Handle) -> Bytes {
256 cubecl_common::reader::read_sync(self.read_async(vec![handle]))
257 .unwrap()
258 .remove(0)
259 }
260
261 pub fn read_tensor_async(
263 &self,
264 descriptors: Vec<CopyDescriptor>,
265 ) -> impl Future<Output = Result<Vec<Bytes>, ServerError>> + Send {
266 self.do_read(descriptors)
267 }
268
269 pub fn read_tensor(&self, descriptors: Vec<CopyDescriptor>) -> Vec<Bytes> {
282 cubecl_common::reader::read_sync(self.read_tensor_async(descriptors)).expect("TODO")
283 }
284
285 pub fn read_one_tensor_async(
288 &self,
289 descriptor: CopyDescriptor,
290 ) -> impl Future<Output = Result<Bytes, ServerError>> + Send {
291 let fut = self.read_tensor_async(vec![descriptor]);
292
293 async { Ok(fut.await?.remove(0)) }
294 }
295
296 pub fn read_one_unchecked_tensor(&self, descriptor: CopyDescriptor) -> Bytes {
303 self.read_tensor(vec![descriptor]).remove(0)
304 }
305
306 #[cfg(not(target_family = "wasm"))]
316 pub fn read_lazy(&self, descriptor: CopyDescriptor) -> Bytes {
317 let len = descriptor.shape.iter().product::<usize>() * descriptor.elem_size;
318 let controller = lazy::LazyDeviceController::new(self.clone(), Arc::new(descriptor));
319 unsafe { Bytes::from_controller(alloc::boxed::Box::new(controller), len) }
321 }
322
323 #[cfg(not(target_family = "wasm"))]
328 pub fn read_lazy_async(
329 &self,
330 descriptor: CopyDescriptor,
331 ) -> impl Future<Output = Result<Bytes, ServerError>> + Send {
332 let len = descriptor.shape.iter().product::<usize>() * descriptor.elem_size;
333 let controller = lazy::LazyDeviceController::new(self.clone(), Arc::new(descriptor));
334 let bytes = unsafe { Bytes::from_controller(alloc::boxed::Box::new(controller), len) };
336 core::future::ready(Ok(bytes))
337 }
338
339 #[cfg(target_family = "wasm")]
345 pub fn read_lazy_async(
346 &self,
347 descriptor: CopyDescriptor,
348 ) -> impl Future<Output = Result<Bytes, ServerError>> + Send {
349 self.read_one_tensor_async(descriptor)
350 }
351
352 pub fn get_resource(
354 &self,
355 handle: Handle,
356 ) -> Result<
357 ManagedResource<<<R::Server as ComputeServer>::Storage as ComputeStorage>::Resource>,
358 ServerError,
359 > {
360 let stream_id = self.stream_id();
361 let binding = handle.binding();
362
363 self.device
364 .submit_blocking(move |state| state.get_resource(binding, stream_id))
365 .unwrap_or_resume()
366 }
367
368 fn do_create_from_slices(
369 &self,
370 descriptors: Vec<MemoryLayoutDescriptor>,
371 slices: Vec<Vec<u8>>,
372 ) -> Result<Vec<MemoryLayout>, IoError> {
373 let stream_id = self.stream_id();
374 let (handle_base, layouts) = self.utilities.layout_policy.apply(stream_id, &descriptors);
375
376 let descriptors = descriptors
377 .into_iter()
378 .zip(layouts.iter())
379 .zip(slices)
380 .map(|((desc, alloc), data)| {
381 (
382 CopyDescriptor::new(
383 alloc.memory.clone().binding(),
384 desc.shape,
385 alloc.strides.clone(),
386 desc.elem_size,
387 ),
388 Bytes::from_bytes_vec(data.to_vec()),
389 )
390 })
391 .collect::<Vec<_>>();
392
393 let (size, memory) = (handle_base.size(), handle_base.memory);
394 self.device.submit(move |server| {
395 server.initialize_memory(memory, size, stream_id);
396 server.write(descriptors, stream_id);
397 });
398
399 Ok(layouts)
400 }
401
402 fn do_create(
403 &self,
404 descriptors: Vec<MemoryLayoutDescriptor>,
405 data: Vec<Bytes>,
406 ) -> Result<Vec<MemoryLayout>, IoError> {
407 let stream_id = self.stream_id();
408 let (handle_base, layouts) = self.utilities.layout_policy.apply(stream_id, &descriptors);
409
410 let descriptors = descriptors
411 .into_iter()
412 .zip(layouts.iter())
413 .zip(data)
414 .map(|((desc, layout), data)| {
415 (
416 CopyDescriptor::new(
417 layout.memory.clone().binding(),
418 desc.shape,
419 layout.strides.clone(),
420 desc.elem_size,
421 ),
422 data,
423 )
424 })
425 .collect::<Vec<_>>();
426
427 let (size, memory) = (handle_base.size(), handle_base.memory);
428 self.device.submit(move |server| {
429 server.initialize_memory(memory, size, stream_id);
430 server.write(descriptors, stream_id);
431 });
432
433 Ok(layouts)
434 }
435
436 pub fn create_from_slice(&self, slice: &[u8]) -> Handle {
442 let shape: Shape = [slice.len()].into();
443
444 self.do_create_from_slices(
445 vec![MemoryLayoutDescriptor::new(
446 MemoryLayoutStrategy::Contiguous,
447 shape,
448 1,
449 )],
450 vec![slice.to_vec()],
451 )
452 .unwrap()
453 .remove(0)
454 .memory
455 }
456
457 pub fn exclusive<'a, Re: Send + 'static, F: FnOnce() -> Re + Send + 'a>(
459 &'a self,
460 task: F,
461 ) -> Result<Re, ServerError> {
462 self.device
464 .exclusive(task)
465 .map_err(|err| ServerError::Generic {
466 reason: format!("{err:?}"),
467 backtrace: BackTrace::capture(),
468 })
469 }
470
471 pub fn memory_persistent_allocation<
473 'a,
474 Re: Send,
475 Input: Send,
476 F: FnOnce(Input) -> Re + Send + 'a,
477 >(
478 &'a self,
479 input: Input,
480 task: F,
481 ) -> Result<Re, ServerError> {
482 let stream_id = StreamId::current();
483
484 self.device.submit(move |server| {
485 server.allocation_mode(MemoryAllocationMode::Persistent, stream_id);
486 });
487
488 let output = task(input);
490
491 self.device.submit(move |server| {
492 server.allocation_mode(MemoryAllocationMode::Auto, stream_id);
493 });
494
495 Ok(output)
496 }
497
498 pub fn write(&self, handle: &Handle, data: Bytes) {
508 let stream_id = self.stream_id();
509 let descriptor =
510 CopyDescriptor::new(handle.clone().binding(), [data.len()].into(), [1].into(), 1);
511 self.device.submit(move |server| {
512 server.write(vec![(descriptor, data)], stream_id);
513 });
514 }
515
516 pub fn create(&self, data: Bytes) -> Handle {
518 let shape = [data.len()].into();
519
520 self.do_create(
521 vec![MemoryLayoutDescriptor::new(
522 MemoryLayoutStrategy::Contiguous,
523 shape,
524 1,
525 )],
526 vec![data],
527 )
528 .unwrap()
529 .remove(0)
530 .memory
531 }
532
533 pub fn create_tensor_from_slice(
551 &self,
552 slice: &[u8],
553 shape: Shape,
554 elem_size: usize,
555 ) -> MemoryLayout {
556 self.do_create_from_slices(
557 vec![MemoryLayoutDescriptor::new(
558 MemoryLayoutStrategy::Optimized,
559 shape,
560 elem_size,
561 )],
562 vec![slice.to_vec()],
563 )
564 .unwrap()
565 .remove(0)
566 }
567
568 pub fn create_tensor(&self, bytes: Bytes, shape: Shape, elem_size: usize) -> MemoryLayout {
582 self.do_create(
583 vec![MemoryLayoutDescriptor::new(
584 MemoryLayoutStrategy::Optimized,
585 shape,
586 elem_size,
587 )],
588 vec![bytes],
589 )
590 .unwrap()
591 .remove(0)
592 }
593
594 pub fn create_tensors_from_slices(
602 &self,
603 descriptors: Vec<(MemoryLayoutDescriptor, &[u8])>,
604 ) -> Vec<MemoryLayout> {
605 let mut data = Vec::with_capacity(descriptors.len());
606 let mut descriptors_ = Vec::with_capacity(descriptors.len());
607 for (a, b) in descriptors {
608 data.push(b.to_vec());
609 descriptors_.push(a);
610 }
611
612 self.do_create_from_slices(descriptors_, data).unwrap()
613 }
614
615 pub fn create_tensors(
619 &self,
620 descriptors: Vec<(MemoryLayoutDescriptor, Bytes)>,
621 ) -> Vec<MemoryLayout> {
622 let (descriptors, data) = descriptors.into_iter().unzip();
623
624 self.do_create(descriptors, data).unwrap()
625 }
626
627 fn do_empty(
628 &self,
629 descriptors: Vec<MemoryLayoutDescriptor>,
630 ) -> Result<Vec<MemoryLayout>, IoError> {
631 let stream_id = self.stream_id();
632 let (handle_base, layouts) = self.utilities.layout_policy.apply(stream_id, &descriptors);
633
634 let (size, memory) = (handle_base.size(), handle_base.memory);
635 self.device.submit(move |server| {
636 server.initialize_memory(memory, size, stream_id);
637 });
638
639 Ok(layouts)
640 }
641
642 pub fn empty(&self, size: usize) -> Handle {
644 let shape: Shape = [size].into();
645 let descriptor = MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Contiguous, shape, 1);
646 self.do_empty(vec![descriptor]).unwrap().remove(0).memory
647 }
648
649 pub fn empty_tensor(&self, shape: Shape, elem_size: usize) -> MemoryLayout {
652 let descriptor =
653 MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Optimized, shape, elem_size);
654 self.do_empty(vec![descriptor]).unwrap().remove(0)
655 }
656
657 pub fn empty_tensors(&self, descriptors: Vec<MemoryLayoutDescriptor>) -> Vec<MemoryLayout> {
660 self.do_empty(descriptors).unwrap()
661 }
662
663 pub fn staging<'a, I>(&self, bytes: I, file_only: bool)
668 where
669 I: Iterator<Item = &'a mut Bytes>,
670 {
671 let has_staging = |b: &Bytes| match b.property() {
672 AllocationProperty::Pinned => false,
673 AllocationProperty::File => true,
674 AllocationProperty::Device => false,
677 AllocationProperty::Native | AllocationProperty::Other => !file_only,
678 };
679
680 let mut to_be_updated = Vec::new();
681 let sizes = bytes
682 .filter_map(|b| match has_staging(b) {
683 true => {
684 let len = b.len();
685 to_be_updated.push(b);
686 Some(len)
687 }
688 false => None,
689 })
690 .collect::<Vec<usize>>();
691
692 if sizes.is_empty() {
693 return;
694 }
695
696 let stream_id = self.stream_id();
697 let sizes = sizes.to_vec();
698 let stagings = self
699 .device
700 .submit_blocking(move |server| server.staging(&sizes, stream_id))
701 .unwrap_or_resume();
702
703 let stagings = match stagings {
704 Ok(val) => val,
705 Err(_) => return,
706 };
707
708 to_be_updated
709 .into_iter()
710 .zip(stagings)
711 .for_each(|(b, mut staging)| {
712 b.copy_into(&mut staging);
713 core::mem::swap(b, &mut staging);
714 });
715 }
716
717 #[cfg_attr(
719 feature = "tracing",
720 tracing::instrument(level = "trace", skip(self, src, dst_server))
721 )]
722 pub fn to_client(&mut self, src: Handle, dst_server: &Self, dtype: ElemType) -> Handle {
723 let shape = [src.size_in_used() as usize];
724 let src_descriptor = src.copy_descriptor(shape.into(), [1].into(), 1);
725
726 if R::Server::SERVER_COMM_ENABLED {
727 self.to_client_tensor(src_descriptor, dst_server, dtype)
728 } else {
729 let alloc_desc = MemoryLayoutDescriptor::new(
730 MemoryLayoutStrategy::Contiguous,
731 src_descriptor.shape.clone(),
732 src_descriptor.elem_size,
733 );
734 self.change_client_sync(src_descriptor, alloc_desc, dst_server)
735 .memory
736 }
737 }
738
739 #[cfg_attr(
741 feature = "tracing",
742 tracing::instrument(level = "trace", skip(self, device_ids))
743 )]
744 pub fn ensure_init_collective(&mut self, device_ids: Vec<DeviceId>) {
745 let comm_id = CommunicationId::from(device_ids.clone());
746 let is_comms_init = self
747 .utilities
748 .initialized_comms
749 .read()
750 .unwrap()
751 .contains(&comm_id);
752 if !is_comms_init {
753 self.device
754 .submit(move |server| server.comm_init(device_ids).unwrap());
755 let mut initialized_comms = self.utilities.initialized_comms.write().unwrap();
756 initialized_comms.insert(comm_id);
757 self.device.flush_queue();
759 }
760 }
761
762 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
764 pub fn sync_collective(&self) {
765 if DeviceHandle::<R::Server>::is_blocking() {
766 panic!("Can't use `sync_collective` with a blocking device handle");
767 }
768 let stream_id = self.stream_id();
769
770 self.device.submit(move |server| {
771 server.sync_collective(stream_id).unwrap();
772 });
773
774 self.device.flush_queue();
777 }
778
779 #[cfg_attr(
781 feature = "tracing",
782 tracing::instrument(level = "trace", skip(self, src, dst, dtype, device_ids, op))
783 )]
784 pub fn all_reduce(
785 &mut self,
786 src: Handle,
787 dst: Handle,
788 dtype: ElemType,
789 device_ids: Vec<DeviceId>,
790 op: ReduceOperation,
791 ) {
792 if DeviceHandle::<R::Server>::is_blocking() {
793 panic!("Can't use `all_reduce` with a blocking device handle");
794 }
795
796 let stream_id = self.stream_id();
797 let src = src.binding();
798 let dst = dst.binding();
799
800 self.ensure_init_collective(device_ids.clone());
801
802 self.device.submit(move |server| {
803 server
804 .all_reduce(src, dst, dtype, stream_id, op, device_ids)
805 .unwrap();
806 });
807 }
808
809 #[cfg_attr(
813 feature = "tracing",
814 tracing::instrument(level = "trace", skip(self, src_descriptor, dst_server))
815 )]
816 pub fn to_client_tensor(
817 &mut self,
818 src_descriptor: CopyDescriptor,
819 dst_server: &Self,
820 dtype: ElemType,
821 ) -> Handle {
822 let stream_id_src = self.stream_id();
823 let stream_id_dst = dst_server.stream_id();
824
825 let device_id_src = self.device.device_id();
826 let device_id_dst = dst_server.device.device_id();
827
828 let mut dst_server = dst_server.clone();
829 let handle = Handle::new(stream_id_dst, src_descriptor.handle.size_in_used());
830 let handle_cloned = handle.clone();
831
832 let device_ids = vec![device_id_src, device_id_dst];
833 self.ensure_init_collective(device_ids.clone());
834 dst_server.ensure_init_collective(device_ids);
835
836 self.device.submit(move |server_src| {
837 server_src
838 .send(src_descriptor, dtype, stream_id_src, device_id_dst)
839 .unwrap()
840 });
841
842 dst_server.device.submit(move |server_dst| {
843 server_dst
844 .recv(handle_cloned, dtype, stream_id_dst, device_id_src)
845 .unwrap();
846 server_dst.sync_collective(stream_id_dst).unwrap();
847 });
848
849 self.device.flush_queue();
853 dst_server.device.flush_queue();
854
855 handle
856 }
857
858 #[track_caller]
859 #[cfg_attr(feature = "tracing", tracing::instrument(level="trace",
860 skip(self, kernel, bindings),
861 fields(
862 kernel.name = %kernel.name(),
863 kernel.id = %kernel.id(),
864 )
865 ))]
866 unsafe fn launch_inner(
867 &self,
868 kernel: <R::Server as ComputeServer>::Kernel,
869 count: CubeCount,
870 bindings: KernelArguments,
871 mode: ExecutionMode,
872 stream_id: StreamId,
873 ) {
874 if let CubeCount::Static(x, y, z) = &count
876 && (*x == 0 || *y == 0 || *z == 0)
877 {
878 return;
879 }
880
881 let level = self.utilities.logger.profile_level();
882
883 match level {
884 None | Some(ProfileLevel::ExecutionOnly) => {
885 let utilities = self.utilities.clone();
886 self.device.submit(move |state| {
887 let name = kernel.name();
888 unsafe { state.launch(kernel, count, bindings, mode, stream_id) };
889
890 if matches!(level, Some(ProfileLevel::ExecutionOnly)) {
891 let info = type_name_format(name, TypeNameFormatLevel::Balanced);
892 utilities.logger.register_execution(info);
893 }
894 });
895 }
896 Some(level) => {
897 let name = kernel.name();
898 let kernel_id = kernel.id();
899 let context = self.device.clone();
900 let count_moved = count.clone();
901 let (result, profile) = self
902 .profile(
903 move || {
904 context
905 .submit_blocking(move |state| unsafe {
906 state.launch(kernel, count_moved, bindings, mode, stream_id)
907 })
908 .unwrap_or_resume()
909 },
910 name,
911 )
912 .unwrap();
913 let info = match level {
914 ProfileLevel::Full => {
915 format!("{name}: {kernel_id} CubeCount {count:?}")
916 }
917 _ => type_name_format(name, TypeNameFormatLevel::Balanced),
918 };
919 self.utilities.logger.register_profiled(info, profile);
920 result
921 }
922 }
923 }
924
925 #[track_caller]
927 pub fn launch(
928 &self,
929 kernel: <R::Server as ComputeServer>::Kernel,
930 count: CubeCount,
931 bindings: KernelArguments,
932 ) {
933 unsafe {
935 self.launch_inner(
936 kernel,
937 count,
938 bindings,
939 ExecutionMode::Checked,
940 self.stream_id(),
941 )
942 }
943 }
944
945 #[track_caller]
953 pub unsafe fn launch_unchecked(
954 &self,
955 kernel: <R::Server as ComputeServer>::Kernel,
956 count: CubeCount,
957 bindings: KernelArguments,
958 ) {
959 unsafe {
961 self.launch_inner(
962 kernel,
963 count,
964 bindings,
965 match self.utilities.check_mode {
966 crate::config::compilation::BoundsCheckMode::Enforce => ExecutionMode::Checked,
967 crate::config::compilation::BoundsCheckMode::Validate => {
968 ExecutionMode::Validate
969 }
970 crate::config::compilation::BoundsCheckMode::Auto => ExecutionMode::Unchecked,
971 },
972 self.stream_id(),
973 )
974 }
975 }
976
977 pub fn flush(&self) -> Result<(), ServerError> {
979 let stream_id = self.stream_id();
980
981 self.device
982 .submit_blocking(move |server| server.flush(stream_id))
983 .unwrap_or_resume()
984 }
985
986 pub fn graph_prepare(&self) -> Result<(), ServerError> {
991 let stream_id = self.stream_id();
992 self.device
993 .submit_blocking(move |server| server.graph_prepare(stream_id))
994 .unwrap_or_resume()
995 }
996
997 pub fn start_capture(&self) -> Result<(), ServerError> {
1004 let stream_id = self.stream_id();
1005 self.device
1006 .submit_blocking(move |server| server.begin_capture(stream_id))
1007 .unwrap_or_resume()
1008 }
1009
1010 pub fn stop_capture(&self) -> Result<Graph<R>, ServerError> {
1013 let stream_id = self.stream_id();
1014 let id = self
1015 .device
1016 .submit_blocking(move |server| server.end_capture(stream_id))
1017 .unwrap_or_resume()?;
1018
1019 Ok(Graph {
1020 inner: Arc::new(GraphHandle {
1021 id,
1022 device: self.device.clone(),
1023 stream_id,
1024 }),
1025 })
1026 }
1027
1028 pub fn sync(&self) -> DynFut<Result<(), ServerError>> {
1030 let stream_id = self.stream_id();
1031
1032 let fut = self
1033 .device
1034 .submit_blocking(move |server| server.sync(stream_id))
1035 .unwrap_or_resume();
1036
1037 self.utilities.logger.profile_summary();
1038
1039 fut
1040 }
1041
1042 pub fn properties(&self) -> &DeviceProperties {
1044 &self.utilities.properties
1045 }
1046
1047 pub fn features(&self) -> &Features {
1049 &self.utilities.properties.features
1050 }
1051
1052 pub fn properties_mut(&mut self) -> Option<&mut DeviceProperties> {
1056 Arc::get_mut(&mut self.utilities).map(|state| &mut state.properties)
1057 }
1058
1059 pub fn memory_usage(&self) -> Result<MemoryUsage, ServerError> {
1065 self.device
1066 .submit_blocking(move |server| {
1067 server
1068 .stream_ids()
1069 .into_iter()
1070 .try_fold(MemoryUsage::default(), |acc, id| {
1071 Ok(acc.combine(server.memory_usage(id)?))
1072 })
1073 })
1074 .unwrap_or_resume()
1075 }
1076
1077 pub fn enumerate_devices(&self, type_id: u16) -> Vec<DeviceId> {
1079 R::enumerate_devices(type_id, self.info())
1080 }
1081
1082 pub fn enumerate_all_devices(&self) -> Vec<DeviceId> {
1084 R::enumerate_all_devices(self.info())
1085 }
1086
1087 pub fn device_count(&self, type_id: u16) -> usize {
1089 self.enumerate_devices(type_id).len()
1090 }
1091
1092 pub fn device_count_total(&self) -> usize {
1094 self.enumerate_all_devices().len()
1095 }
1096
1097 pub unsafe fn allocation_mode(&self, mode: MemoryAllocationMode) {
1103 let stream_id = self.stream_id();
1104 self.device
1105 .submit(move |server| server.allocation_mode(mode, stream_id));
1106 }
1107
1108 pub fn memory_cleanup(&self) {
1113 self.device.submit(move |server| {
1114 for id in server.stream_ids() {
1115 server.memory_cleanup(id);
1116 }
1117 });
1118 }
1119
1120 #[must_use = "a `false` return means the current stream kept its old pool layout"]
1145 pub fn configure_memory_pools(&self, pools: &MemoryPoolsConfig) -> bool {
1146 let config =
1147 match MemoryConfiguration::default().resolve(Some(pools), &self.properties().memory) {
1148 Ok(config) => config,
1149 Err(err) => panic!("Invalid memory pools configuration: {err}"),
1150 };
1151 let stream_id = self.stream_id();
1152 self.device
1153 .submit_blocking(move |server| server.configure_memory_pools(config, stream_id))
1154 .unwrap_or_resume()
1155 }
1156
1157 #[track_caller]
1159 pub fn profile<O: Send + 'static>(
1160 &self,
1161 func: impl FnOnce() -> O + Send,
1162 #[allow(unused)] func_name: &str,
1163 ) -> Result<(O, ProfileDuration), ProfileError> {
1164 #[cfg(feature = "profile-tracy")]
1167 let location = std::panic::Location::caller();
1168
1169 #[cfg(feature = "profile-tracy")]
1171 let _span = tracy_client::Client::running().unwrap().span_alloc(
1172 None,
1173 func_name,
1174 location.file(),
1175 location.line(),
1176 0,
1177 );
1178
1179 let stream_id = self.stream_id();
1180
1181 #[cfg(feature = "profile-tracy")]
1182 let gpu_span = if self.utilities.properties.timing_method == TimingMethod::Device {
1183 let gpu_span = self
1184 .utilities
1185 .gpu_client
1186 .span_alloc(func_name, "profile", location.file(), location.line())
1187 .unwrap();
1188 Some(gpu_span)
1189 } else {
1190 None
1191 };
1192
1193 let device = self.device.clone();
1194 #[allow(unused_mut, reason = "Used in profile-tracy")]
1195 let mut result = self
1196 .device
1197 .exclusive(move || {
1198 let token =
1201 match device.submit_blocking(move |server| server.start_profile(stream_id)) {
1202 Ok(token) => match token {
1203 Ok(token) => token,
1204 Err(err) => return Err(err),
1205 },
1206 Err(err) => {
1207 return Err(ServerError::Generic {
1208 reason: alloc::format!(
1209 "Can't start profiling because of a call error: {err:?}"
1210 ),
1211 backtrace: BackTrace::capture(),
1212 });
1213 }
1214 };
1215
1216 let out = func();
1218
1219 let result = device
1221 .submit_blocking(move |server| {
1222 let mut result = server.end_profile(stream_id, token);
1223
1224 match result {
1225 Ok(result) => Ok((out, result)),
1226 Err(err) => Err(err),
1227 }
1228 })
1229 .unwrap_or_resume();
1230
1231 Ok(result)
1232 })
1233 .unwrap_or_resume()
1234 .map_err(|err| ProfileError::Unknown {
1235 reason: alloc::format!("{err}"),
1236 backtrace: BackTrace::capture(),
1237 })?;
1238
1239 #[cfg(feature = "profile-tracy")]
1240 if let Some(mut gpu_span) = gpu_span {
1241 gpu_span.end_zone();
1242 let epoch = self.utilities.epoch_time;
1243 result = result.map(|(o, result)| {
1245 (
1246 o,
1247 ProfileDuration::new(
1248 alloc::boxed::Box::pin(async move {
1249 let ticks = result.resolve().await;
1250 let start_duration =
1251 ticks.start_duration_since(epoch).as_nanos() as i64;
1252 let end_duration = ticks.end_duration_since(epoch).as_nanos() as i64;
1253 gpu_span.upload_timestamp_start(start_duration);
1254 gpu_span.upload_timestamp_end(end_duration);
1255 ticks
1256 }),
1257 TimingMethod::Device,
1258 ),
1259 )
1260 });
1261 }
1262
1263 result
1264 }
1265
1266 #[cfg_attr(
1268 feature = "tracing",
1269 tracing::instrument(
1270 level = "trace",
1271 skip(self, src_descriptor, alloc_descriptor, dst_server)
1272 )
1273 )]
1274 fn change_client_sync(
1275 &self,
1276 src_descriptor: CopyDescriptor,
1277 alloc_descriptor: MemoryLayoutDescriptor,
1278 dst_server: &Self,
1279 ) -> MemoryLayout {
1280 let shape = src_descriptor.shape.clone();
1281 let elem_size = src_descriptor.elem_size;
1282 let stream_id = self.stream_id();
1283
1284 let read = self
1285 .device
1286 .submit_blocking(move |server| server.read(vec![src_descriptor], stream_id))
1287 .unwrap_or_resume();
1288
1289 let mut data = cubecl_common::future::block_on(read).unwrap();
1290
1291 let (handle_base, mut layouts) = self
1292 .utilities
1293 .layout_policy
1294 .apply(stream_id, &[alloc_descriptor]);
1295 let alloc = layouts.remove(0);
1296
1297 let desc_descriptor = CopyDescriptor {
1298 handle: handle_base.clone().binding(),
1299 shape,
1300 strides: alloc.strides.clone(),
1301 elem_size,
1302 };
1303
1304 let (size, memory) = (handle_base.size(), handle_base.memory);
1305 dst_server.device.submit(move |server| {
1306 server.initialize_memory(memory, size, stream_id);
1307 server.write(vec![(desc_descriptor, data.remove(0))], stream_id)
1308 });
1309
1310 alloc
1311 }
1312
1313 pub fn io_optimized_vector_sizes(
1315 &self,
1316 size: usize,
1317 ) -> impl Iterator<Item = VectorSize> + Clone {
1318 let load_width = self.properties().hardware.load_width as usize;
1319 let size_bits = size * 8;
1320 let max = load_width / size_bits;
1321 let max = usize::min(self.properties().hardware.max_vector_size, max);
1322
1323 let num_candidates = max.trailing_zeros() + 1;
1325
1326 (0..num_candidates).map(|i| 2usize.pow(i)).rev()
1327 }
1328
1329 fn device_key(&self) -> String {
1331 format!("{}_dev{}", R::name(self), self.device.device_id().index_id)
1332 }
1333
1334 pub fn measure_throughput(
1336 &self,
1337 key: ThroughputKey,
1338 kernel_config: KernelConfig,
1339 ) -> ThroughputValue {
1340 let cache = ThroughputCache::get_for_device(&self.device_key());
1341 let mut throughputs = ThroughputBenchmarker::new(cache);
1342 throughputs.measure(self, key, kernel_config)
1343 }
1344
1345 pub fn measure_launch_overhead(
1347 &self,
1348 sample: impl Fn() -> core::time::Duration,
1349 ) -> core::time::Duration {
1350 crate::throughput::launch_overhead_or_measure(&self.device_key(), sample)
1351 }
1352}