1use cubecl::prelude::*;
4use cubecl::unexpanded;
5
6use crate::dialect::LevelOp;
7use crate::{
8 Axis, ComputeScope, Coords, Integer, IntegerExpand, Level, Planes, Region, RegionExpand, Space,
9 StageOwner,
10};
11
12use super::distribution::{AxisDistribution, Distribution};
13use crate::Spread;
14use crate::space::partition::{GridCount, in_plane_axes, launch_tiles, swizzled_positions};
15
16#[derive(CubeType)]
18pub struct Walk {
19 tiles: Coords<usize>,
21 counts: Coords<usize>,
22 positions: Coords<usize>,
23 scales: Coords<usize>,
24 route: Coords<u32>,
26 #[cube(comptime)]
28 routed_at: Vec<usize>,
29 base: usize,
30 steps: usize,
31 pub(crate) parent: Region,
32 #[cube(comptime)]
33 pub(crate) space: Space,
34 #[cube(comptime)]
35 pub(crate) level: Level,
36 #[cube(comptime)]
38 distributes: Vec<AxisDistribution>,
39 #[cube(comptime)]
41 pub(crate) unroll: bool,
42 #[cube(comptime)]
45 pub(crate) run: Option<Axis>,
46}
47
48#[cube]
49impl Walk {
50 #[allow(clippy::needless_range_loop)]
52 pub(crate) fn of(space: &Space, #[comptime] level: Level, parent: Region) -> Walk {
53 let host = comptime!(space.clone());
54 let rank = comptime!(host.rank());
55 let in_plane = comptime!(
57 level
58 .order()
59 .swizzles()
60 .then(|| in_plane_axes(&host, &level))
61 );
62 let distributes = comptime!(
63 (0..rank)
64 .map(|p| AxisDistribution::new(&level, &host, p, in_plane))
65 .collect::<Vec<_>>()
66 );
67
68 let mut grid = Coords::<usize>::new();
69 #[unroll]
70 for p in 0..rank {
71 match comptime!(level.grid(space.axis_at(p))) {
72 GridCount::Const(n) => grid.push(n.runtime()),
73 GridCount::Extent(tile) => grid.push(space.count(p, tile)),
74 }
75 }
76
77 let (counts, positions, scales) = match comptime!(level.coverage().scope()) {
79 Some(_) => intrinsic!(|scope| LevelOp::trace(scope, |scope| {
80 Walk::__expand_tiles_of(
81 scope,
82 host.clone(),
83 level.clone(),
84 distributes.clone(),
85 &grid,
86 )
87 })),
88 None => Walk::tiles_of(
89 comptime!(host.clone()),
90 comptime!(level.clone()),
91 comptime!(distributes.clone()),
92 &grid,
93 ),
94 };
95
96 let steps = counts.product(comptime!((0..rank).collect::<Vec<_>>()));
98
99 Walk {
100 tiles: grid,
101 counts,
102 positions,
103 scales,
104 route: Coords::constant(comptime!(vec![0; rank])),
105 routed_at: comptime!(Vec::new()),
106 base: 0usize,
107 steps,
108 parent,
109 distributes,
110 space: host,
111 level,
112 unroll: comptime!(false),
113 run: comptime!(None),
114 }
115 }
116
117 fn tiles_of(
119 #[comptime] host: Space,
120 #[comptime] level: Level,
121 #[comptime] distributes: Vec<AxisDistribution>,
122 grid: &Coords<usize>,
123 ) -> (Coords<usize>, Coords<usize>, Coords<usize>) {
124 match comptime!(level.spans_launch()) {
125 true => Walk::spanning_launch(host, level, grid),
126 false => Walk::dealt(host, level, distributes, grid),
127 }
128 }
129
130 fn spanning_launch(
134 #[comptime] host: Space,
135 #[comptime] level: Level,
136 grid: &Coords<usize>,
137 ) -> (Coords<usize>, Coords<usize>, Coords<usize>) {
138 let (positions, counts) = launch_tiles(comptime!(host.clone()), level, grid);
139 let mut scales = Coords::<usize>::new();
140 #[unroll]
141 for _ in 0..comptime!(host.rank()) {
142 scales.push(1usize);
143 }
144 (counts, positions, scales)
145 }
146
147 #[allow(clippy::needless_range_loop)]
150 fn dealt(
151 #[comptime] host: Space,
152 #[comptime] level: Level,
153 #[comptime] distributes: Vec<AxisDistribution>,
154 grid: &Coords<usize>,
155 ) -> (Coords<usize>, Coords<usize>, Coords<usize>) {
156 let rank = comptime!(host.rank());
157 let mut instances = Coords::<usize>::new();
159 #[unroll]
160 for p in 0..rank {
161 match comptime!(distributes[p].clone()) {
162 AxisDistribution::Distributed(Distribution {
163 across: Some(workers),
164 ..
165 }) => instances.push(workers.runtime()),
166 AxisDistribution::Distributed(Distribution { in_turns: true, .. }) => {
168 instances.push(CUBE_DIM_X as usize)
169 }
170 AxisDistribution::Distributed(_) => instances.push(grid.at(p)),
171 AxisDistribution::Walked => instances.push(1usize),
172 }
173 }
174 let swizzled = swizzled_positions(
175 comptime!(host.clone()),
176 comptime!(level.clone()),
177 &instances,
178 );
179
180 let mut counts = Coords::<usize>::new();
181 let mut positions = Coords::<usize>::new();
182 let mut scales = Coords::<usize>::new();
183 #[unroll]
184 for p in 0..rank {
185 match comptime!(distributes[p].clone()) {
186 AxisDistribution::Walked => {
187 counts.push(grid.at(p));
188 positions.push(0usize);
189 scales.push(1usize);
190 }
191 AxisDistribution::Distributed(Distribution {
192 scope: compute_scope,
193 dim,
194 spread,
195 across,
196 in_turns: _,
197 divides,
198 inner,
199 unspanned,
200 swizzled: joint,
201 }) => {
202 let inner_weight = instances.product(inner) * comptime!(unspanned).runtime();
204 let position = match comptime!(joint) {
205 Some(i) => swizzled.at(i),
206 None => AxisDistribution::hardware(compute_scope, dim)
207 .divided_by(inner_weight)
208 .remainder(instances.at(p)),
209 };
210 let run = match comptime!(across) {
211 Some(workers) => grid
212 .at(p)
213 .plus(comptime!(workers - 1).runtime())
214 .divided_by(workers.runtime()),
215 None => 1usize.runtime(),
216 };
217 counts.push(AxisDistribution::tiles(
218 grid.at(p),
219 position,
220 instances.at(p),
221 run,
222 spread,
223 divides,
224 ));
225 positions.push(position);
226 match comptime!(spread) {
227 Spread::Contiguous => scales.push(run),
228 Spread::Interleaved => scales.push(instances.at(p)),
229 }
230 }
231 }
232 }
233
234 (counts, positions, scales)
235 }
236
237 pub fn routed(self, #[comptime] axis: Axis, coord: usize) -> Walk {
240 let rank = comptime!(self.space.rank());
241 let at = comptime!(self.space.position(axis));
242 comptime!(assert!(
243 self.space.contains(axis),
244 "Walk::routed: {axis:?} is not an axis of this walk's space, so it has no \
245 coordinate to state"
246 ));
247
248 let mut counts = Coords::<usize>::new();
249 let mut route = Coords::<u32>::new();
250 #[unroll]
251 for p in 0..rank {
252 if comptime!(p == at) {
254 let last = comptime!(self.level.tiles(&self.space, axis) - 1).runtime();
256 counts.push(1usize);
257 route.push(coord.min_with(last).cast::<u32>());
258 } else {
259 counts.push(self.counts.at(p));
260 route.push(self.route.at(p));
261 }
262 }
263 let steps = counts.product(comptime!((0..rank).collect::<Vec<_>>()));
264
265 Walk {
266 tiles: self.tiles,
267 counts,
268 positions: self.positions,
269 scales: self.scales,
270 route,
271 routed_at: comptime!({
272 let mut routed_at = self.routed_at.clone();
273 routed_at.push(at);
274 routed_at
275 }),
276 base: self.base,
277 steps,
278 parent: self.parent,
279 distributes: comptime!(self.distributes.clone()),
280 space: comptime!(self.space.clone()),
281 level: comptime!(self.level.clone()),
282 unroll: comptime!(self.unroll),
283 run: comptime!(self.run),
284 }
285 }
286
287 pub(crate) fn stage_owner(&self) -> comptime_type!(StageOwner) {
291 comptime!(StageOwner::new(
292 self.parent
293 .path
294 .scope()
295 .min(self.level.coverage().scope().unwrap_or(ComputeScope::Cube)),
296 self.parent.path.planes_per_cube(),
297 self.level.fillers(),
298 ))
299 }
300
301 pub fn total(&self) -> usize {
303 self.steps
304 }
305
306 pub(crate) fn position_at(&self, #[comptime] p: usize) -> usize {
309 self.positions.at(p)
310 }
311
312 pub fn position(&self, #[comptime] axis: Axis) -> usize {
316 self.positions.at(comptime!(self.space.position(axis)))
317 }
318
319 pub fn region(&self, i: usize) -> Region {
321 let idx = self.base.plus(i);
322 self.parent
323 .below(self.resolve(idx), comptime!(self.level.clone()))
324 }
325
326 pub fn planes(&self) -> Planes {
329 self.region(0).planes()
330 }
331
332 fn resolve(&self, idx: usize) -> Coords<u32> {
334 let mut coords = Coords::<u32>::new();
335
336 #[unroll]
337 for p in 0..comptime!(self.space.rank()) {
338 if comptime!(self.routed_at.contains(&p)) {
339 coords.push(self.route.at(p));
340 } else {
341 coords.push(self.tile_coord(self.step_coord(idx, p), p).cast::<u32>());
342 }
343 }
344 coords
345 }
346
347 fn step_coord(&self, idx: usize, #[comptime] p: usize) -> usize {
349 let rank = comptime!(self.space.rank());
350 let quot = idx.divided_by(
351 self.counts
352 .product(comptime!(((p + 1)..rank).collect::<Vec<_>>())),
353 );
354 let count = self.counts.at(p);
357 let one = count.constant();
358 let earlier = self
359 .counts
360 .product(comptime!((0..p).collect::<Vec<_>>()))
361 .constant();
362 if comptime!(one != Some(1) && earlier == Some(1)) {
363 quot
364 } else {
365 quot.remainder(count)
366 }
367 }
368
369 fn tile_coord(&self, coord: usize, #[comptime] p: usize) -> usize {
373 let tile = match comptime!(self.distributes[p].clone()) {
374 AxisDistribution::Walked => coord,
375 AxisDistribution::Distributed(Distribution {
376 spread: Spread::Contiguous,
377 ..
378 }) => coord.plus(self.positions.at(p).times(self.scales.at(p))),
379 AxisDistribution::Distributed(Distribution {
380 spread: Spread::Interleaved,
381 ..
382 }) => coord.times(self.scales.at(p)).plus(self.positions.at(p)),
383 };
384 let tiles = self.tiles.at(p);
385 if comptime!(self.level.is_reversed(self.space.axis_at(p))) && tile < tiles {
387 tiles.minus(1usize).minus(tile)
388 } else {
389 tile
390 }
391 }
392}
393
394impl IntoIterator for Walk {
396 type Item = Region;
397 type IntoIter = std::vec::IntoIter<Region>;
398
399 fn into_iter(self) -> Self::IntoIter {
400 let mut regions = Vec::new();
401 for i in 0..self.total() {
402 regions.push(self.region(i));
403 }
404 regions.into_iter()
405 }
406}
407
408impl Iterable for WalkExpand {
409 type Item = RegionExpand;
410
411 fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
412 let start = 0usize.into_expand(scope);
413 let total = self.__expand_total_method(scope);
414 let range = RangeExpand::new(start, total);
415 if self.unroll {
416 range.expand_unroll(scope, &mut |scope, i| {
417 body(scope, self.__expand_region_method(scope, i));
418 });
419 } else {
420 range.expand(scope, &mut |scope, i| {
421 body(scope, self.__expand_region_method(scope, i));
422 });
423 }
424 }
425
426 fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
427 let start = 0usize.into_expand(scope);
428 let total = self.__expand_total_method(scope);
429 RangeExpand::new(start, total).expand_unroll(scope, &mut |scope, i| {
430 body(scope, self.__expand_region_method(scope, i));
431 });
432 }
433
434 fn const_len(&self) -> Option<usize> {
436 crate::algebra::constant(&self.steps).map(|n| n as usize)
437 }
438}
439
440impl Walk {
442 pub fn unrolled(self) -> Walk {
444 unexpanded!()
445 }
446
447 pub fn range(self, _base: usize, _steps: usize) -> Walk {
450 unexpanded!()
451 }
452
453 pub(crate) fn depth(&self) -> usize {
455 self.parent.path.depth() + 1
456 }
457}
458
459impl WalkExpand {
460 pub fn __expand_unrolled_method(mut self, _scope: &Scope) -> Self {
461 self.unroll = true;
462 self
463 }
464
465 pub fn __expand_range_method(
466 mut self,
467 _scope: &Scope,
468 base: NativeExpand<usize>,
469 steps: NativeExpand<usize>,
470 ) -> Self {
471 self.base = base;
472 self.steps = steps;
473 self
474 }
475
476 pub(crate) fn depth(&self) -> usize {
477 self.parent.path.depth() + 1
478 }
479}