use super::geometry::stride_order;
use crate::{Axis, Geometry, gcd, layout::StoragePartitioning};
use cubecl::prelude::TensorBinding;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ElemAlignment(Widths);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum Widths {
Dividing(usize),
Exactly(usize),
}
impl ElemAlignment {
pub const ONE: Self = Self(Widths::Dividing(1));
pub fn in_stride_order(shape: &[usize], strides: &[usize]) -> Self {
let mut dims: Vec<(usize, usize)> =
shape.iter().copied().zip(strides.iter().copied()).collect();
dims.sort_by_key(|&dim| stride_order(dim));
let (shape, strides): (Vec<usize>, Vec<usize>) = dims.into_iter().unzip();
Self::new(&shape, &strides)
}
pub fn dense_rows(columns: usize) -> Self {
Self::new(&[columns], &[1])
}
pub fn new(shape: &[usize], strides: &[usize]) -> Self {
let (shape, strides): (Vec<usize>, Vec<usize>) = shape
.iter()
.zip(strides)
.filter(|&(&extent, _)| extent != 1)
.unzip();
let divides = match (shape.split_last(), strides.split_last()) {
(Some((&extent, _)), Some((&1, coarser))) => {
coarser.iter().fold(extent, |acc, &stride| gcd(acc, stride))
}
_ => 1,
};
Self(Widths::Dividing(divides))
}
pub fn at_start(start_bytes: u64, element_bytes: usize) -> Self {
let element_bytes = element_bytes.max(1) as u64;
let divides = match start_bytes.is_multiple_of(element_bytes) {
true => (start_bytes / element_bytes) as usize,
false => 1,
};
Self(Widths::Dividing(divides))
}
pub const fn stored_piece(read: usize) -> Self {
Self(Widths::Exactly(read))
}
pub fn is_stored_piece(self) -> bool {
matches!(self.0, Widths::Exactly(_))
}
pub fn admits(self, width: usize) -> bool {
match self.0 {
_ if width == 1 => true,
Widths::Exactly(read) => width == read,
Widths::Dividing(divides) => divides.is_multiple_of(width),
}
}
}
impl From<&TensorBinding> for ElemAlignment {
fn from(binding: &TensorBinding) -> Self {
if !binding.tiling.is_tiled() {
return Self::new(&binding.shape, &binding.strides);
}
const ROWS: Axis = Axis(0);
const COLS: Axis = Axis(1);
let geometry = Geometry::from(binding);
let storage = StoragePartitioning::new(&geometry, &geometry.labels(&[ROWS, COLS]));
match storage.as_ref().and_then(|storage| storage.tiles().first()) {
Some(&(COLS, read)) => Self::stored_piece(read),
_ => Self::stored_piece(1),
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn a_dim_of_one_element_aligns_nothing() {
let k = 8;
let row = ElemAlignment::new(&[1, k], &[1, 1]);
assert_eq!(row, ElemAlignment::dense_rows(k));
assert!(ElemAlignment::in_stride_order(&[1, k], &[1, 1]).admits(k));
}
#[test]
fn a_dim_of_one_element_is_never_innermost_in_stride_order() {
let k = 4;
assert_eq!(
ElemAlignment::in_stride_order(&[k, 1], &[1, 1]),
ElemAlignment::dense_rows(k)
);
let (_, axes) = Geometry::new(&[(k, 1), (1, 1)]).in_stride_order(&[Axis(2), Axis(1)]);
assert_eq!(axes, [Axis(1), Axis(2)]);
}
#[test]
fn a_view_is_aligned_in_the_order_its_buffer_steps() {
let (k, n, pitch) = (64, 48, 72);
let col = ElemAlignment::in_stride_order(&[4, k, n], &[n * pitch, 1, pitch]);
assert!(col.admits(8) && !col.admits(16));
assert!(ElemAlignment::in_stride_order(&[4, k, n], &[k * n, n, 1]).admits(16));
}
use super::*;
#[test]
fn a_start_is_aligned_to_its_offset_in_elements() {
assert!(ElemAlignment::at_start(0, 2).admits(16));
let partway = ElemAlignment::at_start(24, 2);
assert!(partway.admits(4) && !partway.admits(8));
assert!(!ElemAlignment::at_start(3, 2).admits(2));
}
#[test]
fn a_layout_is_aligned_to_the_divisors_of_its_extent_and_strides() {
let dense = ElemAlignment::new(&[4, 1500], &[1500, 1]);
assert!(dense.admits(4) && !dense.admits(8));
let pitched = ElemAlignment::new(&[4, 1504], &[1508, 1]);
assert!(pitched.admits(4) && !pitched.admits(8));
let col = ElemAlignment::new(&[4096, 4096], &[1, 4096]);
assert!(col.admits(1) && !col.admits(2));
let broadcast = ElemAlignment::new(&[8, 64], &[0, 1]);
assert!(broadcast.admits(64));
}
}