use cubecl::prelude::*;
use crate::{
Axis, ComputeScope, Count, CubeAxis, Extent, Integer, IntegerExpand, Level, Space, Spread,
};
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) enum AxisDistribution {
Walked,
Distributed(Distribution),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct Distribution {
pub(crate) scope: ComputeScope,
pub(crate) dim: Option<CubeAxis>,
pub(crate) spread: Spread,
pub(crate) across: Option<usize>,
pub(crate) in_turns: bool,
pub(crate) divides: bool,
pub(crate) inner: Vec<usize>,
pub(crate) unspanned: usize,
pub(crate) swizzled: Option<usize>,
}
impl AxisDistribution {
pub(crate) fn new(
level: &Level,
space: &Space,
p: usize,
swizzled: Option<(usize, usize)>,
) -> AxisDistribution {
let axis = space.axis_at(p);
if !level.distributes(axis) {
return AxisDistribution::Walked;
}
let dim = level.cube_axis(axis);
let same_dim = |q: usize| {
let other = space.axis_at(q);
level.distributes(other) && level.cube_axis(other) == dim
};
AxisDistribution::Distributed(Distribution {
scope: level
.coverage()
.scope()
.expect("a level that distributes an axis distributes it over a scope"),
dim,
spread: level.cut(axis).spread,
across: match level.cut(axis).count {
Count::AllAcross(workers) => Some(workers),
Count::Stated(_) | Count::All | Count::Distributed(_) => None,
},
in_turns: matches!(level.cut(axis).count, Count::Distributed(_)),
divides: AxisDistribution::divides(level, space, axis),
inner: ((p + 1)..space.rank()).filter(|&q| same_dim(q)).collect(),
unspanned: AxisDistribution::unspanned_weight(level, space, axis),
swizzled: match swizzled {
Some((x, _)) if x == p => Some(0),
Some((_, y)) if y == p => Some(1),
_ => None,
},
})
}
fn divides(level: &Level, space: &Space, axis: Axis) -> bool {
match level.cut(axis).count {
Count::AllAcross(workers) => match space.extent_raw(axis) {
Extent::Static(extent) => extent
.div_ceil(level.cut(axis).tile)
.is_multiple_of(workers),
Extent::Dynamic => false,
},
Count::Distributed(_) => false,
Count::Stated(_) | Count::All => true,
}
}
pub(crate) fn unspanned_weight(level: &Level, space: &Space, axis: Axis) -> usize {
let dim = level.cube_axis(axis);
level
.axes()
.iter()
.skip_while(|&&a| a != axis)
.skip(1)
.filter(|&&a| !space.contains(a) && level.distributes(a) && level.cube_axis(a) == dim)
.map(|&a| {
level.cut(a).count.stated().unwrap_or_else(|| {
panic!(
"AxisDistribution: {a:?} is distributed inside {axis:?} over the same scope but this \
operand does not span it, and its instance count is not comptime, so \
{axis:?}'s digit of the instance index cannot be decoded"
)
})
})
.product()
}
}
#[cube]
impl AxisDistribution {
pub(crate) fn tiles(
grid: usize,
pos: usize,
instances: usize,
run: usize,
#[comptime] spread: Spread,
#[comptime] divides: bool,
) -> usize {
if divides {
run
} else {
match spread {
Spread::Contiguous => {
let start = pos.times(run);
run.min_with(grid.max_with(start).minus(start))
}
Spread::Interleaved => grid
.max_with(pos)
.minus(pos)
.plus(instances.minus(1usize))
.divided_by(instances),
}
}
}
pub(crate) fn hardware(
#[comptime] compute_scope: ComputeScope,
#[comptime] dim: Option<CubeAxis>,
) -> usize {
match comptime!(compute_scope) {
ComputeScope::Cube => CubeAxis::position(comptime!(
dim.expect("a cube level's distributed axis rides a grid dimension")
)),
ComputeScope::Plane | ComputeScope::Unit => ComputeScope::position(compute_scope),
}
}
}
#[cube]
impl ComputeScope {
pub fn position(#[comptime] compute_scope: ComputeScope) -> usize {
match comptime!(compute_scope) {
ComputeScope::Plane => UNIT_POS_Y as usize,
ComputeScope::Unit => UNIT_POS_X as usize,
ComputeScope::Cube => {
panic!(
"ComputeScope::position: a cube has one position per grid dimension; say which"
)
}
}
}
}
#[cube]
impl CubeAxis {
pub(crate) fn position(#[comptime] dim: CubeAxis) -> usize {
let cube_pos = match comptime!(dim) {
CubeAxis::X => CUBE_POS_X,
CubeAxis::Y => CUBE_POS_Y,
CubeAxis::Z => CUBE_POS_Z,
};
cube_pos as usize
}
}