use cubecl::prelude::*;
use crate::*;
define_size!(pub(crate) RA);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum Accumulation {
Contraction(Semiring),
Reduction(Monoid),
}
impl Accumulation {
pub(crate) fn monoid(self) -> Monoid {
match self {
Accumulation::Contraction(semiring) => semiring.add(),
Accumulation::Reduction(monoid) => monoid,
}
}
pub(crate) fn semiring(self, site: &str) -> Semiring {
match self {
Accumulation::Contraction(semiring) => semiring,
Accumulation::Reduction(monoid) => {
panic!("{site}: a block opened to reduce under {monoid:?} contracts nothing")
}
}
}
}
#[derive(CubeType, Clone)]
#[expand(derive(Clone))]
pub(crate) struct RegisterData {
pub(crate) data: Raw<Registers<RA>>,
#[cube(comptime)]
pub(crate) vector_size: usize,
#[cube(comptime)]
pub(crate) contracted_per_step: usize,
#[cube(comptime)]
pub(crate) mr: usize,
#[cube(comptime)]
pub(crate) nr: usize,
#[cube(comptime)]
pub(crate) axes: MatrixAxes,
#[cube(comptime)]
pub(crate) config: RegisterBlock,
#[cube(comptime)]
pub(crate) accumulation: Accumulation,
}
pub(crate) fn holds_partials(contracted_per_step: usize, vector_size: usize) -> bool {
contracted_per_step > 1 && vector_size > 1
}
fn cells_per_line(contracted_per_step: usize, vector_size: usize) -> usize {
match holds_partials(contracted_per_step, vector_size) {
true => 1,
false => vector_size,
}
}
pub(crate) fn assert_whole_cells(contracted_per_step: usize, vector_size: usize, site: &str) {
assert!(
!holds_partials(contracted_per_step, vector_size),
"{site}: the block holds each cell as {vector_size} partials, summed only at its drain; \
open it without `RegisterBlock::partials` to read its cells"
);
}
#[cube]
pub(crate) fn register_block_size(#[comptime] vector_size: usize) {
intrinsic!(|scope| {
scope.register_size::<RA>(vector_size);
});
}
#[cube]
impl RegisterData {
#[allow(clippy::too_many_arguments)]
pub(crate) fn alloc<T: Numeric>(
#[comptime] m: usize,
#[comptime] n: usize,
#[comptime] axes: MatrixAxes,
#[comptime] vector_size: usize,
#[comptime] contracted_per_step: usize,
#[comptime] config: RegisterBlock,
#[comptime] accumulation: Accumulation,
) -> RegisterData {
comptime!(assert!(
contracted_per_step == 1 || vector_size == 1 || vector_size == contracted_per_step,
"RegisterData::alloc: a block contracting {contracted_per_step} values of a cell a step sums them into \
the cell, one value wide, or keeps them a lane each, {contracted_per_step} wide; not {vector_size}"
));
let cells = comptime!(cells_per_line(contracted_per_step, vector_size));
comptime!(assert!(
vector_size > 0 && n.is_multiple_of(cells),
"RegisterData::alloc: n ({n}) must be a whole number of {vector_size}-wide lines"
));
register_block_size(vector_size);
let nr = comptime!(n / cells);
RegisterData {
data: Raw::<Registers<RA>>::of::<T>(Array::<Vector<T, RA>>::new(comptime!(m * nr))),
vector_size,
contracted_per_step,
mr: m,
nr,
axes,
config,
accumulation,
}
}
pub(crate) fn zero<T: Numeric>(&mut self) {
self.init::<T>(T::from_int(0));
}
pub(crate) fn init<T: Numeric>(&mut self, val: T) {
register_block_size(self.vector_size);
let data = self.data.at_mut::<T>();
let count = comptime!(self.mr * self.nr);
let mut line = Vector::<T, RA>::cast_from(val);
if comptime!(holds_partials(self.contracted_per_step, self.vector_size)) {
line = Vector::<T, RA>::cast_from(Monoid::identity::<T>(comptime!(
self.accumulation.monoid()
)));
line.insert(0usize, val);
}
#[allow(clippy::needless_range_loop)]
#[unroll]
for i in 0..count {
data[i] = line;
}
}
pub(crate) fn add_cast_from<T: Numeric, S: Numeric>(&mut self, src: &RegisterData) {
let data = self.data.at_mut::<T>();
comptime!(assert!(
self.mr == src.mr
&& self.nr == src.nr
&& self.contracted_per_step == src.contracted_per_step
&& self.vector_size == src.vector_size,
"RegisterData::add_cast_from: the blocks are {}x{} over {} contracted and {}x{} over \
{}; a promotion adds line for line and both are opened against one sink",
self.mr,
self.nr,
self.contracted_per_step,
src.mr,
src.nr,
src.contracted_per_step
));
register_block_size(self.vector_size);
let count = comptime!(self.mr * self.nr);
#[allow(clippy::needless_range_loop)]
#[unroll]
for i in 0..count {
data[i] += Vector::<T, RA>::cast_from(src.data.at::<S>()[i]);
}
}
pub(crate) fn mul_along<T: Numeric>(&mut self, factors: &Array<T>, #[comptime] first: usize) {
register_block_size(self.vector_size);
let data = self.data.at_mut::<T>();
#[unroll]
for r in 0..self.mr {
let factor = Vector::<T, RA>::cast_from(factors[first + r]);
#[unroll]
for n in 0..self.nr {
data[r * self.nr + n] *= factor;
}
}
}
pub(crate) fn scale<T: Numeric>(&mut self, factor: T) {
register_block_size(self.vector_size);
let data = self.data.at_mut::<T>();
let count = comptime!(self.mr * self.nr);
let line = Vector::<T, RA>::cast_from(factor);
#[allow(clippy::needless_range_loop)]
#[unroll]
for i in 0..count {
data[i] *= line;
}
}
}
#[cube]
impl RegisterData {
pub(crate) fn store_cast_window<T: Numeric, Out: Numeric>(
&self,
mem: &mut Memory,
#[comptime] space: Space,
) {
register_block_size(self.vector_size);
let size!(A) = comptime!(cells_per_line(self.contracted_per_step, self.vector_size));
self.drain::<T, Out, A>(mem, space);
}
fn drained<T: Numeric, Out: Numeric, A: Size>(&self, line: Vector<T, RA>) -> Vector<Out, A> {
if comptime!(holds_partials(self.contracted_per_step, self.vector_size)) {
let monoid = comptime!(self.accumulation.monoid());
Vector::<Out, A>::cast_from(Monoid::reduce::<T, RA>(line, self.vector_size, monoid))
} else {
Vector::<Out, A>::cast_from(line)
}
}
fn drain<T: Numeric, Out: Numeric, A: Size>(&self, mem: &mut Memory, #[comptime] space: Space) {
let data = self.data.at::<T>();
let mem_write = comptime!(mem.access.write);
let unit_share = comptime!(mem.unit_share);
let monoid = comptime!(self.accumulation.monoid());
let mut sink = mem.matrix_mut::<Out, A>(0usize, comptime!(self.axes), space);
match comptime!(Drain::of(unit_share, mem_write)) {
Drain::EachUnit =>
{
#[unroll]
for i in 0..comptime!(self.mr) {
#[unroll]
for n in 0..comptime!(self.nr) {
let cell = self.drained::<T, Out, A>(data[comptime!(i * self.nr + n)]);
sink.write(((i as u32).runtime(), (n as u32).runtime()), cell);
}
}
}
Drain::UnitZero =>
{
#[unroll]
for i in 0..comptime!(self.mr) {
#[unroll]
for n in 0..comptime!(self.nr) {
let cell = self.drained::<T, Out, A>(data[comptime!(i * self.nr + n)]);
if UNIT_POS_X == 0 {
sink.write(((i as u32).runtime(), (n as u32).runtime()), cell);
}
}
}
}
Drain::PlaneReduce =>
{
#[unroll]
for i in 0..comptime!(self.mr) {
#[unroll]
for n in 0..comptime!(self.nr) {
let combined = UnitShare::Plane
.reduce::<Vector<T, RA>>(data[comptime!(i * self.nr + n)], monoid);
let cell = self.drained::<T, Out, A>(combined);
if UNIT_POS_X == 0 {
sink.write(((i as u32).runtime(), (n as u32).runtime()), cell);
}
}
}
}
Drain::GroupReduce { unit_bits } =>
{
#[unroll]
for i in 0..comptime!(self.mr) {
#[unroll]
for n in 0..comptime!(self.nr) {
let combined = comptime!(UnitShare::Group { unit_bits })
.reduce::<Vector<T, RA>>(data[comptime!(i * self.nr + n)], monoid);
let cell = self.drained::<T, Out, A>(combined);
let unit_in_group = UNIT_POS_X & comptime!(unit_bits as u32);
if unit_in_group == 0 {
sink.write(((i as u32).runtime(), (n as u32).runtime()), cell);
}
}
}
}
}
}
}
#[cube]
impl RegisterData {
pub(crate) fn scale_add_along<E: Float>(
&mut self,
scale: E,
bias: &Procedural,
#[comptime] space: Space,
) {
let data = self.data.at_mut::<E>();
#[unroll]
for r in 0..self.mr {
#[unroll]
for n in 0..self.nr {
let at = r * self.nr + n;
let mut line = data[at];
#[unroll]
for j in 0..self.vector_size {
let column = n * self.vector_size + j;
let cell = bias.value_at::<E>(r as u32, column as u32, space.clone());
line.insert(j, line.extract(j) * scale + cell);
}
data[at] = line;
}
}
}
pub(crate) fn maxima_along<E: Float>(&self, seed: &Array<E>) -> Array<E> {
let data = self.data.at::<E>();
let mut maxima = Array::<E>::new(self.mr);
#[unroll]
for r in 0..self.mr {
let mut slice = seed[r];
#[unroll]
for n in 0..self.nr {
let line = data[r * self.nr + n];
#[unroll]
for j in 0..self.vector_size {
slice = max(slice, line.extract(j));
}
}
maxima[r] = slice;
}
maxima
}
pub(crate) fn exp_minus_along<E: Float>(&mut self, slices: &Array<E>) {
let data = self.data.at_mut::<E>();
#[unroll]
for r in 0..self.mr {
#[unroll]
for n in 0..self.nr {
let at = r * self.nr + n;
let mut line = data[at];
#[unroll]
for j in 0..self.vector_size {
line.insert(
j,
AxisSlices::exp_minus_cell::<E>(line.extract(j), slices[r]),
);
}
data[at] = line;
}
}
}
pub(crate) fn probabilities_along<E: Float>(
&mut self,
scale: E,
bias: &Procedural,
logsumexp: &Array<E>,
#[comptime] space: Space,
#[comptime] broadcast: Broadcast,
) {
let data = self.data.at_mut::<E>();
#[unroll]
for r in 0..self.mr {
#[unroll]
for n in 0..self.nr {
let at = r * self.nr + n;
let mut line = data[at];
#[unroll]
for j in 0..self.vector_size {
let column = n * self.vector_size + j;
let score = line.extract(j) * scale
+ bias.value_at::<E>(r as u32, column as u32, space.clone());
let slot = comptime!(broadcast.at(r, n * self.vector_size + j));
line.insert(j, AxisSlices::exp_minus_cell::<E>(score, logsumexp[slot]));
}
data[at] = line;
}
}
}
pub(crate) fn softmax_gradient_along<E: Float>(
&mut self,
probabilities: &RegisterData,
delta: &Array<E>,
#[comptime] broadcast: Broadcast,
) {
comptime!(assert!(
self.mr == probabilities.mr
&& self.nr == probabilities.nr
&& self.vector_size == probabilities.vector_size,
"RegisterData::softmax_gradient_along: the probabilities' block is {}×{} lines of {}, \
the gradient's {}×{} lines of {}",
probabilities.mr,
probabilities.nr,
probabilities.vector_size,
self.mr,
self.nr,
self.vector_size
));
let read = probabilities.data.at::<E>();
let data = self.data.at_mut::<E>();
#[unroll]
for r in 0..self.mr {
#[unroll]
for n in 0..self.nr {
let at = r * self.nr + n;
let mut line = data[at];
let probability = read[at];
#[unroll]
for j in 0..self.vector_size {
let slot = comptime!(broadcast.at(r, n * self.vector_size + j));
line.insert(j, probability.extract(j) * (line.extract(j) - delta[slot]));
}
data[at] = line;
}
}
}
pub(crate) fn sums_along<E: Float>(&self) -> Array<E> {
let data = self.data.at::<E>();
let mut sums = Array::<E>::new(self.mr);
#[unroll]
for r in 0..self.mr {
let mut slice = E::from_int(0);
#[unroll]
for n in 0..self.nr {
let line = data[r * self.nr + n];
#[unroll]
for j in 0..self.vector_size {
slice += line.extract(j);
}
}
sums[r] = slice;
}
sums
}
}