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 bytes::{AllocationProperty, Bytes},
26 device::{Device, DeviceId},
27 device_handle::{CallResultExt, DeviceHandle},
28 profile::ProfileDuration,
29};
30use cubecl_environment::backtrace::BackTrace;
31use cubecl_environment::future::DynFut;
32use cubecl_ir::{DeviceProperties, ElemType, VectorSize, features::Features};
33use cubecl_zspace::Shape;
34
35#[allow(unused)]
36use cubecl_common::profile::TimingMethod;
37use cubecl_environment::stream::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_environment::future::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_environment::future::reader::read_sync(self.read_async(vec![handle]))?.remove(0))
248 }
249
250 pub fn read_one_unchecked(&self, handle: Handle) -> Bytes {
256 cubecl_environment::future::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_environment::future::reader::read_sync(self.read_tensor_async(descriptors))
283 .expect("TODO")
284 }
285
286 pub fn read_one_tensor_async(
289 &self,
290 descriptor: CopyDescriptor,
291 ) -> impl Future<Output = Result<Bytes, ServerError>> + Send {
292 let fut = self.read_tensor_async(vec![descriptor]);
293
294 async { Ok(fut.await?.remove(0)) }
295 }
296
297 pub fn read_one_unchecked_tensor(&self, descriptor: CopyDescriptor) -> Bytes {
304 self.read_tensor(vec![descriptor]).remove(0)
305 }
306
307 #[cfg(not(target_family = "wasm"))]
317 pub fn read_lazy(&self, descriptor: CopyDescriptor) -> Bytes {
318 let len = descriptor.shape.iter().product::<usize>() * descriptor.elem_size;
319 let controller = lazy::LazyDeviceController::new(self.clone(), Arc::new(descriptor));
320 unsafe { Bytes::from_controller(alloc::boxed::Box::new(controller), len) }
322 }
323
324 #[cfg(not(target_family = "wasm"))]
329 pub fn read_lazy_async(
330 &self,
331 descriptor: CopyDescriptor,
332 ) -> impl Future<Output = Result<Bytes, ServerError>> + Send {
333 let len = descriptor.shape.iter().product::<usize>() * descriptor.elem_size;
334 let controller = lazy::LazyDeviceController::new(self.clone(), Arc::new(descriptor));
335 let bytes = unsafe { Bytes::from_controller(alloc::boxed::Box::new(controller), len) };
337 core::future::ready(Ok(bytes))
338 }
339
340 #[cfg(target_family = "wasm")]
346 pub fn read_lazy_async(
347 &self,
348 descriptor: CopyDescriptor,
349 ) -> impl Future<Output = Result<Bytes, ServerError>> + Send {
350 self.read_one_tensor_async(descriptor)
351 }
352
353 pub fn get_resource(
355 &self,
356 handle: Handle,
357 ) -> Result<
358 ManagedResource<<<R::Server as ComputeServer>::Storage as ComputeStorage>::Resource>,
359 ServerError,
360 > {
361 let stream_id = self.stream_id();
362 let binding = handle.binding();
363
364 self.device
365 .submit_blocking(move |state| state.get_resource(binding, stream_id))
366 .unwrap_or_resume()
367 }
368
369 fn do_create_from_slices(
370 &self,
371 descriptors: Vec<MemoryLayoutDescriptor>,
372 slices: Vec<Vec<u8>>,
373 ) -> Result<Vec<MemoryLayout>, IoError> {
374 let stream_id = self.stream_id();
375 let (handle_base, layouts) = self.utilities.layout_policy.apply(stream_id, &descriptors);
376
377 let descriptors = descriptors
378 .into_iter()
379 .zip(layouts.iter())
380 .zip(slices)
381 .map(|((desc, alloc), data)| {
382 (
383 CopyDescriptor::new(
384 alloc.memory.clone().binding(),
385 desc.shape,
386 alloc.strides.clone(),
387 desc.elem_size,
388 ),
389 Bytes::from_bytes_vec(data.to_vec()),
390 )
391 })
392 .collect::<Vec<_>>();
393
394 let (size, memory) = (handle_base.size(), handle_base.memory);
395 self.device.submit(move |server| {
396 server.initialize_memory(memory, size, stream_id);
397 server.write(descriptors, stream_id);
398 });
399
400 Ok(layouts)
401 }
402
403 fn do_create(
404 &self,
405 descriptors: Vec<MemoryLayoutDescriptor>,
406 data: Vec<Bytes>,
407 ) -> Result<Vec<MemoryLayout>, IoError> {
408 let stream_id = self.stream_id();
409 let (handle_base, layouts) = self.utilities.layout_policy.apply(stream_id, &descriptors);
410
411 let descriptors = descriptors
412 .into_iter()
413 .zip(layouts.iter())
414 .zip(data)
415 .map(|((desc, layout), data)| {
416 (
417 CopyDescriptor::new(
418 layout.memory.clone().binding(),
419 desc.shape,
420 layout.strides.clone(),
421 desc.elem_size,
422 ),
423 data,
424 )
425 })
426 .collect::<Vec<_>>();
427
428 let (size, memory) = (handle_base.size(), handle_base.memory);
429 self.device.submit(move |server| {
430 server.initialize_memory(memory, size, stream_id);
431 server.write(descriptors, stream_id);
432 });
433
434 Ok(layouts)
435 }
436
437 pub fn create_from_slice(&self, slice: &[u8]) -> Handle {
443 let shape: Shape = [slice.len()].into();
444
445 self.do_create_from_slices(
446 vec![MemoryLayoutDescriptor::new(
447 MemoryLayoutStrategy::Contiguous,
448 shape,
449 1,
450 )],
451 vec![slice.to_vec()],
452 )
453 .unwrap()
454 .remove(0)
455 .memory
456 }
457
458 pub fn exclusive<'a, Re: Send + 'static, F: FnOnce() -> Re + Send + 'a>(
460 &'a self,
461 task: F,
462 ) -> Result<Re, ServerError> {
463 self.device
465 .exclusive(task)
466 .map_err(|err| ServerError::Generic {
467 reason: format!("{err:?}"),
468 backtrace: BackTrace::capture(),
469 })
470 }
471
472 pub fn memory_persistent_allocation<
474 'a,
475 Re: Send,
476 Input: Send,
477 F: FnOnce(Input) -> Re + Send + 'a,
478 >(
479 &'a self,
480 input: Input,
481 task: F,
482 ) -> Result<Re, ServerError> {
483 let stream_id = StreamId::current();
484
485 self.device.submit(move |server| {
486 server.allocation_mode(MemoryAllocationMode::Persistent, stream_id);
487 });
488
489 let output = task(input);
491
492 self.device.submit(move |server| {
493 server.allocation_mode(MemoryAllocationMode::Auto, stream_id);
494 });
495
496 Ok(output)
497 }
498
499 pub fn write(&self, handle: &Handle, data: Bytes) {
509 let stream_id = self.stream_id();
510 let descriptor =
511 CopyDescriptor::new(handle.clone().binding(), [data.len()].into(), [1].into(), 1);
512 self.device.submit(move |server| {
513 server.write(vec![(descriptor, data)], stream_id);
514 });
515 }
516
517 pub fn create(&self, data: Bytes) -> Handle {
519 let shape = [data.len()].into();
520
521 self.do_create(
522 vec![MemoryLayoutDescriptor::new(
523 MemoryLayoutStrategy::Contiguous,
524 shape,
525 1,
526 )],
527 vec![data],
528 )
529 .unwrap()
530 .remove(0)
531 .memory
532 }
533
534 pub fn create_tensor_from_slice(
552 &self,
553 slice: &[u8],
554 shape: Shape,
555 elem_size: usize,
556 ) -> MemoryLayout {
557 self.do_create_from_slices(
558 vec![MemoryLayoutDescriptor::new(
559 MemoryLayoutStrategy::Optimized,
560 shape,
561 elem_size,
562 )],
563 vec![slice.to_vec()],
564 )
565 .unwrap()
566 .remove(0)
567 }
568
569 pub fn create_tensor(&self, bytes: Bytes, shape: Shape, elem_size: usize) -> MemoryLayout {
583 self.do_create(
584 vec![MemoryLayoutDescriptor::new(
585 MemoryLayoutStrategy::Optimized,
586 shape,
587 elem_size,
588 )],
589 vec![bytes],
590 )
591 .unwrap()
592 .remove(0)
593 }
594
595 pub fn create_tensors_from_slices(
603 &self,
604 descriptors: Vec<(MemoryLayoutDescriptor, &[u8])>,
605 ) -> Vec<MemoryLayout> {
606 let mut data = Vec::with_capacity(descriptors.len());
607 let mut descriptors_ = Vec::with_capacity(descriptors.len());
608 for (a, b) in descriptors {
609 data.push(b.to_vec());
610 descriptors_.push(a);
611 }
612
613 self.do_create_from_slices(descriptors_, data).unwrap()
614 }
615
616 pub fn create_tensors(
620 &self,
621 descriptors: Vec<(MemoryLayoutDescriptor, Bytes)>,
622 ) -> Vec<MemoryLayout> {
623 let (descriptors, data) = descriptors.into_iter().unzip();
624
625 self.do_create(descriptors, data).unwrap()
626 }
627
628 fn do_empty(
629 &self,
630 descriptors: Vec<MemoryLayoutDescriptor>,
631 ) -> Result<Vec<MemoryLayout>, IoError> {
632 let stream_id = self.stream_id();
633 let (handle_base, layouts) = self.utilities.layout_policy.apply(stream_id, &descriptors);
634
635 let (size, memory) = (handle_base.size(), handle_base.memory);
636 self.device.submit(move |server| {
637 server.initialize_memory(memory, size, stream_id);
638 });
639
640 Ok(layouts)
641 }
642
643 pub fn empty(&self, size: usize) -> Handle {
645 let shape: Shape = [size].into();
646 let descriptor = MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Contiguous, shape, 1);
647 self.do_empty(vec![descriptor]).unwrap().remove(0).memory
648 }
649
650 pub fn empty_tensor(&self, shape: Shape, elem_size: usize) -> MemoryLayout {
653 let descriptor =
654 MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Optimized, shape, elem_size);
655 self.do_empty(vec![descriptor]).unwrap().remove(0)
656 }
657
658 pub fn empty_tensors(&self, descriptors: Vec<MemoryLayoutDescriptor>) -> Vec<MemoryLayout> {
661 self.do_empty(descriptors).unwrap()
662 }
663
664 pub fn staging<'a, I>(&self, bytes: I, file_only: bool)
669 where
670 I: Iterator<Item = &'a mut Bytes>,
671 {
672 let has_staging = |b: &Bytes| match b.property() {
673 AllocationProperty::Pinned => false,
674 AllocationProperty::File => true,
675 AllocationProperty::Device => false,
678 AllocationProperty::Native | AllocationProperty::Other => !file_only,
679 };
680
681 let mut to_be_updated = Vec::new();
682 let sizes = bytes
683 .filter_map(|b| match has_staging(b) {
684 true => {
685 let len = b.len();
686 to_be_updated.push(b);
687 Some(len)
688 }
689 false => None,
690 })
691 .collect::<Vec<usize>>();
692
693 if sizes.is_empty() {
694 return;
695 }
696
697 let stream_id = self.stream_id();
698 let sizes = sizes.to_vec();
699 let stagings = self
700 .device
701 .submit_blocking(move |server| server.staging(&sizes, stream_id))
702 .unwrap_or_resume();
703
704 let stagings = match stagings {
705 Ok(val) => val,
706 Err(_) => return,
707 };
708
709 to_be_updated
710 .into_iter()
711 .zip(stagings)
712 .for_each(|(b, mut staging)| {
713 b.copy_into(&mut staging);
714 core::mem::swap(b, &mut staging);
715 });
716 }
717
718 #[cfg_attr(
720 feature = "tracing",
721 tracing::instrument(level = "trace", skip(self, src, dst_server))
722 )]
723 pub fn to_client(&mut self, src: Handle, dst_server: &Self, dtype: ElemType) -> Handle {
724 let shape = [src.size_in_used() as usize];
725 let src_descriptor = src.copy_descriptor(shape.into(), [1].into(), 1);
726
727 if R::Server::SERVER_COMM_ENABLED {
728 self.to_client_tensor(src_descriptor, dst_server, dtype)
729 } else {
730 let alloc_desc = MemoryLayoutDescriptor::new(
731 MemoryLayoutStrategy::Contiguous,
732 src_descriptor.shape.clone(),
733 src_descriptor.elem_size,
734 );
735 self.change_client_sync(src_descriptor, alloc_desc, dst_server)
736 .memory
737 }
738 }
739
740 #[cfg_attr(
742 feature = "tracing",
743 tracing::instrument(level = "trace", skip(self, device_ids))
744 )]
745 pub fn ensure_init_collective(&mut self, device_ids: Vec<DeviceId>) {
746 let comm_id = CommunicationId::from(device_ids.clone());
747 let is_comms_init = self.utilities.initialized_comms.read().contains(&comm_id);
748 if !is_comms_init {
749 self.device
750 .submit(move |server| server.comm_init(device_ids).unwrap());
751 let mut initialized_comms = self.utilities.initialized_comms.write();
752 initialized_comms.insert(comm_id);
753 self.device.flush_queue();
755 }
756 }
757
758 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
760 pub fn sync_collective(&self) {
761 if DeviceHandle::<R::Server>::is_blocking() {
762 panic!("Can't use `sync_collective` with a blocking device handle");
763 }
764 let stream_id = self.stream_id();
765
766 self.device.submit(move |server| {
767 server.sync_collective(stream_id).unwrap();
768 });
769
770 self.device.flush_queue();
773 }
774
775 #[cfg_attr(
777 feature = "tracing",
778 tracing::instrument(level = "trace", skip(self, src, dst, dtype, device_ids, op))
779 )]
780 pub fn all_reduce(
781 &mut self,
782 src: Handle,
783 dst: Handle,
784 dtype: ElemType,
785 device_ids: Vec<DeviceId>,
786 op: ReduceOperation,
787 ) {
788 if DeviceHandle::<R::Server>::is_blocking() {
789 panic!("Can't use `all_reduce` with a blocking device handle");
790 }
791
792 let stream_id = self.stream_id();
793 let src = src.binding();
794 let dst = dst.binding();
795
796 self.ensure_init_collective(device_ids.clone());
797
798 self.device.submit(move |server| {
799 server
800 .all_reduce(src, dst, dtype, stream_id, op, device_ids)
801 .unwrap();
802 });
803 }
804
805 #[cfg_attr(
809 feature = "tracing",
810 tracing::instrument(level = "trace", skip(self, src_descriptor, dst_server))
811 )]
812 pub fn to_client_tensor(
813 &mut self,
814 src_descriptor: CopyDescriptor,
815 dst_server: &Self,
816 dtype: ElemType,
817 ) -> Handle {
818 let stream_id_src = self.stream_id();
819 let stream_id_dst = dst_server.stream_id();
820
821 let device_id_src = self.device.device_id();
822 let device_id_dst = dst_server.device.device_id();
823
824 let mut dst_server = dst_server.clone();
825 let handle = Handle::new(stream_id_dst, src_descriptor.handle.size_in_used());
826 let handle_cloned = handle.clone();
827
828 let device_ids = vec![device_id_src, device_id_dst];
829 self.ensure_init_collective(device_ids.clone());
830 dst_server.ensure_init_collective(device_ids);
831
832 self.device.submit(move |server_src| {
833 server_src
834 .send(src_descriptor, dtype, stream_id_src, device_id_dst)
835 .unwrap()
836 });
837
838 dst_server.device.submit(move |server_dst| {
839 server_dst
840 .recv(handle_cloned, dtype, stream_id_dst, device_id_src)
841 .unwrap();
842 server_dst.sync_collective(stream_id_dst).unwrap();
843 });
844
845 self.device.flush_queue();
849 dst_server.device.flush_queue();
850
851 handle
852 }
853
854 #[track_caller]
855 #[cfg_attr(feature = "tracing", tracing::instrument(level="trace",
856 skip(self, kernel, bindings),
857 fields(
858 kernel.name = %kernel.name(),
859 kernel.id = %kernel.id(),
860 )
861 ))]
862 unsafe fn launch_inner(
863 &self,
864 kernel: <R::Server as ComputeServer>::Kernel,
865 count: CubeCount,
866 bindings: KernelArguments,
867 mode: ExecutionMode,
868 stream_id: StreamId,
869 ) {
870 if let CubeCount::Static(x, y, z) = &count
872 && (*x == 0 || *y == 0 || *z == 0)
873 {
874 return;
875 }
876
877 let launch_mode = crate::dry_run::launch_mode();
881
882 let level = self.utilities.logger.profile_level();
883
884 match level {
885 None | Some(ProfileLevel::ExecutionOnly) => {
886 let utilities = self.utilities.clone();
887 self.device.submit(move |state| {
888 let name = kernel.name();
889 unsafe { state.launch(kernel, count, bindings, mode, stream_id, launch_mode) };
890
891 if matches!(level, Some(ProfileLevel::ExecutionOnly)) {
892 let info = type_name_format(name, TypeNameFormatLevel::Balanced);
893 utilities.logger.register_execution(info);
894 }
895 });
896 }
897 Some(level) => {
898 let name = kernel.name();
899 let kernel_id = kernel.id();
900 let context = self.device.clone();
901 let count_moved = count.clone();
902 let (result, profile) = self
903 .profile(
904 move || {
905 context
906 .submit_blocking(move |state| unsafe {
907 state.launch(
908 kernel,
909 count_moved,
910 bindings,
911 mode,
912 stream_id,
913 launch_mode,
914 )
915 })
916 .unwrap_or_resume()
917 },
918 name,
919 )
920 .unwrap();
921 let info = match level {
922 ProfileLevel::Full => {
923 format!("{name}: {kernel_id} CubeCount {count:?}")
924 }
925 _ => type_name_format(name, TypeNameFormatLevel::Balanced),
926 };
927 self.utilities.logger.register_profiled(info, profile);
928 result
929 }
930 }
931 }
932
933 #[track_caller]
935 pub fn launch(
936 &self,
937 kernel: <R::Server as ComputeServer>::Kernel,
938 count: CubeCount,
939 bindings: KernelArguments,
940 ) {
941 unsafe {
943 self.launch_inner(
944 kernel,
945 count,
946 bindings,
947 ExecutionMode::Checked,
948 self.stream_id(),
949 )
950 }
951 }
952
953 #[track_caller]
961 pub unsafe fn launch_unchecked(
962 &self,
963 kernel: <R::Server as ComputeServer>::Kernel,
964 count: CubeCount,
965 bindings: KernelArguments,
966 ) {
967 unsafe {
969 self.launch_inner(
970 kernel,
971 count,
972 bindings,
973 match self.utilities.check_mode {
974 crate::config::compilation::BoundsCheckMode::Enforce => ExecutionMode::Checked,
975 crate::config::compilation::BoundsCheckMode::Validate => {
976 ExecutionMode::Validate
977 }
978 crate::config::compilation::BoundsCheckMode::Auto => ExecutionMode::Unchecked,
979 },
980 self.stream_id(),
981 )
982 }
983 }
984
985 pub fn flush(&self) -> Result<(), ServerError> {
987 let stream_id = self.stream_id();
988
989 self.device
990 .submit_blocking(move |server| server.flush(stream_id))
991 .unwrap_or_resume()
992 }
993
994 pub fn graph_prepare(&self) -> Result<(), ServerError> {
999 let stream_id = self.stream_id();
1000 self.device
1001 .submit_blocking(move |server| server.graph_prepare(stream_id))
1002 .unwrap_or_resume()
1003 }
1004
1005 pub fn start_capture(&self) -> Result<(), ServerError> {
1012 let stream_id = self.stream_id();
1013 self.device
1014 .submit_blocking(move |server| server.begin_capture(stream_id))
1015 .unwrap_or_resume()
1016 }
1017
1018 pub fn stop_capture(&self) -> Result<Graph<R>, ServerError> {
1021 let stream_id = self.stream_id();
1022 let id = self
1023 .device
1024 .submit_blocking(move |server| server.end_capture(stream_id))
1025 .unwrap_or_resume()?;
1026
1027 Ok(Graph {
1028 inner: Arc::new(GraphHandle {
1029 id,
1030 device: self.device.clone(),
1031 stream_id,
1032 }),
1033 })
1034 }
1035
1036 pub fn sync(&self) -> DynFut<Result<(), ServerError>> {
1038 let stream_id = self.stream_id();
1039
1040 let fut = self
1041 .device
1042 .submit_blocking(move |server| server.sync(stream_id))
1043 .unwrap_or_resume();
1044
1045 self.utilities.logger.profile_summary();
1046
1047 fut
1048 }
1049
1050 pub fn properties(&self) -> &DeviceProperties {
1052 &self.utilities.properties
1053 }
1054
1055 pub fn features(&self) -> &Features {
1057 &self.utilities.properties.features
1058 }
1059
1060 pub fn properties_mut(&mut self) -> Option<&mut DeviceProperties> {
1064 Arc::get_mut(&mut self.utilities).map(|state| &mut state.properties)
1065 }
1066
1067 pub fn memory_usage(&self) -> Result<MemoryUsage, ServerError> {
1073 self.device
1074 .submit_blocking(move |server| {
1075 server
1076 .stream_ids()
1077 .into_iter()
1078 .try_fold(MemoryUsage::default(), |acc, id| {
1079 Ok(acc.combine(server.memory_usage(id)?))
1080 })
1081 })
1082 .unwrap_or_resume()
1083 }
1084
1085 pub fn enumerate_devices(&self, type_id: u16) -> Vec<DeviceId> {
1087 R::enumerate_devices(type_id, self.info())
1088 }
1089
1090 pub fn enumerate_all_devices(&self) -> Vec<DeviceId> {
1092 R::enumerate_all_devices(self.info())
1093 }
1094
1095 pub fn device_count(&self, type_id: u16) -> usize {
1097 self.enumerate_devices(type_id).len()
1098 }
1099
1100 pub fn device_count_total(&self) -> usize {
1102 self.enumerate_all_devices().len()
1103 }
1104
1105 pub unsafe fn allocation_mode(&self, mode: MemoryAllocationMode) {
1111 let stream_id = self.stream_id();
1112 self.device
1113 .submit(move |server| server.allocation_mode(mode, stream_id));
1114 }
1115
1116 pub fn memory_cleanup(&self) {
1121 self.device.submit(move |server| {
1122 for id in server.stream_ids() {
1123 server.memory_cleanup(id);
1124 }
1125 });
1126 }
1127
1128 #[must_use = "a `false` return means the current stream kept its old pool layout"]
1153 pub fn configure_memory_pools(&self, pools: &MemoryPoolsConfig) -> bool {
1154 let config =
1155 match MemoryConfiguration::default().resolve(Some(pools), &self.properties().memory) {
1156 Ok(config) => config,
1157 Err(err) => panic!("Invalid memory pools configuration: {err}"),
1158 };
1159 let stream_id = self.stream_id();
1160 self.device
1161 .submit_blocking(move |server| server.configure_memory_pools(config, stream_id))
1162 .unwrap_or_resume()
1163 }
1164
1165 #[track_caller]
1167 pub fn profile<O: Send + 'static>(
1168 &self,
1169 func: impl FnOnce() -> O + Send,
1170 #[allow(unused)] func_name: &str,
1171 ) -> Result<(O, ProfileDuration), ProfileError> {
1172 #[cfg(feature = "profile-tracy")]
1175 let location = std::panic::Location::caller();
1176
1177 #[cfg(feature = "profile-tracy")]
1179 let _span = tracy_client::Client::running().unwrap().span_alloc(
1180 None,
1181 func_name,
1182 location.file(),
1183 location.line(),
1184 0,
1185 );
1186
1187 let stream_id = self.stream_id();
1188
1189 #[cfg(feature = "profile-tracy")]
1190 let gpu_span = if self.utilities.properties.timing_method == TimingMethod::Device {
1191 let gpu_span = self
1192 .utilities
1193 .gpu_client
1194 .span_alloc(func_name, "profile", location.file(), location.line())
1195 .unwrap();
1196 Some(gpu_span)
1197 } else {
1198 None
1199 };
1200
1201 let device = self.device.clone();
1202 #[allow(unused_mut, reason = "Used in profile-tracy")]
1203 let mut result = self
1204 .device
1205 .exclusive(move || {
1206 let token =
1209 match device.submit_blocking(move |server| server.start_profile(stream_id)) {
1210 Ok(token) => match token {
1211 Ok(token) => token,
1212 Err(err) => return Err(err),
1213 },
1214 Err(err) => {
1215 return Err(ServerError::Generic {
1216 reason: alloc::format!(
1217 "Can't start profiling because of a call error: {err:?}"
1218 ),
1219 backtrace: BackTrace::capture(),
1220 });
1221 }
1222 };
1223
1224 let out = func();
1226
1227 let result = device
1229 .submit_blocking(move |server| {
1230 let mut result = server.end_profile(stream_id, token);
1231
1232 match result {
1233 Ok(result) => Ok((out, result)),
1234 Err(err) => Err(err),
1235 }
1236 })
1237 .unwrap_or_resume();
1238
1239 Ok(result)
1240 })
1241 .unwrap_or_resume()
1242 .map_err(|err| ProfileError::Unknown {
1243 reason: alloc::format!("{err}"),
1244 backtrace: BackTrace::capture(),
1245 })?;
1246
1247 #[cfg(feature = "profile-tracy")]
1248 if let Some(mut gpu_span) = gpu_span {
1249 gpu_span.end_zone();
1250 let epoch = self.utilities.epoch_time;
1251 result = result.map(|(o, result)| {
1253 (
1254 o,
1255 ProfileDuration::new(
1256 alloc::boxed::Box::pin(async move {
1257 let ticks = result.resolve().await;
1258 let start_duration =
1259 ticks.start_duration_since(epoch).as_nanos() as i64;
1260 let end_duration = ticks.end_duration_since(epoch).as_nanos() as i64;
1261 gpu_span.upload_timestamp_start(start_duration);
1262 gpu_span.upload_timestamp_end(end_duration);
1263 ticks
1264 }),
1265 TimingMethod::Device,
1266 ),
1267 )
1268 });
1269 }
1270
1271 result
1272 }
1273
1274 #[cfg_attr(
1276 feature = "tracing",
1277 tracing::instrument(
1278 level = "trace",
1279 skip(self, src_descriptor, alloc_descriptor, dst_server)
1280 )
1281 )]
1282 fn change_client_sync(
1283 &self,
1284 src_descriptor: CopyDescriptor,
1285 alloc_descriptor: MemoryLayoutDescriptor,
1286 dst_server: &Self,
1287 ) -> MemoryLayout {
1288 let shape = src_descriptor.shape.clone();
1289 let elem_size = src_descriptor.elem_size;
1290 let stream_id = self.stream_id();
1291
1292 let read = self
1293 .device
1294 .submit_blocking(move |server| server.read(vec![src_descriptor], stream_id))
1295 .unwrap_or_resume();
1296
1297 let mut data = cubecl_environment::future::block_on(read).unwrap();
1298
1299 let (handle_base, mut layouts) = self
1300 .utilities
1301 .layout_policy
1302 .apply(stream_id, &[alloc_descriptor]);
1303 let alloc = layouts.remove(0);
1304
1305 let desc_descriptor = CopyDescriptor {
1306 handle: handle_base.clone().binding(),
1307 shape,
1308 strides: alloc.strides.clone(),
1309 elem_size,
1310 };
1311
1312 let (size, memory) = (handle_base.size(), handle_base.memory);
1313 dst_server.device.submit(move |server| {
1314 server.initialize_memory(memory, size, stream_id);
1315 server.write(vec![(desc_descriptor, data.remove(0))], stream_id)
1316 });
1317
1318 alloc
1319 }
1320
1321 pub fn io_optimized_vector_sizes(
1323 &self,
1324 size: usize,
1325 ) -> impl Iterator<Item = VectorSize> + Clone {
1326 let load_width = self.properties().hardware.load_width as usize;
1327 let size_bits = size * 8;
1328 let max = load_width / size_bits;
1329 let max = usize::min(self.properties().hardware.max_vector_size, max);
1330
1331 let num_candidates = max.trailing_zeros() + 1;
1333
1334 (0..num_candidates).map(|i| 2usize.pow(i)).rev()
1335 }
1336
1337 fn device_key(&self) -> String {
1339 format!("{}_dev{}", R::name(self), self.device.device_id().index_id)
1340 }
1341
1342 pub fn measure_throughput(
1344 &self,
1345 key: ThroughputKey,
1346 kernel_config: KernelConfig,
1347 ) -> ThroughputValue {
1348 let cache = ThroughputCache::get_for_device(&self.device_key());
1349 let mut throughputs = ThroughputBenchmarker::new(cache);
1350 throughputs.measure(key, kernel_config)
1351 }
1352}