use cubecl::{prelude::*, unexpanded};
use crate::{
Axis, ComputeScope, Count, Coverage, CubeAxis, Level, LevelTable, Region, RegionExpand, Space,
Walk,
};
#[derive(CubeType, CubeLaunch, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Partitioning {
pub(crate) space: Space,
#[cube(comptime)]
pub(crate) levels: Vec<Level>,
}
impl PartitioningExpand {
#[allow(clippy::should_implement_trait)]
pub fn clone(&self) -> Partitioning {
Partitioning::new(self.space.clone(), self.levels.clone())
}
pub fn space(&self) -> Space {
self.space.clone()
}
}
impl PartitioningExpand {
pub fn levels(&self) -> &[Level] {
&self.levels
}
pub fn depth(&self) -> usize {
self.levels.len()
}
}
impl Partitioning {
pub fn levels(&self) -> &[Level] {
&self.levels
}
pub fn level(&self, i: usize) -> Level {
self.levels[i].clone()
}
pub fn depth(&self) -> usize {
self.levels.len()
}
}
#[cube]
impl Partitioning {
pub fn walk(&self) -> Walk {
Region::root(self).walk()
}
pub fn over(&self, #[comptime] level: &Level) -> Walk {
Walk::of(&self.space, comptime!(level.clone()), Region::root(self))
}
}
impl IntoIterator for Partitioning {
type Item = Region;
type IntoIter = std::vec::IntoIter<Region>;
fn into_iter(self) -> Self::IntoIter {
unexpanded!()
}
}
impl IntoIterator for &Partitioning {
type Item = Region;
type IntoIter = std::vec::IntoIter<Region>;
fn into_iter(self) -> Self::IntoIter {
unexpanded!()
}
}
impl Iterable for PartitioningExpand {
type Item = RegionExpand;
fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
RegionExpand::__expand_root(scope, &self).expand(scope, body)
}
fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
RegionExpand::__expand_root(scope, &self).expand_unroll(scope, body)
}
}
impl Iterable for &PartitioningExpand {
type Item = RegionExpand;
fn expand(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
RegionExpand::__expand_root(scope, self).expand(scope, body)
}
fn expand_unroll(self, scope: &Scope, body: &mut dyn FnMut(&Scope, RegionExpand)) {
RegionExpand::__expand_root(scope, self).expand_unroll(scope, body)
}
}
impl Partitioning {
pub fn new(space: Space, levels: Vec<Level>) -> Self {
for (i, level) in levels.iter().enumerate() {
if let Some(scope) = level.coverage().scope() {
assert!(
levels[i + 1..]
.iter()
.all(|other| other.coverage() != level.coverage()),
"Partitioning::new: two levels distribute over {scope:?}"
);
}
}
Self { space, levels }
}
pub fn space(&self) -> &Space {
&self.space
}
pub fn all_dynamic(self) -> Self {
Partitioning {
space: self.space.all_dynamic(),
levels: self.levels,
}
}
pub fn table<'a>(&'a self, labels: &'a [(Axis, &'a str)]) -> LevelTable<'a> {
LevelTable::new(self, labels)
}
pub fn leaf(&self) -> Space {
self.space.leaf(&self.levels)
}
pub fn overhangs(&self, axis: Axis) -> bool {
assert!(
!self.space.is_dynamic(axis),
"Partitioning::overhangs: axis {axis:?} is Dynamic; ask the concrete space"
);
let mut space = self.space.clone();
for level in &self.levels {
if level.overhangs(&space, axis) {
return true;
}
space = level.child(&space);
}
false
}
pub(crate) fn overhanging(&self) -> Vec<Axis> {
self.space
.axes()
.filter(|&axis| self.overhangs(axis))
.collect()
}
pub fn level_distributed(&self, scope: ComputeScope) -> Option<&Level> {
let coverage = Coverage::Distribute(scope);
self.levels
.iter()
.find(|level| level.coverage() == coverage)
}
pub fn box_of(&self, scope: ComputeScope) -> Space {
let coverage = Coverage::Distribute(scope);
let mut space = self.space.clone();
for level in &self.levels {
space = level.child(&space);
if level.coverage() == coverage {
return space;
}
}
panic!("Partitioning::box_of: no level distributes over {scope:?}")
}
pub fn walks(&self) -> impl Iterator<Item = &Level> + '_ {
self.levels
.iter()
.filter(|level| level.coverage() == Coverage::Walk)
}
pub(crate) fn instances(&self, coverage: Coverage) -> u32 {
match coverage {
Coverage::Distribute(ComputeScope::Cube) => [CubeAxis::X, CubeAxis::Y, CubeAxis::Z]
.into_iter()
.map(|dim| self.cube_instances(dim))
.product(),
Coverage::Distribute(ComputeScope::Plane)
| Coverage::Distribute(ComputeScope::Unit) => {
self.count_instances(|level| level.coverage() == coverage, |_, _| true)
}
Coverage::Walk => 1,
}
}
fn cube_instances(&self, dim: CubeAxis) -> u32 {
self.count_instances(
|level| {
level.coverage() == Coverage::Distribute(ComputeScope::Cube)
&& (level.shared_by().is_none() || dim == CubeAxis::X)
},
|level, axis| level.cube_axis(axis) == Some(dim),
)
}
fn count_instances(
&self,
levels: impl Fn(&Level) -> bool,
axes: impl Fn(&Level, Axis) -> bool,
) -> u32 {
let mut total = 1u32;
let mut space = self.space.clone();
for level in &self.levels {
if levels(level) {
match level.shared_by() {
Some(workers) => total *= workers as u32,
None => {
for axis in space.axes() {
if level.distributes(axis) && axes(level, axis) {
total *= match level.count(axis) {
Some(Count::AllAcross(workers)) => workers,
Some(Count::Distributed(_)) => 1,
_ => level.tiles(&space, axis),
} as u32;
}
}
}
}
}
space = level.child(&space);
}
total
}
pub fn cube_count(&self) -> CubeCount {
CubeCount::Static(
self.cube_instances(CubeAxis::X),
self.cube_instances(CubeAxis::Y),
self.cube_instances(CubeAxis::Z),
)
}
pub fn planes_per_cube(&self) -> u32 {
self.instances(Coverage::Distribute(ComputeScope::Plane)) + self.fillers()
}
pub fn fillers(&self) -> u32 {
self.levels.iter().map(|level| level.fillers() as u32).sum()
}
pub fn units(&self) -> u32 {
self.instances(Coverage::Distribute(ComputeScope::Unit))
}
pub fn cube_dim(&self, plane_size: u32) -> CubeDim {
CubeDim::new_2d(plane_size, self.planes_per_cube())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Levels;
const M: Axis = Axis(0);
const N: Axis = Axis(1);
const K: Axis = Axis(2);
fn staged(fillers: usize) -> Partitioning {
Partitioning::new(
Space::new(&[(M, 256), (N, 256), (K, 512)]),
Levels::leaf(&[(M, 64), (N, 64), (K, 64)])
.planes(&[(M, 2), (N, 2)])
.walk_every(&[K])
.filled_by(fillers)
.cubes(&[M, N])
.build(),
)
}
#[test]
fn a_walk_with_no_fillers_is_the_partitioning_it_always_was() {
let plain = staged(0);
assert_eq!(plain.fillers(), 0);
assert_eq!(plain.planes_per_cube(), 4);
assert_eq!(plain.cube_dim(32), CubeDim::new_2d(32, 4));
}
#[test]
fn a_filling_plane_widens_the_cube_and_nothing_else() {
let plain = staged(0);
let filled = staged(1);
assert_eq!(filled.planes_per_cube(), plain.planes_per_cube() + 1);
assert_eq!(filled.cube_dim(32), CubeDim::new_2d(32, 5));
assert_eq!(
filled.instances(Coverage::Distribute(ComputeScope::Plane)),
plain.instances(Coverage::Distribute(ComputeScope::Plane))
);
assert_eq!(filled.units(), plain.units());
for dim in [CubeAxis::X, CubeAxis::Y, CubeAxis::Z] {
assert_eq!(filled.cube_instances(dim), plain.cube_instances(dim));
}
}
#[test]
fn the_fillers_of_every_level_are_counted() {
assert_eq!(staged(3).fillers(), 3);
}
#[test]
#[should_panic(expected = "only a walk's regions are staged")]
fn a_level_that_distributes_its_tiles_cannot_be_filled_by_anyone() {
Levels::leaf(&[(M, 64)])
.planes(&[(M, 2)])
.filled_by(1)
.build();
}
#[test]
#[should_panic(expected = "two levels distribute over Plane")]
fn two_levels_over_one_scope_are_refused() {
Partitioning::new(
Space::new(&[(M, 64), (N, 64)]),
Levels::leaf(&[(M, 8), (N, 8)])
.planes(&[(M, 2)])
.planes(&[(N, 2)])
.build(),
);
}
#[test]
fn a_scope_covers_the_box_its_level_cuts() {
let partitioning = staged(0);
let cube = Space::new(&[(M, 128), (N, 128), (K, 512)]);
assert_eq!(partitioning.box_of(ComputeScope::Cube), cube);
let plane = Space::new(&[(M, 64), (N, 64), (K, 64)]);
assert_eq!(partitioning.box_of(ComputeScope::Plane), plane);
}
}