use cubecl::{prelude::*, tensor_vector_size_parallel};
use cubek_std::cube_count::cube_count_spread_with_total;
use crate::N_VALUES_PER_THREAD;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum PrngBlueprint {
Interleaved,
Blocked,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum PrngStrategy {
Inferred,
#[allow(dead_code)]
Forced(PrngBlueprint),
}
pub(crate) struct PrngLaunchSettings {
pub blueprint: PrngBlueprint,
pub cube_dim: CubeDim,
pub cube_count: CubeCount,
pub line_size: usize,
pub vectors_per_unit: u32,
}
impl PrngLaunchSettings {
pub(crate) fn new(
client: &Client,
output: &TensorBinding,
dtype: ElemType,
vectors_per_draw: usize,
strategy: PrngStrategy,
) -> Self {
let blueprint = match strategy {
PrngStrategy::Forced(blueprint) => blueprint,
PrngStrategy::Inferred => match client.properties().hardware.num_cpu_cores {
Some(_) => PrngBlueprint::Blocked,
None => PrngBlueprint::Interleaved,
},
};
match blueprint {
PrngBlueprint::Interleaved => Self::interleaved(client, output, dtype),
PrngBlueprint::Blocked => Self::blocked(client, output, dtype, vectors_per_draw),
}
}
fn interleaved(client: &Client, output: &TensorBinding, dtype: ElemType) -> Self {
let size = output.size();
let line_size = tensor_vector_size_parallel(
client.io_optimized_vector_sizes(dtype.size()),
&output.shape,
&output.strides,
output.strides.len() - 1,
);
let units = size.div_ceil(N_VALUES_PER_THREAD);
let cube_dim = CubeDim::new(client, units);
let cubes = units.div_ceil(cube_dim.num_elems() as usize);
let (cube_count, _) = cube_count_spread_with_total(client, cubes);
Self {
blueprint: PrngBlueprint::Interleaved,
cube_dim,
cube_count,
line_size,
vectors_per_unit: (N_VALUES_PER_THREAD / line_size) as u32,
}
}
fn blocked(
client: &Client,
output: &TensorBinding,
dtype: ElemType,
vectors_per_draw: usize,
) -> Self {
let hardware = &client.properties().hardware;
let line_size = tensor_vector_size_parallel(
client.io_optimized_vector_sizes(size_of::<u32>()),
&output.shape,
&output.strides,
output.strides.len() - 1,
);
let vectors = output.size().div_ceil(line_size);
let min_bytes_per_unit = hardware.max_shared_memory_size / 2;
let min_vectors_per_unit = min_bytes_per_unit.div_ceil(line_size * dtype.size()).max(1);
let unit_budget = hardware
.num_cpu_cores
.unwrap_or(hardware.max_units_per_cube) as usize;
let units = unit_budget.min(vectors / min_vectors_per_unit).max(1);
let vectors_per_unit = (vectors.div_ceil(units).div_ceil(vectors_per_draw)
* vectors_per_draw)
.max(vectors_per_draw);
let units = vectors.div_ceil(vectors_per_unit).max(1);
let units_per_cube = units.min(hardware.max_units_per_cube as usize);
Self {
blueprint: PrngBlueprint::Blocked,
cube_dim: CubeDim::new_1d(units_per_cube as u32),
cube_count: CubeCount::Static(units.div_ceil(units_per_cube) as u32, 1, 1),
line_size,
vectors_per_unit: u32::try_from(vectors_per_unit)
.unwrap_or_else(|_| panic!("vectors_per_unit {vectors_per_unit} overflows u32")),
}
}
}
#[cfg(test)]
mod tests {
use cubecl::std::tensor::TensorHandle;
use super::*;
#[test]
fn inferred_maps_num_cpu_cores_to_blocked() {
let client = cubecl::test_device().client();
let dtype = f32::elem_type_native();
let expected = if client.properties().hardware.num_cpu_cores.is_some() {
PrngBlueprint::Blocked
} else {
PrngBlueprint::Interleaved
};
let output = TensorHandle::empty(&client, vec![64, 64], dtype);
let settings =
PrngLaunchSettings::new(&client, &output.binding(), dtype, 1, PrngStrategy::Inferred);
assert_eq!(settings.blueprint, expected);
}
}