#![allow(dead_code)]
use cubecl::prelude::{Numeric, Size};
use cubecl::std::tensor::{
TensorHandle,
layout::tiled_view::{TileSpec, TiledViewLaunch, TiledViewLayout},
};
use cubecl::{
TestRuntime, bytes::Bytes, client::ComputeClient, prelude::CubePrimitive, prelude::TensorArg,
quant::scheme::QuantScheme, zspace::Shape,
};
use cubek_tile::{
DequantAt, Leaf, Projection, QuantTileArgLaunch, Space, StorageTiling, TileArgLaunch,
TileSpec as CubekTileSpec,
};
use crate::{TestInput, TestInputBuilder};
pub struct TileInput {
handle: TensorHandle<TestRuntime>,
space: Space,
levels: usize,
leaf: Leaf,
}
impl TileInput {
pub fn builder(client: &ComputeClient<TestRuntime>, space: Space) -> TileInputBuilder {
TileInputBuilder {
client: client.clone(),
space,
levels: None,
leaf: Leaf::Memory,
}
}
pub fn view(&self) -> TiledViewLaunch<TestRuntime> {
TiledViewLaunch::new_tensor::<TiledViewLayout>(
self.handle.clone().binding().into_tensor_arg(),
TileSpec {
start_axis: 0,
num_tiled: self.space.rank(),
levels: self.levels,
},
)
}
pub fn view_vectorized(&self, vector_size: usize) -> TiledViewLaunch<TestRuntime> {
let shape = self.handle.shape();
let strides = self.handle.strides();
let inner = shape.len() - 1;
let new_shape: Vec<usize> = shape
.iter()
.enumerate()
.map(|(i, &s)| if i == inner { s / vector_size } else { s })
.collect();
let new_strides: Vec<usize> = strides
.iter()
.enumerate()
.map(|(i, &s)| if i == inner { s } else { s / vector_size })
.collect();
let lined = TensorHandle::<TestRuntime>::new(
self.handle.handle.clone(),
new_shape,
new_strides,
self.handle.dtype,
);
TiledViewLaunch::new_tensor::<TiledViewLayout>(
lined.binding().into_tensor_arg(),
TileSpec {
start_axis: 0,
num_tiled: self.space.rank(),
levels: self.levels,
},
)
}
pub fn tensor_arg(&self, vector_size: usize) -> TensorArg<TestRuntime> {
if vector_size <= 1 {
return self.handle.clone().binding().into_tensor_arg();
}
let shape = self.handle.shape();
let strides = self.handle.strides();
let inner = shape.len() - 1;
let new_shape: Vec<usize> = shape
.iter()
.enumerate()
.map(|(i, &s)| if i == inner { s / vector_size } else { s })
.collect();
let new_strides: Vec<usize> = strides
.iter()
.enumerate()
.map(|(i, &s)| if i == inner { s } else { s / vector_size })
.collect();
TensorHandle::<TestRuntime>::new(
self.handle.handle.clone(),
new_shape,
new_strides,
self.handle.dtype,
)
.binding()
.into_tensor_arg()
}
pub fn arg<E: Numeric, V: Size>(&self) -> TileArgLaunch<'static, E, V, TestRuntime> {
TileArgLaunch::new(self.tensor_arg(1), self.spec())
}
pub fn spec(&self) -> CubekTileSpec {
let axes: Vec<_> = (0..self.space.rank())
.map(|i| self.space.axis_at(i))
.collect();
let levels = self.handle.shape().len() / self.space.rank() - 1;
let tiling = StorageTiling::uniform(self.space.rank(), levels);
CubekTileSpec::new(Projection::tiled(&axes, tiling)).leaf(self.leaf)
}
pub fn space(&self) -> Space {
self.space.clone()
}
pub fn handle(&self) -> TensorHandle<TestRuntime> {
self.handle.clone()
}
}
enum TileLevel {
Split(Vec<usize>),
Tile(Vec<usize>),
}
pub struct TileInputBuilder {
client: ComputeClient<TestRuntime>,
space: Space,
levels: Option<Vec<TileLevel>>,
leaf: Leaf,
}
impl TileInputBuilder {
pub fn leaf(mut self, leaf: Leaf) -> Self {
self.leaf = leaf;
self
}
pub fn split(mut self, counts: &[usize]) -> Self {
self.levels
.get_or_insert_with(Vec::new)
.push(TileLevel::Split(counts.to_vec()));
self
}
pub fn tile(mut self, edges: &[usize]) -> Self {
self.levels
.get_or_insert_with(Vec::new)
.push(TileLevel::Tile(edges.to_vec()));
self
}
pub fn untiled(mut self) -> Self {
self.levels = Some(Vec::new());
self
}
pub fn arange(self) -> TileInput {
self.build(TestInputBuilder::arange)
}
pub fn zeros(self) -> TileInput {
self.build(TestInputBuilder::zeros)
}
pub fn uniform(self, seed: u64, lo: f32, hi: f32) -> TileInput {
self.build(|b: TestInputBuilder| b.uniform(seed, lo, hi))
}
pub fn packed(self, scheme: &QuantScheme, dequant_at: DequantAt) -> QuantizedTileInputBuilder {
let levels = self
.levels
.expect("TileInput: set .untiled() before .packed");
assert!(
levels.is_empty(),
"TileInput::packed: a packed input is untiled"
);
QuantizedTileInputBuilder {
client: self.client,
space: self.space,
scheme: *scheme,
leaf: self.leaf,
dequant_at,
}
}
fn build(self, fill: impl FnOnce(TestInputBuilder) -> TestInput) -> TileInput {
let levels = self
.levels
.expect("TileInput: set .split/.tile(...) or .untiled() before a finalizer");
let rank = self.space.rank();
let mut current: Vec<usize> = (0..rank)
.map(|i| self.space.extent(self.space.axis_at(i)))
.collect();
let mut blocks: Vec<Vec<usize>> = Vec::with_capacity(levels.len() + 1);
for level in &levels {
let (values, is_split) = match level {
TileLevel::Split(values) => (values, true),
TileLevel::Tile(values) => (values, false),
};
assert_eq!(
values.len(),
rank,
"TileInput: a tile level needs one value per axis (rank {rank})"
);
let block: Vec<usize> = (0..rank)
.map(|i| {
let (edge, value) = (current[i], values[i]);
assert!(
value != 0 && edge % value == 0,
"TileInput: tile value {value} does not divide the current edge {edge} on axis {i}"
);
let (dim, next) = if is_split {
(value, edge / value)
} else {
(edge / value, value)
};
current[i] = next;
dim
})
.collect();
blocks.push(block);
}
blocks.push(current);
let mut dims = Vec::with_capacity(blocks.len() * rank);
for block in &blocks {
dims.extend_from_slice(block);
}
let builder = TestInput::builder(self.client, Shape::from(dims));
TileInput {
handle: fill(builder).generate_without_host_data(),
space: self.space,
levels: levels.len(),
leaf: self.leaf,
}
}
}
pub struct QuantizedTileInputBuilder {
leaf: Leaf,
dequant_at: DequantAt,
client: ComputeClient<TestRuntime>,
space: Space,
scheme: QuantScheme,
}
impl QuantizedTileInputBuilder {
pub fn arange(self) -> QuantizedTileInput {
let rank = self.space.rank();
let shape: Vec<usize> = (0..rank)
.map(|i| self.space.extent(self.space.axis_at(i)))
.collect();
let (lo, hi) = self.scheme.value.range();
let (lo, hi) = (lo as i32, hi as i32);
let span = hi - lo + 1;
let q: Vec<i32> = (0..shape.iter().product())
.map(|i| lo + (i as i32 % span))
.collect();
let words = crate::stubs::quant::pack_q_values(&q, &self.scheme);
let handle = self.client.create(Bytes::from_elems(words));
let block = crate::stubs::quant::block_dims(&self.scheme, &shape);
let grid = crate::stubs::quant::scales_shape(&shape, &block);
let scale_values: Vec<f32> = (0..grid.iter().product())
.map(|g| 0.05 * (g + 1) as f32)
.collect();
let scales = TestInput::builder(self.client, Shape::from(grid))
.custom(scale_values.clone())
.generate_without_host_data();
QuantizedTileInput {
tile: TileInput {
handle: TensorHandle::new_contiguous(
shape,
handle,
u32::as_type_native_unchecked().storage_type(),
),
space: self.space,
levels: 0,
leaf: self.leaf,
},
scales,
scheme: self.scheme,
dequant_at: self.dequant_at,
q,
scale_values,
}
}
}
pub struct QuantizedTileInput {
pub dequant_at: DequantAt,
pub tile: TileInput,
scales: TensorHandle<TestRuntime>,
scheme: QuantScheme,
pub q: Vec<i32>,
pub scale_values: Vec<f32>,
}
impl QuantizedTileInput {
pub fn scales_arg(&self) -> TensorArg<TestRuntime> {
self.scales.clone().binding().into_tensor_arg()
}
pub fn arg<E: Numeric, V: Size>(&self) -> QuantTileArgLaunch<'static, E, V, TestRuntime> {
QuantTileArgLaunch::new(
self.tile.tensor_arg(1),
self.scales_arg(),
self.tile.spec(),
self.scheme,
self.dequant_at,
)
}
}