use core::marker::PhantomData;
use cubecl::ir::{ExpandValue, VectorSize};
use cubecl::prelude::*;
use cubecl::std::tensor::{
ErasedTensor, ErasedTensorExpand, ErasedTensorOperationsExpand, WriteOnly, WritesLines,
};
use cubecl::unexpanded;
use crate::kind::Write;
use crate::launch::arg::tensor::TileArgExpand;
use crate::space::partition::GridCount;
use crate::*;
#[derive(CubeType)]
pub struct Relay<'a, E: Numeric> {
carry: Tile<E>,
sink: Tile<E>,
turns: &'a [Atomic<u32>],
walk: Walk,
counter: usize,
turn: usize,
holders: usize,
}
#[cube]
impl<'a, E: Numeric, V: Size> TileArg<'a, E, V> {
pub fn relay<'t>(&self, partitioning: &Partitioning, turns: &'t [Atomic<u32>]) -> Relay<'t, E> {
let space = comptime!(partitioning.clone());
let split = comptime!(Relay::<'t, E>::split(&space, &self.spec));
let walk = partitioning.walk();
let counter = Relay::<'t, E>::counter(partitioning, &walk, split);
let turn = walk.position_at(split);
let holders = Relay::<'t, E>::holders(partitioning, split);
let carry = self.tile(comptime!(space.clone()));
let first = turn == 0usize;
let geometry = RuntimeGeometry::of_tensor::<Vector<E, V>>(
self.tensor,
comptime!(self.spec.projection.physical_rank()),
);
let sink = GlobalOperand::<E>::sink(
relay_sink::<E, V>(self.tensor, first),
geometry,
V::value(),
comptime!(space.space().clone()),
comptime!(self.spec.clone()),
comptime!(Write::Exclusive(Schedule::Sequential)),
)
.tile(comptime!(space.levels().to_vec()));
Relay::<'t, E> {
carry,
sink,
turns,
walk,
counter,
turn,
holders,
}
}
}
#[cube]
impl<'a, E: Numeric> Relay<'a, E> {
pub fn has_turn(&self) -> bool {
self.turn < self.holders
}
pub fn tile(&self) -> Tile<E> {
self.sink.at(&self.walk.region(0usize))
}
pub fn carried(&self) -> Tile<E> {
self.carry.at(&self.walk.region(0usize))
}
pub fn is_first(&self) -> bool {
self.turn == 0
}
pub fn is_last(&self) -> bool {
self.turn + 1 == self.holders
}
pub fn take(&self) {
if UNIT_POS == 0 {
loop {
if self.turns[self.counter].load() == self.turn as u32 {
break;
}
}
}
sync_storage();
}
pub fn pass<O: Numeric>(&self, out: &mut Tile<O>) {
let last = self.is_last();
sync_storage();
if last {
let region = self.walk.region(0usize);
let mut out_box = out.at(®ion);
out_box.copy_cast_from(&self.carry.at(®ion));
}
if UNIT_POS == 0 {
if last {
self.turns[self.counter].store(0u32);
} else {
self.turns[self.counter].store(self.turn as u32 + 1);
}
}
}
fn counter(partitioning: &Partitioning, walk: &Walk, #[comptime] split: usize) -> usize {
let space = comptime!(partitioning.clone());
let level = comptime!(space.levels()[0].clone());
let mut counter = 0usize;
#[unroll]
for p in 0..comptime!(space.space().rank()) {
let axis = comptime!(space.space().axis_at(p));
if comptime!(level.distributes(axis)) {
let radix = Self::radix(partitioning, comptime!(level.clone()), p);
let digit = match comptime!(p == split) {
true => 0usize.runtime(),
false => walk.position_at(p),
};
counter = counter.times(radix).plus(digit);
}
}
counter
}
fn holders(partitioning: &Partitioning, #[comptime] split: usize) -> usize {
let space = comptime!(partitioning.clone());
let level = comptime!(space.levels()[0].clone());
let axis = comptime!(space.space().axis_at(split));
let workers = comptime!(match level.count(axis) {
Some(Count::AllAcross(workers)) => workers,
_ => unreachable!("Relay::split names an axis spread across cubes"),
});
let grid = Self::grid(partitioning, comptime!(level.clone()), split);
match comptime!(level.cut(axis).spread) {
Spread::Contiguous => {
let run = grid
.plus(comptime!(workers - 1).runtime())
.divided_by(workers.runtime())
.max_with(1usize.runtime());
grid.plus(run.minus(1usize.runtime())).divided_by(run)
}
Spread::Interleaved => grid.min_with(workers.runtime()),
}
}
fn radix(partitioning: &Partitioning, #[comptime] level: Level, #[comptime] p: usize) -> usize {
let axis = comptime!(partitioning.space().axis_at(p));
match comptime!(level.count(axis)) {
Some(Count::AllAcross(workers)) => workers.runtime(),
_ => Self::grid(partitioning, level, p),
}
}
fn grid(partitioning: &Partitioning, #[comptime] level: Level, #[comptime] p: usize) -> usize {
let axis = comptime!(partitioning.space().axis_at(p));
match comptime!(level.grid(axis)) {
GridCount::Const(n) => n.runtime(),
GridCount::Extent(tile) => partitioning.space.count(p, tile),
}
}
}
impl<E: Numeric> Relay<'_, E> {
fn split(space: &Partitioning, spec: &TileSpec) -> usize {
let level = &space.levels()[0];
assert!(
level.coverage() == Coverage::Distribute(ComputeScope::Cube)
&& level.shared_by().is_none(),
"TileArg::relay: the first level must hand each cube its own boxes; a relay's turns are \
taken by the cubes of one box"
);
let split: Vec<usize> = (0..space.space().rank())
.filter(|&p| {
let axis = space.space().axis_at(p);
matches!(level.count(axis), Some(Count::AllAcross(_)))
&& !spec.axes().contains(&axis)
})
.collect();
assert!(
split.len() == 1,
"TileArg::relay: the carry must leave out exactly one axis the cube level spreads across \
cubes (`Levels::across`), the one whose runs take the turns; it leaves out {}",
split.len()
);
split[0]
}
}
impl Partitioning {
pub fn relay_counters(&self) -> usize {
self.instances(Coverage::Distribute(ComputeScope::Cube)) as usize
}
}
#[allow(clippy::extra_unused_type_parameters)]
fn relay_sink<E: Numeric, N: Size>(
_values: &Tensor<Vector<E, N>>,
_first: bool,
) -> ErasedTensor<E, WriteOnly> {
unexpanded!()
}
mod relay_sink {
use super::*;
pub fn expand<E: Numeric, N: Size>(
_scope: &Scope,
values: &<Tensor<Vector<E, N>> as CubeType>::ExpandType,
first: NativeExpand<bool>,
) -> ErasedTensorExpand<E, WriteOnly> {
ErasedTensorExpand::new(RelaySink::<E, N> {
values: ExpandTypeClone::clone_unchecked(values),
first,
_n: PhantomData,
})
}
}
struct RelaySink<E: Numeric, N: Size> {
values: <Tensor<Vector<E, N>> as CubeType>::ExpandType,
first: NativeExpand<bool>,
_n: PhantomData<N>,
}
impl<E: Numeric, N: Size> ErasedTensorOperationsExpand<E> for RelaySink<E, N> {
fn __expand_vector_size_method(&self, scope: &Scope) -> VectorSize {
<N as Size>::__expand_value(scope)
}
fn __expand_lines_method(&self, scope: &Scope) -> NativeExpand<usize> {
self.values.__expand_buffer_len_method(scope)
}
fn __expand_write_line_method(
&mut self,
scope: &Scope,
index: NativeExpand<usize>,
value: ExpandValue,
) {
relay_line::expand::<E, N>(scope, &mut self.values, index, value.into(), self.first);
}
}
impl<E: Numeric, N: Size> WritesLines<E> for RelaySink<E, N> {}
#[cube]
fn relay_line<E: Numeric, N: Size>(
values: &mut Tensor<Vector<E, N>>,
index: usize,
value: Vector<E, N>,
first: bool,
) {
if first {
values[index] = value;
} else {
values[index] += value;
}
}