use cubecl::zspace::SmallVec;
#[cfg(test)]
use crate::Composition;
use crate::{Axis, PhysicalAxisMap, Projection, Space};
#[derive(Clone, Debug)]
pub struct DimsBuilder {
physical: SmallVec<[PhysicalAxisMap; Space::MAX_RANK]>,
spanning: SmallVec<[Axis; Space::MAX_RANK]>,
}
impl DimsBuilder {
pub(crate) fn new() -> Self {
DimsBuilder {
physical: SmallVec::new(),
spanning: SmallVec::new(),
}
}
pub fn dim(mut self, dim: impl Into<PhysicalAxisMap>) -> Self {
self.physical.push(dim.into());
self
}
pub fn spanning(mut self, axis: Axis) -> Self {
self.spanning.push(axis);
self
}
pub fn build(self) -> Projection {
assert!(
!self.physical.is_empty(),
"Projection::dims: an operand has at least one dim"
);
let (innermost, outer) = self.physical.split_last().expect("checked non-empty");
let mut axes: SmallVec<[Axis; Space::MAX_RANK]> = SmallVec::new();
let mention = |axis: Axis, axes: &mut SmallVec<[Axis; Space::MAX_RANK]>| {
if !axes.contains(&axis) {
axes.push(axis);
}
};
for map in outer {
for term in map.terms() {
mention(term.axis, &mut axes);
}
}
for &axis in &self.spanning {
assert!(
!self.physical.iter().any(|map| map.addresses(axis)),
"Projection::dims: {axis:?} is stated as spanning (addressed by no dim) but a dim \
addresses it"
);
mention(axis, &mut axes);
}
for term in innermost.terms() {
mention(term.axis, &mut axes);
}
Projection::new(&axes, &self.physical)
}
}
impl From<Axis> for PhysicalAxisMap {
fn from(axis: Axis) -> Self {
PhysicalAxisMap::of(axis)
}
}
pub fn split(extents: &[(Axis, usize)]) -> PhysicalAxisMap {
assert!(
!extents.is_empty(),
"split: a dim is cut by at least one axis"
);
let mut terms: SmallVec<[(Axis, usize); Space::MAX_RANK]> = SmallVec::new();
let mut coefficient = 1;
for &(axis, extent) in extents.iter().rev() {
assert!(extent > 0, "split: {axis:?} has extent 0");
terms.push((axis, coefficient));
coefficient *= extent;
}
terms.reverse();
PhysicalAxisMap::disjoint(&terms)
}
#[cfg(test)]
pub(crate) fn stencil(coefficients: &[(Axis, usize)]) -> StencilDim {
StencilDim {
coefficients: SmallVec::from_slice(coefficients),
pad: 0,
}
}
#[cfg(test)]
#[derive(Clone, Debug)]
pub(crate) struct StencilDim {
coefficients: SmallVec<[(Axis, usize); Space::MAX_RANK]>,
pad: usize,
}
#[cfg(test)]
impl StencilDim {
pub(crate) fn pad(mut self, pad: usize) -> Self {
self.pad = pad;
self
}
}
#[cfg(test)]
impl From<StencilDim> for PhysicalAxisMap {
fn from(window: StencilDim) -> Self {
let map = PhysicalAxisMap::affine(&window.coefficients).shifted(-(window.pad as isize));
debug_assert!(
window.coefficients.len() == 1 || map.composition() == Composition::Overlapping,
"a window over several axes takes the overlapping reading"
);
map
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Offset;
const B: Axis = Axis(0);
const H: Axis = Axis(1);
const G: Axis = Axis(2);
const T: Axis = Axis(3);
const D: Axis = Axis(4);
const K: Axis = Axis(5);
const N: Axis = Axis(6);
const KB: Axis = Axis(7);
const KI: Axis = Axis(8);
const OH: Axis = Axis(9);
const RH: Axis = Axis(10);
const C: Axis = Axis(11);
const M: Axis = Axis(12);
fn index(p: &Projection, dim: usize, coordinate: &[(Axis, usize)]) -> isize {
let sum: usize = coordinate
.iter()
.map(|&(axis, at)| at * p.scale(dim, axis))
.sum();
let offset = match p.offset(dim) {
Offset::Static(o) => o,
Offset::Dynamic => unreachable!("nothing here builds a dynamic offset"),
};
sum as isize + offset
}
#[test]
fn one_axis_per_dim_is_direct() {
let p = Projection::dims().dim(K).dim(N).build();
assert_eq!(p, Projection::direct(&[K, N]));
}
#[test]
fn split_derives_the_coefficients_from_extents() {
let p = Projection::dims()
.dim(N)
.dim(split(&[(KB, 4), (KI, 32)]))
.build();
assert_eq!(p.scale(1, KB), 32);
assert_eq!(p.scale(1, KI), 1);
assert_eq!(index(&p, 1, &[(KB, 3), (KI, 4)]), 100);
assert_eq!(p.composition(), Composition::Disjoint);
assert_eq!(p.logical_axes(), &[N, KB, KI]);
}
#[test]
fn split_composes_more_than_two_axes() {
let p = Projection::dims()
.dim(split(&[(B, 2), (H, 3), (D, 5)]))
.build();
assert_eq!(p.scale(0, B), 15);
assert_eq!(p.scale(0, H), 5);
assert_eq!(p.scale(0, D), 1);
assert_eq!(index(&p, 0, &[(B, 1), (H, 2), (D, 4)]), 29);
}
#[test]
fn a_partition_in_the_middle_keeps_the_innermost_last() {
let (group, splits) = (4, 8);
let p = Projection::dims()
.dim(B)
.dim(H)
.dim(split(&[(G, group), (T, splits)]))
.dim(D)
.build();
assert_eq!(index(&p, 2, &[(G, 2), (T, 5)]), 21);
assert_eq!(index(&p, 2, &[(G, 2), (T, 6)]), 22);
assert_eq!(p.logical_axes(), &[B, H, G, T, D]);
}
#[test]
fn stencil_is_an_overlapping_affine_map_with_padding() {
let p = Projection::dims()
.dim(B)
.dim(stencil(&[(OH, 2), (RH, 1)]).pad(1))
.dim(C)
.build();
assert_eq!(index(&p, 1, &[(OH, 3), (RH, 2)]), 7);
assert_eq!(index(&p, 1, &[(OH, 4), (RH, 0)]), 7);
assert_eq!(index(&p, 1, &[(OH, 0), (RH, 0)]), -1);
assert_eq!(p.composition(), Composition::Overlapping);
assert_eq!(p.logical_axes(), &[B, OH, RH, C]);
}
#[test]
fn a_spanning_axis_sits_before_the_innermost() {
let p = Projection::dims()
.dim(B)
.dim(H)
.dim(T)
.dim(D)
.spanning(G)
.build();
assert!(!p.addresses(G));
assert_eq!(p.logical_axes(), &[B, H, T, G, D]);
p.validate(4).unwrap();
}
#[test]
fn a_repeated_axis_is_storage_tiled() {
let p = Projection::dims()
.dim(B)
.dim(M)
.dim(K)
.dim(M)
.dim(K)
.dim(C)
.build();
assert_eq!(p.logical_axes(), &[B, M, K, C]);
assert_eq!(p.tiling().fragments(4).as_slice(), &[1, 2, 2, 1]);
}
#[test]
#[should_panic(expected = "stated as spanning")]
fn spanning_an_addressed_axis_is_refused() {
let _ = Projection::dims().dim(B).dim(D).spanning(D).build();
}
}