use cubecl::prelude::*;
use crate::launch::Relay;
use crate::*;
#[derive(CubeType)]
pub struct OnlineSoftmax<E: Float> {
m: Array<E>,
l: Array<E>,
#[cube(comptime)]
slices: usize,
#[cube(comptime)]
axis: Axis,
}
#[derive(CubeType)]
pub struct RelayedFactors<E: Float> {
pub sum: Array<E>,
pub carried: Array<E>,
}
#[cube]
impl<E: Float> OnlineSoftmax<E> {
pub fn along<S: Numeric>(score: &Tile<S>, #[comptime] axis: Axis) -> OnlineSoftmax<E> {
OnlineSoftmax::<E>::new(comptime!(score.place.space.slices_along(axis)), axis)
}
pub fn new(#[comptime] slices: usize, #[comptime] axis: Axis) -> OnlineSoftmax<E> {
let mut m = Array::<E>::new(slices);
let mut l = Array::<E>::new(slices);
#[unroll]
for i in 0..slices {
m[i] = E::min_value();
l[i] = E::from_int(0);
}
OnlineSoftmax::<E> { m, l, slices, axis }
}
pub fn step(&mut self, score: &Tile<E>, bias: &Tile<E>, scale: E) -> Array<E> {
let mut slices = score.along(self.axis);
slices.scale_add(scale, bias);
let max = slices.maxima(&self.m);
slices.exp_minus(&max);
let sum = slices.sums();
let mut correction = Array::<E>::new(self.slices);
#[unroll]
for i in 0..self.slices {
correction[i] = (self.m[i] - max[i]).exp();
self.l[i] = correction[i] * self.l[i] + sum[i];
self.m[i] = max[i];
}
correction
}
pub fn normalizer(&mut self, plane: &Region) -> Array<E> {
let axis = self.axis;
let planes = comptime!(
plane
.path
.planes_along(|along| along == axis)
.iter()
.map(|&(_, count)| count)
.product::<usize>()
);
let mut factor = Array::<E>::new(self.slices);
if comptime!(planes == 1) {
factor = self.recip_l();
} else {
let correction = self.merge_planes(plane.coord(axis), planes);
let recip = self.recip_l();
#[unroll]
for i in 0..self.slices {
factor[i] = correction[i] * recip[i];
}
}
factor
}
fn merge_planes(&mut self, plane: usize, #[comptime] planes: usize) -> Array<E> {
let slices = self.slices;
let mut shared = Shared::<[E]>::new_slice(comptime!(planes * slices));
let writes = UNIT_POS_PLANE == 0;
let own = plane * slices;
if writes {
#[unroll]
for i in 0..slices {
shared[own + i] = self.m[i];
}
}
sync_cube();
let mut max = Array::<E>::new(slices);
#[unroll]
for i in 0..slices {
max[i] = E::min_value();
#[unroll]
for p in 0..planes {
max[i] = max[i].max(shared[p * slices + i]);
}
}
sync_cube();
let mut correction = Array::<E>::new(slices);
#[unroll]
for i in 0..slices {
correction[i] = (self.m[i] - max[i]).exp();
}
if writes {
#[unroll]
for i in 0..slices {
shared[own + i] = correction[i] * self.l[i];
}
}
sync_cube();
#[unroll]
for i in 0..slices {
let mut sum = E::from_int(0);
#[unroll]
for p in 0..planes {
sum += shared[p * slices + i];
}
self.l[i] = sum;
self.m[i] = max[i];
}
correction
}
pub fn relayed<C: Numeric>(
&mut self,
relay: &Relay<'_, C>,
carried_max: &mut Tile<E>,
carried_sum: &mut Tile<E>,
) -> RelayedFactors<E> {
let slices = self.slices;
let (first, last) = (relay.is_first(), relay.is_last());
let stored_max = carried_max.cells(slices);
let stored_sum = carried_sum.cells(slices);
let mut own = Array::<E>::new(slices);
let mut carried = Array::<E>::new(slices);
#[unroll]
for i in 0..slices {
let before_max = select(first, E::min_value(), stored_max[i]);
let before_sum = select(first, E::from_int(0), stored_sum[i]);
let merged = before_max.max(self.m[i]);
own[i] = AxisSlices::<E>::exp_minus_cell(self.m[i], merged);
carried[i] = AxisSlices::<E>::exp_minus_cell(before_max, merged);
self.l[i] = carried[i] * before_sum + own[i] * self.l[i];
self.m[i] = merged;
}
carried_max.set_cells(&self.m, slices);
carried_sum.set_cells(&self.l, slices);
let recip = self.recip_l();
#[unroll]
for i in 0..slices {
let normalize = select(last, recip[i], E::from_int(1));
own[i] *= normalize;
carried[i] *= normalize;
}
RelayedFactors::<E> { sum: own, carried }
}
pub fn recip_l(&self) -> Array<E> {
let mut recip = Array::<E>::new(self.slices);
#[unroll]
for i in 0..self.slices {
let live = self.l[i] > E::from_int(0);
recip[i] = select(live, self.l[i].recip(), E::from_int(0));
}
recip
}
}