1use cubecl::prelude::*;
4
5use crate::ops::matmul::leaf::memory;
6use crate::*;
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
10pub(crate) struct GridShape {
11 pub(crate) tiles: (usize, usize),
12 pub(crate) m: usize,
13 pub(crate) n: usize,
14 pub(crate) k: usize,
15}
16
17impl GridShape {
18 pub(crate) fn new(place: &Placement, lhs: &Space) -> Self {
20 let levels = place.below();
21 let tiles = partition_shape(&place.space, levels);
22 let leaf = place.space.leaf(levels);
23 let lhs_leaf = lhs.leaf(levels);
24 let axes = MatrixAxes::accumulator(&leaf, &lhs_leaf);
25 GridShape {
27 tiles,
28 m: axes.rows(&leaf),
29 n: axes.cols(&leaf),
30 k: lhs_leaf.contracted_extent(&place.space),
31 }
32 }
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
38pub enum Scratch {
39 None,
42 OneTile,
44 #[serde(rename = "WholePartition")]
46 WholeGrid,
47}
48
49impl core::fmt::Display for Scratch {
51 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52 match self {
53 Scratch::None => write!(f, "none"),
54 Scratch::OneTile => write!(f, "one_tile"),
55 Scratch::WholeGrid => write!(f, "whole_partition"),
56 }
57 }
58}
59
60impl Scratch {
61 pub fn slots(self, tiles: usize) -> usize {
63 match self {
64 Scratch::None => 0,
65 Scratch::OneTile => 1,
66 Scratch::WholeGrid => tiles,
67 }
68 }
69
70 pub(crate) fn slot_of(self, i: usize, tiles: usize) -> usize {
72 match self {
73 Scratch::WholeGrid => i,
74 Scratch::None | Scratch::OneTile => {
75 let _ = (i, tiles);
76 0
77 }
78 }
79 }
80
81 pub fn bounces(self) -> bool {
84 !matches!(self, Scratch::None)
85 }
86
87 pub fn at_least_bouncing(self, bouncing: bool) -> Scratch {
89 match (bouncing, self) {
90 (true, Scratch::None) => Scratch::OneTile,
91 (_, scratch) => scratch,
92 }
93 }
94}
95
96pub(crate) fn plane_windows(space: &Space, levels: &[Level]) -> usize {
102 let mut handed = space.clone();
103 let mut planes = 1;
104 for level in levels {
105 if let Some(order) = level.plane_order() {
106 planes *= order.planes_per_cube();
107 } else if let Some(cube) = level.unit_cube() {
108 planes *= cube.planes;
109 } else if let Some(per_instance) = level.coverage().scope().and_then(ComputeScope::planes) {
110 let mut instances = 1;
111 for axis in level.axes() {
112 if level.distributes(axis) {
113 instances *= level.instances_along(&handed, axis).unwrap_or_else(|| {
114 panic!(
115 "Tile::with_scratch: {axis:?} is distributed across the cube's planes \
116 at a count only the launch knows, so the plane's windows cannot be \
117 sized"
118 )
119 });
120 }
121 }
122 planes *= instances * per_instance;
123 }
124 handed = level.child(&handed);
125 }
126 planes
127}
128
129#[cube]
131impl Tile {
132 pub fn accumulator<EA: Numeric>(
135 &self,
136 lhs: &Tile,
137 rhs: &Tile,
138 #[comptime] instruction: Instruction,
139 #[comptime] semiring: Semiring,
140 ) -> Tile {
141 match comptime!(instruction) {
142 Instruction::Registers { config } => {
143 register_accumulator::<EA>(self, lhs, rhs, config, semiring)
144 }
145 Instruction::Cmma | Instruction::Mma { .. } | Instruction::Wgmma => {
146 if comptime!(instruction == Instruction::Wgmma) {
147 comptime!(assert_warpgroups_hold(&self.place));
148 comptime!(assert_groups_own_their_box(&self.place));
149 }
150 let vector_size = self.vector_size();
151 accumulator_in::<EA>(
152 self,
153 lhs,
154 instruction,
155 vector_size,
156 1usize,
157 comptime!(Accumulation::Contraction(semiring)),
158 )
159 }
160 }
161 }
162
163 pub fn block_reducer<EA: Numeric>(
166 &self,
167 input: &Tile,
168 #[comptime] config: RegisterBlock,
169 #[comptime] monoid: Monoid,
170 ) -> Tile {
171 let vector_size = self.vector_size();
172 accumulator_in::<EA>(
173 self,
174 input,
175 comptime!(Instruction::Registers { config }),
176 vector_size,
177 1usize,
178 comptime!(Accumulation::Reduction(monoid)),
179 )
180 }
181
182 pub fn with_scratch(self, #[comptime] scratch: Scratch) -> Tile {
187 let define!(Served) = self.element;
188 match comptime!(scratch) {
189 Scratch::None => self,
190 _ => match &self.kind {
191 TileKind::PlanePartition(p) => {
192 let (m, n) = p.at(0usize, 0usize).shape();
193 let cells = comptime!(m * n);
194 let tiles = comptime!(p.m_tiles * p.n_tiles);
195 let slots = comptime!(scratch.slots(tiles));
196 let planes = comptime!(plane_windows(&self.place.space, &self.place.levels));
199 let plane = PLANE_POS.cast::<usize>() * comptime!(cells * slots);
200 let shared = Shared::<[Served]>::new_slice(comptime!(cells * slots * planes));
201 let mut frags = Sequence::<PlaneTile>::new();
203 #[unroll]
204 for i in 0..tiles {
205 let start = plane + comptime!(cells * scratch.slot_of(i, tiles));
206 let slot = shared.clone().map(|s| &s[start..start + cells]);
207 frags.push(p.frags.index(i).clone().with_scratch(slot));
208 }
209 Tile {
210 kind: TileKind::new_PlanePartition(PlanePartition {
211 frags,
212 m_tiles: comptime!(p.m_tiles),
213 n_tiles: comptime!(p.n_tiles),
214 rows: comptime!(p.rows),
215 cols: comptime!(p.cols),
216 scratch: ComptimeOption::new_Some(Raw::<SharedSlice>::of::<Served>(
217 shared.clone().map(|s| &s[plane..plane + cells]),
218 )),
219 held: comptime!(scratch),
220 }),
221 factor: self.factor.clone(),
222 place: comptime!(self.place.clone()),
223 element: comptime!(self.element),
224 }
225 }
226 TileKind::Memory(_)
227 | TileKind::PlaneTile(_)
228 | TileKind::TmaGmem(_)
229 | TileKind::Procedural(_)
230 | TileKind::Lines(_) => {
231 panic!("Tile::with_scratch: a scratch backs a plane-resident accumulator")
232 }
233 },
234 }
235 }
236
237 pub fn drained_into(&self, dest: &Tile) {
241 let define!(Served) = self.element;
242 let define!(Out) = dest.element;
243 let levels = self.factor_levels();
247 let scaled = comptime!(levels > 0);
248 let unopened = self.scratch_unopened();
249 let in_place = self.casts_in_place_to::<Served, Out>();
250 let bounces = dest.fragments_bounce(in_place);
251 if comptime!(unopened && (scaled || bounces)) {
252 let opened = self.clone().with_scratch(Scratch::OneTile);
253 opened.drained_into(dest);
254 } else if comptime!(scaled) {
255 let mut sum = self.unscaled();
256 sum.scale_by::<f32>(self.factor.whole());
257 sum.drained_into(dest);
258 } else {
259 self.drain_as_opened::<Served, Out>(dest);
260 }
261 }
262}
263
264#[cube]
265impl Tile {
266 pub fn cast<E: Numeric>(&self) -> Tile {
272 let define!(Served) = self.element;
273 let kind = match &self.kind {
274 TileKind::PlanePartition(partition) => {
275 TileKind::new_PlanePartition(partition.cast::<Served, E>())
276 }
277 TileKind::PlaneTile(tile) => TileKind::new_PlaneTile(tile.cast::<Served, E>()),
278 TileKind::Memory(_)
279 | TileKind::TmaGmem(_)
280 | TileKind::Procedural(_)
281 | TileKind::Lines(_) => {
282 panic!("Tile::cast: only a plane-resident accumulator is cast in its registers")
283 }
284 };
285 Tile {
286 kind,
287 factor: self.factor.clone(),
288 place: comptime!(self.place.clone()),
289 element: elem_type_of::<E>(),
290 }
291 }
292
293 fn casts_in_place_to<A: Numeric, Out: Numeric>(&self) -> comptime_type!(bool) {
297 match &self.kind {
300 TileKind::PlanePartition(p) => {
302 let cmma = p.is_cmma();
303 if comptime!(cmma) {
304 casts_in_place::<A, Out>(comptime!(p.rows), comptime!(p.cols))
305 } else {
306 comptime!(true)
307 }
308 }
309 TileKind::PlaneTile(t) => match t {
310 PlaneTile::Cmma(_) => {
311 let (_, m, n) = self.fragment_grid();
312 casts_in_place::<A, Out>(m, n)
313 }
314 PlaneTile::Mma(_) | PlaneTile::Registers(_) | PlaneTile::Wgmma(_) => {
315 comptime!(true)
316 }
317 },
318 TileKind::Memory(_)
319 | TileKind::TmaGmem(_)
320 | TileKind::Procedural(_)
321 | TileKind::Lines(_) => comptime!(true),
322 }
323 }
324
325 fn sync_scratch(&self) {
329 match comptime!(self.place.holder()) {
330 ComputeScope::Unit | ComputeScope::Plane => sync_plane(),
331 ComputeScope::PlaneGroup { .. } | ComputeScope::Cube => sync_cube(),
332 }
333 }
334
335 fn scratch_unopened(&self) -> comptime_type!(bool) {
337 match &self.kind {
338 TileKind::PlanePartition(p) => {
339 let cmma = p.is_cmma();
340 comptime!(cmma && p.held == Scratch::None)
341 }
342 TileKind::Memory(_)
343 | TileKind::PlaneTile(_)
344 | TileKind::TmaGmem(_)
345 | TileKind::Procedural(_)
346 | TileKind::Lines(_) => comptime!(false),
347 }
348 }
349
350 pub(crate) fn drained_chunk_into<A: Numeric, Out: Numeric>(
356 &self,
357 dest: &Tile,
358 turn: usize,
359 #[comptime] chunks: usize,
360 ) {
361 let unopened = self.scratch_unopened();
362 let in_place = self.casts_in_place_to::<A, Out>();
363 let bounces = dest.fragments_bounce(in_place);
364 if comptime!(unopened && bounces) {
365 let opened = self.clone().with_scratch(Scratch::OneTile);
366 opened.drained_chunk_into::<A, Out>(dest, turn, chunks);
367 } else {
368 self.drain_chunk_as_opened::<A, Out>(dest, turn, chunks);
369 }
370 }
371
372 fn drain_chunk_as_opened<A: Numeric, Out: Numeric>(
375 &self,
376 dest: &Tile,
377 turn: usize,
378 #[comptime] chunks: usize,
379 ) {
380 let in_place = self.casts_in_place_to::<A, Out>();
381 let bounces = dest.fragments_bounce(in_place);
382 match &self.kind {
383 TileKind::PlanePartition(p) => match comptime!(DrainPlan::new(p.held, bounces)) {
384 DrainPlan::Straight => drain_chunk_below::<A, Out>(
385 self,
386 dest,
387 comptime!(self.place.below().to_vec()),
388 0usize,
389 comptime!(DrainPass::Copy),
390 0usize,
391 turn,
392 chunks,
393 ),
394 DrainPlan::BounceEach => drain_chunk_below::<A, Out>(
395 self,
396 dest,
397 comptime!(self.place.below().to_vec()),
398 0usize,
399 comptime!(DrainPass::Bounce),
400 0usize,
401 turn,
402 chunks,
403 ),
404 DrainPlan::BounceTogether => {
405 self.sync_scratch();
406 drain_chunk_below::<A, Out>(
407 self,
408 dest,
409 comptime!(self.place.below().to_vec()),
410 0usize,
411 comptime!(DrainPass::Spill),
412 0usize,
413 turn,
414 chunks,
415 );
416 self.sync_scratch();
417 drain_chunk_below::<A, Out>(
418 self,
419 dest,
420 comptime!(self.place.below().to_vec()),
421 0usize,
422 comptime!(DrainPass::Add),
423 0usize,
424 turn,
425 chunks,
426 );
427 self.sync_scratch();
428 }
429 },
430 TileKind::PlaneTile(t) => {
434 let cmma = match t {
435 PlaneTile::Cmma(_) => comptime!(true),
436 PlaneTile::Mma(_) | PlaneTile::Registers(_) | PlaneTile::Wgmma(_) => {
437 comptime!(false)
438 }
439 };
440 let in_place = self.casts_in_place_to::<A, Out>();
441 let bounces = dest.fragments_bounce(in_place);
442 if comptime!(cmma && bounces) {
443 drain_chunk_leaf::<A, Out>(self, dest, comptime!(DrainPass::Bounce), turn == 0);
444 } else {
445 drain_chunk_leaf::<A, Out>(self, dest, comptime!(DrainPass::Copy), turn == 0);
446 }
447 }
448 TileKind::Memory(_)
449 | TileKind::TmaGmem(_)
450 | TileKind::Procedural(_)
451 | TileKind::Lines(_) => {
452 panic!(
453 "Tile::drained_chunk_into: a plane-resident accumulator drains; nothing else does"
454 )
455 }
456 }
457 }
458
459 fn drain_as_opened<A: Numeric, Out: Numeric>(&self, dest: &Tile) {
462 let in_place = self.casts_in_place_to::<A, Out>();
463 let bounces = dest.fragments_bounce(in_place);
464 match &self.kind {
465 TileKind::PlanePartition(p) => match comptime!(DrainPlan::new(p.held, bounces)) {
466 DrainPlan::Straight => drain_below::<A, Out>(
467 self,
468 dest,
469 comptime!(self.place.below().to_vec()),
470 0usize,
471 comptime!(DrainPass::Copy),
472 ),
473 DrainPlan::BounceEach => drain_below::<A, Out>(
475 self,
476 dest,
477 comptime!(self.place.below().to_vec()),
478 0usize,
479 comptime!(DrainPass::Bounce),
480 ),
481 DrainPlan::BounceTogether => {
483 self.sync_scratch();
484 drain_below::<A, Out>(
485 self,
486 dest,
487 comptime!(self.place.below().to_vec()),
488 0usize,
489 comptime!(DrainPass::Spill),
490 );
491 self.sync_scratch();
492 drain_below::<A, Out>(
493 self,
494 dest,
495 comptime!(self.place.below().to_vec()),
496 0usize,
497 comptime!(DrainPass::Add),
498 );
499 self.sync_scratch();
500 }
501 },
502 TileKind::PlaneTile(_) => drain_leaf::<A, Out>(self, dest, comptime!(DrainPass::Copy)),
504 TileKind::Memory(_)
505 | TileKind::TmaGmem(_)
506 | TileKind::Procedural(_)
507 | TileKind::Lines(_) => {
508 panic!("Tile::drained_into: a plane-resident accumulator drains; nothing else does")
509 }
510 }
511 }
512}
513
514fn assert_warpgroups_hold(place: &Placement) {
518 let holder = place
519 .below()
520 .iter()
521 .filter_map(|level| level.coverage().scope())
522 .min()
523 .unwrap_or_else(|| place.holder());
524 assert!(
525 holder == ComputeScope::WARPGROUP,
526 "Tile::accumulator: a warpgroup MMA's tile is held by {:?}, its four planes together, \
527 and this one's by {holder:?}; deal the tiles to `plane_groups(4, ..)`",
528 ComputeScope::WARPGROUP
529 );
530}
531
532fn assert_groups_own_their_box(place: &Placement) {
535 for level in &place.levels {
536 let Coverage::Distribute(ComputeScope::PlaneGroup { .. }) = level.coverage() else {
537 continue;
538 };
539 for axis in level.axes() {
540 let split = !matches!(level.count(axis), Some(Count::Stated(1)));
541 assert!(
542 !(level.distributes(axis) && split && !place.space.contains(axis)),
543 "Tile::accumulator: plane groups split {axis:?}, which the output lacks, so each \
544 would hold a partial of the same cells, and nothing merges plane groups' \
545 partials yet; deal each group a box of its own"
546 );
547 }
548 }
549}
550
551#[cube]
553fn register_accumulator<EA: Numeric>(
554 out: &Tile,
555 lhs: &Tile,
556 rhs: &Tile,
557 #[comptime] config: RegisterBlock,
558 #[comptime] semiring: Semiring,
559) -> Tile {
560 let lw = lhs.vector_size();
561 let rhs_load = rhs.vector_tile();
564 let rw = comptime!(rhs_load.run_length());
565 let aw = out.vector_size();
566 let contracted_per_step = comptime!(memory::contracted_per_step(
567 &lhs.place.space,
568 &rhs.place.space,
569 &out.place.space,
570 lw,
571 rw,
572 aw
573 ));
574 comptime!(assert!(
577 contracted_per_step > 1 || rw == aw,
578 "Tile::accumulator: the block's lines are the rhs's ({rw} wide) and drain into \
579 {aw}-wide cells; a stage served wider than its sink is the memory-backed leaf's \
580 (Tile::accumulating)"
581 ));
582 let width = comptime!(match (contracted_per_step > 1, config.partials) {
585 (true, true) => contracted_per_step,
586 (true, false) => 1,
587 (false, _) => rw,
588 });
589 accumulator_in::<EA>(
590 out,
591 lhs,
592 comptime!(Instruction::Registers { config }),
593 width,
594 contracted_per_step,
595 comptime!(Accumulation::Contraction(semiring)),
596 )
597}
598
599#[cube]
601fn accumulator_in<EA: Numeric>(
602 out: &Tile,
603 lhs: &Tile,
604 #[comptime] form: Instruction,
605 #[comptime] vector_size: usize,
606 #[comptime] contracted_per_step: usize,
607 #[comptime] accumulation: Accumulation,
608) -> Tile {
609 comptime!(assert!(
610 matches!(form, Instruction::Registers { .. })
611 || accumulation == Accumulation::Contraction(Semiring::SUM_PROD),
612 "Tile::accumulator: a hardware instruction accumulates under the sum of products alone, \
613 not {accumulation:?}; contract in a register block to sum under another"
614 ));
615 let mut acc = PlanePartition::mirror::<EA>(
616 comptime!(out.place.space.clone()),
617 comptime!(MatrixAxes::accumulator(&out.place.space, &lhs.place.space)),
618 comptime!(form),
619 comptime!(GridShape::new(&out.place, &lhs.place.space)),
620 vector_size,
621 contracted_per_step,
622 accumulation,
623 comptime!(out.place.depth),
624 comptime!(out.place.levels.clone()),
625 );
626 acc.init_identity::<EA>(comptime!(accumulation.monoid()));
627 acc
628}
629
630#[cube]
631impl Tile {
632 pub fn stage_accumulator<EA: Numeric>(
637 walk: &Walk,
638 #[comptime] axes: Vec<Axis>,
639 lhs: &Tile,
640 #[comptime] instruction: Instruction,
641 ) -> Tile {
642 comptime!(assert!(
643 !matches!(instruction, Instruction::Registers { .. }),
644 "Tile::stage_accumulator: a register block's accumulator is a unit's, opened on the \
645 output it drains into"
646 ));
647 let place = comptime!(Placement::new(
648 walk.level.child(&walk.space).subspace(&axes),
649 walk.depth(),
650 walk.parent.path.root_levels(),
651 ));
652 let accumulation = comptime!(Accumulation::Contraction(Semiring::SUM_PROD));
653 let mut acc = PlanePartition::mirror::<EA>(
654 comptime!(place.space.clone()),
655 comptime!(MatrixAxes::accumulator(&place.space, &lhs.place.space)),
656 instruction,
657 comptime!(GridShape::new(&place, &lhs.place.space)),
658 1usize,
659 1usize,
660 accumulation,
661 comptime!(place.depth),
662 comptime!(place.levels.clone()),
663 );
664 acc.init_identity::<EA>(comptime!(accumulation.monoid()));
665 acc
666 }
667}
668
669#[cube]
671fn drain_below<A: Numeric, O: Numeric>(
672 acc: &Tile,
673 dest: &Tile,
674 #[comptime] levels: Vec<Level>,
675 #[comptime] i: usize,
676 #[comptime] pass: DrainPass,
677) {
678 if comptime!(i == levels.len()) {
679 drain_leaf::<A, O>(acc, dest, pass);
680 } else {
681 let level = comptime!(levels[i].clone());
682 if comptime!(level.coverage() == Coverage::Walk) {
684 for region in dest.over(&level).unrolled() {
685 drain_below::<A, O>(
686 &acc.at(®ion),
687 &dest.at(®ion),
688 comptime!(levels.clone()),
689 comptime!(i + 1),
690 pass,
691 );
692 }
693 } else {
694 for region in dest.over(&level) {
695 drain_below::<A, O>(
696 &acc.at(®ion),
697 &dest.at(®ion),
698 comptime!(levels.clone()),
699 comptime!(i + 1),
700 pass,
701 );
702 }
703 }
704 }
705}
706
707#[cube]
711#[allow(clippy::needless_range_loop)] fn drain_chunk_below<A: Numeric, O: Numeric>(
713 acc: &Tile,
714 dest: &Tile,
715 #[comptime] levels: Vec<Level>,
716 #[comptime] i: usize,
717 #[comptime] pass: DrainPass,
718 place: usize,
719 turn: usize,
720 #[comptime] chunks: usize,
721) {
722 if comptime!(i == levels.len()) {
723 drain_chunk_leaf::<A, O>(acc, dest, pass, place % chunks == turn);
724 } else {
725 let level = comptime!(levels[i].clone());
726 let axes = comptime!(dest.place.space.axes().collect::<Vec<_>>());
727 if comptime!(level.coverage() == Coverage::Walk) {
730 for region in dest.over(&level).unrolled() {
731 let mut here = place;
732 #[unroll]
733 for a in 0..comptime!(axes.len()) {
734 here += region.coord(comptime!(axes[a]));
735 }
736 drain_chunk_below::<A, O>(
737 &acc.at(®ion),
738 &dest.at(®ion),
739 comptime!(levels.clone()),
740 comptime!(i + 1),
741 pass,
742 here,
743 turn,
744 chunks,
745 );
746 }
747 } else {
748 for region in dest.over(&level) {
749 drain_chunk_below::<A, O>(
750 &acc.at(®ion),
751 &dest.at(®ion),
752 comptime!(levels.clone()),
753 comptime!(i + 1),
754 pass,
755 place,
756 turn,
757 chunks,
758 );
759 }
760 }
761 }
762}
763
764#[cube]
767fn drain_chunk_leaf<A: Numeric, O: Numeric>(
768 acc: &Tile,
769 dest: &Tile,
770 #[comptime] pass: DrainPass,
771 lands: bool,
772) {
773 match comptime!(pass) {
774 DrainPass::Copy => {
775 if lands {
776 let mut window = dest.clone();
777 window.copy_cast_from(acc);
778 }
779 }
780 DrainPass::Spill => {
781 if lands {
782 acc.spill_to_scratch::<A>();
783 }
784 }
785 DrainPass::Add => {
786 if lands {
787 let mut window = dest.clone();
788 window.add_from_scratch::<O, A>(acc);
789 }
790 }
791 DrainPass::Bounce => {
792 let mut window = dest.clone();
793 acc.sync_scratch();
794 if lands {
795 acc.spill_to_scratch::<A>();
796 }
797 acc.sync_scratch();
798 if lands {
799 window.add_from_scratch::<O, A>(acc);
800 }
801 acc.sync_scratch();
802 }
803 }
804}
805
806#[cube]
808fn drain_leaf<A: Numeric, O: Numeric>(acc: &Tile, dest: &Tile, #[comptime] pass: DrainPass) {
809 match comptime!(pass) {
810 DrainPass::Copy => {
811 let mut window = dest.clone();
812 window.copy_cast_from(acc);
813 }
814 DrainPass::Spill => acc.spill_to_scratch::<A>(),
815 DrainPass::Add => {
816 let mut window = dest.clone();
817 window.add_from_scratch::<O, A>(acc);
818 }
819 DrainPass::Bounce => {
820 let mut window = dest.clone();
821 acc.sync_scratch();
822 acc.spill_to_scratch::<A>();
823 acc.sync_scratch();
824 window.add_from_scratch::<O, A>(acc);
825 acc.sync_scratch();
826 }
827 }
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833 use crate::space::{CubeOrder, PlaneOrder};
834
835 const M: Axis = Axis(0);
836 const N: Axis = Axis(1);
837 const K: Axis = Axis(2);
838
839 fn plane() -> Levels {
841 Levels::leaf(&[(M, 16), (N, 16), (K, 16)]).walk_every(&[K])
842 }
843
844 fn windows(levels: Vec<Level>) -> usize {
846 let partitioning =
847 Partitioning::new(Space::new(&[(M, 64), (N, 64), (K, 64)]), levels.clone());
848 plane_windows(&partitioning.box_of(ComputeScope::Plane), &levels)
849 }
850
851 #[test]
854 fn the_launch_planes_size_a_window_for_every_plane_of_the_cube() {
855 let of_each_cube = plane().planes(&[(M, 2), (N, 2)]).cubes(&[M, N]).build();
856 let of_the_launch = plane()
857 .launch_planes(
858 &[M, N],
859 PlaneOrder::new(&[(M, 2), (N, 2)], CubeOrder::RowMajor),
860 )
861 .build();
862 assert_eq!(windows(of_each_cube), 4);
863 assert_eq!(windows(of_the_launch), 4);
864 }
865
866 #[test]
868 fn blocks_grouped_in_a_cube_size_a_window_for_each_of_its_planes() {
869 let levels = plane()
870 .launch_planes(
871 &[M, N],
872 PlaneOrder::new(&[], CubeOrder::RowMajor).cubes_of(2),
873 )
874 .build();
875 assert_eq!(windows(levels), 2);
876 }
877}