1use std::sync::Arc;
4
5use cubecl::ir::{ElemType, HardwareProperties, OpaqueType};
6use cubecl::prelude::*;
7
8use crate::{
9 Arg, Axis, Geometry, Partitioning, PartitioningLaunch, Refusal, Space, SpaceLaunch, Unlabelled,
10};
11
12#[derive(Clone, Debug)]
14pub enum Grid {
15 Stated {
16 cube_count: CubeCount,
17 cube_dim: CubeDim,
18 },
19 FromLevels,
20}
21
22pub fn spawnable(
34 hardware: &HardwareProperties,
35 cube_dim: CubeDim,
36 cube_count: &CubeCount,
37) -> Result<(), Refusal> {
38 let units = cube_dim.num_elems();
39 if units > hardware.max_units_per_cube {
40 return Err(Refusal::CubePastDevice {
41 units,
42 most: hardware.max_units_per_cube,
43 });
44 }
45 if units == 0 {
46 return Err(Refusal::NoUnits);
47 }
48 let (most_x, most_y, most_z) = hardware.max_cube_dim;
49 if cube_dim.x > most_x || cube_dim.y > most_y || cube_dim.z > most_z {
50 return Err(Refusal::CubeShapePastDevice {
51 shape: (cube_dim.x, cube_dim.y, cube_dim.z),
52 most: hardware.max_cube_dim,
53 });
54 }
55 if let CubeCount::Static(x, y, z) = *cube_count {
56 if x == 0 || y == 0 || z == 0 {
57 return Err(Refusal::NoCubes);
58 }
59 let (most_x, most_y, most_z) = hardware.max_cube_count;
60 if x > most_x || y > most_y || z > most_z {
61 return Err(Refusal::GridPastDevice {
62 count: (x, y, z),
63 most: hardware.max_cube_count,
64 });
65 }
66 }
67 Ok(())
68}
69
70#[derive(Clone)]
73pub struct Launcher {
74 client: Client,
75 partitioning: Arc<Partitioning>,
76 concrete: Arc<Space>,
77 cube_count: CubeCount,
78 cube_dim: CubeDim,
79 overhanging: Arc<[Axis]>,
82 leaf: Arc<Space>,
84}
85
86impl Launcher {
87 pub fn new(
90 client: &Client,
91 partitioning: Partitioning,
92 concrete: &Space,
93 grid: Grid,
94 ) -> Result<Self, Refusal> {
95 let over_concrete = Partitioning::new(concrete.clone(), partitioning.levels().to_vec());
97 let (cube_count, cube_dim) = match grid {
98 Grid::Stated {
99 cube_count,
100 cube_dim,
101 } => (cube_count, cube_dim),
102 Grid::FromLevels => {
103 let plane_size = client.properties().hardware.plane_size_max;
104 let plane_units = partitioning.units();
105 if plane_units != 1 && plane_units != plane_size {
106 return Err(Refusal::GridNotReadOffLevels {
107 units: plane_units,
108 plane: plane_size,
109 });
110 }
111 (
112 over_concrete.cube_count(),
113 over_concrete.cube_dim(plane_size),
114 )
115 }
116 };
117 spawnable(&client.properties().hardware, cube_dim, &cube_count)?;
118 let fillers = partitioning.fillers();
119 let barriers = client
121 .properties()
122 .features
123 .types
124 .opaque
125 .contains(&OpaqueType::Barrier);
126 if fillers > 0 && !barriers {
127 return Err(Refusal::FillersWithoutBarriers { fillers });
128 }
129 Ok(Launcher {
130 client: client.clone(),
131 partitioning: Arc::new(partitioning),
132 concrete: Arc::new(concrete.clone()),
133 cube_count,
134 cube_dim,
135 overhanging: over_concrete.overhanging().into(),
136 leaf: Arc::new(over_concrete.leaf()),
137 })
138 }
139
140 pub fn holding(self, bytes: usize) -> Result<Self, Refusal> {
143 let hardware = &self.client.properties().hardware;
144 let most = hardware
145 .max_shared_memory_size
146 .saturating_sub(hardware.cube_mma_reserved_shared_memory);
147 match bytes > most {
148 true => Err(Refusal::SharedMemoryPastDevice { bytes, most }),
149 false => Ok(self),
150 }
151 }
152
153 pub fn client(&self) -> &Client {
154 &self.client
155 }
156
157 pub fn cube_count(&self) -> CubeCount {
158 self.cube_count.clone()
159 }
160
161 pub fn cube_dim(&self) -> CubeDim {
162 self.cube_dim
163 }
164
165 pub fn space(&self) -> &Space {
167 &self.concrete
168 }
169
170 pub fn partitioning(&self) -> &Partitioning {
172 &self.partitioning
173 }
174
175 pub fn partitioning_arg(&self) -> PartitioningLaunch {
177 PartitioningLaunch::new(self.space_arg(), self.partitioning.levels().to_vec())
178 }
179
180 fn space_arg(&self) -> SpaceLaunch {
182 let kernel = self.partitioning.space();
183 let mut sizes = SequenceArg::new();
184 if !kernel.is_static() {
185 for axis in kernel.axes() {
186 sizes.push(self.concrete.extent(axis));
187 }
188 }
189 SpaceLaunch::new(kernel.shape().clone(), sizes)
190 }
191
192 pub(crate) fn overhangs(&self) -> &[Axis] {
194 &self.overhanging
195 }
196
197 pub(crate) fn may_overhang(&self) -> Vec<Axis> {
202 let kernel = self.partitioning.space();
203 let levels = self.partitioning.levels();
204 let mut axes = self.overhanging.to_vec();
205 for axis in kernel.axes().filter(|&axis| kernel.is_dynamic(axis)) {
206 let cut_short = levels.iter().any(|level| {
207 level
208 .tile(axis)
209 .is_some_and(|tile| tile > 1 || level.launch_order().is_some())
210 });
211 if cut_short && !axes.contains(&axis) {
212 axes.push(axis);
213 }
214 }
215 axes
216 }
217
218 fn leaf_edge(&self, axis: Axis) -> usize {
220 self.leaf
221 .extents()
222 .iter()
223 .find(|&&(a, _)| a == axis)
224 .map(|&(_, edge)| edge)
225 .unwrap_or_else(|| self.concrete.extent(axis))
226 }
227
228 pub fn arg(&self, binding: TensorBinding, element: ElemType) -> Arg<'_, Unlabelled> {
231 Arg::bound(self, binding, element)
232 }
233
234 pub fn unbound(&self, geometry: &Geometry) -> Arg<'_, Unlabelled> {
237 Arg::unbound(self, geometry)
238 }
239
240 pub fn vector_size(
243 &self,
244 axis: Axis,
245 operands: &[(&Geometry, &[Axis])],
246 type_size: usize,
247 ) -> usize {
248 for (_, axes) in operands {
249 assert_eq!(
250 axes.last(),
251 Some(&axis),
252 "Launcher::vector_size: axis {axis:?} must label each operand's innermost dim"
253 );
254 }
255 let overhangs = self.overhangs();
257 let masked = operands
258 .iter()
259 .any(|(_, axes)| axes.iter().any(|a| overhangs.contains(a)));
260 if masked {
261 return 1;
262 }
263 let leaf = self.leaf_edge(axis);
264 self.client
265 .io_optimized_vector_sizes(type_size)
266 .filter(|&v| {
267 leaf.is_multiple_of(v)
268 && operands
269 .iter()
270 .all(|(g, axes)| g.serves(&[(axis, v)], axes).is_ok())
271 })
272 .max()
273 .unwrap_or(1)
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
285 fn a_grid_with_a_zero_axis_is_refused() {
286 let hardware = HardwareProperties {
287 load_width: 128,
288 vector_register_count: None,
289 plane_size_min: 32,
290 plane_size_max: 32,
291 max_bindings: 30,
292 max_shared_memory_size: 32 * 1024,
293 max_cube_count: (u32::MAX, u32::MAX, u32::MAX),
294 max_units_per_cube: 1024,
295 max_cube_dim: (1024, 1024, 1024),
296 num_streaming_multiprocessors: None,
297 num_cpu_cores: None,
298 last_level_cache_size: None,
299 num_tensor_cores: None,
300 min_tensor_cores_dim: None,
301 max_vector_size: 4,
302 cube_mma_reserved_shared_memory: 0,
303 };
304 let cube_dim = CubeDim::new_2d(32, 1);
305 for count in [
306 CubeCount::Static(0, 8, 1),
307 CubeCount::Static(64, 0, 1),
308 CubeCount::Static(64, 8, 0),
309 ] {
310 assert_eq!(
311 spawnable(&hardware, cube_dim, &count),
312 Err(Refusal::NoCubes),
313 "{count:?}"
314 );
315 }
316 assert_eq!(
317 spawnable(&hardware, cube_dim, &CubeCount::Static(64, 8, 1)),
318 Ok(())
319 );
320 }
321}