use cubecl::prelude::*;
use crate::*;
#[allow(clippy::large_enum_variant)]
#[expect(
dead_code,
reason = "built through the expand type's generated constructors"
)]
#[derive(CubeType, Clone)]
#[expand(derive(Clone))]
pub(crate) enum AxisSlicesKind {
Registers(RegisterData),
Window(Memory),
Fragments(PlanePartition),
Fragment(PlaneTile),
}
#[derive(CubeType)]
pub struct AxisSlices {
kind: AxisSlicesKind,
#[cube(comptime)]
space: Space,
#[cube(comptime)]
axis: Axis,
}
#[cube]
impl Tile {
pub fn along(&self, #[comptime] axis: Axis) -> AxisSlices {
let holder = comptime!(self.place.holder());
let space = comptime!(self.place.space.clone());
comptime!(assert!(
space.axis_at(space.rank() - 1) == axis,
"Tile::along: a slice runs along the tile's innermost axis, and {axis:?} is not the \
innermost of {space:?}"
));
let kind = match &self.kind {
TileKind::PlanePartition(partition) => partition.slices(),
TileKind::PlaneTile(tile) => tile.slices(),
TileKind::Memory(window) => {
comptime!(assert!(
holder == ComputeScope::Plane,
"Tile::along: a window of shared memory is read slice by slice by the plane \
that holds it; this one is held by {holder:?}"
));
AxisSlicesKind::new_Window(window.clone())
}
TileKind::TmaGmem(_) | TileKind::Procedural(_) | TileKind::Lines(_) => panic!(
"Tile::along: a register block, a plane's fragments, or a plane's window holds \
slices"
),
};
AxisSlices { kind, space, axis }
}
}
#[cube]
impl Tile {
pub fn slices_per_unit(&self, #[comptime] axis: Axis) -> comptime_type!(usize) {
let define!(Served) = self.element;
let every = comptime!(self.place.space.slices_along(axis));
match &self.kind {
TileKind::PlanePartition(partition) => match partition.at(0usize, 0usize) {
PlaneTile::Mma(fragment) => {
let rows = fragment.rows_per_unit::<Served>();
comptime!(partition.m_tiles * rows)
}
PlaneTile::Cmma(_) | PlaneTile::Registers(_) => comptime!(every),
PlaneTile::Wgmma(_) => panic!(
"Tile::slices_per_unit: a warpgroup accumulator's slices are not read in \
place; drain it first"
),
},
TileKind::PlaneTile(tile) => match tile {
PlaneTile::Mma(fragment) => fragment.rows_per_unit::<Served>(),
PlaneTile::Cmma(_) | PlaneTile::Registers(_) => comptime!(every),
PlaneTile::Wgmma(_) => panic!(
"Tile::slices_per_unit: a warpgroup accumulator's slices are not read in \
place; drain it first"
),
},
TileKind::Memory(_)
| TileKind::TmaGmem(_)
| TileKind::Procedural(_)
| TileKind::Lines(_) => comptime!(every),
}
}
}
#[cube]
impl AxisSlices {
pub(crate) fn scale_add<E: Float>(&mut self, scale: E, bias: &Tile) {
let recipe = bias.recipe();
match &mut self.kind {
AxisSlicesKind::Registers(block) => {
block.scale_add_along::<E>(scale, recipe, self.space.clone())
}
AxisSlicesKind::Window(window) => {
window.scale_add_along::<E>(scale, recipe, self.space.clone(), self.axis)
}
AxisSlicesKind::Fragments(partition) => {
partition.scale_add_along::<E>(scale, recipe, self.space.clone())
}
AxisSlicesKind::Fragment(tile) => {
let mut fragment = tile.manual_fragment();
fragment.scale_add_along::<E>(scale, recipe, self.space.clone(), (0usize, 0usize))
}
}
}
pub(crate) fn maxima<E: Float>(&self, seed: &Array<E>) -> Array<E> {
match &self.kind {
AxisSlicesKind::Registers(block) => block.maxima_along::<E>(seed),
AxisSlicesKind::Window(window) => {
window.maxima_along::<E>(seed, self.space.clone(), self.axis)
}
AxisSlicesKind::Fragments(partition) => partition.maxima_along::<E>(seed),
AxisSlicesKind::Fragment(tile) => tile.manual_fragment().maxima_along(seed),
}
}
pub(crate) fn exp_minus<E: Float>(&mut self, slices: &Array<E>) {
match &mut self.kind {
AxisSlicesKind::Registers(block) => block.exp_minus_along::<E>(slices),
AxisSlicesKind::Window(window) => {
window.exp_minus_along::<E>(slices, self.space.clone(), self.axis)
}
AxisSlicesKind::Fragments(partition) => partition.exp_minus_along::<E>(slices),
AxisSlicesKind::Fragment(tile) => {
let mut fragment = tile.manual_fragment();
fragment.exp_minus_along(slices, 0usize)
}
}
}
pub(crate) fn sums<E: Float>(&self) -> Array<E> {
match &self.kind {
AxisSlicesKind::Registers(block) => block.sums_along::<E>(),
AxisSlicesKind::Window(window) => window.sums_along::<E>(self.space.clone(), self.axis),
AxisSlicesKind::Fragments(partition) => partition.sums_along::<E>(),
AxisSlicesKind::Fragment(tile) => tile.manual_fragment().sums_along(),
}
}
pub fn mul<E: Float>(&mut self, factors: &Array<E>) {
match &mut self.kind {
AxisSlicesKind::Registers(block) => block.mul_along::<E>(factors, 0usize),
AxisSlicesKind::Window(window) => {
window.mul_along::<E>(factors, self.space.clone(), self.axis)
}
AxisSlicesKind::Fragments(partition) => partition.mul_along::<E>(factors),
AxisSlicesKind::Fragment(tile) => tile.mul_along(factors, 0usize),
}
}
pub fn probabilities<E: Float>(&mut self, scale: E, bias: &Tile, logsumexp: &Tile) {
let recipe = bias.recipe();
let broadcast = comptime!(Broadcast::new(&self.space, &logsumexp.place.space));
let values = logsumexp.cells::<E>(comptime!(logsumexp.place.space.cells()));
match &mut self.kind {
AxisSlicesKind::Registers(block) => block.probabilities_along::<E>(
scale,
recipe,
&values,
self.space.clone(),
broadcast,
),
AxisSlicesKind::Window(window) => window.probabilities_along::<E>(
scale,
recipe,
&values,
self.space.clone(),
broadcast,
),
AxisSlicesKind::Fragments(_) | AxisSlicesKind::Fragment(_) => {
AxisSlices::refuse_fragments("AxisSlices::probabilities")
}
}
}
pub fn softmax_gradient<E: Float>(&mut self, probabilities: &AxisSlices, delta: &Tile) {
comptime!(assert!(
self.space == probabilities.space && self.axis == probabilities.axis,
"AxisSlices::softmax_gradient: the probabilities are cut as the gradient is, along \
{:?} of {:?}, not along {:?} of {:?}",
self.axis,
self.space,
probabilities.axis,
probabilities.space
));
let broadcast = comptime!(Broadcast::new(&self.space, &delta.place.space));
let values = delta.cells::<E>(comptime!(delta.place.space.cells()));
match (&mut self.kind, &probabilities.kind) {
(AxisSlicesKind::Registers(block), AxisSlicesKind::Registers(probabilities)) => {
block.softmax_gradient_along::<E>(probabilities, &values, broadcast)
}
(AxisSlicesKind::Window(window), AxisSlicesKind::Window(probabilities)) => window
.softmax_gradient_along::<E>(probabilities, &values, self.space.clone(), broadcast),
_ => AxisSlices::refuse_fragments("AxisSlices::softmax_gradient"),
}
}
pub(crate) fn store_held<E: Float>(&self, values: &Array<E>, window: &mut Tile) {
comptime!(assert!(
window.place.holder() == ComputeScope::Plane
&& window.place.space.cells() == self.space.slices_along(self.axis),
"AxisSlices::store_held: a plane's window of one cell for each of the {} slices of \
{:?}, not {:?} held by {:?}",
self.space.slices_along(self.axis),
self.space,
window.place.space,
window.place.holder()
));
match &mut window.kind {
TileKind::Memory(memory) => self.store_held_in::<E>(values, memory),
TileKind::PlanePartition(_)
| TileKind::PlaneTile(_)
| TileKind::TmaGmem(_)
| TileKind::Procedural(_)
| TileKind::Lines(_) => panic!(
"AxisSlices::store_held: the slices' state lands in a window of shared memory"
),
}
}
fn store_held_in<E: Float>(&self, values: &Array<E>, memory: &mut Memory) {
match &self.kind {
AxisSlicesKind::Fragments(partition) => partition.store_along::<E>(values, memory),
AxisSlicesKind::Fragment(tile) => tile
.manual_fragment()
.store_along::<E>(values, memory, 0usize, 0usize),
AxisSlicesKind::Registers(_) | AxisSlicesKind::Window(_) => panic!(
"AxisSlices::store_held: a unit's block and a plane's window keep every slice's \
slot alike; only a manual-mma grid's units keep their own rows"
),
}
}
pub(crate) fn exp_minus_cell<E: Float>(value: E, slice: E) -> E {
let live = slice > E::min_value();
select(live, (value - slice).exp(), E::from_int(0))
}
}
#[cube]
impl AxisSlices {
fn refuse_fragments(#[comptime] who: &str) -> ! {
panic!("{who}: a unit's register blocks or a plane's windows hold the cells, not fragments")
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Broadcast {
pub(crate) slice: Vec<usize>,
pub(crate) position: usize,
}
impl Broadcast {
pub(crate) fn new(space: &Space, broadcast: &Space) -> Self {
assert!(
broadcast.axes().all(|axis| match space.contains(axis) {
true => space.extent(axis) == broadcast.extent(axis),
false => broadcast.extent(axis) == 1,
}),
"Broadcast::new: every axis of {broadcast:?} is one of {space:?}'s at its extent, or \
one wide"
);
let stride = |axis: Axis| {
(broadcast.position(axis) + 1..broadcast.rank())
.map(|p| broadcast.extent_at(p))
.product::<usize>()
};
let innermost = space.axis_at(space.rank() - 1);
let outer: Vec<Axis> = (0..space.rank() - 1).map(|p| space.axis_at(p)).collect();
let slices = outer.iter().map(|&a| space.extent(a)).product::<usize>();
let slice = (0..slices)
.map(|index| {
let mut rest = index;
let mut offset = 0;
for &axis in outer.iter().rev() {
let extent = space.extent(axis);
if broadcast.contains(axis) {
offset += (rest % extent) * stride(axis);
}
rest /= extent;
}
offset
})
.collect();
let position = match broadcast.contains(innermost) {
true => stride(innermost),
false => 0,
};
Self { slice, position }
}
pub(crate) fn at(&self, slice: usize, position: usize) -> usize {
self.slice[slice] + position * self.position
}
}