use cubecl::prelude::*;
#[derive(Debug, Clone)]
pub struct LaunchGeometry {
pub cube_dim: CubeDim,
pub cube_count: CubeCount,
}
#[cube]
pub fn sample_position() -> u32 {
(CUBE_POS_Z * CUBE_COUNT_X + CUBE_POS_X) * CUBE_DIM_X + UNIT_POS_X
}
#[cube]
pub fn channel_position() -> u32 {
ABSOLUTE_POS_Y
}
#[cube]
pub fn row_position() -> u32 {
CUBE_POS_Y * CUBE_COUNT_X + CUBE_POS_X
}
impl LaunchGeometry {
pub fn elementwise(client: &Client, num_elements: usize) -> Self {
let n = num_elements.max(1);
let cube_dim = CubeDim::new(client, n);
let cube_count = cubecl::calculate_cube_count_elemwise(client, n, cube_dim);
Self { cube_dim, cube_count }
}
pub fn channels_samples(client: &Client, channels: usize, samples: usize) -> Self {
let cube_dim = CubeDim::new(client, channels.max(1) * samples.max(1));
let (x, z) = Self::spill(client, samples, cube_dim.x);
let y = (channels.max(1) as u32).div_ceil(cube_dim.y);
assert!(
y <= client.properties().hardware.max_cube_count.1,
"{channels} channels exceed the runtime's cube grid"
);
Self { cube_dim, cube_count: CubeCount::Static(x, y, z) }
}
pub fn per_sample(client: &Client, samples: usize) -> Self {
let cube_dim = Self::flat(client, samples);
let (x, z) = Self::spill(client, samples, cube_dim.x);
Self { cube_dim, cube_count: CubeCount::Static(x, 1, z) }
}
pub fn per_channel(client: &Client, channels: usize) -> Self {
let cube_dim = Self::flat(client, channels);
Self { cube_dim, cube_count: CubeCount::Static((channels.max(1) as u32).div_ceil(cube_dim.x), 1, 1) }
}
pub fn per_row(client: &Client, rows: usize, cols: usize) -> Self {
let units = Self::flat(client, cols).x;
let cube_dim = CubeDim::new_1d(1 << (u32::BITS - 1 - units.max(1).leading_zeros()));
let (x, y) = Self::spill(client, rows, 1);
assert!(y <= client.properties().hardware.max_cube_count.1, "{rows} rows exceed the runtime's cube grid");
Self { cube_dim, cube_count: CubeCount::Static(x, y, 1) }
}
pub fn tiles(client: &Client, points: usize, shared_bytes_per_unit: usize) -> Self {
let shared = client.properties().hardware.max_shared_memory_size;
let fit = shared / shared_bytes_per_unit.max(1);
assert!(fit >= 1, "{shared_bytes_per_unit} bytes of shared memory per unit exceed the device's {shared}");
let units = Self::flat(client, points).x.min(u32::try_from(fit).unwrap_or(u32::MAX));
let units = 1 << (u32::BITS - 1 - units.max(1).leading_zeros());
let (x, y) = Self::spill(client, points, units);
assert!(y <= client.properties().hardware.max_cube_count.1, "{points} points exceed the runtime's cube grid");
Self { cube_dim: CubeDim::new_1d(units), cube_count: CubeCount::Static(x, y, 1) }
}
pub fn plane_lanes(client: &Client) -> u32 {
client.properties().hardware.plane_size_max.max(1)
}
fn flat(client: &Client, work: usize) -> CubeDim {
CubeDim::new_1d(CubeDim::new(client, work.max(1)).num_elems())
}
fn spill(client: &Client, units: usize, per_cube: u32) -> (u32, u32) {
let max = client.properties().hardware.max_cube_count;
let cubes = (units.max(1) as u64).div_ceil(per_cube as u64);
let x = cubes.min(max.0 as u64);
let overflow = cubes.div_ceil(x);
assert!(overflow <= max.2.min(max.1) as u64, "{units} units exceed the runtime's cube grid");
(x as u32, overflow as u32)
}
}
#[cfg(all(test, feature = "wgpu"))]
mod tests {
use super::*;
use cubecl::device::WgpuDevice;
fn cubes(count: &CubeCount) -> u64 {
match count {
CubeCount::Static(x, y, z) => *x as u64 * *y as u64 * *z as u64,
_ => unreachable!(),
}
}
#[test]
fn geometries_cover_their_ranges_within_limits() {
let client = crate::compute::open_device(cubecl::Device::Wgpu(WgpuDevice::default()));
let max = client.properties().hardware.max_cube_count;
let within = |c: &CubeCount| matches!(c, CubeCount::Static(x, y, z) if *x <= max.0 && *y <= max.1 && *z <= max.2);
let n = 384 * 330_000;
let geom = LaunchGeometry::elementwise(&client, n);
assert!(within(&geom.cube_count) && cubes(&geom.cube_count) * geom.cube_dim.num_elems() as u64 >= n as u64);
for samples in [30_001usize, 5_000_000] {
let geom = LaunchGeometry::channels_samples(&client, 385, samples);
let CubeCount::Static(x, y, z) = geom.cube_count else { unreachable!() };
assert!(within(&geom.cube_count));
assert!(x as u64 * z as u64 * geom.cube_dim.x as u64 >= samples as u64 && y * geom.cube_dim.y >= 385);
assert_eq!(geom.cube_dim.x, LaunchGeometry::plane_lanes(&client));
}
let geom = LaunchGeometry::per_channel(&client, 385);
assert!(cubes(&geom.cube_count) * geom.cube_dim.num_elems() as u64 >= 385);
}
}