use cubecl::prelude::*;
use cubecl::std::tensor::layout::CoordsDyn;
use crate::*;
use super::base::{GatherProblem, LhsRole};
use crate::ops::matmul::leaf::memory::resolve_nd_coords;
#[cube]
pub(super) fn cell_read<T: Numeric, W: Size>(
view: &Masked<'_, Vector<T, W>, CoordsDyn>,
batch: &Coords<u32>,
row: u32,
col: u32,
reduce_coords: &Coords<u32>,
#[comptime] operand: Space,
#[comptime] problem: GatherProblem,
#[comptime] width: usize,
) -> Vector<T, W> {
view.read(cell_position(
batch,
row,
col,
reduce_coords,
operand,
problem,
width,
))
}
#[cube]
pub(super) fn cell_position(
batch: &Coords<u32>,
row: u32,
col: u32,
reduce_coords: &Coords<u32>,
#[comptime] operand: Space,
#[comptime] problem: GatherProblem,
#[comptime] width: usize,
) -> CoordsDyn {
let acc_coords = acc_cell_coords(
batch,
row,
col,
comptime!(problem.block.row_extents()),
comptime!(problem.block.column_line_extents()),
);
resolve_nd_coords(
operand,
comptime!(problem.block.space.clone()),
comptime!(problem.block.reduce.clone()),
&acc_coords,
reduce_coords,
width,
false,
)
}
#[cube]
pub(super) fn offset_last(coords: &CoordsDyn, #[comptime] rank: usize, delta: u32) -> CoordsDyn {
let mut out = CoordsDyn::new();
#[unroll]
for p in 0..rank {
out.push(if comptime!(p == rank - 1) {
coords[p].plus(delta)
} else {
coords[p]
});
}
out
}
#[cube]
fn acc_cell_coords(
batch: &Coords<u32>,
row: u32,
col: u32,
#[comptime] row_extents: Vec<usize>,
#[comptime] col_extents: Vec<usize>,
) -> Coords<u32> {
let mut out = Coords::<u32>::new();
#[unroll]
for p in 0..batch.len() {
out.push(batch.at(p));
}
let rows = Coords::constant(comptime!(row_extents.clone())).unravel(row);
#[unroll]
for p in 0..rows.len() {
out.push(rows.at(p));
}
let cols = Coords::constant(comptime!(col_extents.clone())).unravel(col);
#[unroll]
for p in 0..cols.len() {
out.push(cols.at(p));
}
out
}
pub(super) fn assert_separable_shapes(rhs: &Projection, acc: &Space, rhs_spans_col: bool) {
let col = acc.axis_at(acc.rank() - 1);
assert!(
rhs_spans_col,
"contract gather: the separable schedule walks the accumulator's columns by stepping the \
rhs's innermost physical axis, so the rhs must span {col:?}"
);
let innermost = rhs.physical_axis(rhs.physical_rank() - 1);
assert!(
innermost.is_identity(col),
"contract gather: the separable schedule steps the rhs's innermost physical axis once per \
accumulator column, so that axis must be {col:?} at coefficient 1"
);
}
#[allow(clippy::too_many_arguments)]
pub(super) fn assert_operand_shapes(
lhs: &Space,
rhs: &Space,
acc: &Space,
reduce: &[Axis],
lhs_vec_len: usize,
rhs_vec_len: usize,
lhs_role: LhsRole,
) {
assert!(
!reduce.is_empty(),
"contract gather: the operands contract no axis against the accumulator"
);
for &axis in reduce {
assert!(
lhs.contains(axis),
"contract gather: the lhs must span every contracted axis, but {axis:?} is contracted by \
the rhs alone"
);
}
let fastest = reduce[reduce.len() - 1];
assert!(
lhs_role == LhsRole::LinedAlongColumn || lhs.axis_at(lhs.rank() - 1) == fastest,
"contract gather: the lhs must line along the fastest contracted axis {fastest:?}"
);
let rhs_lined = rhs.axis_at(rhs.rank() - 1);
assert!(
rhs_vec_len == 1 || rhs_lined == acc.axis_at(acc.rank() - 1) || rhs_lined == fastest,
"contract gather: a vectorized rhs must line along the accumulator's innermost axis or \
the fastest contracted axis {fastest:?}"
);
assert!(
lhs_role != LhsRole::PerCell || rhs_vec_len == 1,
"contract gather: an lhs spanning the accumulator's innermost axis needs a value per \
column, so that axis must be the one it lines along (the accumulator is {rhs_vec_len} \
wide, the lhs lines {lhs_vec_len})"
);
assert!(
lhs_role != LhsRole::LinedAlongColumn || lhs_vec_len == rhs_vec_len,
"contract gather: a col-lined lhs is read as the cell itself, so its line width \
({lhs_vec_len}) must be the accumulator's ({rhs_vec_len})"
);
}