use cubecl::prelude::*;
use crate::{Axis, Carrier, ComputeScope, Count, Coverage, Level, Monoid, Space};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum UnitShare {
Repeated,
Whole,
Plane,
Group { unit_bits: usize },
}
impl UnitShare {
pub fn new(level: &Level, spanned: &Space) -> UnitShare {
if level.coverage() != Coverage::Distribute(ComputeScope::Unit) {
return UnitShare::Repeated;
}
let (mut weight, mut unit_bits) = (1usize, 0usize);
for axis in level.axes().into_iter().rev() {
if let Count::Distributed(_) = level.cut(axis).count {
match spanned.contains(axis) {
true => continue,
false => return UnitShare::Plane,
}
}
let plane_units = level
.cut(axis)
.count
.stated()
.expect("Level::new: a units level carries stated counts");
if plane_units == 1 {
continue;
}
assert!(
plane_units.is_power_of_two(),
"UnitShare: {axis:?} rides {plane_units} units, which is not a power of two, so its \
partials are not a bit range"
);
if !spanned.contains(axis) {
unit_bits |= (plane_units - 1) * weight;
}
weight *= plane_units;
}
match unit_bits {
0 if weight == 1 => UnitShare::Repeated,
0 => UnitShare::Whole,
mask if mask == weight - 1 => UnitShare::Plane,
unit_bits => UnitShare::Group { unit_bits },
}
}
pub fn under(self, parent: UnitShare) -> UnitShare {
match (parent, self) {
(UnitShare::Repeated, share) | (share, UnitShare::Repeated) => share,
(UnitShare::Whole, share) | (share, UnitShare::Whole) => share,
(UnitShare::Group { unit_bits: a }, UnitShare::Group { unit_bits: b }) => {
UnitShare::Group { unit_bits: a | b }
}
_ => {
panic!("UnitShare::under: {self:?} under {parent:?}: nothing reduces under a plane")
}
}
}
pub fn reduces(self) -> bool {
matches!(self, UnitShare::Plane | UnitShare::Group { .. })
}
pub fn of_units(plane_units: usize, plane: usize) -> UnitShare {
match plane_units {
1 => UnitShare::Repeated,
n if n == plane => UnitShare::Plane,
n => {
assert!(
n.is_power_of_two() && n < plane,
"UnitShare::of_units: a group of {n} units in a plane of {plane} is not a bit \
range of the unit index"
);
UnitShare::Group { unit_bits: n - 1 }
}
}
}
}
#[cube]
impl UnitShare {
pub(crate) fn reduce_of<T: Carrier + CubePrimitive<Scalar: PlaneNumeric>>(
value: T,
#[comptime] share: UnitShare,
#[comptime] monoid: Monoid,
) -> T {
match comptime!(share) {
UnitShare::Repeated | UnitShare::Whole => value,
UnitShare::Plane => match comptime!(monoid) {
Monoid::Sum => plane_sum(value),
Monoid::Prod => plane_prod(value),
Monoid::Max => plane_max(value),
Monoid::Min => plane_min(value),
},
UnitShare::Group { unit_bits } => {
let mut total = value;
#[unroll]
for bit in 0..comptime!(usize::BITS - unit_bits.leading_zeros()) {
if comptime!(unit_bits & (1 << bit) != 0) {
total = monoid
.combine::<T>(total, plane_shuffle_xor(total, comptime!(1u32 << bit)));
}
}
total
}
}
}
}
impl UnitShare {
pub fn reduce<T: Carrier + CubePrimitive<Scalar: PlaneNumeric>>(
self,
value: T,
monoid: Monoid,
) -> T {
UnitShare::reduce_of::<T>(value, self, monoid)
}
pub fn __expand_reduce_method<T: Carrier + CubePrimitive<Scalar: PlaneNumeric>>(
self,
scope: &Scope,
value: T::ExpandType,
monoid: Monoid,
) -> T::ExpandType {
UnitShare::__expand_reduce_of::<T>(scope, value, self, monoid)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum SplitShare {
Whole,
PartialAcrossCubes,
PartialAcrossPlanes,
}
impl SplitShare {
pub(crate) fn new(level: &Level, space: &Space, spanned: &Space) -> SplitShare {
match level.coverage() {
Coverage::Walk | Coverage::Distribute(ComputeScope::Unit) => return SplitShare::Whole,
Coverage::Distribute(ComputeScope::Plane)
| Coverage::Distribute(ComputeScope::Cube) => {}
}
let split = match level.shared_by() {
Some(_) => level.axes().iter().any(|axis| !spanned.contains(*axis)),
None => level.axes().into_iter().any(|axis: Axis| {
!spanned.contains(axis) && level.instances_along(space, axis) != Some(1)
}),
};
match (split, level.coverage()) {
(false, _) => SplitShare::Whole,
(true, Coverage::Distribute(ComputeScope::Plane)) => SplitShare::PartialAcrossPlanes,
(true, _) => SplitShare::PartialAcrossCubes,
}
}
pub(crate) fn under(self, parent: SplitShare) -> SplitShare {
match (parent, self) {
(SplitShare::PartialAcrossPlanes, _) | (_, SplitShare::PartialAcrossPlanes) => {
SplitShare::PartialAcrossPlanes
}
(SplitShare::PartialAcrossCubes, _) | (_, SplitShare::PartialAcrossCubes) => {
SplitShare::PartialAcrossCubes
}
(SplitShare::Whole, SplitShare::Whole) => SplitShare::Whole,
}
}
}
impl From<Count> for usize {
fn from(count: Count) -> usize {
count
.stated()
.expect("a units level's count is stated; every tile is the launch's")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Levels, Space};
const M: Axis = Axis(0);
const N: Axis = Axis(1);
const K: Axis = Axis(2);
#[test]
fn a_cube_cut_contraction_is_partial_to_the_output() {
let space = Space::new(&[(M, 4), (N, 4), (K, 8)]);
let level = Levels::leaf(&[(K, 4)]).cubes(&[K]).level();
assert_eq!(
SplitShare::new(&level, &space, &space.subspace(&[M, N])),
SplitShare::PartialAcrossCubes
);
assert_eq!(
SplitShare::new(&level, &space, &space.subspace(&[M, K])),
SplitShare::Whole
);
}
#[test]
fn a_plane_cut_contraction_is_partial_to_the_output() {
let space = Space::new(&[(M, 4), (N, 4), (K, 8)]);
let level = Levels::leaf(&[(K, 4)]).planes(&[(K, 2)]).level();
assert_eq!(
SplitShare::new(&level, &space, &space.subspace(&[M, N])),
SplitShare::PartialAcrossPlanes
);
}
#[test]
fn a_shared_grid_is_partial_to_the_output() {
let space = Space::new(&[(M, 8), (N, 8), (K, 8)]);
let level = Levels::leaf(&[(M, 4), (N, 4), (K, 8)])
.cubes(&[M, N, K])
.shared_by(3)
.level();
assert_eq!(
SplitShare::new(&level, &space, &space.subspace(&[M, N])),
SplitShare::PartialAcrossCubes
);
assert_eq!(SplitShare::new(&level, &space, &space), SplitShare::Whole);
}
#[test]
fn a_cube_cut_of_the_whole_axis_is_not_a_split() {
let space = Space::new(&[(M, 4), (N, 4), (K, 8)]);
let level = Levels::leaf(&[(N, 1), (K, 8)]).cubes(&[N, K]).level();
assert_eq!(
SplitShare::new(&level, &space, &space.subspace(&[M, N])),
SplitShare::Whole
);
}
#[test]
fn a_cube_cut_output_axis_stays_whole() {
let space = Space::new(&[(M, 4), (N, 8), (K, 4)]);
let level = Levels::leaf(&[(N, 4)]).cubes(&[N]).level();
assert_eq!(
SplitShare::new(&level, &space, &space.subspace(&[M, N])),
SplitShare::Whole
);
}
#[test]
fn a_units_level_reduces_what_the_operand_does_not_span() {
let space = Space::new(&[(M, 4), (N, 8), (K, 32)]);
let out = space.subspace(&[M, N]);
let plane = Levels::leaf(&[(K, 1)]).units(&[(K, 32)]).level();
assert_eq!(UnitShare::new(&plane, &out), UnitShare::Plane);
let whole = Levels::leaf(&[(N, 1)]).units(&[(N, 8)]).level();
assert_eq!(UnitShare::new(&whole, &out), UnitShare::Whole);
let team = Levels::leaf(&[(N, 1), (K, 1)])
.units(&[(N, 8), (K, 4)])
.level();
assert_eq!(
UnitShare::new(&team, &out),
UnitShare::Group { unit_bits: 3 }
);
let walk = Levels::leaf(&[(K, 8)]).walk_every(&[K]).level();
assert_eq!(UnitShare::new(&walk, &out), UnitShare::Repeated);
}
#[test]
fn shares_compose_level_by_level() {
let group = UnitShare::Group { unit_bits: 3 };
assert_eq!(group.under(UnitShare::Repeated), group);
assert_eq!(UnitShare::Whole.under(group), group);
assert_eq!(
UnitShare::Group { unit_bits: 12 }.under(group),
UnitShare::Group { unit_bits: 15 }
);
assert_eq!(
UnitShare::Repeated.under(UnitShare::Whole),
UnitShare::Whole
);
assert_eq!(
SplitShare::Whole.under(SplitShare::PartialAcrossCubes),
SplitShare::PartialAcrossCubes
);
assert_eq!(
SplitShare::PartialAcrossPlanes.under(SplitShare::PartialAcrossCubes),
SplitShare::PartialAcrossPlanes
);
assert_eq!(
SplitShare::PartialAcrossCubes.under(SplitShare::PartialAcrossPlanes),
SplitShare::PartialAcrossPlanes
);
}
}