Skip to main content

cubecl_tile/space/
region.rs

1//! Where a loop is: the box a kernel's loops reached ([`Region`]), and the leaves and planes below
2//! it.
3
4use super::{Level, Partitioning, Space};
5use crate::{
6    Axis, ComputeScope, Coords, Coverage, Integer, IntegerExpand, MatrixAxes, Path, Step, Walk,
7};
8use cubecl::{prelude::*, unexpanded};
9
10/// The path a kernel's loops took to a box, with the coordinates each loop handed out.
11/// Iterating distributes the next level.
12#[derive(CubeType, Clone)]
13#[expand(derive(Clone))]
14pub struct Region {
15    step_coords: Sequence<Coords<u32>>,
16    /// The runtime sizes of the root's dynamic axes, positional.
17    sizes: Sequence<usize>,
18    #[cube(comptime)]
19    pub(crate) path: Path,
20}
21
22#[cube]
23impl Region {
24    pub(crate) fn new(
25        step_coords: Sequence<Coords<u32>>,
26        sizes: Sequence<usize>,
27        #[comptime] path: Path,
28    ) -> Region {
29        Region {
30            step_coords,
31            sizes,
32            path,
33        }
34    }
35
36    /// The empty path at the partitioning's space.
37    pub(crate) fn root(partitioning: &Partitioning) -> Region {
38        Region::rooted(
39            &partitioning.space,
40            comptime!(partitioning.levels.clone()),
41            0usize,
42        )
43    }
44
45    /// The empty path at `space`, which sits at `depth` in a partitioning of `levels`.
46    pub(crate) fn rooted(
47        space: &Space,
48        #[comptime] levels: Vec<Level>,
49        #[comptime] depth: usize,
50    ) -> Region {
51        Region::new(
52            Sequence::new(),
53            space.sizes.clone(),
54            comptime!(Path::new(
55                depth,
56                Partitioning::new(space.clone(), levels),
57                Vec::new()
58            )),
59        )
60    }
61
62    /// The regions of the level below this one.
63    pub fn walk(&self) -> Walk {
64        Walk::of(&self.child(), comptime!(self.path.next()), self.clone())
65    }
66
67    /// The regions at the partitioning's innermost level below this one, one loop per level
68    /// between: a kernel reads the same however many levels its partitioning stacks there.
69    pub fn leaves(&self) -> Leaves {
70        Leaves {
71            region: self.clone(),
72        }
73    }
74
75    /// The planes below this region, each once, at the first step of every walk between: what a
76    /// plane holds across a walk, such as its rows, is the same at every step, while its
77    /// coordinates along a walked axis are the first step's.
78    pub fn planes(&self) -> Planes {
79        Planes {
80            region: self.clone(),
81        }
82    }
83
84    /// The region one level below the root at trailing-two coordinates `(c0, c1)` under `level`,
85    /// `0` elsewhere, for a tile at `depth`. Comptime coordinates stay constant.
86    pub fn trailing(
87        #[comptime] depth: usize,
88        #[comptime] space: Space,
89        #[comptime] level: Level,
90        c0: usize,
91        c1: usize,
92    ) -> Region {
93        let edges = comptime!(MatrixAxes::edges(&space));
94        Region::cell(depth, space, level, edges, c0, c1)
95    }
96
97    /// The region one level below the root at coordinates `(c0, c1)` along the first axis of
98    /// each of `edges`' two groups under `level`, `0` elsewhere, for a tile at `depth`.
99    pub(crate) fn cell(
100        #[comptime] depth: usize,
101        #[comptime] space: Space,
102        #[comptime] level: Level,
103        #[comptime] edges: MatrixAxes,
104        c0: usize,
105        c1: usize,
106    ) -> Region {
107        let rank = comptime!(space.rank());
108        let mut coords = Coords::<u32>::new();
109        #[unroll]
110        for p in 0..rank {
111            // `cast`, not `as`, and `runtime` on the `0`: both keep a comptime coordinate constant.
112            let c = if comptime!(p == edges.row_split) {
113                c0.cast::<u32>()
114            } else if comptime!(p == edges.col_split) {
115                c1.cast::<u32>()
116            } else {
117                0u32.runtime()
118            };
119            coords.push(c);
120        }
121        let mut step_coords = Sequence::<Coords<u32>>::new();
122        step_coords.push(coords);
123        Region::new(
124            step_coords,
125            Sequence::new(),
126            comptime!(Path::new(
127                depth,
128                Partitioning::new(space.clone(), Vec::new()),
129                vec![level]
130            )),
131        )
132    }
133
134    /// The `i`-th step of the path.
135    pub(crate) fn step(&self, #[comptime] i: usize) -> Step {
136        Step::new(
137            self.step_coords.index(i).clone(),
138            comptime!(self.path.space_at(i)),
139            comptime!(self.path.level(i).clone()),
140            comptime!(self.path.depth() - self.path.len() + i),
141        )
142    }
143
144    /// The box this region covers, as a runtime space.
145    pub(crate) fn child(&self) -> Space {
146        Space::with_sizes(comptime!(self.path.child()), self.sizes.clone())
147    }
148
149    /// The innermost level's coordinate along `axis`; `0` when the axis is absent.
150    pub fn coord(&self, #[comptime] axis: Axis) -> usize {
151        let last = comptime!(self.path.len() - 1);
152        self.step(last).coord(axis)
153    }
154
155    /// The first coordinate along `axis` of the box this region covers, counted from its root's:
156    /// every level's coordinate along `axis` times the tile it cuts there. What a kernel bounds a
157    /// walk of its own by, as an attention's causal queries bound the keys they read.
158    pub fn origin(&self, #[comptime] axis: Axis) -> usize {
159        let mut origin = 0usize;
160        #[unroll]
161        for i in 0..comptime!(self.path.len()) {
162            if let Some(tile) = comptime!(self.path.level(i).tile(axis)) {
163                origin += self.step(i).coord(axis) * tile;
164            }
165        }
166        origin
167    }
168
169    /// This path with one more level's coordinates below it.
170    pub(crate) fn below(&self, coords: Coords<u32>, #[comptime] level: Level) -> Region {
171        let n = comptime!(self.step_coords.len());
172        let mut step_coords = Sequence::<Coords<u32>>::new();
173        #[unroll]
174        for i in 0..n {
175            step_coords.push(self.step_coords.index(i).clone());
176        }
177        step_coords.push(coords);
178        Region::new(
179            step_coords,
180            self.sizes.clone(),
181            comptime!(self.path.below(level)),
182        )
183    }
184
185    /// The regions of `level` over this region's own box.
186    pub fn over(&self, #[comptime] level: &Level) -> Walk {
187        Walk::of(&self.child(), comptime!(level.clone()), self.clone())
188    }
189}
190
191/// Host-side stand-in for `for plane in cube`; never runs.
192impl IntoIterator for Region {
193    type Item = Region;
194    type IntoIter = std::vec::IntoIter<Region>;
195
196    fn into_iter(self) -> Self::IntoIter {
197        unexpanded!()
198    }
199}
200
201impl IntoIterator for &Region {
202    type Item = Region;
203    type IntoIter = std::vec::IntoIter<Region>;
204
205    fn into_iter(self) -> Self::IntoIter {
206        unexpanded!()
207    }
208}
209
210/// `for plane in cube` iterates the level below `cube`.
211impl Iterable for RegionExpand {
212    type Item = RegionExpand;
213
214    fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
215        let walk = self.__expand_walk_method(scope);
216        if walk.const_len() == Some(1) {
217            walk.expand_unroll(scope, body)
218        } else {
219            walk.expand(scope, body)
220        }
221    }
222
223    fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
224        self.__expand_walk_method(scope).expand_unroll(scope, body)
225    }
226}
227
228/// `for plane in &cube`: the same, leaving `cube` to the body.
229impl Iterable for &RegionExpand {
230    type Item = RegionExpand;
231
232    fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
233        self.clone().expand(scope, body)
234    }
235
236    fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
237        self.clone().expand_unroll(scope, body)
238    }
239}
240
241/// The regions at a partitioning's innermost level below one region ([`Region::leaves`]).
242#[derive(CubeType, Clone)]
243#[expand(derive(Clone))]
244pub struct Leaves {
245    #[allow(dead_code)] // Read at expansion, through `LeavesExpand`.
246    region: Region,
247}
248
249/// Host-side stand-in for `for unit in plane.leaves()`; never runs.
250impl IntoIterator for Leaves {
251    type Item = Region;
252    type IntoIter = std::vec::IntoIter<Region>;
253
254    fn into_iter(self) -> Self::IntoIter {
255        unexpanded!()
256    }
257}
258
259/// One loop per level between the region and the partitioning's innermost level.
260impl Iterable for LeavesExpand {
261    type Item = RegionExpand;
262
263    fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
264        self.region.each_leaf(scope, body, false);
265    }
266
267    fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
268        self.region.each_leaf(scope, body, true);
269    }
270}
271
272/// The planes below one region, each once ([`Region::planes`]).
273#[derive(CubeType, Clone)]
274#[expand(derive(Clone))]
275pub struct Planes {
276    #[allow(dead_code)] // Read at expansion, through `PlanesExpand`.
277    region: Region,
278}
279
280/// Host-side stand-in for `for plane in cube.planes()`; never runs.
281impl IntoIterator for Planes {
282    type Item = Region;
283    type IntoIter = std::vec::IntoIter<Region>;
284
285    fn into_iter(self) -> Self::IntoIter {
286        unexpanded!()
287    }
288}
289
290/// A walk's first step per walk level, then a loop over the plane level.
291impl Iterable for PlanesExpand {
292    type Item = RegionExpand;
293
294    fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
295        self.region.each_plane(scope, body, false);
296    }
297
298    fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
299        self.region.each_plane(scope, body, true);
300    }
301}
302
303impl RegionExpand {
304    /// `body` over the planes below this region. A plane's place is the same at every step of a
305    /// walk, so each walk between is read at its first step.
306    fn each_plane(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand), unroll: bool) {
307        match self.path.next().coverage() {
308            Coverage::Distribute(ComputeScope::Plane) => match unroll {
309                true => self.expand_unroll(scope, body),
310                false => self.expand(scope, body),
311            },
312            Coverage::Walk => {
313                let first = self
314                    .__expand_walk_method(scope)
315                    .__expand_region_method(scope, 0usize.into_expand(scope));
316                first.each_plane(scope, body, unroll)
317            }
318            Coverage::Distribute(other) => panic!(
319                "Region::planes: a level dealt to each {other:?} lies below this region before any \
320                 plane level, so this region has no planes of its own"
321            ),
322        }
323    }
324
325    /// `body` over every region at the bottom of this region's partitioning below it.
326    fn each_leaf(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand), unroll: bool) {
327        if self.path.at_bottom() {
328            body(scope, self);
329            return;
330        }
331        let mut below = |scope: &Scope, child: RegionExpand| child.each_leaf(scope, body, unroll);
332        match unroll {
333            true => self.expand_unroll(scope, &mut below),
334            false => self.expand(scope, &mut below),
335        }
336    }
337}