cubecl_tile/space/partition/
cube_order.rs1use crate::{AxisDistribution, Coords, CubeAxis, Integer, IntegerExpand, Level, Space};
4use cubecl::prelude::*;
5use cubecl_std::tensor::layout::Coords2d;
6
7#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
10pub enum CubeOrder {
11 #[default]
13 RowMajor,
14 SwizzleRow(usize),
16 SwizzleCol(usize),
18}
19
20impl CubeOrder {
21 pub fn canonicalize(self) -> Self {
23 match self {
24 CubeOrder::SwizzleRow(1) | CubeOrder::SwizzleCol(1) => CubeOrder::RowMajor,
25 other => other,
26 }
27 }
28
29 pub(crate) fn swizzles(self) -> bool {
31 !matches!(self.canonicalize(), CubeOrder::RowMajor)
32 }
33}
34
35#[cube]
37pub(crate) fn cube_positions(
38 flat: usize,
39 cubes: (usize, usize),
40 #[comptime] order: CubeOrder,
41) -> (usize, usize) {
42 let (count_x, count_y) = cubes;
43 match comptime!(order.canonicalize()) {
44 CubeOrder::RowMajor => (flat.remainder(count_x), flat.divided_by(count_x)),
45 CubeOrder::SwizzleRow(width) => {
46 let (step, along) = swizzle_ragged(flat, count_y, comptime!(width as u32), count_x);
47 (along as usize, step as usize)
48 }
49 CubeOrder::SwizzleCol(width) => {
50 let (step, along) = swizzle_ragged(flat, count_x, comptime!(width as u32), count_y);
51 (step as usize, along as usize)
52 }
53 }
54}
55
56#[cube]
59pub(crate) fn swizzle_ragged(
60 index: usize,
61 num_steps: usize,
62 #[comptime] step_length: u32,
63 strip_axis: usize,
64) -> Coords2d {
65 comptime!(assert!(
66 step_length > 0,
67 "swizzle: a strip holds at least one box"
68 ));
69 let full = num_steps as u32 * step_length;
70 let index = index as u32;
71 let strip_index = index / full;
72 let strip_offset = step_length * strip_index;
73 let left = (strip_axis as u32).saturating_sub(strip_offset);
75 let width = if left < step_length && left > 0 {
76 left
77 } else {
78 step_length.runtime()
79 };
80 snake(
81 strip_index,
82 index - strip_index * full,
83 num_steps,
84 width,
85 strip_offset,
86 )
87}
88
89#[cube]
90pub fn swizzle(index: usize, num_steps: usize, #[comptime] step_length: u32) -> Coords2d {
93 comptime!(assert!(
94 step_length > 0,
95 "swizzle: a strip holds at least one box"
96 ));
97 let full = num_steps as u32 * step_length;
98 let index = index as u32;
99 let strip_index = index / full;
100 snake(
101 strip_index,
102 index - strip_index * full,
103 num_steps,
104 step_length,
105 step_length * strip_index,
106 )
107}
108
109#[cube]
111fn snake(
112 strip_index: u32,
113 pos_in_strip: u32,
114 num_steps: usize,
115 width: u32,
116 strip_offset: u32,
117) -> Coords2d {
118 let abs_step_index = pos_in_strip / width;
119 let abs_pos_in_step = pos_in_strip % width;
120
121 let strip_direction = strip_index % 2;
122 let step_direction = abs_step_index % 2;
123
124 let step_index = strip_direction * (num_steps as u32 - abs_step_index - 1)
125 + (1 - strip_direction) * abs_step_index;
126 let pos_in_step =
127 step_direction * (width - abs_pos_in_step - 1) + (1 - step_direction) * abs_pos_in_step;
128
129 (step_index, pos_in_step + strip_offset)
130}
131
132#[cube]
134pub(crate) fn swizzled_positions(
135 #[comptime] space: Space,
136 #[comptime] level: Level,
137 instances: &Coords<usize>,
138) -> Coords<usize> {
139 let mut out = Coords::<usize>::new();
140 if comptime!(level.order().swizzles()) {
141 let (x_at, y_at) = comptime!(in_plane_axes(&space, &level));
142 let (count_x, count_y) = (instances.at(x_at), instances.at(y_at));
143 let flat =
144 CubeAxis::position(CubeAxis::X).plus(CubeAxis::position(CubeAxis::Y).times(count_x));
145 let (x, y) = cube_positions(flat, (count_x, count_y), comptime!(level.order()));
146 out.push(x);
147 out.push(y);
148 } else {
149 out.push(0usize);
150 out.push(0usize);
151 }
152 out
153}
154
155pub(crate) fn in_plane_axes(space: &Space, level: &Level) -> (usize, usize) {
158 let at = |wanted: CubeAxis| {
159 let found: Vec<usize> = (0..space.rank())
160 .filter(|&p| level.cube_axis(space.axis_at(p)) == Some(wanted))
161 .collect();
162 assert_eq!(
163 found.len(),
164 1,
165 "Walk: a {:?} cube level distributes the grid's {wanted:?} dimension to {} of its axes, \
166 and a swizzle can put back only an axis that owns a whole dimension",
167 level.order(),
168 found.len()
169 );
170 let p = found[0];
171 assert_eq!(
172 AxisDistribution::unspanned_weight(level, space, space.axis_at(p)),
173 1,
174 "Walk: a {:?} cube level shares the grid's {wanted:?} dimension with an axis this \
175 space does not span, whose coordinate a swizzle has no way to put back",
176 level.order()
177 );
178 p
179 };
180 (at(CubeAxis::X), at(CubeAxis::Y))
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
189 fn every_order_is_a_permutation_of_the_grid() {
190 for &(x, y) in &[
191 (8usize, 8usize),
192 (16, 4),
193 (4, 16),
194 (32, 8),
195 (6, 10),
196 (1, 7),
197 (7, 5),
198 (10, 3),
199 (3, 9),
200 (13, 1),
201 ] {
202 for order in [
203 CubeOrder::RowMajor,
204 CubeOrder::SwizzleRow(2),
205 CubeOrder::SwizzleRow(4),
206 CubeOrder::SwizzleCol(2),
207 CubeOrder::SwizzleCol(8),
208 ] {
209 let mut seen = vec![false; x * y];
210 for flat in 0..x * y {
211 let (px, py) = positions_host(flat, (x, y), order);
212 assert!(
213 px < x && py < y,
214 "{order:?} on {x}x{y}: ({px}, {py}) is off the grid"
215 );
216 let at = py * x + px;
217 assert!(
218 !seen[at],
219 "{order:?} on {x}x{y}: two cubes hold ({px}, {py})"
220 );
221 seen[at] = true;
222 }
223 }
224 }
225 }
226
227 #[test]
229 fn a_strip_one_box_wide_is_the_grids_own_order() {
230 assert_eq!(CubeOrder::SwizzleRow(1).canonicalize(), CubeOrder::RowMajor);
231 assert_eq!(CubeOrder::SwizzleCol(1).canonicalize(), CubeOrder::RowMajor);
232 assert!(!CubeOrder::SwizzleRow(1).swizzles());
233 assert!(CubeOrder::SwizzleRow(4).swizzles());
234 }
235
236 #[test]
238 fn an_index_past_the_grid_lands_off_it() {
239 for index in [12, 18, 30] {
240 let (_, along) = swizzle_ragged(index, 3, 2, 4);
241 assert!(
242 along >= 4,
243 "index {index}, past the grid, landed on it at {along}"
244 );
245 }
246 }
247
248 #[test]
250 fn a_swizzle_keeps_the_cubes_that_run_together_close() {
251 let span = |order: CubeOrder| {
252 let boxes: Vec<_> = (0..16)
253 .map(|f| positions_host(f, (16, 16), order))
254 .collect();
255 let xs = boxes.iter().map(|b| b.0);
256 xs.clone().max().unwrap() - xs.min().unwrap() + 1
257 };
258 assert_eq!(span(CubeOrder::RowMajor), 16);
259 assert_eq!(span(CubeOrder::SwizzleRow(4)), 4);
260 }
261
262 fn positions_host(flat: usize, cubes: (usize, usize), order: CubeOrder) -> (usize, usize) {
264 let (count_x, count_y) = cubes;
265 match order.canonicalize() {
266 CubeOrder::RowMajor => (flat % count_x, flat / count_x),
267 CubeOrder::SwizzleRow(width) => {
268 let (step, along) = swizzle_ragged(flat, count_y, width as u32, count_x);
269 (along as usize, step as usize)
270 }
271 CubeOrder::SwizzleCol(width) => {
272 let (step, along) = swizzle_ragged(flat, count_x, width as u32, count_y);
273 (step as usize, along as usize)
274 }
275 }
276 }
277}