use core::marker::PhantomData;
use cubecl::prelude::*;
use super::analysis::{Boundaries, Labels, Refusal};
use crate::{
Axis, Boundary, Delivery, Field, Geometry, Launcher, LineMisfit, Packing, Projection, Storage,
StoragePartitioning, TileArgLaunch, TileSpec, VectorTile,
};
pub struct Unlabelled;
pub struct Labelled;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BoundaryPolicy {
Unchecked,
Every(Boundary),
}
struct ArgData<'a> {
launch: &'a Launcher,
binding: Option<TensorBinding>,
geometry: Geometry,
axes: &'a [Axis],
batches: &'a [Axis],
projection: Option<Projection>,
width: usize,
boundary: Option<BoundaryPolicy>,
packing: Packing,
delivery: Delivery,
in_stride_order: bool,
}
pub struct Arg<'a, S> {
data: ArgData<'a>,
_state: PhantomData<S>,
}
impl<'a> Arg<'a, Unlabelled> {
pub(crate) fn bound(launch: &'a Launcher, binding: TensorBinding) -> Self {
let geometry = Geometry::from(&binding);
Self::over(launch, Some(binding), geometry)
}
pub(crate) fn unbound(launch: &'a Launcher, geometry: &Geometry) -> Self {
Self::over(launch, None, geometry.clone())
}
fn over(launch: &'a Launcher, binding: Option<TensorBinding>, geometry: Geometry) -> Self {
Arg {
data: ArgData {
launch,
binding,
geometry,
axes: &[],
batches: &[],
projection: None,
width: 1,
boundary: None,
packing: Packing::Plain,
delivery: Delivery::SyncPerUnit,
in_stride_order: false,
},
_state: PhantomData,
}
}
pub fn axes(mut self, axes: &'a [Axis]) -> Arg<'a, Labelled> {
self.data.axes = axes;
self.labelled()
}
pub fn gathered(mut self, projection: Projection) -> Arg<'a, Labelled> {
self.data.projection = Some(projection);
self.labelled()
}
fn labelled(self) -> Arg<'a, Labelled> {
Arg {
data: self.data,
_state: PhantomData,
}
}
}
impl<'a> Arg<'a, Labelled> {
pub fn batches(mut self, axes: &'a [Axis]) -> Self {
self.data.batches = axes;
self
}
pub fn in_stride_order(mut self) -> Self {
self.data.in_stride_order = true;
self
}
pub fn vectorize(mut self, width: usize) -> Self {
self.data.width = width;
self
}
pub fn boundary(mut self, policy: BoundaryPolicy) -> Self {
self.data.boundary = Some(policy);
self
}
pub fn packed(mut self, field: impl Into<Field>) -> Self {
self.data.packing = Packing::Packed {
field: field.into(),
};
self
}
pub fn delivery(mut self, delivery: Delivery) -> Self {
self.data.delivery = delivery;
self
}
pub fn build(self) -> Result<Bound, Refusal> {
self.realize().map(|(bound, _)| bound)
}
pub fn build_spec(self) -> Result<Unbound, Refusal> {
let (bound, geometry) = self.realize()?;
Ok(Unbound {
spec: bound.spec,
vector_size: bound.vector_size,
geometry,
})
}
fn realize(self) -> Result<(Bound, Geometry), Refusal> {
let data = self.data;
let launch = data.launch;
let width = data.width;
let stored = data.binding.as_ref().map(|b| b.tiling).unwrap_or_default();
let stated = data.projection.is_some() || stored.is_tiled();
let (geometry, axes) =
stride_ordered(data.geometry, data.axes, data.in_stride_order, stated)?;
let (storage, stored_tiles) =
storage_of(&geometry, &axes, data.projection.is_none(), launch);
let labels = match data.projection {
Some(projection) => {
Labels::stated(&geometry, launch, projection, &axes, data.batches, stored)?
}
None => Labels::new(&geometry, &axes, data.batches)?,
};
let Labels {
geometry,
projection,
addressed,
} = labels;
projection.validate(width)?;
let labels = projection.dense_labels();
let served = match labels.last() {
Some(&axis) => VectorTile::new(&stored_tiles, axis, width)
.map_err(LineMisfit::Tile)
.and_then(|tile| geometry.serves(tile.extents(), &labels)),
None if width == 1 => Ok(()),
None => Err(LineMisfit::NoDims),
};
served.map_err(|why| Refusal::WidthNotServed { width, why })?;
let overhangs = launch.overhangs();
let boundaries = Boundaries::new(
data.boundary,
&projection,
&addressed,
launch.space(),
&overhangs,
width,
)?;
let spec = TileSpec {
projection,
boundaries: boundaries.modes,
units: launch.cube_dim().num_elems() as usize,
packing: data.packing.read_on(launch.client()),
storage,
stored_tiles,
delivery: Delivery::SyncPerUnit,
}
.delivery(data.delivery);
let tensor = data
.binding
.map(|binding| settled_tensor(binding, &geometry));
let bound = Bound {
tensor,
vector_size: width,
spec,
};
Ok((bound, geometry))
}
}
fn stride_ordered(
geometry: Geometry,
axes: &[Axis],
in_stride_order: bool,
stated: bool,
) -> Result<(Geometry, Vec<Axis>), Refusal> {
match in_stride_order {
true if stated => Err(Refusal::StrideOrderOfAStatedLayout),
true => Ok(geometry.in_stride_order(axes)),
false => Ok((geometry, axes.to_vec())),
}
}
fn storage_of(
geometry: &Geometry,
axes: &[Axis],
labelled: bool,
launch: &Launcher,
) -> (Storage, Vec<(Axis, usize)>) {
if !(labelled && geometry.tiling().is_tiled()) {
return (Storage::Strided, Vec::new());
}
let labels = geometry.labels(axes);
let partitioning = StoragePartitioning::new(geometry, &labels);
let stored_tiles = partitioning
.as_ref()
.map_or_else(Vec::new, |storage| storage.tiles().to_vec());
let tiles = partitioning
.map(|storage| storage.contiguous_tiles(&geometry.extents(&labels)))
.unwrap_or_default();
let (space, levels) = (launch.space(), launch.partitioning().levels());
let kernel = launch.partitioning().space();
let cuts_to = |level: usize, tile: &[(Axis, usize)]| {
let leaf = space.leaf(&levels[..=level]);
let fixed = kernel.leaf(&levels[..=level]);
axes.iter().all(|&axis| !fixed.is_dynamic(axis))
&& axes.iter().all(|&axis| {
let stored = tile
.iter()
.find(|&&(a, _)| a == axis)
.map_or(1, |&(_, e)| e);
leaf.extent(axis) == stored
})
};
let level = tiles
.iter()
.rev()
.find_map(|tile| (0..levels.len()).find(|&level| cuts_to(level, tile)));
(Storage::Tiled(level), stored_tiles)
}
fn settled_tensor(mut binding: TensorBinding, geometry: &Geometry) -> TensorArg {
binding.shape = geometry.shape().into();
binding.strides = geometry.strides().into();
binding.tiling = geometry.tiling();
binding.into_tensor_arg()
}
pub struct Bound {
tensor: Option<TensorArg>,
pub vector_size: usize,
pub spec: TileSpec,
}
impl Bound {
pub fn arg<E: Numeric, V: Size>(self) -> TileArgLaunch<'static, E, V> {
let Bound { spec, .. } = &self;
let spec = spec.clone();
TileArgLaunch::new(self.tensor(), spec)
}
pub fn tensor(self) -> TensorArg {
self.tensor
.expect("Bound: this operand was built over geometry alone and has no tensor to bind")
}
pub fn bound_width(&self) -> usize {
self.spec.packing.physical(self.vector_size)
}
}
pub struct Unbound {
pub spec: TileSpec,
pub vector_size: usize,
pub geometry: Geometry,
}