use cubecl::{
prelude::*,
std::tensor::{ErasedTensor, WriteOnly},
};
use crate::*;
#[derive(CubeType)]
pub struct GlobalOperand<T: Numeric> {
pub(crate) backing: Backing<T>,
pub geometry: RuntimeGeometry,
#[cube(comptime)]
pub bound_width: usize,
#[cube(comptime)]
pub space: Space,
#[cube(comptime)]
pub spec: TileSpec,
#[cube(comptime)]
pub write: Write,
pub coefficients: Coords<u32>,
pub offsets: Coords<i32>,
}
#[cube]
impl<T: Numeric> GlobalOperand<T> {
pub fn tensor<E: CubePrimitive<Scalar = T>>(
tensor: &Tensor<E>,
#[comptime] space: Space,
#[comptime] spec: TileSpec,
) -> GlobalOperand<T> {
GlobalOperand::<T>::of_tensor::<E>(
tensor,
space,
spec,
Coords::<u32>::new(),
Coords::<i32>::new(),
)
}
pub fn gathered<E: CubePrimitive<Scalar = T>>(
tensor: &Tensor<E>,
#[comptime] space: Space,
#[comptime] spec: TileSpec,
coefficients: Coords<u32>,
offsets: Coords<i32>,
) -> GlobalOperand<T> {
GlobalOperand::<T>::of_tensor::<E>(tensor, space, spec, coefficients, offsets)
}
pub fn stored<E: CubePrimitive>(
values: &Tensor<E>,
#[comptime] space: Space,
#[comptime] spec: TileSpec,
) -> GlobalOperand<T> {
let stored = elem_type_of::<E>();
let read = elem_type_of::<T>();
comptime!(assert!(
spec.packing != Packing::Plain || stored == read,
"GlobalOperand::stored: a binding that states no packing is read at the element it \
is bound at, {stored:?}, not {read:?}"
));
GlobalOperand::<T>::of_tensor::<E>(
values,
space,
spec,
Coords::<u32>::new(),
Coords::<i32>::new(),
)
}
pub(crate) fn of_tensor<E: CubePrimitive>(
tensor: &Tensor<E>,
#[comptime] space: Space,
#[comptime] spec: TileSpec,
coefficients: Coords<u32>,
offsets: Coords<i32>,
) -> GlobalOperand<T> {
let rank = comptime!(spec.projection.physical_rank());
let backing = Backing::<T>::new_Buffer(unsafe {
tensor
.as_slice()
.downcast_unchecked::<T>()
.as_boxed_unchecked()
});
GlobalOperand::<T> {
backing,
geometry: RuntimeGeometry::of_tensor::<E>(tensor, rank),
bound_width: tensor.vector_size(),
space,
spec,
write: comptime!(Write::Replace),
coefficients,
offsets,
}
}
pub fn sink(
sink: ErasedTensor<T, WriteOnly>,
geometry: RuntimeGeometry,
#[comptime] vector_size: usize,
#[comptime] space: Space,
#[comptime] spec: TileSpec,
#[comptime] write: Write,
) -> GlobalOperand<T> {
comptime!(assert!(
spec.packing == Packing::Plain,
"GlobalOperand::sink: a sink is written at the width it states, so its spec may not \
state a packing ({:?}) on top of it",
spec.packing
));
GlobalOperand::<T> {
backing: Backing::<T>::new_WriteCall(sink),
geometry,
bound_width: vector_size,
space,
spec,
write,
coefficients: Coords::<u32>::new(),
offsets: Coords::<i32>::new(),
}
}
pub fn source(
source: ErasedTensor<T, ReadOnly>,
geometry: RuntimeGeometry,
#[comptime] vector_size: usize,
#[comptime] space: Space,
#[comptime] spec: TileSpec,
) -> GlobalOperand<T> {
comptime!(assert!(
spec.packing == Packing::Plain,
"GlobalOperand::source: a source is read at the width it states, so its spec may not \
state a packing ({:?}) on top of it",
spec.packing
));
GlobalOperand::<T> {
backing: Backing::<T>::new_ReadCall(source),
geometry,
bound_width: vector_size,
space,
spec,
write: comptime!(Write::Replace),
coefficients: Coords::<u32>::new(),
offsets: Coords::<i32>::new(),
}
}
pub fn tile(self, #[comptime] levels: Vec<Level>) -> Tile<T> {
let place = comptime!(Placement::root(
self.space.subspace(self.spec.axes()),
levels
));
Tile::new(TileKind::new_Memory(Memory::<T>::global(self)), place)
}
}
#[cube]
impl<T: Numeric> Memory<T> {
#[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
pub(crate) fn global(operand: GlobalOperand<T>) -> Memory<T> {
let backing = operand.backing;
let geometry = operand.geometry;
let coefficients = operand.coefficients;
let offsets = operand.offsets;
let bound_width = comptime!(operand.bound_width);
let space = comptime!(operand.space.clone());
let spec = comptime!(operand.spec.clone());
let write = comptime!(operand.write);
let split_share = comptime!(SplitShare::Whole);
let space = comptime!(space.subspace(spec.axes()));
let projection = comptime!(spec.projection.clone());
let coords = comptime!(projection.untiled());
let packing = comptime!(spec.packing);
let vector_size = comptime!(packing.served(bound_width));
let dims_given = geometry.shape.len();
let coefficients_given = coefficients.len();
let offsets_given = offsets.len();
comptime!(assert!(
dims_given == projection.physical_rank(),
"GlobalOperand: the projection addresses {} physical dims but {dims_given} were given",
projection.physical_rank()
));
comptime!(assert!(
coefficients_given == coords.dynamic_coefficient_count(),
"GlobalOperand: the projection has {} Dynamic coefficients and divisors but \
{coefficients_given} were given",
coords.dynamic_coefficient_count()
));
comptime!(assert!(
offsets_given == coords.dynamic_offset_count(),
"GlobalOperand: the projection has {} Dynamic offsets but {offsets_given} were given",
coords.dynamic_offset_count()
));
comptime!(check_operand(&space, &spec, vector_size));
let rank = comptime!(projection.physical_rank());
let load = comptime!(
VectorTile::new(
&spec.stored_tiles,
space.axis_at(space.rank() - 1),
vector_size
)
.unwrap_or_else(|why| panic!("GlobalOperand: {vector_size} values a load: {why}"))
);
let parts = comptime!(load.parts(&spec.stored_tiles, &projection.dense_labels(), rank));
let mut physical_shape = Coords::<u32>::new();
let mut physical_strides = Coords::<u32>::new();
#[unroll]
for i in 0..rank {
let part = comptime!(parts[i] as u32);
physical_shape.push(geometry.shape.at(i) / part);
physical_strides.push(geometry.strides.at(i) * part / comptime!(vector_size as u32));
}
let gmem_projection = comptime!(projection.positional());
let bound = logical_extent(comptime!(gmem_projection.clone()), &physical_shape);
let (origin, extent, map) = top_window(
comptime!(space.clone()),
&bound,
&offsets,
coefficients,
comptime!(load.clone()),
comptime!(coords.clone()),
);
Memory::<T> {
address: comptime!(AddressSpace::Global),
store: Store::<T> {
backing,
vector_size: comptime!(vector_size),
packing: comptime!(packing),
stored_tiles: comptime!(spec.stored_tiles.clone()),
},
layout: BufferLayout {
physical_shape,
physical_strides,
projection: gmem_projection,
rows: RowArrangement::InOrder,
},
window: Window::new(
origin,
extent,
bound,
comptime!(coords.may_underflow()),
comptime!(spec.boundaries.clone()),
),
source_window: ComptimeOption::new_None(),
projection: comptime!(coords),
map,
offsets,
window_start: 0u32,
access: comptime!(Access {
whole: true,
overhang: if spec.is_checked() {
Overhang::Masked
} else {
Overhang::Fits
},
write,
fill: FillUnits::cube(spec.units),
storage: WindowStorage::Stored(spec.storage),
delivery: spec.delivery,
}),
unit_share: comptime!(UnitShare::Repeated),
split_share,
init_from: comptime!(InitFrom::Cell),
contraction: comptime!(None),
factor: Factor::none(),
codebook: Codebook::none(),
}
}
}
fn check_operand(space: &Space, spec: &TileSpec, vector_size: usize) {
let projection = &spec.projection;
if let Err(refusal) = projection.validate(vector_size) {
panic!("{refusal}");
}
projection.validate_composition(|axis| space.extent(axis));
let coord_rank = projection.coordinate_rank();
assert!(
spec.boundaries.is_empty() || spec.boundaries.len() == coord_rank,
"GlobalOperand: boundaries rank ({}) does not match coordinate rank ({coord_rank})",
spec.boundaries.len()
);
assert!(
vector_size == 1 || spec.boundaries.last().copied().flatten() != Some(Boundary::Clamp),
"GlobalOperand: Boundary::Clamp cannot clamp the vectorized innermost axis (served at \
{vector_size})"
);
}
#[cube]
fn top_window(
#[comptime] space: Space,
bound: &Coords<u32>,
offsets: &Coords<i32>,
coefficients: Coords<u32>,
#[comptime] load: VectorTile,
#[comptime] projection: Projection,
) -> (Coords<i32>, Coords<u32>, RuntimeMap) {
let mut origin = Coords::<i32>::new();
let mut extent = Coords::<u32>::new();
let mut residues = Coords::<u32>::new();
let rank = comptime!(projection.physical_rank());
#[unroll]
for pa in 0..rank {
let size = if comptime!(projection.is_direct()) {
origin.push(0);
residues.push(0u32);
let axis = comptime!(space.axis_at(pa));
match comptime!(space.extent_raw(axis)) {
Extent::Static(e) => (comptime!(e / load.extent_along(axis)) as u32).runtime(),
Extent::Dynamic => bound.at(pa),
}
} else {
let (start, phase) =
gathered_origin(comptime!(projection.clone()), offsets, &coefficients, pa);
origin.push(start);
residues.push(phase);
bound.at(pa)
};
extent.push(size);
}
(
origin,
extent,
RuntimeMap {
coefficients,
residues,
},
)
}
#[cube]
fn gathered_origin(
#[comptime] projection: Projection,
offsets: &Coords<i32>,
coefficients: &Coords<u32>,
#[comptime] pa: usize,
) -> (i32, u32) {
let axis_map = comptime!(projection.physical_axis(pa));
if comptime!(axis_map.origin().is_some()) {
(
comptime!(axis_map.origin().unwrap() as i32).runtime(),
comptime!(axis_map.residue().unwrap() as u32).runtime(),
)
} else {
let offset = match comptime!(axis_map.offset()) {
Offset::Static(o) => comptime!(o as i32).runtime(),
Offset::Dynamic => offsets.at(comptime!(projection.dynamic_offset_index(pa).unwrap())),
};
if comptime!(!axis_map.is_rational()) {
(offset, 0u32)
} else {
let divisor = match comptime!(axis_map.divisor()) {
Divisor::Static(d) => comptime!(d as i32).runtime(),
Divisor::Dynamic { .. } => coefficients
.at(comptime!(projection.dynamic_divisor_index(pa).unwrap()))
.cast::<i32>(),
};
let (start, residue) = floor_div_rem(offset, divisor);
(start, residue.cast::<u32>())
}
}
}