use cubecl::cmma::MatrixLayout;
use cubecl::prelude::*;
use cubecl::wgmma::{Accumulator, Major, MatrixDescriptor, Swizzle, WARPGROUP_M, WgmmaTileLayout};
use crate::ops::matmul::leaf::window_layouts;
use crate::*;
#[derive(CubeType, Clone)]
#[expand(derive(Clone))]
pub(crate) struct WgmmaData {
pub(crate) acc: Pending<Accumulator<Held>>,
#[cube(comptime)]
pub n: usize,
#[cube(comptime)]
pub axes: MatrixAxes,
}
#[cube]
impl WgmmaData {
pub(crate) fn new<T: Numeric>(
#[comptime] m: usize,
#[comptime] n: usize,
#[comptime] axes: MatrixAxes,
) -> WgmmaData {
comptime!(assert!(
m == WARPGROUP_M,
"WgmmaData: a warpgroup MMA computes {WARPGROUP_M} rows, and this tile has {m}; give \
each plane group a {WARPGROUP_M}-row tile"
));
WgmmaData {
acc: unsafe {
Accumulator::<T>::new(n)
.start()
.downcast_unchecked::<Held>()
},
n,
axes,
}
}
pub(crate) fn zero<T: Numeric>(&mut self) {
self.acc.downcast::<T>().reset();
}
pub(crate) fn mma<T: Numeric, L: Numeric, R: Numeric>(
&mut self,
lhs: &Tile,
rhs: &Tile,
#[comptime] out: Space,
) {
comptime!(assert_operands_fit(
&lhs.place.space,
&rhs.place.space,
&out,
self.n
));
let (lhs_layout, rhs_layout) =
comptime!(window_layouts(&lhs.place.space, &rhs.place.space));
comptime!(assert!(
lhs_layout == MatrixLayout::RowMajor && rhs_layout == MatrixLayout::ColMajor,
"WgmmaData::mma: a warpgroup MMA reads both operands K-major here, the lhs `{{m, k}}` \
and the rhs `{{n, k}}`; stage them so"
));
let k = comptime!(trailing_extent(&lhs.place.space));
let elem_size = L::size().comptime();
let k_step = comptime!(WgmmaTileLayout::k_step(elem_size));
comptime!(assert!(
k.is_multiple_of(k_step),
"WgmmaData::mma: a window of {k} along k is not a whole number of the MMA's {k_step}"
));
let a = descriptor::<L>(lhs, WARPGROUP_M, k);
let b = descriptor::<R>(rhs, comptime!(self.n), k);
let mut acc = self.acc.downcast::<T>();
#[unroll]
for step in 0..comptime!(k / k_step) {
let at = step * k_step;
acc.execute(&a.at(0usize, at), &b.at(0usize, at));
}
}
pub(crate) fn commit<T: Numeric>(&mut self) -> Pending<()> {
self.acc.downcast::<T>().commit()
}
pub(crate) fn store_cast_window<T: Numeric, Out: Numeric>(
&self,
mem: &mut Memory,
#[comptime] space: Space,
) {
comptime!(assert!(
mem.store.vector_size == 1,
"WgmmaData: an accumulator drained cell by cell writes one element at a time, and \
its destination is bound {} wide; bind it one element wide",
mem.store.vector_size
));
let acc = self.acc.downcast::<T>().wait();
let unit = ComputeScope::unit(ComputeScope::WARPGROUP) as u32;
let mut sink = mem.matrix_mut::<Out, Const<1>>(0usize, comptime!(self.axes), space);
#[unroll]
for nth in 0..acc.len() {
let at = acc.position_of_nth(unit, nth as u32);
let mut value = Vector::<Out, Const<1>>::empty();
value.insert(0usize, Out::cast_from(acc.get(nth)));
sink.write(at, value);
}
}
}
fn assert_operands_fit(lhs: &Space, rhs: &Space, out: &Space, n: usize) {
let contracted: Vec<_> = lhs.axes().filter(|&axis| !out.contains(axis)).collect();
let k = lhs.axis_at(lhs.rank() - 1);
assert!(
contracted == [k] && rhs.axis_at(rhs.rank() - 1) == k,
"WgmmaData::mma: a warpgroup MMA contracts one axis, the trailing one of both operands; \
these contract {contracted:?}, the lhs {lhs:?} and the rhs {rhs:?}"
);
let rows = |space: &Space| {
space
.axes()
.filter(|&axis| axis != k)
.map(|axis| space.extent(axis))
.product::<usize>()
};
assert!(
rows(lhs) == WARPGROUP_M && rows(rhs) == n,
"WgmmaData::mma: a warpgroup MMA reads {WARPGROUP_M} rows of the lhs and {n} of the rhs, \
and these windows hold {} and {}",
rows(lhs),
rows(rhs)
);
}
fn trailing_extent(space: &Space) -> usize {
space.extent_at(space.rank() - 1)
}
#[cube]
fn descriptor<E: Numeric>(
tile: &Tile,
#[comptime] rows: usize,
#[comptime] k: usize,
) -> MatrixDescriptor<E> {
let elem_size = E::size().comptime();
let mem = tile.mem("wgmma");
let swizzle = comptime!(stage_swizzle(&mem.layout.rows));
let row = comptime!(swizzle.width() / elem_size);
comptime!(assert!(
k <= row,
"WgmmaData: a window of {k} along k runs past its stage's row of {row}"
));
let layout = comptime!(WgmmaTileLayout {
major: Major::K,
swizzle,
rows,
k: row,
});
let size!(W) = tile.vector_size();
let view = tile.fragment_matrix_packed::<E, W>(rows, k);
let origin = view.line_slice(
(0u32.runtime(), 0u32.runtime()),
(1u32.runtime(), 1u32.runtime()),
);
MatrixDescriptor::<E>::new(origin, layout)
}
fn stage_swizzle(rows: &RowArrangement) -> Swizzle {
match rows.tma_swizzle() {
Ok(TensorMapSwizzle::B32) => Swizzle::B32,
Ok(TensorMapSwizzle::B64) => Swizzle::B64,
Ok(TensorMapSwizzle::B128) => Swizzle::B128,
Ok(other) => panic!(
"WgmmaData: the stage keeps its rows under {other:?}, which no warpgroup MMA reads; \
stage them RowChunks::Swizzled, one swizzle span a row"
),
Err(why) => panic!("WgmmaData: a warpgroup MMA cannot read the stage: {why}"),
}
}