1use super::Handle;
2use crate::{
3 client::ComputeClient,
4 compiler::CompilationError,
5 config::{CubeClRuntimeConfig, RuntimeConfig, compilation::BoundsCheckMode},
6 id::GraphId,
7 kernel::KernelMetadata,
8 logging::ServerLogger,
9 memory_management::{
10 ManagedMemoryHandle, MemoryAllocationMode, MemoryConfiguration, MemoryUsage,
11 },
12 runtime::Runtime,
13 server::Binding,
14 storage::{ComputeStorage, ManagedResource},
15 tma::{OobFill, TensorMapFormat, TensorMapInterleave, TensorMapPrefetch, TensorMapSwizzle},
16};
17use ahash::AHasher;
18use alloc::boxed::Box;
19#[cfg(feature = "profile-tracy")]
20use alloc::format;
21use alloc::string::String;
22use alloc::sync::Arc;
23use alloc::vec::Vec;
24use core::{
25 fmt::Debug,
26 hash::{Hash, Hasher},
27};
28use cubecl_common::{
29 backtrace::BackTrace,
30 bytes::Bytes,
31 device::{self, DeviceId},
32 future::DynFut,
33 profile::ProfileDuration,
34 stream_id::StreamId,
35 stub::RwLock,
36};
37use cubecl_ir::{DeviceProperties, ElemType, StorageType};
38use cubecl_zspace::{Shape, Strides, metadata::Metadata};
39use hashbrown::HashSet;
40use itertools::Itertools;
41use thiserror::Error;
42
43#[derive(Error, Clone)]
44#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
45pub enum ProfileError {
47 #[error(
49 "An unknown error happened during profiling\nCaused by:\n {reason}\nBacktrace:\n{backtrace}"
50 )]
51 Unknown {
52 reason: String,
54 #[cfg_attr(std_io, serde(skip))]
56 backtrace: BackTrace,
57 },
58
59 #[error("No profiling registered\nBacktrace:\n{backtrace}")]
61 NotRegistered {
62 #[cfg_attr(std_io, serde(skip))]
64 backtrace: BackTrace,
65 },
66
67 #[error("A launch error happened during profiling\nCaused by:\n {0}")]
69 Launch(#[from] LaunchError),
70
71 #[error("An execution error happened during profiling\nCaused by:\n {0}")]
73 Server(#[from] Box<ServerError>),
74}
75
76impl core::fmt::Debug for ProfileError {
77 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78 f.write_fmt(format_args!("{self}"))
79 }
80}
81
82pub struct ServerUtilities<Server: ComputeServer> {
84 #[cfg(feature = "profile-tracy")]
86 pub epoch_time: web_time::Instant,
87 #[cfg(feature = "profile-tracy")]
89 pub gpu_client: tracy_client::GpuContext,
90 pub properties: DeviceProperties,
92 pub properties_hash: u64,
94 pub info: Server::Info,
96 pub logger: Arc<ServerLogger>,
98 pub layout_policy: Server::MemoryLayoutPolicy,
100 pub check_mode: BoundsCheckMode,
102 pub initialized_comms: RwLock<HashSet<CommunicationId>>,
104}
105
106pub trait MemoryLayoutPolicy: Send + Sync + 'static {
108 fn apply(
113 &self,
114 stream_id: StreamId,
115 descriptors: &[MemoryLayoutDescriptor],
116 ) -> (Handle, Vec<MemoryLayout>);
117}
118
119impl<Server: core::fmt::Debug> core::fmt::Debug for ServerUtilities<Server>
120where
121 Server: ComputeServer,
122 Server::Info: core::fmt::Debug,
123{
124 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
125 f.debug_struct("ServerUtilities")
126 .field("properties", &self.properties)
127 .field("info", &self.info)
128 .field("logger", &self.logger)
129 .finish()
130 }
131}
132
133impl<S: ComputeServer> ServerUtilities<S> {
134 pub fn new(
136 properties: DeviceProperties,
137 logger: Arc<ServerLogger>,
138 info: S::Info,
139 allocator: S::MemoryLayoutPolicy,
140 ) -> Self {
141 #[cfg(feature = "profile-tracy")]
143 let client = tracy_client::Client::start();
144
145 Self {
146 properties_hash: properties.checksum(),
147 properties,
148 logger,
149 #[cfg(feature = "profile-tracy")]
151 gpu_client: client
152 .clone()
153 .new_gpu_context(
154 Some(&format!("{info:?}")),
155 tracy_client::GpuContextType::Invalid,
157 0, 1.0, )
160 .unwrap(),
161 #[cfg(feature = "profile-tracy")]
162 epoch_time: web_time::Instant::now(),
163 info,
164 layout_policy: allocator,
165 check_mode: CubeClRuntimeConfig::get().compilation.check_mode,
166 initialized_comms: RwLock::new(HashSet::default()),
167 }
168 }
169}
170
171#[derive(Error, Clone)]
173#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
174pub enum LaunchError {
175 #[error("A compilation error happened during launch\nCaused by:\n {0}")]
177 CompilationError(#[from] CompilationError),
178
179 #[error(
181 "An out-of-memory error happened during launch\nCaused by:\n {reason}\nBacktrace\n{backtrace}"
182 )]
183 OutOfMemory {
184 reason: String,
186 #[cfg_attr(std_io, serde(skip))]
188 backtrace: BackTrace,
189 },
190
191 #[error("Too many resources were requested during launch\n{0}")]
193 TooManyResources(#[from] ResourceLimitError),
194
195 #[error(
197 "An unknown error happened during launch\nCaused by:\n {reason}\nBacktrace\n{backtrace}"
198 )]
199 Unknown {
200 reason: String,
202 #[cfg_attr(std_io, serde(skip))]
204 backtrace: BackTrace,
205 },
206
207 #[error("An io error happened during launch\nCaused by:\n {0}")]
209 IoError(#[from] IoError),
210}
211
212#[derive(Error, Clone)]
214#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
215pub enum ResourceLimitError {
216 #[error(
218 "Too much shared memory requested.\nRequested {requested} bytes, maximum {max} bytes available.\nBacktrace\n{backtrace}"
219 )]
220 SharedMemory {
221 requested: usize,
223 max: usize,
225 #[cfg_attr(std_io, serde(skip))]
227 backtrace: BackTrace,
228 },
229 #[error(
231 "Total unit count exceeds maximum.\nRequested {requested} units, max units is {max}.\nBacktrace\n{backtrace}"
232 )]
233 Units {
234 requested: u32,
236 max: u32,
238 #[cfg_attr(std_io, serde(skip))]
240 backtrace: BackTrace,
241 },
242 #[error(
244 "Cube dim exceeds maximum bounds.\nRequested {requested:?}, max is {max:?}.\nBacktrace\n{backtrace}"
245 )]
246 CubeDim {
247 requested: (u32, u32, u32),
249 max: (u32, u32, u32),
251 #[cfg_attr(std_io, serde(skip))]
253 backtrace: BackTrace,
254 },
255 #[error(
257 "Max units per cube exceeds maximum bounds.\nRequested {requested}, max is {max}.\nBacktrace\n{backtrace}"
258 )]
259 MaxUnitPerCube {
260 requested: u32,
262 max: u32,
264 #[cfg_attr(std_io, serde(skip))]
266 backtrace: BackTrace,
267 },
268}
269
270impl core::fmt::Debug for LaunchError {
271 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272 f.write_fmt(format_args!("{self}"))
273 }
274}
275
276impl core::fmt::Debug for ResourceLimitError {
277 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
278 f.write_fmt(format_args!("{self}"))
279 }
280}
281
282fn graph_capture_unsupported() -> ServerError {
284 ServerError::Generic {
285 reason: alloc::string::String::from("graph capture is not supported by this backend"),
286 backtrace: BackTrace::capture(),
287 }
288}
289
290#[derive(Error, Clone)]
292#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
293pub enum ServerError {
294 #[error(
296 "A validation error happened during execution\nCaused by:\n {message}\nBacktrace:\n{backtrace}"
297 )]
298 Validation {
299 message: String,
301 #[cfg_attr(std_io, serde(skip))]
303 backtrace: BackTrace,
304 },
305
306 #[error("An error happened during execution\nCaused by:\n {reason}\nBacktrace:\n{backtrace}")]
308 Generic {
309 reason: String,
311 #[cfg_attr(std_io, serde(skip))]
313 backtrace: BackTrace,
314 },
315
316 #[error("A launch error happened\nCaused by:\n {0}")]
318 Launch(#[from] LaunchError),
319
320 #[error("An execution error happened during profiling\nCaused by:\n {0}")]
322 Profile(#[from] ProfileError),
323
324 #[error("An IO error happened\nCaused by:\n {0}")]
326 Io(#[from] IoError),
327
328 #[error("The server is in an invalid state\nCaused by:\n {}", errors.iter().join("\n"))]
330 ServerUnhealthy {
331 errors: Vec<Self>,
333 #[cfg_attr(std_io, serde(skip))]
335 backtrace: BackTrace,
336 },
337}
338
339impl Debug for ServerError {
340 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341 write!(f, "{self}")
342 }
343}
344
345#[derive(Clone, Copy)]
347pub struct StreamErrorMode {
348 pub ignore: bool,
350 pub flush: bool,
352}
353
354pub trait ComputeServer:
359 Send + core::fmt::Debug + ServerCommunication + device::DeviceService + 'static
360where
361 Self: Sized,
362{
363 type Kernel: KernelMetadata;
365 type Info: Debug + Send + Sync;
367 type MemoryLayoutPolicy: MemoryLayoutPolicy;
369 type Storage: ComputeStorage;
371
372 fn initialize_memory(&mut self, memory: ManagedMemoryHandle, size: u64, stream_id: StreamId);
374
375 fn staging(
377 &mut self,
378 _sizes: &[usize],
379 _stream_id: StreamId,
380 ) -> Result<Vec<Bytes>, ServerError> {
381 Err(IoError::UnsupportedIoOperation {
382 backtrace: BackTrace::capture(),
383 }
384 .into())
385 }
386
387 fn logger(&self) -> Arc<ServerLogger>;
389
390 fn utilities(&self) -> Arc<ServerUtilities<Self>>;
392
393 fn read(
395 &mut self,
396 descriptors: Vec<CopyDescriptor>,
397 stream_id: StreamId,
398 ) -> DynFut<Result<Vec<Bytes>, ServerError>>;
399
400 fn write(&mut self, descriptors: Vec<(CopyDescriptor, Bytes)>, stream_id: StreamId);
402
403 fn sync(&mut self, stream_id: StreamId) -> DynFut<Result<(), ServerError>>;
405
406 fn get_resource(
408 &mut self,
409 binding: Binding,
410 stream_id: StreamId,
411 ) -> Result<ManagedResource<<Self::Storage as ComputeStorage>::Resource>, ServerError>;
412
413 unsafe fn launch(
422 &mut self,
423 kernel: Self::Kernel,
424 count: CubeCount,
425 bindings: KernelArguments,
426 kind: ExecutionMode,
427 stream_id: StreamId,
428 );
429
430 fn flush(&mut self, stream_id: StreamId) -> Result<(), ServerError>;
432
433 fn graph_prepare(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
450 let _ = stream_id;
451 Ok(())
452 }
453
454 fn begin_capture(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
463 let _ = stream_id;
464 Err(graph_capture_unsupported())
465 }
466
467 fn end_capture(&mut self, stream_id: StreamId) -> Result<GraphId, ServerError> {
471 let _ = stream_id;
472 Err(graph_capture_unsupported())
473 }
474
475 fn replay(&mut self, graph: GraphId, stream_id: StreamId) {
485 let _ = (graph, stream_id);
486 }
487
488 fn graph_destroy(&mut self, graph: GraphId, stream_id: StreamId) {
493 let _ = (graph, stream_id);
494 }
495
496 fn memory_usage(&mut self, stream_id: StreamId) -> Result<MemoryUsage, ServerError>;
498
499 fn stream_ids(&self) -> Vec<StreamId> {
505 Vec::from([StreamId::current()])
506 }
507
508 fn memory_cleanup(&mut self, stream_id: StreamId);
510
511 fn configure_memory_pools(&mut self, config: MemoryConfiguration, stream_id: StreamId) -> bool {
528 let _ = (config, stream_id);
529 log::warn!("Memory pool configuration isn't supported by this server; keeping defaults");
530 false
531 }
532
533 fn start_profile(&mut self, stream_id: StreamId) -> Result<ProfilingToken, ServerError>;
535
536 fn end_profile(
538 &mut self,
539 stream_id: StreamId,
540 token: ProfilingToken,
541 ) -> Result<ProfileDuration, ProfileError>;
542
543 fn allocation_mode(&mut self, mode: MemoryAllocationMode, stream_id: StreamId);
545}
546
547#[derive(Clone, Debug, Hash, Eq, PartialEq)]
549pub struct CommunicationId {
550 pub id: u64,
552}
553
554impl From<Vec<DeviceId>> for CommunicationId {
555 fn from(mut value: Vec<DeviceId>) -> Self {
556 value.sort();
558 let mut hasher = AHasher::default();
559 value.hash(&mut hasher);
560 CommunicationId {
561 id: hasher.finish(),
562 }
563 }
564}
565
566pub enum ReduceOperation {
568 Sum,
570 Mean,
572}
573
574pub trait ServerCommunication {
577 const SERVER_COMM_ENABLED: bool;
579
580 #[allow(unused_variables)]
590 fn sync_collective(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
591 todo!() }
593
594 #[allow(unused_variables)]
604 fn comm_init(&mut self, device_ids: Vec<DeviceId>) -> Result<(), ServerError> {
605 unimplemented!()
606 }
607
608 #[allow(unused_variables)]
624 fn all_reduce(
625 &mut self,
626 src: Binding,
627 dst: Binding,
628 dtype: ElemType,
629 stream_id: StreamId,
630 op: ReduceOperation,
631 device_ids: Vec<DeviceId>,
632 ) -> Result<(), ServerError> {
633 unimplemented!()
634 }
635
636 #[allow(unused_variables)]
649 fn send(
650 &mut self,
651 desc: CopyDescriptor,
652 dtype: ElemType,
653 stream_id: StreamId,
654 device_id_dst: DeviceId,
655 ) -> Result<(), ServerError> {
656 unimplemented!()
657 }
658
659 #[allow(unused_variables)]
672 fn recv(
673 &mut self,
674 handle: Handle,
675 dtype: ElemType,
676 stream_id: StreamId,
677 device_id_src: DeviceId,
678 ) -> Result<(), ServerError> {
679 unimplemented!()
680 }
681}
682
683#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
684pub struct ProfilingToken {
686 pub id: u64,
688}
689
690#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
692pub enum MemoryLayoutStrategy {
693 Contiguous,
695 Optimized,
698}
699
700#[derive(new, Debug, Clone)]
702pub struct MemoryLayoutDescriptor {
703 pub strategy: MemoryLayoutStrategy,
705 pub shape: Shape,
707 pub elem_size: usize,
709}
710
711impl MemoryLayoutDescriptor {
712 pub fn optimized(shape: Shape, elem_size: usize) -> Self {
714 MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Optimized, shape, elem_size)
715 }
716
717 pub fn contiguous(shape: Shape, elem_size: usize) -> Self {
719 MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Contiguous, shape, elem_size)
720 }
721}
722
723#[derive(Debug, Clone)]
725pub struct MemoryLayout {
726 pub memory: Handle,
728 pub strides: Strides,
732}
733
734impl MemoryLayout {
735 pub fn new(handle: Handle, strides: impl Into<Strides>) -> Self {
737 MemoryLayout {
738 memory: handle,
739 strides: strides.into(),
740 }
741 }
742}
743
744#[derive(Default, Clone)]
746pub struct Reason {
747 inner: ReasonInner,
748}
749
750#[cfg(std_io)]
751mod _reason_serde {
752 use super::*;
753
754 use alloc::string::ToString;
755 use serde::{Deserialize, Deserializer, Serialize, Serializer};
756
757 impl Serialize for Reason {
758 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
759 where
760 S: Serializer,
761 {
762 serializer.serialize_str(&self.to_string())
764 }
765 }
766
767 impl<'de> Deserialize<'de> for Reason {
768 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
769 where
770 D: Deserializer<'de>,
771 {
772 let s = String::deserialize(deserializer)?;
774
775 Ok(Reason {
778 inner: ReasonInner::Dynamic(Arc::new(s)),
779 })
780 }
781 }
782}
783
784#[derive(Default, Clone)]
785enum ReasonInner {
786 Static(&'static str),
787 Dynamic(Arc<String>),
788 #[default]
789 NotProvided,
790}
791
792impl core::fmt::Display for Reason {
793 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
794 match &self.inner {
795 ReasonInner::Static(content) => f.write_str(content),
796 ReasonInner::Dynamic(content) => f.write_str(content),
797 ReasonInner::NotProvided => f.write_str("No reason provided for the error"),
798 }
799 }
800}
801
802impl core::fmt::Debug for Reason {
803 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
804 core::fmt::Display::fmt(&self, f)
805 }
806}
807
808impl From<&'static str> for Reason {
809 fn from(value: &'static str) -> Self {
810 Self {
811 inner: ReasonInner::Static(value),
812 }
813 }
814}
815
816impl From<String> for Reason {
817 fn from(value: String) -> Self {
818 Self {
819 inner: ReasonInner::Dynamic(Arc::new(value)),
820 }
821 }
822}
823
824#[derive(Error, Clone)]
827#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
828pub enum IoError {
829 #[error("can't allocate buffer of size: {size}\n{backtrace}")]
831 BufferTooBig {
832 size: u64,
834 #[cfg_attr(std_io, serde(skip))]
836 backtrace: BackTrace,
837 },
838
839 #[error(
847 "memory pool capacity exceeded: failed to reserve {size} bytes, pool is capped at {capacity} bytes ({in_use} bytes in use)\n{backtrace}"
848 )]
849 PoolCapacityExceeded {
850 size: u64,
852 capacity: u64,
854 in_use: u64,
856 #[cfg_attr(std_io, serde(skip))]
858 backtrace: BackTrace,
859 },
860
861 #[error("the provided strides are not supported for this operation\n{backtrace}")]
863 UnsupportedStrides {
864 #[cfg_attr(std_io, serde(skip))]
866 backtrace: BackTrace,
867 },
868
869 #[error("couldn't find resource for that handle: {reason}\n{backtrace}")]
871 NotFound {
872 #[cfg_attr(std_io, serde(skip))]
874 backtrace: BackTrace,
875 reason: Reason,
877 },
878
879 #[error("couldn't free the handle, since it is currently in used. \n{backtrace}")]
881 FreeError {
882 #[cfg_attr(std_io, serde(skip))]
884 backtrace: BackTrace,
885 },
886
887 #[error("Unknown error happened during execution\n{backtrace}")]
889 Unknown {
890 description: String,
892 #[cfg_attr(std_io, serde(skip))]
894 backtrace: BackTrace,
895 },
896
897 #[error("The current IO operation is not supported\n{backtrace}")]
899 UnsupportedIoOperation {
900 #[cfg_attr(std_io, serde(skip))]
902 backtrace: BackTrace,
903 },
904
905 #[error("Can't perform the IO operation because of a runtime error: {0}")]
907 Execution(#[from] Box<ServerError>),
908}
909
910impl core::fmt::Debug for IoError {
911 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
912 f.write_fmt(format_args!("{self}"))
913 }
914}
915
916#[derive(Debug, Default)]
918pub struct KernelArguments {
919 pub buffers: Vec<Binding>,
921 pub info: MetadataBindingInfo,
924 pub tensor_maps: Vec<TensorMapBinding>,
926}
927
928impl core::fmt::Display for KernelArguments {
929 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
930 f.write_str("KernelArguments")?;
931 for b in self.buffers.iter() {
932 f.write_fmt(format_args!("\n - buffer: {b:?}\n"))?;
933 }
934
935 Ok(())
936 }
937}
938
939impl KernelArguments {
940 pub fn new() -> Self {
942 Self::default()
943 }
944
945 pub fn with_buffer(mut self, binding: Binding) -> Self {
947 self.buffers.push(binding);
948 self
949 }
950
951 pub fn with_buffers(mut self, bindings: Vec<Binding>) -> Self {
953 self.buffers.extend(bindings);
954 self
955 }
956
957 pub fn with_info(mut self, info: MetadataBindingInfo) -> Self {
959 self.info = info;
960 self
961 }
962
963 pub fn with_tensor_maps(mut self, bindings: Vec<TensorMapBinding>) -> Self {
965 self.tensor_maps.extend(bindings);
966 self
967 }
968}
969
970#[derive(new, Debug, Default)]
975pub struct MetadataBindingInfo {
976 pub data: Vec<u64>,
978 pub dynamic_metadata_offset: usize,
980}
981
982impl MetadataBindingInfo {
983 pub fn custom(data: Vec<u64>) -> Self {
985 Self::new(data, 0)
986 }
987}
988
989#[derive(new, Debug)]
991pub struct CopyDescriptor {
992 pub handle: Binding,
994 pub shape: Shape,
996 pub strides: Strides,
998 pub elem_size: usize,
1000}
1001
1002#[derive(new, Debug)]
1004pub struct TensorMapBinding {
1005 pub binding: Binding,
1007 pub map: TensorMapMeta,
1009}
1010
1011#[derive(Debug, Clone)]
1013pub struct TensorMapMeta {
1014 pub format: TensorMapFormat,
1016 pub metadata: Metadata,
1018 pub elem_stride: Strides,
1021 pub interleave: TensorMapInterleave,
1023 pub swizzle: TensorMapSwizzle,
1025 pub prefetch: TensorMapPrefetch,
1027 pub oob_fill: OobFill,
1029 pub storage_ty: StorageType,
1031}
1032
1033#[allow(clippy::large_enum_variant)]
1037pub enum CubeCount {
1038 Static(u32, u32, u32),
1040 Dynamic(Binding),
1042}
1043
1044pub enum CubeCountSelection {
1046 Exact(CubeCount),
1048 Approx(CubeCount, u32),
1052}
1053
1054impl CubeCountSelection {
1055 pub fn new<R: Runtime>(client: &ComputeClient<R>, num_cubes: u32) -> Self {
1057 let cube_count = cube_count_spread(&client.properties().hardware.max_cube_count, num_cubes);
1058
1059 let num_cubes_actual = cube_count[0] * cube_count[1] * cube_count[2];
1060 let cube_count = CubeCount::Static(cube_count[0], cube_count[1], cube_count[2]);
1061
1062 match num_cubes_actual == num_cubes {
1063 true => CubeCountSelection::Exact(cube_count),
1064 false => CubeCountSelection::Approx(cube_count, num_cubes_actual),
1065 }
1066 }
1067
1068 pub fn has_idle(&self) -> bool {
1070 matches!(self, Self::Approx(..))
1071 }
1072
1073 pub fn cube_count(self) -> CubeCount {
1075 match self {
1076 CubeCountSelection::Exact(cube_count) => cube_count,
1077 CubeCountSelection::Approx(cube_count, _) => cube_count,
1078 }
1079 }
1080}
1081
1082impl From<CubeCountSelection> for CubeCount {
1083 fn from(value: CubeCountSelection) -> Self {
1084 value.cube_count()
1085 }
1086}
1087
1088impl CubeCount {
1089 pub fn new_single() -> Self {
1091 CubeCount::Static(1, 1, 1)
1092 }
1093
1094 pub fn new_1d(x: u32) -> Self {
1096 CubeCount::Static(x, 1, 1)
1097 }
1098
1099 pub fn new_2d(x: u32, y: u32) -> Self {
1101 CubeCount::Static(x, y, 1)
1102 }
1103
1104 pub fn new_3d(x: u32, y: u32, z: u32) -> Self {
1106 CubeCount::Static(x, y, z)
1107 }
1108
1109 pub fn is_empty(&self) -> bool {
1111 match self {
1112 Self::Static(x, y, z) => *x == 0 || *y == 0 || *z == 0,
1113 Self::Dynamic(_) => false,
1114 }
1115 }
1116}
1117
1118impl Debug for CubeCount {
1119 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1120 match self {
1121 CubeCount::Static(x, y, z) => f.write_fmt(format_args!("({x}, {y}, {z})")),
1122 CubeCount::Dynamic(_) => f.write_str("binding"),
1123 }
1124 }
1125}
1126
1127impl Clone for CubeCount {
1128 fn clone(&self) -> Self {
1129 match self {
1130 Self::Static(x, y, z) => Self::Static(*x, *y, *z),
1131 Self::Dynamic(binding) => Self::Dynamic(binding.clone()),
1132 }
1133 }
1134}
1135
1136#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
1137#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
1138#[allow(missing_docs)]
1139pub struct CubeDim {
1141 pub x: u32,
1143 pub y: u32,
1145 pub z: u32,
1147}
1148
1149impl CubeDim {
1150 pub fn new<R: Runtime>(client: &ComputeClient<R>, working_units: usize) -> Self {
1158 let properties = client.properties();
1159 let plane_size = properties.hardware.plane_size_max;
1160 let plane_count = Self::calculate_plane_count_per_cube(
1161 working_units as u32,
1162 plane_size,
1163 properties.hardware.num_cpu_cores,
1164 );
1165
1166 let limit = properties.hardware.max_units_per_cube / plane_size;
1168
1169 Self::new_2d(plane_size, u32::min(limit, plane_count).max(1))
1171 }
1172
1173 fn calculate_plane_count_per_cube(
1174 working_units: u32,
1175 plane_dim: u32,
1176 num_cpu_cores: Option<u32>,
1177 ) -> u32 {
1178 match num_cpu_cores {
1179 Some(num_cores) => core::cmp::min(num_cores, working_units),
1180 None => {
1181 let plane_count_max = core::cmp::max(1, working_units / plane_dim);
1182
1183 const NUM_PLANE_MAX: u32 = 8u32;
1185 const NUM_PLANE_MAX_LOG2: u32 = NUM_PLANE_MAX.ilog2();
1186 let plane_count_max_log2 =
1187 core::cmp::min(NUM_PLANE_MAX_LOG2, u32::ilog2(plane_count_max));
1188 2u32.pow(plane_count_max_log2)
1189 }
1190 }
1191 }
1192
1193 pub const fn new_single() -> Self {
1195 Self { x: 1, y: 1, z: 1 }
1196 }
1197
1198 pub const fn new_1d(x: u32) -> Self {
1200 Self { x, y: 1, z: 1 }
1201 }
1202
1203 pub const fn new_2d(x: u32, y: u32) -> Self {
1205 Self { x, y, z: 1 }
1206 }
1207
1208 pub const fn new_3d(x: u32, y: u32, z: u32) -> Self {
1211 Self { x, y, z }
1212 }
1213
1214 pub const fn num_elems(&self) -> u32 {
1216 self.x * self.y * self.z
1217 }
1218
1219 pub const fn can_contain(&self, other: CubeDim) -> bool {
1221 self.x >= other.x && self.y >= other.y && self.z >= other.z
1222 }
1223}
1224
1225impl From<(u32, u32, u32)> for CubeDim {
1226 fn from(value: (u32, u32, u32)) -> Self {
1227 CubeDim::new_3d(value.0, value.1, value.2)
1228 }
1229}
1230
1231impl From<CubeDim> for (u32, u32, u32) {
1232 fn from(val: CubeDim) -> Self {
1233 (val.x, val.y, val.z)
1234 }
1235}
1236
1237#[derive(Default, Hash, PartialEq, Eq, Clone, Debug, Copy)]
1239#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
1240pub enum ExecutionMode {
1241 #[default]
1243 Checked,
1244 Validate,
1246 Unchecked,
1248}
1249
1250fn cube_count_spread(max: &(u32, u32, u32), num_cubes: u32) -> [u32; 3] {
1251 let max_cube_counts = [max.0, max.1, max.2];
1252 let mut num_cubes = [num_cubes, 1, 1];
1253 let base = 2;
1254
1255 let mut reduce_count = |i: usize| {
1256 if num_cubes[i] <= max_cube_counts[i] {
1257 return true;
1258 }
1259
1260 loop {
1261 num_cubes[i] = num_cubes[i].div_ceil(base);
1262 num_cubes[i + 1] *= base;
1263
1264 if num_cubes[i] <= max_cube_counts[i] {
1265 return false;
1266 }
1267 }
1268 };
1269
1270 for i in 0..2 {
1271 if reduce_count(i) {
1272 break;
1273 }
1274 }
1275
1276 num_cubes
1277}
1278
1279#[cfg(test)]
1280mod tests {
1281 use super::*;
1282
1283 #[test_log::test]
1284 fn safe_num_cubes_even() {
1285 let max = (32, 32, 32);
1286 let required = 2048;
1287
1288 let actual = cube_count_spread(&max, required);
1289 let expected = [32, 32, 2];
1290 assert_eq!(actual, expected);
1291 }
1292
1293 #[test_log::test]
1294 fn safe_num_cubes_odd() {
1295 let max = (48, 32, 16);
1296 let required = 3177;
1297
1298 let actual = cube_count_spread(&max, required);
1299 let expected = [25, 32, 4];
1300 assert_eq!(actual, expected);
1301 }
1302}