use core::marker::PhantomData;
use cubecl::ir::{ExpandValue, VectorSize};
use cubecl::prelude::*;
use cubecl::std::tensor::{
ErasedTensor, ErasedTensorExpand, ErasedTensorOperationsExpand, ReadsLines, WriteOnly,
WritesLines,
};
use cubecl::unexpanded;
use crate::*;
#[derive(CubeType)]
pub struct SmemCyclicAccumulation<A: Numeric, T: Numeric> {
values: Shared<[A]>,
like: Tile<T>,
pub source: Tile<T>,
#[cube(comptime)]
writers: usize,
}
#[cube]
impl<T: Numeric> Tile<T> {
pub fn smem_cyclic_accumulation<A: Numeric>(
&self,
#[comptime] writers: usize,
) -> SmemCyclicAccumulation<A, T> {
let space = comptime!(self.place.space.clone());
let width = self.vector_size();
let size!(W) = width;
let form = comptime!(StageForm::dense(
&space,
width,
StageStorage::Strided,
LineBytes(A::size().comptime() * width)
));
let values = Shared::<[A]>::new_slice(comptime!(form.cells() * width));
sync_cube();
let units = self.units();
let source = Memory::<T>::smem_backed(
space,
width,
comptime!(FillUnits::cube(units)),
Backing::<T>::new_ReadCall(ErasedTensor::<T, ReadOnly>::of_smem_plain_load::<A, W>(
&values,
)),
comptime!(Packing::Plain),
comptime!(form.clone()),
RuntimeMap::integral(comptime!(form.physical_rank())),
ComptimeOption::new_None(),
comptime!(Write::Replace),
);
SmemCyclicAccumulation::<A, T> {
values,
like: self.clone(),
source: source.nested_like::<T>(self),
writers,
}
}
}
#[cube]
impl<A: Numeric, T: Numeric> SmemCyclicAccumulation<A, T> {
pub fn sink(&self, round: usize) -> Tile<A> {
let space = comptime!(self.like.place.space.clone());
let width = self.like.vector_size();
let size!(W) = width;
let form = comptime!(StageForm::dense(
&space,
width,
StageStorage::Strided,
LineBytes(A::size().comptime() * width)
));
let units = self.like.units();
let sink = Memory::<A>::smem_backed(
space,
width,
comptime!(FillUnits::cube(units)),
Backing::<A>::new_WriteCall(ErasedTensor::<A, WriteOnly>::of_smem_cyclic::<W>(
&self.values,
round == 0,
)),
comptime!(Packing::Plain),
comptime!(form.clone()),
RuntimeMap::integral(comptime!(form.physical_rank())),
ComptimeOption::new_None(),
comptime!(Write::Exclusive(Schedule::Cyclic)),
);
sink.nested_like::<T>(&self.like)
}
pub fn drain<P: Numeric>(&self, partial: &Tile<P>, writer: usize) {
#[unroll]
for round in 0..self.writers {
let turn = (writer + round) % comptime!(self.writers);
partial.drained_chunk_into(&self.sink(round), turn, comptime!(self.writers));
sync_cube();
}
}
pub fn drain_at<P: Numeric>(&self, partial: &Tile<P>, window: &Region, writer: usize) {
#[unroll]
for round in 0..self.writers {
let turn = (writer + round) % comptime!(self.writers);
partial.drained_chunk_into(&self.sink(round).at(window), turn, comptime!(self.writers));
sync_cube();
}
}
}
pub(crate) trait SmemCyclicSink<E: Numeric> {
fn of_smem_cyclic<W: Size>(_values: &Shared<[E]>, _first: bool) -> ErasedTensor<E, WriteOnly> {
unexpanded!()
}
fn __expand_of_smem_cyclic<W: Size>(
_scope: &Scope,
values: &<Shared<[E]> as CubeType>::ExpandType,
first: NativeExpand<bool>,
) -> ErasedTensorExpand<E, WriteOnly> {
ErasedTensorExpand::new(SmemCyclic::<E, W> {
values: ExpandTypeClone::clone_unchecked(values),
first,
_e: PhantomData,
})
}
}
impl<E: Numeric> SmemCyclicSink<E> for ErasedTensor<E, WriteOnly> {}
pub(crate) trait SmemPlainLoadSource<T: Numeric> {
fn of_smem_plain_load<A: Numeric, W: Size>(_values: &Shared<[A]>) -> ErasedTensor<T, ReadOnly> {
unexpanded!()
}
fn __expand_of_smem_plain_load<A: Numeric, W: Size>(
_scope: &Scope,
values: &<Shared<[A]> as CubeType>::ExpandType,
) -> ErasedTensorExpand<T, ReadOnly> {
ErasedTensorExpand::new(SmemPlainLoad::<A, T, W> {
values: ExpandTypeClone::clone_unchecked(values),
_t: PhantomData,
})
}
}
impl<T: Numeric> SmemPlainLoadSource<T> for ErasedTensor<T, ReadOnly> {}
struct SmemCyclic<E: Numeric, W: Size> {
values: <Shared<[E]> as CubeType>::ExpandType,
first: NativeExpand<bool>,
_e: PhantomData<(E, W)>,
}
impl<E: Numeric, W: Size> ErasedTensorOperationsExpand<E> for SmemCyclic<E, W> {
fn __expand_vector_size_method(&self, scope: &Scope) -> VectorSize {
W::__expand_value(scope)
}
fn __expand_lines_method(&self, scope: &Scope) -> NativeExpand<usize> {
lines_of::expand::<E, W>(scope, &self.values)
}
fn __expand_write_line_method(
&mut self,
scope: &Scope,
index: NativeExpand<usize>,
value: ExpandValue,
) {
cyclic_line::expand::<E, W>(scope, &mut self.values, index, value.into(), self.first);
}
}
impl<E: Numeric, W: Size> WritesLines<E> for SmemCyclic<E, W> {}
struct SmemPlainLoad<A: Numeric, T: Numeric, W: Size> {
values: <Shared<[A]> as CubeType>::ExpandType,
_t: PhantomData<(T, W)>,
}
impl<A: Numeric, T: Numeric, W: Size> ErasedTensorOperationsExpand<T> for SmemPlainLoad<A, T, W> {
fn __expand_vector_size_method(&self, scope: &Scope) -> VectorSize {
W::__expand_value(scope)
}
fn __expand_lines_method(&self, scope: &Scope) -> NativeExpand<usize> {
lines_of::expand::<A, W>(scope, &self.values)
}
fn __expand_read_line_method(&self, scope: &Scope, index: NativeExpand<usize>) -> ExpandValue {
load_plain_line::expand::<A, T, W>(scope, &self.values, index).expand
}
}
impl<A: Numeric, T: Numeric, W: Size> ReadsLines<T> for SmemPlainLoad<A, T, W> {}
#[cube]
fn lines_of<E: Numeric, W: Size>(values: &Shared<[E]>) -> usize {
values.len() / W::value()
}
#[cube]
fn cyclic_line<E: Numeric, W: Size>(
values: &mut Shared<[E]>,
index: usize,
value: Vector<E, W>,
first: bool,
) {
#[unroll]
for j in 0..W::value() {
let cell = index * W::value() + j;
if first {
values[cell] = value.extract(j);
} else {
values[cell] += value.extract(j);
}
}
}
#[cube]
fn load_plain_line<A: Numeric, T: Numeric, W: Size>(
values: &Shared<[A]>,
index: usize,
) -> Vector<T, W> {
let mut line = Vector::<T, W>::empty();
#[unroll]
for j in 0..W::value() {
line.insert(j, T::cast_from(values[index * W::value() + j]));
}
line
}