use super::kernel::{TOUCHED_PER_WRITTEN, touch_vectors};
use cubecl::calculate_cube_count_elemwise;
use cubecl::client::Client;
use cubecl::config::{CubeClRuntimeConfig, RuntimeConfig};
use cubecl::ir::{ElemType, FloatKind};
use cubecl::prelude::*;
use cubecl::std::tensor::TensorHandle;
use cubecl::std::tensor::layout::linear::linear_view;
use cubecl::std::throughput::{compute_bound, measure_launch_overhead, memory_bound};
use cubecl::tensor_vector_size_parallel;
use cubecl::throughput::{MemoryAccess, ThroughputKey};
use cubecl::tune::{
AutotuneError, Bounds, BoundsGenerator, Eviction, Thresholds, TuneInputs, Work,
};
use cubecl::zspace::Shape;
use std::sync::Arc;
pub struct Roofline {
pub client: Client,
pub ceiling: Ceiling,
pub work: Work,
}
impl Roofline {
fn read_cold(&self) {
let bytes = cold_read_bytes(&self.client);
if self.work.bytes >= bytes {
return;
}
touch(&self.client, bytes);
}
}
pub enum Ceiling {
Compute(ThroughputKey),
Memory(MemoryAccess),
Either {
compute: ThroughputKey,
memory: MemoryAccess,
},
}
pub const COLD_READ: usize = 256 << 20;
pub fn cold_read_bytes(client: &Client) -> usize {
client
.properties()
.hardware
.last_level_cache_size
.map_or(COLD_READ, |cache| 2 * cache)
}
pub fn touch(client: &Client, bytes: usize) {
let elem = ElemType::Float(FloatKind::F32);
let scratch = TensorHandle::empty(client, Shape::new([bytes / elem.size()]), elem);
let _ = read_through(client, scratch);
}
pub fn read_through(client: &Client, scratch: TensorHandle) -> TensorHandle {
let elem = scratch.dtype;
let vector_size = tensor_vector_size_parallel(
client.io_optimized_vector_sizes(elem.size()),
scratch.shape(),
scratch.strides(),
0,
);
let elements = scratch.shape().num_elements();
let written = (elements / vector_size).div_ceil(TOUCHED_PER_WRITTEN as usize) * vector_size;
let output = TensorHandle::empty(client, Shape::new([written]), elem);
let working_units = written / vector_size;
let cube_dim = CubeDim::new(client, working_units);
let cube_count = calculate_cube_count_elemwise(client, working_units, cube_dim);
touch_vectors::launch(
client,
cube_count,
cube_dim,
scratch
.required_address_type()
.max(output.required_address_type()),
vector_size,
linear_view(scratch.binding()),
linear_view(output.clone().binding()),
elem,
);
output
}
pub(super) struct ReadCold<F>(pub(super) Arc<F>);
impl<K, I, F> Eviction<K, I> for ReadCold<F>
where
K: 'static,
I: TuneInputs,
F: for<'a> Fn(&K, &<I as TuneInputs>::At<'a>) -> Roofline + Send + Sync + 'static,
{
fn evict<'a>(&self, key: &K, inputs: &<I as TuneInputs>::At<'a>) -> Result<(), AutotuneError> {
(self.0)(key, inputs).read_cold();
Ok(())
}
}
pub(super) struct Bounded<F>(pub(super) Arc<F>);
impl<K, I, F> BoundsGenerator<K, I> for Bounded<F>
where
K: 'static,
I: TuneInputs,
F: for<'a> Fn(&K, &<I as TuneInputs>::At<'a>) -> Roofline + Send + Sync + 'static,
{
fn generate<'a>(&self, key: &K, inputs: &<I as TuneInputs>::At<'a>) -> Bounds {
let thresholds = Thresholds::for_level(&CubeClRuntimeConfig::get().autotune.level);
let Roofline {
client,
ceiling,
work,
} = (self.0)(key, inputs);
let bounds = match ceiling {
Ceiling::Compute(instruction) => {
vec![compute_bound(
&client,
instruction,
work,
thresholds.compute,
)]
}
Ceiling::Memory(access) => vec![memory_bound(&client, access, work, thresholds.memory)],
Ceiling::Either { compute, memory } => vec![
compute_bound(&client, compute, work, thresholds.compute),
memory_bound(&client, memory, work, thresholds.memory),
],
};
Bounds {
bounds,
launch_overhead: measure_launch_overhead(&client),
}
}
}