Skip to main content

cubecl_tile/space/partition/
cube_order.rs

1//! The order the cube level hands its boxes to the grid ([`CubeOrder`]).
2
3use crate::{AxisDistribution, Coords, CubeAxis, Integer, IntegerExpand, Level, Space};
4use cubecl::prelude::*;
5use cubecl_std::tensor::layout::Coords2d;
6
7/// The order a cube level distributes its boxes to the grid. A swizzle width need not divide
8/// the grid; the last strip is ragged.
9#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
10pub enum CubeOrder {
11    /// The box at `(x, y)` goes to the cube at `(x, y)`.
12    #[default]
13    RowMajor,
14    /// Cubes snake down the second axis in strips `width` wide along the first.
15    SwizzleRow(usize),
16    /// Cubes snake along the first axis in strips `width` wide along the second.
17    SwizzleCol(usize),
18}
19
20impl CubeOrder {
21    /// A strip one box wide is [`RowMajor`](CubeOrder::RowMajor).
22    pub fn canonicalize(self) -> Self {
23        match self {
24            CubeOrder::SwizzleRow(1) | CubeOrder::SwizzleCol(1) => CubeOrder::RowMajor,
25            other => other,
26        }
27    }
28
29    /// Whether this order permutes anything.
30    pub(crate) fn swizzles(self) -> bool {
31        !matches!(self.canonicalize(), CubeOrder::RowMajor)
32    }
33}
34
35/// The two in-plane positions this order gives the cube at flat dispatch index `flat`.
36#[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/// [`swizzle`](fn@swizzle) over strips cut from an axis of `strip_axis` elements that
57/// `step_length` need not divide; the last strip holds what is left.
58#[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    // An index past the grid leaves nothing, and takes a whole width rather than divide by zero.
74    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]
90/// Maps a linear `index` to 2D zigzag coordinates `(x, y)` within strips of `num_steps` steps
91/// of `step_length` (> 0) elements each.
92pub 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/// The snake both swizzles walk.
110#[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/// The swizzled cube level's two in-plane positions; zeros where no order is stated.
133#[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
155/// Where this space holds the cube level's two in-plane axes, `(x, y)`. Panics unless each
156/// owns its whole grid dimension.
157pub(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    /// Every order is a permutation of the grid, divisible or ragged.
188    #[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    /// A strip one box wide is the grid's own order.
228    #[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    /// An index past the grid lands off it without dividing by zero.
237    #[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    /// Over a 16x16 grid, the first sixteen cubes of `SwizzleRow(4)` span four columns.
249    #[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    /// The host twin of [`cube_positions`].
263    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}