1use cubecl::{prelude::*, unexpanded};
4
5use crate::{
6 Axis, ComputeScope, Count, Coverage, CubeAxis, Level, LevelTable, Region, RegionExpand, Space,
7 Walk,
8};
9
10use super::UnitCube;
11
12#[derive(CubeType, CubeLaunch, Debug, Clone, PartialEq, Eq, Hash)]
14pub struct Partitioning {
15 pub(crate) space: Space,
16 #[cube(comptime)]
17 pub(crate) levels: Vec<Level>,
18}
19
20impl PartitioningExpand {
22 #[allow(clippy::should_implement_trait)]
23 pub fn clone(&self) -> Partitioning {
24 Partitioning::new(self.space.clone(), self.levels.clone())
25 }
26
27 pub fn space(&self) -> Space {
28 self.space.clone()
29 }
30}
31
32impl PartitioningExpand {
34 pub fn levels(&self) -> &[Level] {
36 &self.levels
37 }
38}
39
40impl Partitioning {
41 pub fn levels(&self) -> &[Level] {
43 &self.levels
44 }
45
46 pub fn level(&self, i: usize) -> Level {
48 self.levels[i].clone()
49 }
50}
51
52#[cube]
53impl Partitioning {
54 pub fn walk(&self) -> Walk {
56 Region::root(self).walk()
57 }
58
59 pub fn over(&self, #[comptime] level: &Level) -> Walk {
61 Walk::of(&self.space, comptime!(level.clone()), Region::root(self))
62 }
63}
64
65impl IntoIterator for Partitioning {
67 type Item = Region;
68 type IntoIter = std::vec::IntoIter<Region>;
69
70 fn into_iter(self) -> Self::IntoIter {
71 unexpanded!()
72 }
73}
74
75impl IntoIterator for &Partitioning {
76 type Item = Region;
77 type IntoIter = std::vec::IntoIter<Region>;
78
79 fn into_iter(self) -> Self::IntoIter {
80 unexpanded!()
81 }
82}
83
84impl Iterable for PartitioningExpand {
86 type Item = RegionExpand;
87
88 fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
89 RegionExpand::__expand_root(scope, &self).expand(scope, body)
90 }
91
92 fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
93 RegionExpand::__expand_root(scope, &self).expand_unroll(scope, body)
94 }
95}
96
97impl Iterable for &PartitioningExpand {
99 type Item = RegionExpand;
100
101 fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
102 RegionExpand::__expand_root(scope, self).expand(scope, body)
103 }
104
105 fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
106 RegionExpand::__expand_root(scope, self).expand_unroll(scope, body)
107 }
108}
109
110impl Partitioning {
111 pub fn new(space: Space, levels: Vec<Level>) -> Self {
114 for (i, level) in levels.iter().enumerate() {
115 if let Some(scope) = level.coverage().scope() {
116 assert!(
117 levels[i + 1..]
118 .iter()
119 .all(|other| other.coverage() != level.coverage()),
120 "Partitioning::new: two levels distribute over {scope:?}"
121 );
122 }
123 if level.spans_launch() {
124 assert!(
125 i == 0,
126 "Partitioning::new: a level spanning the launch is the outermost level, and \
127 this one sits {i} levels down"
128 );
129 assert!(
130 levels.iter().all(|other| other.coverage()
131 != Coverage::Distribute(ComputeScope::Cube)
132 && other.fillers() == 0),
133 "Partitioning::new: the launch's planes or units take the outermost level, so \
134 no cube level groups them and no plane only fills a cube's stages"
135 );
136 }
137 if level.unit_cube().is_some() {
138 assert!(
139 levels.iter().all(|other| !matches!(
140 other.coverage(),
141 Coverage::Distribute(ComputeScope::Plane | ComputeScope::PlaneGroup { .. })
142 )),
143 "Partitioning::new: the launch's units take the outermost level, so no plane \
144 level holds them"
145 );
146 }
147 }
148 Self { space, levels }
149 }
150
151 pub fn space(&self) -> &Space {
153 &self.space
154 }
155
156 pub fn all_dynamic(self) -> Self {
158 Partitioning {
159 space: self.space.all_dynamic(),
160 levels: self.levels,
161 }
162 }
163
164 pub fn table<'a>(&'a self, labels: &'a [(Axis, &'a str)]) -> LevelTable<'a> {
166 LevelTable::new(self, labels)
167 }
168
169 pub fn leaf(&self) -> Space {
171 self.space.leaf(&self.levels)
172 }
173
174 pub fn overhangs(&self, axis: Axis) -> bool {
176 assert!(
177 !self.space.is_dynamic(axis),
178 "Partitioning::overhangs: axis {axis:?} is Dynamic; ask the concrete space"
179 );
180 let mut space = self.space.clone();
181 for level in &self.levels {
182 if level.overhangs(&space, axis) {
183 return true;
184 }
185 space = level.child(&space);
186 }
187 false
188 }
189
190 pub(crate) fn overhanging(&self) -> Vec<Axis> {
197 let axes: Vec<Axis> = self.space.axes().collect();
198 for &axis in &axes {
199 assert!(
200 !self.space.is_dynamic(axis),
201 "Partitioning::overhanging: axis {axis:?} is Dynamic; ask the concrete space"
202 );
203 }
204 let mut overhanging = vec![false; axes.len()];
205 let mut space = self.space.clone();
206 for level in &self.levels {
207 for (hangs, &axis) in overhanging.iter_mut().zip(&axes) {
208 *hangs |= level.overhangs(&space, axis);
209 }
210 space = level.child(&space);
211 }
212 axes.into_iter()
213 .zip(overhanging)
214 .filter_map(|(axis, hangs)| hangs.then_some(axis))
215 .collect()
216 }
217
218 pub fn level_distributed(&self, scope: ComputeScope) -> Option<&Level> {
220 let coverage = Coverage::Distribute(scope);
221 self.levels
222 .iter()
223 .find(|level| level.coverage() == coverage)
224 }
225
226 pub fn box_of(&self, scope: ComputeScope) -> Space {
229 let coverage = Coverage::Distribute(scope);
230 let mut space = self.space.clone();
231 for level in &self.levels {
232 space = level.child(&space);
233 if level.coverage() == coverage {
234 return space;
235 }
236 }
237 panic!("Partitioning::box_of: no level distributes over {scope:?}")
238 }
239
240 pub fn walks(&self) -> impl Iterator<Item = &Level> + '_ {
242 self.levels
243 .iter()
244 .filter(|level| level.coverage() == Coverage::Walk)
245 }
246
247 pub(crate) fn instances(&self, coverage: Coverage) -> u32 {
249 match coverage {
250 Coverage::Distribute(ComputeScope::Cube) => [CubeAxis::X, CubeAxis::Y, CubeAxis::Z]
251 .into_iter()
252 .map(|dim| self.cube_instances(dim))
253 .product(),
254 Coverage::Distribute(ComputeScope::Plane)
255 | Coverage::Distribute(ComputeScope::PlaneGroup { .. })
256 | Coverage::Distribute(ComputeScope::Unit) => {
257 self.count_instances(|level| level.coverage() == coverage, |_, _| true)
258 }
259 Coverage::Walk => 1,
260 }
261 }
262
263 fn cube_instances(&self, dim: CubeAxis) -> u32 {
265 self.count_instances(
266 |level| {
267 level.coverage() == Coverage::Distribute(ComputeScope::Cube)
268 && (level.shared_by().is_none() || dim == CubeAxis::X)
269 },
270 |level, axis| level.cube_axis(axis) == Some(dim),
271 )
272 }
273
274 fn count_instances(
276 &self,
277 levels: impl Fn(&Level) -> bool,
278 axes: impl Fn(&Level, Axis) -> bool,
279 ) -> u32 {
280 let mut total = 1u32;
281 let mut space = self.space.clone();
282 for level in &self.levels {
283 if levels(level) && level.unit_cube().is_none() {
285 match level.shared_by() {
286 Some(workers) => total *= workers as u32,
287 None => {
288 for axis in space.axes() {
289 if level.distributes(axis) && axes(level, axis) {
290 total *= match level.count(axis) {
291 Some(Count::AllAcross(workers)) => workers,
292 Some(Count::Distributed(_)) => 1,
294 _ => level.tiles(&space, axis),
295 } as u32;
296 }
297 }
298 }
299 }
300 }
301 space = level.child(&space);
302 }
303 total
304 }
305
306 pub fn cube_count(&self) -> CubeCount {
308 if let Some(order) = self.levels.first().and_then(Level::launch_order) {
309 let (x, y) = order.grid(&self.levels[0], &self.space);
310 return CubeCount::Static(x as u32, y as u32, 1);
311 }
312 CubeCount::Static(
313 self.cube_instances(CubeAxis::X),
314 self.cube_instances(CubeAxis::Y),
315 self.cube_instances(CubeAxis::Z),
316 )
317 }
318
319 pub fn planes_per_cube(&self) -> u32 {
322 if let Some(order) = self.levels.first().and_then(Level::plane_order) {
323 return order.planes_per_cube() as u32;
324 }
325 if let Some(cube) = self.unit_cube() {
326 return cube.planes as u32;
327 }
328 let grouped = self.levels.iter().find_map(|level| {
330 let scope = level.coverage().scope()?;
331 let planes = scope.planes().filter(|_| scope != ComputeScope::Plane)?;
332 Some(self.instances(level.coverage()) * planes as u32)
333 });
334 grouped.unwrap_or_else(|| self.instances(Coverage::Distribute(ComputeScope::Plane)))
335 + self.fillers()
336 }
337
338 pub fn fillers(&self) -> u32 {
341 self.levels.iter().map(|level| level.fillers() as u32).sum()
342 }
343
344 pub fn units(&self) -> u32 {
346 self.instances(Coverage::Distribute(ComputeScope::Unit))
347 }
348
349 pub fn cube_dim(&self, plane_size: u32) -> CubeDim {
352 match self.unit_cube() {
353 Some(cube) => CubeDim::new_2d(cube.plane_width as u32, cube.planes as u32),
354 None => CubeDim::new_2d(plane_size, self.planes_per_cube()),
355 }
356 }
357
358 pub fn unit_cube(&self) -> Option<UnitCube> {
360 self.levels.first().and_then(Level::unit_cube)
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use crate::Levels;
368
369 const M: Axis = Axis(0);
370 const N: Axis = Axis(1);
371 const K: Axis = Axis(2);
372
373 fn staged(fillers: usize) -> Partitioning {
375 Partitioning::new(
376 Space::new(&[(M, 256), (N, 256), (K, 512)]),
377 Levels::leaf(&[(M, 64), (N, 64), (K, 64)])
378 .planes(&[(M, 2), (N, 2)])
379 .walk_every(&[K])
380 .filled_by(fillers)
381 .cubes(&[M, N])
382 .build(),
383 )
384 }
385
386 #[test]
387 fn a_walk_with_no_fillers_is_the_partitioning_it_always_was() {
388 let plain = staged(0);
389 assert_eq!(plain.fillers(), 0);
390 assert_eq!(plain.planes_per_cube(), 4);
391 assert_eq!(plain.cube_dim(32), CubeDim::new_2d(32, 4));
392 }
393
394 #[test]
396 fn a_filling_plane_widens_the_cube_and_nothing_else() {
397 let plain = staged(0);
398 let filled = staged(1);
399
400 assert_eq!(filled.planes_per_cube(), plain.planes_per_cube() + 1);
401 assert_eq!(filled.cube_dim(32), CubeDim::new_2d(32, 5));
402
403 assert_eq!(
404 filled.instances(Coverage::Distribute(ComputeScope::Plane)),
405 plain.instances(Coverage::Distribute(ComputeScope::Plane))
406 );
407 assert_eq!(filled.units(), plain.units());
408 for dim in [CubeAxis::X, CubeAxis::Y, CubeAxis::Z] {
409 assert_eq!(filled.cube_instances(dim), plain.cube_instances(dim));
410 }
411 }
412
413 #[test]
414 fn the_fillers_of_every_level_are_counted() {
415 assert_eq!(staged(3).fillers(), 3);
416 }
417
418 #[test]
420 #[should_panic(expected = "only a walk's regions are staged")]
421 fn a_level_that_distributes_its_tiles_cannot_be_filled_by_anyone() {
422 Levels::leaf(&[(M, 64)])
423 .planes(&[(M, 2)])
424 .filled_by(1)
425 .build();
426 }
427
428 #[test]
431 #[should_panic(expected = "two levels distribute over Plane")]
432 fn two_levels_over_one_scope_are_refused() {
433 Partitioning::new(
434 Space::new(&[(M, 64), (N, 64)]),
435 Levels::leaf(&[(M, 8), (N, 8)])
436 .planes(&[(M, 2)])
437 .planes(&[(N, 2)])
438 .build(),
439 );
440 }
441
442 #[test]
445 fn the_overhanging_axes_are_the_ones_some_level_overhangs() {
446 let partitioning = Partitioning::new(
447 Space::new(&[(M, 200), (N, 256), (K, 96)]),
448 Levels::leaf(&[(M, 64), (N, 48), (K, 32)])
449 .planes(&[(M, 2), (N, 2)])
450 .walk_every(&[K])
451 .cubes(&[M, N])
452 .build(),
453 );
454 let asked: Vec<Axis> = [M, N, K]
455 .into_iter()
456 .filter(|&axis| partitioning.overhangs(axis))
457 .collect();
458 assert_eq!(partitioning.overhanging(), asked);
459 assert_eq!(asked, vec![M, N]);
460 assert!(staged(0).overhanging().is_empty());
461 }
462
463 #[test]
465 fn a_scope_covers_the_box_its_level_cuts() {
466 let partitioning = staged(0);
467 let cube = Space::new(&[(M, 128), (N, 128), (K, 512)]);
468 assert_eq!(partitioning.box_of(ComputeScope::Cube), cube);
469 let plane = Space::new(&[(M, 64), (N, 64), (K, 64)]);
470 assert_eq!(partitioning.box_of(ComputeScope::Plane), plane);
471 }
472}