cubecl_tile/space/partition/
plane_order.rs1use cubecl::prelude::*;
6
7use super::{ComputeScope, CubeOrder, Level, cube_positions};
8use crate::{Axis, Coords, CubeAxis, Integer, IntegerExpand, Space};
9
10const CUBES_PER_GRID_DIMENSION: usize = 65_535;
12
13#[derive(Clone, PartialEq, Eq, Hash, Debug)]
29pub struct PlaneOrder {
30 block: Vec<(Axis, usize)>,
32 block_order: CubeOrder,
33 planes_per_cube: usize,
35}
36
37impl PlaneOrder {
38 pub fn new(block: &[(Axis, usize)], block_order: CubeOrder) -> Self {
41 for &(axis, tiles) in block {
42 assert!(
43 tiles > 0,
44 "PlaneOrder: a block holds no tile along {axis:?}"
45 );
46 }
47 let block = block.to_vec();
48 let planes_per_cube = block.iter().map(|&(_, tiles)| tiles).product();
49 Self {
50 block,
51 block_order: block_order.canonicalize(),
52 planes_per_cube,
53 }
54 }
55
56 pub fn cubes_of(self, planes: usize) -> Self {
59 assert!(
60 planes > 0,
61 "PlaneOrder::cubes_of: a cube holds at least one plane"
62 );
63 Self {
64 planes_per_cube: planes,
65 ..self
66 }
67 }
68
69 pub fn planes_per_cube(&self) -> usize {
70 self.planes_per_cube
71 }
72
73 pub fn block_along(&self, axis: Axis) -> usize {
75 self.block
76 .iter()
77 .find(|&&(a, _)| a == axis)
78 .map_or(1, |&(_, tiles)| tiles)
79 }
80
81 pub fn block_order(&self) -> CubeOrder {
82 self.block_order
83 }
84
85 pub(crate) fn block_axes(&self) -> impl Iterator<Item = Axis> + '_ {
86 self.block.iter().map(|&(axis, _)| axis)
87 }
88
89 pub(crate) fn block_planes(&self) -> usize {
91 self.block.iter().map(|&(_, tiles)| tiles).product()
92 }
93
94 pub(crate) fn one_block_a_cube(&self) -> bool {
96 self.block_planes() == self.planes_per_cube
97 }
98
99 pub(crate) fn grid(&self, level: &Level, space: &Space) -> (usize, usize) {
108 let planes = self.blocks(level, space) * self.block_planes();
109 let cubes = planes.div_ceil(self.planes_per_cube).max(1);
110 if cubes <= CUBES_PER_GRID_DIMENSION {
111 return (cubes, 1);
112 }
113 let rows = cubes.div_ceil(CUBES_PER_GRID_DIMENSION);
114 let x = (rows..=CUBES_PER_GRID_DIMENSION)
115 .find(|&rows| cubes.is_multiple_of(rows) && cubes / rows <= CUBES_PER_GRID_DIMENSION)
116 .map_or(CUBES_PER_GRID_DIMENSION, |rows| cubes / rows);
117 (x, cubes.div_ceil(x))
118 }
119
120 pub(crate) fn overruns(&self, level: &Level, space: &Space, axis: Axis) -> bool {
126 let ragged = !level
127 .tiles(space, axis)
128 .is_multiple_of(self.block_along(axis));
129 if ragged {
130 return true;
131 }
132 if Self::spare_blocks_land_past(level, self.block_order) != axis {
133 return false;
134 }
135 if level.axes().into_iter().any(|axis| space.is_dynamic(axis)) {
136 return true;
137 }
138 let (x, y) = self.grid(level, space);
139 x * y * self.planes_per_cube > self.blocks(level, space) * self.block_planes()
140 }
141
142 fn blocks(&self, level: &Level, space: &Space) -> usize {
144 level
145 .axes()
146 .into_iter()
147 .map(|axis| level.tiles(space, axis).div_ceil(self.block_along(axis)))
148 .product()
149 }
150
151 fn spare_blocks_land_past(level: &Level, order: CubeOrder) -> Axis {
154 let axes = level.axes();
155 match axes.len() {
156 1 => axes[0],
157 2 => match order.canonicalize() {
158 CubeOrder::SwizzleRow(_) => axes[0],
159 CubeOrder::RowMajor | CubeOrder::SwizzleCol(_) => axes[1],
160 },
161 _ => axes[axes.len() - 1],
162 }
163 }
164}
165
166#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
170pub struct UnitCube {
171 pub plane_width: usize,
172 pub planes: usize,
173}
174
175impl UnitCube {
176 pub fn units(&self) -> usize {
178 self.plane_width * self.planes
179 }
180
181 pub(crate) fn order(&self) -> PlaneOrder {
184 PlaneOrder::new(&[], CubeOrder::RowMajor).cubes_of(self.units())
185 }
186}
187
188#[cube]
193pub(crate) fn launch_tiles(
194 #[comptime] space: Space,
195 #[comptime] level: Level,
196 grid: &Coords<usize>,
197) -> (Coords<usize>, Coords<usize>) {
198 let order = comptime!(
199 level
200 .launch_order()
201 .expect("a level spanning the launch states the order its instances take")
202 );
203 let in_cube = match comptime!(level.coverage().scope()) {
205 Some(ComputeScope::Unit) => UNIT_POS as usize,
206 _ => UNIT_POS_Y as usize,
207 };
208 let rank = comptime!(space.rank());
209 let named = comptime!(
212 level
213 .axes()
214 .into_iter()
215 .map(|axis| space.position(axis))
216 .collect::<Vec<_>>()
217 );
218 let cube = CubeAxis::position(CubeAxis::X)
221 .plus(CubeAxis::position(CubeAxis::Y).times(CUBE_COUNT_X as usize));
222 let (block, local) = if comptime!(order.one_block_a_cube()) {
223 (cube, in_cube)
224 } else {
225 let flat = cube
226 .times(comptime!(order.planes_per_cube()).runtime())
227 .plus(in_cube);
228 let planes = comptime!(order.block_planes()).runtime();
229 (flat.divided_by(planes), flat.remainder(planes))
230 };
231
232 let mut blocks = Coords::<usize>::new();
234 #[unroll]
235 for p in comptime!(named.clone()) {
236 let along = comptime!(order.block_along(space.axis_at(p)));
237 blocks.push(
238 grid.at(p)
239 .plus(comptime!(along - 1).runtime())
240 .divided_by(along.runtime()),
241 );
242 }
243 let at = block_coords(
244 block,
245 &blocks,
246 comptime!(named.len()),
247 comptime!(order.block_order()),
248 );
249
250 let mut positions = Coords::<usize>::new();
251 let mut counts = Coords::<usize>::new();
252 #[unroll]
253 for p in 0..rank {
254 match comptime!(named.iter().position(|&q| q == p)) {
255 None => {
256 positions.push(0usize);
257 counts.push(grid.at(p));
258 }
259 Some(i) => {
260 let along = comptime!(order.block_along(space.axis_at(p)));
261 let inner = comptime!(
263 named[i + 1..]
264 .iter()
265 .map(|&q| order.block_along(space.axis_at(q)))
266 .product::<usize>()
267 );
268 let within = local.divided_by(inner.runtime()).remainder(along.runtime());
269 let tile = at.at(i).times(along.runtime()).plus(within);
270 positions.push(tile);
271 counts.push(1usize);
272 }
273 }
274 }
275 (positions, counts)
276}
277
278#[cube]
281fn block_coords(
282 block: usize,
283 blocks: &Coords<usize>,
284 #[comptime] named: usize,
285 #[comptime] order: CubeOrder,
286) -> Coords<usize> {
287 let mut out = Coords::<usize>::new();
288 if comptime!(named == 1) {
289 out.push(block);
290 } else {
291 let plane = blocks.at(0).times(blocks.at(1));
292 let first_two = if comptime!(named == 2) {
293 block
294 } else {
295 block.remainder(plane)
296 };
297 let (x, y) = cube_positions(first_two, (blocks.at(0), blocks.at(1)), order);
298 out.push(x);
299 out.push(y);
300 let mut rest = block.divided_by(plane);
301 #[unroll]
302 for i in 2..named {
303 if comptime!(i + 1 == named) {
304 out.push(rest);
305 } else {
306 out.push(rest.remainder(blocks.at(i)));
307 rest = rest.divided_by(blocks.at(i));
308 }
309 }
310 }
311 out
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::{Levels, Partitioning};
318
319 const N: Axis = Axis(0);
320
321 fn counts(partitioning: &Partitioning) -> (u32, u32, u32) {
323 let CubeCount::Static(x, y, z) = partitioning.cube_count() else {
324 unreachable!("a static space lays a static grid");
325 };
326 (x, y, z)
327 }
328
329 fn one_plane_a_cube(columns: usize) -> Partitioning {
331 Partitioning::new(
332 Space::new(&[(N, columns)]),
333 Levels::leaf(&[(N, 2)])
334 .launch_planes(&[N], PlaneOrder::new(&[(N, 1)], CubeOrder::RowMajor))
335 .build(),
336 )
337 }
338
339 #[test]
344 fn a_spill_divides_the_cubes_where_it_can() {
345 let vocabulary = one_plane_a_cube(151_936);
346 assert_eq!(counts(&vocabulary), (37_984, 2, 1));
347 assert!(!vocabulary.overhangs(N));
348
349 let prime = one_plane_a_cube(2 * 65_537);
350 assert_eq!(counts(&prime), (CUBES_PER_GRID_DIMENSION as u32, 2, 1));
351 assert!(prime.overhangs(N));
352 }
353
354 #[test]
356 fn a_grid_one_dimension_holds_is_one_row() {
357 assert_eq!(counts(&one_plane_a_cube(2 * 1000)), (1000, 1, 1));
358 }
359}