1use super::Handle;
2use crate::{
3 client::ComputeClient,
4 compiler::CompilationError,
5 config::{CubeClRuntimeConfig, RuntimeConfig, compilation::BoundsCheckMode},
6 dry_run::LaunchMode,
7 id::GraphId,
8 kernel::KernelMetadata,
9 logging::ServerLogger,
10 memory_management::{
11 ManagedMemoryHandle, MemoryAllocationMode, MemoryConfiguration, MemoryUsage,
12 },
13 runtime::Runtime,
14 server::Binding,
15 storage::{ComputeStorage, ManagedResource},
16 tma::{OobFill, TensorMapFormat, TensorMapInterleave, TensorMapPrefetch, TensorMapSwizzle},
17};
18use ahash::AHasher;
19use alloc::boxed::Box;
20#[cfg(feature = "profile-tracy")]
21use alloc::format;
22use alloc::string::String;
23use alloc::sync::Arc;
24use alloc::vec::Vec;
25use core::{
26 fmt::Debug,
27 hash::{Hash, Hasher},
28};
29use cubecl_common::{
30 bytes::Bytes,
31 device::{self, DeviceId},
32 profile::ProfileDuration,
33};
34use cubecl_environment::backtrace::BackTrace;
35use cubecl_environment::collections::HashSet;
36use cubecl_environment::future::DynFut;
37use cubecl_environment::stream::StreamId;
38use cubecl_environment::sync::RwLock;
39use cubecl_ir::{DeviceProperties, ElemType, StorageType};
40use cubecl_zspace::{Shape, Strides, metadata::Metadata};
41use itertools::Itertools;
42use thiserror::Error;
43
44#[derive(Error, Clone)]
45#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
46pub enum ProfileError {
48 #[error(
50 "An unknown error happened during profiling\nCaused by:\n {reason}\nBacktrace:\n{backtrace}"
51 )]
52 Unknown {
53 reason: String,
55 #[cfg_attr(std_io, serde(skip))]
57 backtrace: BackTrace,
58 },
59
60 #[error("No profiling registered\nBacktrace:\n{backtrace}")]
62 NotRegistered {
63 #[cfg_attr(std_io, serde(skip))]
65 backtrace: BackTrace,
66 },
67
68 #[error("A launch error happened during profiling\nCaused by:\n {0}")]
70 Launch(#[from] LaunchError),
71
72 #[error("An execution error happened during profiling\nCaused by:\n {0}")]
74 Server(#[from] Box<ServerError>),
75}
76
77impl core::fmt::Debug for ProfileError {
78 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79 f.write_fmt(format_args!("{self}"))
80 }
81}
82
83pub struct ServerUtilities<Server: ComputeServer> {
85 #[cfg(feature = "profile-tracy")]
87 pub epoch_time: cubecl_environment::time::Instant,
88 #[cfg(feature = "profile-tracy")]
90 pub gpu_client: tracy_client::GpuContext,
91 pub properties: DeviceProperties,
93 pub properties_hash: u64,
95 pub info: Server::Info,
97 pub logger: Arc<ServerLogger>,
99 pub layout_policy: Server::MemoryLayoutPolicy,
101 pub check_mode: BoundsCheckMode,
103 pub initialized_comms: RwLock<HashSet<CommunicationId>>,
105}
106
107pub trait MemoryLayoutPolicy: Send + Sync + 'static {
109 fn apply(
114 &self,
115 stream_id: StreamId,
116 descriptors: &[MemoryLayoutDescriptor],
117 ) -> (Handle, Vec<MemoryLayout>);
118}
119
120impl<Server: core::fmt::Debug> core::fmt::Debug for ServerUtilities<Server>
121where
122 Server: ComputeServer,
123 Server::Info: core::fmt::Debug,
124{
125 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
126 f.debug_struct("ServerUtilities")
127 .field("properties", &self.properties)
128 .field("info", &self.info)
129 .field("logger", &self.logger)
130 .finish()
131 }
132}
133
134impl<S: ComputeServer> ServerUtilities<S> {
135 pub fn new(
137 properties: DeviceProperties,
138 logger: Arc<ServerLogger>,
139 info: S::Info,
140 allocator: S::MemoryLayoutPolicy,
141 ) -> Self {
142 #[cfg(feature = "profile-tracy")]
144 let client = tracy_client::Client::start();
145
146 Self {
147 properties_hash: properties.checksum(),
148 properties,
149 logger,
150 #[cfg(feature = "profile-tracy")]
152 gpu_client: client
153 .clone()
154 .new_gpu_context(
155 Some(&format!("{info:?}")),
156 tracy_client::GpuContextType::Invalid,
158 0, 1.0, )
161 .unwrap(),
162 #[cfg(feature = "profile-tracy")]
163 epoch_time: cubecl_environment::time::Instant::now(),
164 info,
165 layout_policy: allocator,
166 check_mode: CubeClRuntimeConfig::get().compilation.check_mode,
167 initialized_comms: RwLock::new(HashSet::default()),
168 }
169 }
170}
171
172#[derive(Error, Clone)]
174#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
175pub enum LaunchError {
176 #[error("A compilation error happened during launch\nCaused by:\n {0}")]
178 CompilationError(#[from] CompilationError),
179
180 #[error(
182 "An out-of-memory error happened during launch\nCaused by:\n {reason}\nBacktrace\n{backtrace}"
183 )]
184 OutOfMemory {
185 reason: String,
187 #[cfg_attr(std_io, serde(skip))]
189 backtrace: BackTrace,
190 },
191
192 #[error("Too many resources were requested during launch\n{0}")]
194 TooManyResources(#[from] ResourceLimitError),
195
196 #[error(
198 "An unknown error happened during launch\nCaused by:\n {reason}\nBacktrace\n{backtrace}"
199 )]
200 Unknown {
201 reason: String,
203 #[cfg_attr(std_io, serde(skip))]
205 backtrace: BackTrace,
206 },
207
208 #[error("An io error happened during launch\nCaused by:\n {0}")]
210 IoError(#[from] IoError),
211}
212
213#[derive(Error, Clone)]
215#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
216pub enum ResourceLimitError {
217 #[error(
219 "Too much shared memory requested.\nRequested {requested} bytes, maximum {max} bytes available.\nBacktrace\n{backtrace}"
220 )]
221 SharedMemory {
222 requested: usize,
224 max: usize,
226 #[cfg_attr(std_io, serde(skip))]
228 backtrace: BackTrace,
229 },
230 #[error(
232 "Total unit count exceeds maximum.\nRequested {requested} units, max units is {max}.\nBacktrace\n{backtrace}"
233 )]
234 Units {
235 requested: u32,
237 max: u32,
239 #[cfg_attr(std_io, serde(skip))]
241 backtrace: BackTrace,
242 },
243 #[error(
245 "Cube dim exceeds maximum bounds.\nRequested {requested:?}, max is {max:?}.\nBacktrace\n{backtrace}"
246 )]
247 CubeDim {
248 requested: (u32, u32, u32),
250 max: (u32, u32, u32),
252 #[cfg_attr(std_io, serde(skip))]
254 backtrace: BackTrace,
255 },
256 #[error(
258 "Max units per cube exceeds maximum bounds.\nRequested {requested}, max is {max}.\nBacktrace\n{backtrace}"
259 )]
260 MaxUnitPerCube {
261 requested: u32,
263 max: u32,
265 #[cfg_attr(std_io, serde(skip))]
267 backtrace: BackTrace,
268 },
269}
270
271impl core::fmt::Debug for LaunchError {
272 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273 f.write_fmt(format_args!("{self}"))
274 }
275}
276
277impl core::fmt::Debug for ResourceLimitError {
278 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
279 f.write_fmt(format_args!("{self}"))
280 }
281}
282
283fn graph_capture_unsupported() -> ServerError {
285 ServerError::Generic {
286 reason: alloc::string::String::from("graph capture is not supported by this backend"),
287 backtrace: BackTrace::capture(),
288 }
289}
290
291#[derive(Error, Clone)]
293#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
294pub enum ServerError {
295 #[error(
297 "A validation error happened during execution\nCaused by:\n {message}\nBacktrace:\n{backtrace}"
298 )]
299 Validation {
300 message: String,
302 #[cfg_attr(std_io, serde(skip))]
304 backtrace: BackTrace,
305 },
306
307 #[error("An error happened during execution\nCaused by:\n {reason}\nBacktrace:\n{backtrace}")]
309 Generic {
310 reason: String,
312 #[cfg_attr(std_io, serde(skip))]
314 backtrace: BackTrace,
315 },
316
317 #[error("A launch error happened\nCaused by:\n {0}")]
319 Launch(#[from] LaunchError),
320
321 #[error("An execution error happened during profiling\nCaused by:\n {0}")]
323 Profile(#[from] ProfileError),
324
325 #[error("An IO error happened\nCaused by:\n {0}")]
327 Io(#[from] IoError),
328
329 #[error("The server is in an invalid state\nCaused by:\n {}", errors.iter().join("\n"))]
331 ServerUnhealthy {
332 errors: Vec<Self>,
334 #[cfg_attr(std_io, serde(skip))]
336 backtrace: BackTrace,
337 },
338}
339
340impl Debug for ServerError {
341 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342 write!(f, "{self}")
343 }
344}
345
346#[derive(Clone, Copy)]
348pub struct StreamErrorMode {
349 pub ignore: bool,
351 pub flush: bool,
353}
354
355pub trait ComputeServer:
360 Send + core::fmt::Debug + ServerCommunication + device::DeviceService + 'static
361where
362 Self: Sized,
363{
364 type Kernel: KernelMetadata;
366 type Info: Debug + Send + Sync;
368 type MemoryLayoutPolicy: MemoryLayoutPolicy;
370 type Storage: ComputeStorage;
372
373 fn initialize_memory(&mut self, memory: ManagedMemoryHandle, size: u64, stream_id: StreamId);
375
376 fn staging(
378 &mut self,
379 _sizes: &[usize],
380 _stream_id: StreamId,
381 ) -> Result<Vec<Bytes>, ServerError> {
382 Err(IoError::UnsupportedIoOperation {
383 backtrace: BackTrace::capture(),
384 }
385 .into())
386 }
387
388 fn logger(&self) -> Arc<ServerLogger>;
390
391 fn utilities(&self) -> Arc<ServerUtilities<Self>>;
393
394 fn read(
396 &mut self,
397 descriptors: Vec<CopyDescriptor>,
398 stream_id: StreamId,
399 ) -> DynFut<Result<Vec<Bytes>, ServerError>>;
400
401 fn write(&mut self, descriptors: Vec<(CopyDescriptor, Bytes)>, stream_id: StreamId);
403
404 fn sync(&mut self, stream_id: StreamId) -> DynFut<Result<(), ServerError>>;
406
407 fn get_resource(
409 &mut self,
410 binding: Binding,
411 stream_id: StreamId,
412 ) -> Result<ManagedResource<<Self::Storage as ComputeStorage>::Resource>, ServerError>;
413
414 unsafe fn launch(
429 &mut self,
430 kernel: Self::Kernel,
431 count: CubeCount,
432 bindings: KernelArguments,
433 kind: ExecutionMode,
434 stream_id: StreamId,
435 launch_mode: LaunchMode,
436 );
437
438 fn flush(&mut self, stream_id: StreamId) -> Result<(), ServerError>;
440
441 fn graph_prepare(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
458 let _ = stream_id;
459 Ok(())
460 }
461
462 fn begin_capture(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
471 let _ = stream_id;
472 Err(graph_capture_unsupported())
473 }
474
475 fn end_capture(&mut self, stream_id: StreamId) -> Result<GraphId, ServerError> {
479 let _ = stream_id;
480 Err(graph_capture_unsupported())
481 }
482
483 fn replay(&mut self, graph: GraphId, stream_id: StreamId) {
493 let _ = (graph, stream_id);
494 }
495
496 fn graph_destroy(&mut self, graph: GraphId, stream_id: StreamId) {
501 let _ = (graph, stream_id);
502 }
503
504 fn memory_usage(&mut self, stream_id: StreamId) -> Result<MemoryUsage, ServerError>;
506
507 fn stream_ids(&self) -> Vec<StreamId> {
513 Vec::from([StreamId::current()])
514 }
515
516 fn memory_cleanup(&mut self, stream_id: StreamId);
518
519 fn configure_memory_pools(&mut self, config: MemoryConfiguration, stream_id: StreamId) -> bool {
536 let _ = (config, stream_id);
537 log::warn!("Memory pool configuration isn't supported by this server; keeping defaults");
538 false
539 }
540
541 fn start_profile(&mut self, stream_id: StreamId) -> Result<ProfilingToken, ServerError>;
543
544 fn end_profile(
546 &mut self,
547 stream_id: StreamId,
548 token: ProfilingToken,
549 ) -> Result<ProfileDuration, ProfileError>;
550
551 fn allocation_mode(&mut self, mode: MemoryAllocationMode, stream_id: StreamId);
553}
554
555#[derive(Clone, Debug, Hash, Eq, PartialEq)]
557pub struct CommunicationId {
558 pub id: u64,
560}
561
562impl From<Vec<DeviceId>> for CommunicationId {
563 fn from(mut value: Vec<DeviceId>) -> Self {
564 value.sort();
566 let mut hasher = AHasher::default();
567 value.hash(&mut hasher);
568 CommunicationId {
569 id: hasher.finish(),
570 }
571 }
572}
573
574pub enum ReduceOperation {
576 Sum,
578 Mean,
580}
581
582pub trait ServerCommunication {
585 const SERVER_COMM_ENABLED: bool;
587
588 #[allow(unused_variables)]
598 fn sync_collective(&mut self, stream_id: StreamId) -> Result<(), ServerError> {
599 todo!() }
601
602 #[allow(unused_variables)]
612 fn comm_init(&mut self, device_ids: Vec<DeviceId>) -> Result<(), ServerError> {
613 unimplemented!()
614 }
615
616 #[allow(unused_variables)]
632 fn all_reduce(
633 &mut self,
634 src: Binding,
635 dst: Binding,
636 dtype: ElemType,
637 stream_id: StreamId,
638 op: ReduceOperation,
639 device_ids: Vec<DeviceId>,
640 ) -> Result<(), ServerError> {
641 unimplemented!()
642 }
643
644 #[allow(unused_variables)]
657 fn send(
658 &mut self,
659 desc: CopyDescriptor,
660 dtype: ElemType,
661 stream_id: StreamId,
662 device_id_dst: DeviceId,
663 ) -> Result<(), ServerError> {
664 unimplemented!()
665 }
666
667 #[allow(unused_variables)]
680 fn recv(
681 &mut self,
682 handle: Handle,
683 dtype: ElemType,
684 stream_id: StreamId,
685 device_id_src: DeviceId,
686 ) -> Result<(), ServerError> {
687 unimplemented!()
688 }
689}
690
691#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
692pub struct ProfilingToken {
694 pub id: u64,
696}
697
698#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
700pub enum MemoryLayoutStrategy {
701 Contiguous,
703 Optimized,
706}
707
708#[derive(new, Debug, Clone)]
710pub struct MemoryLayoutDescriptor {
711 pub strategy: MemoryLayoutStrategy,
713 pub shape: Shape,
715 pub elem_size: usize,
717}
718
719impl MemoryLayoutDescriptor {
720 pub fn optimized(shape: Shape, elem_size: usize) -> Self {
722 MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Optimized, shape, elem_size)
723 }
724
725 pub fn contiguous(shape: Shape, elem_size: usize) -> Self {
727 MemoryLayoutDescriptor::new(MemoryLayoutStrategy::Contiguous, shape, elem_size)
728 }
729}
730
731#[derive(Debug, Clone)]
733pub struct MemoryLayout {
734 pub memory: Handle,
736 pub strides: Strides,
740}
741
742impl MemoryLayout {
743 pub fn new(handle: Handle, strides: impl Into<Strides>) -> Self {
745 MemoryLayout {
746 memory: handle,
747 strides: strides.into(),
748 }
749 }
750}
751
752#[derive(Default, Clone)]
754pub struct Reason {
755 inner: ReasonInner,
756}
757
758#[cfg(std_io)]
759mod _reason_serde {
760 use super::*;
761
762 use alloc::string::ToString;
763 use serde::{Deserialize, Deserializer, Serialize, Serializer};
764
765 impl Serialize for Reason {
766 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
767 where
768 S: Serializer,
769 {
770 serializer.serialize_str(&self.to_string())
772 }
773 }
774
775 impl<'de> Deserialize<'de> for Reason {
776 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
777 where
778 D: Deserializer<'de>,
779 {
780 let s = String::deserialize(deserializer)?;
782
783 Ok(Reason {
786 inner: ReasonInner::Dynamic(Arc::new(s)),
787 })
788 }
789 }
790}
791
792#[derive(Default, Clone)]
793enum ReasonInner {
794 Static(&'static str),
795 Dynamic(Arc<String>),
796 #[default]
797 NotProvided,
798}
799
800impl core::fmt::Display for Reason {
801 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
802 match &self.inner {
803 ReasonInner::Static(content) => f.write_str(content),
804 ReasonInner::Dynamic(content) => f.write_str(content),
805 ReasonInner::NotProvided => f.write_str("No reason provided for the error"),
806 }
807 }
808}
809
810impl core::fmt::Debug for Reason {
811 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
812 core::fmt::Display::fmt(&self, f)
813 }
814}
815
816impl From<&'static str> for Reason {
817 fn from(value: &'static str) -> Self {
818 Self {
819 inner: ReasonInner::Static(value),
820 }
821 }
822}
823
824impl From<String> for Reason {
825 fn from(value: String) -> Self {
826 Self {
827 inner: ReasonInner::Dynamic(Arc::new(value)),
828 }
829 }
830}
831
832#[derive(Error, Clone)]
835#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
836pub enum IoError {
837 #[error("can't allocate buffer of size: {size}\n{backtrace}")]
839 BufferTooBig {
840 size: u64,
842 #[cfg_attr(std_io, serde(skip))]
844 backtrace: BackTrace,
845 },
846
847 #[error("out of device memory allocating {size} bytes\n{backtrace}")]
857 OutOfMemory {
858 size: u64,
860 #[cfg_attr(std_io, serde(skip))]
862 backtrace: BackTrace,
863 },
864
865 #[error(
873 "memory pool capacity exceeded: failed to reserve {size} bytes, pool is capped at {capacity} bytes ({in_use} bytes in use)\n{backtrace}"
874 )]
875 PoolCapacityExceeded {
876 size: u64,
878 capacity: u64,
880 in_use: u64,
882 #[cfg_attr(std_io, serde(skip))]
884 backtrace: BackTrace,
885 },
886
887 #[error("the provided strides are not supported for this operation\n{backtrace}")]
889 UnsupportedStrides {
890 #[cfg_attr(std_io, serde(skip))]
892 backtrace: BackTrace,
893 },
894
895 #[error("couldn't find resource for that handle: {reason}\n{backtrace}")]
897 NotFound {
898 #[cfg_attr(std_io, serde(skip))]
900 backtrace: BackTrace,
901 reason: Reason,
903 },
904
905 #[error("couldn't free the handle, since it is currently in used. \n{backtrace}")]
907 FreeError {
908 #[cfg_attr(std_io, serde(skip))]
910 backtrace: BackTrace,
911 },
912
913 #[error("Unknown error happened during execution: {description}\n{backtrace}")]
915 Unknown {
916 description: String,
918 #[cfg_attr(std_io, serde(skip))]
920 backtrace: BackTrace,
921 },
922
923 #[error("The current IO operation is not supported\n{backtrace}")]
925 UnsupportedIoOperation {
926 #[cfg_attr(std_io, serde(skip))]
928 backtrace: BackTrace,
929 },
930
931 #[error("Can't perform the IO operation because of a runtime error: {0}")]
933 Execution(#[from] Box<ServerError>),
934}
935
936impl core::fmt::Debug for IoError {
937 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
938 f.write_fmt(format_args!("{self}"))
939 }
940}
941
942#[derive(Debug, Default)]
944pub struct KernelArguments {
945 pub buffers: Vec<Binding>,
947 pub info: MetadataBindingInfo,
950 pub tensor_maps: Vec<TensorMapBinding>,
952}
953
954impl core::fmt::Display for KernelArguments {
955 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
956 f.write_str("KernelArguments")?;
957 for b in self.buffers.iter() {
958 f.write_fmt(format_args!("\n - buffer: {b:?}\n"))?;
959 }
960
961 Ok(())
962 }
963}
964
965impl KernelArguments {
966 pub fn new() -> Self {
968 Self::default()
969 }
970
971 pub fn with_buffer(mut self, binding: Binding) -> Self {
973 self.buffers.push(binding);
974 self
975 }
976
977 pub fn with_buffers(mut self, bindings: Vec<Binding>) -> Self {
979 self.buffers.extend(bindings);
980 self
981 }
982
983 pub fn with_info(mut self, info: MetadataBindingInfo) -> Self {
985 self.info = info;
986 self
987 }
988
989 pub fn with_tensor_maps(mut self, bindings: Vec<TensorMapBinding>) -> Self {
991 self.tensor_maps.extend(bindings);
992 self
993 }
994}
995
996#[derive(new, Debug, Default)]
1001pub struct MetadataBindingInfo {
1002 pub data: Vec<u64>,
1004 pub dynamic_metadata_offset: usize,
1006}
1007
1008impl MetadataBindingInfo {
1009 pub fn custom(data: Vec<u64>) -> Self {
1011 Self::new(data, 0)
1012 }
1013}
1014
1015#[derive(new, Debug)]
1017pub struct CopyDescriptor {
1018 pub handle: Binding,
1020 pub shape: Shape,
1022 pub strides: Strides,
1024 pub elem_size: usize,
1026}
1027
1028#[derive(new, Debug)]
1030pub struct TensorMapBinding {
1031 pub binding: Binding,
1033 pub map: TensorMapMeta,
1035}
1036
1037#[derive(Debug, Clone)]
1039pub struct TensorMapMeta {
1040 pub format: TensorMapFormat,
1042 pub metadata: Metadata,
1044 pub elem_stride: Strides,
1047 pub interleave: TensorMapInterleave,
1049 pub swizzle: TensorMapSwizzle,
1051 pub prefetch: TensorMapPrefetch,
1053 pub oob_fill: OobFill,
1055 pub storage_ty: StorageType,
1057}
1058
1059#[allow(clippy::large_enum_variant)]
1063pub enum CubeCount {
1064 Static(u32, u32, u32),
1066 Dynamic(Binding),
1068}
1069
1070pub enum CubeCountSelection {
1072 Exact(CubeCount),
1074 Approx(CubeCount, u32),
1078}
1079
1080impl CubeCountSelection {
1081 pub fn new<R: Runtime>(client: &ComputeClient<R>, num_cubes: u32) -> Self {
1083 let cube_count = cube_count_spread(&client.properties().hardware.max_cube_count, num_cubes);
1084
1085 let num_cubes_actual = cube_count[0] * cube_count[1] * cube_count[2];
1086 let cube_count = CubeCount::Static(cube_count[0], cube_count[1], cube_count[2]);
1087
1088 match num_cubes_actual == num_cubes {
1089 true => CubeCountSelection::Exact(cube_count),
1090 false => CubeCountSelection::Approx(cube_count, num_cubes_actual),
1091 }
1092 }
1093
1094 pub fn has_idle(&self) -> bool {
1096 matches!(self, Self::Approx(..))
1097 }
1098
1099 pub fn cube_count(self) -> CubeCount {
1101 match self {
1102 CubeCountSelection::Exact(cube_count) => cube_count,
1103 CubeCountSelection::Approx(cube_count, _) => cube_count,
1104 }
1105 }
1106}
1107
1108impl From<CubeCountSelection> for CubeCount {
1109 fn from(value: CubeCountSelection) -> Self {
1110 value.cube_count()
1111 }
1112}
1113
1114impl CubeCount {
1115 pub fn new_single() -> Self {
1117 CubeCount::Static(1, 1, 1)
1118 }
1119
1120 pub fn new_1d(x: u32) -> Self {
1122 CubeCount::Static(x, 1, 1)
1123 }
1124
1125 pub fn new_2d(x: u32, y: u32) -> Self {
1127 CubeCount::Static(x, y, 1)
1128 }
1129
1130 pub fn new_3d(x: u32, y: u32, z: u32) -> Self {
1132 CubeCount::Static(x, y, z)
1133 }
1134
1135 pub fn is_empty(&self) -> bool {
1137 match self {
1138 Self::Static(x, y, z) => *x == 0 || *y == 0 || *z == 0,
1139 Self::Dynamic(_) => false,
1140 }
1141 }
1142}
1143
1144impl Debug for CubeCount {
1145 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1146 match self {
1147 CubeCount::Static(x, y, z) => f.write_fmt(format_args!("({x}, {y}, {z})")),
1148 CubeCount::Dynamic(_) => f.write_str("binding"),
1149 }
1150 }
1151}
1152
1153impl Clone for CubeCount {
1154 fn clone(&self) -> Self {
1155 match self {
1156 Self::Static(x, y, z) => Self::Static(*x, *y, *z),
1157 Self::Dynamic(binding) => Self::Dynamic(binding.clone()),
1158 }
1159 }
1160}
1161
1162#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
1163#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
1164#[allow(missing_docs)]
1165pub struct CubeDim {
1167 pub x: u32,
1169 pub y: u32,
1171 pub z: u32,
1173}
1174
1175impl CubeDim {
1176 pub fn new<R: Runtime>(client: &ComputeClient<R>, working_units: usize) -> Self {
1184 let properties = client.properties();
1185 let plane_size = properties.hardware.plane_size_max;
1186 let plane_count = Self::calculate_plane_count_per_cube(
1187 working_units as u32,
1188 plane_size,
1189 properties.hardware.num_cpu_cores,
1190 );
1191
1192 let limit = properties.hardware.max_units_per_cube / plane_size;
1194
1195 Self::new_2d(plane_size, u32::min(limit, plane_count).max(1))
1197 }
1198
1199 fn calculate_plane_count_per_cube(
1200 working_units: u32,
1201 plane_dim: u32,
1202 num_cpu_cores: Option<u32>,
1203 ) -> u32 {
1204 match num_cpu_cores {
1205 Some(num_cores) => core::cmp::min(num_cores, working_units),
1206 None => {
1207 let plane_count_max = core::cmp::max(1, working_units / plane_dim);
1208
1209 const NUM_PLANE_MAX: u32 = 8u32;
1211 const NUM_PLANE_MAX_LOG2: u32 = NUM_PLANE_MAX.ilog2();
1212 let plane_count_max_log2 =
1213 core::cmp::min(NUM_PLANE_MAX_LOG2, u32::ilog2(plane_count_max));
1214 2u32.pow(plane_count_max_log2)
1215 }
1216 }
1217 }
1218
1219 pub const fn new_single() -> Self {
1221 Self { x: 1, y: 1, z: 1 }
1222 }
1223
1224 pub const fn new_1d(x: u32) -> Self {
1226 Self { x, y: 1, z: 1 }
1227 }
1228
1229 pub const fn new_2d(x: u32, y: u32) -> Self {
1231 Self { x, y, z: 1 }
1232 }
1233
1234 pub const fn new_3d(x: u32, y: u32, z: u32) -> Self {
1237 Self { x, y, z }
1238 }
1239
1240 pub const fn num_elems(&self) -> u32 {
1242 self.x * self.y * self.z
1243 }
1244
1245 pub const fn can_contain(&self, other: CubeDim) -> bool {
1247 self.x >= other.x && self.y >= other.y && self.z >= other.z
1248 }
1249}
1250
1251impl From<(u32, u32, u32)> for CubeDim {
1252 fn from(value: (u32, u32, u32)) -> Self {
1253 CubeDim::new_3d(value.0, value.1, value.2)
1254 }
1255}
1256
1257impl From<CubeDim> for (u32, u32, u32) {
1258 fn from(val: CubeDim) -> Self {
1259 (val.x, val.y, val.z)
1260 }
1261}
1262
1263#[derive(Default, Hash, PartialEq, Eq, Clone, Debug, Copy)]
1265#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
1266pub enum ExecutionMode {
1267 #[default]
1269 Checked,
1270 Validate,
1272 Unchecked,
1274}
1275
1276fn cube_count_spread(max: &(u32, u32, u32), num_cubes: u32) -> [u32; 3] {
1277 let max_cube_counts = [max.0, max.1, max.2];
1278 let mut num_cubes = [num_cubes, 1, 1];
1279 let base = 2;
1280
1281 let mut reduce_count = |i: usize| {
1282 if num_cubes[i] <= max_cube_counts[i] {
1283 return true;
1284 }
1285
1286 loop {
1287 num_cubes[i] = num_cubes[i].div_ceil(base);
1288 num_cubes[i + 1] *= base;
1289
1290 if num_cubes[i] <= max_cube_counts[i] {
1291 return false;
1292 }
1293 }
1294 };
1295
1296 for i in 0..2 {
1297 if reduce_count(i) {
1298 break;
1299 }
1300 }
1301
1302 num_cubes
1303}
1304
1305#[cfg(test)]
1306mod tests {
1307 use super::*;
1308
1309 #[test_log::test]
1310 fn safe_num_cubes_even() {
1311 let max = (32, 32, 32);
1312 let required = 2048;
1313
1314 let actual = cube_count_spread(&max, required);
1315 let expected = [32, 32, 2];
1316 assert_eq!(actual, expected);
1317 }
1318
1319 #[test_log::test]
1320 fn safe_num_cubes_odd() {
1321 let max = (48, 32, 16);
1322 let required = 3177;
1323
1324 let actual = cube_count_spread(&max, required);
1325 let expected = [25, 32, 4];
1326 assert_eq!(actual, expected);
1327 }
1328}