use crate::dsl::prelude::*;
use ruda_core::ir::ElemType;
use ruda::runtime::{
server::Handle,
throughput::{DEFAULT_WORKING_SET_BYTES, MemorySpec, ThroughputError},
};
use crate::library::throughput::LaunchConfig;
use ruda_core::profile::Duration;
const POOL_WINDOWS: usize = 2;
pub(crate) fn window_cap(max_alloc: u64) -> u64 {
(DEFAULT_WORKING_SET_BYTES.min(max_alloc / POOL_WINDOWS as u64)).max(1)
}
#[derive(Clone, Copy, Debug)]
pub struct MemoryProbe {
pub pool_lines: usize,
pub window_lines: usize,
pub buffer_bytes: usize,
pub ruda_count: usize,
pub blocked: bool,
}
#[derive(Clone, Copy)]
struct DeviceShape {
max_alloc: usize,
ruda_dim: usize,
ruda_count: usize,
}
impl MemoryProbe {
pub fn min_iterations(&self) -> usize {
self.pool_lines.div_ceil(self.window_lines)
}
pub fn new<R: Runtime>(client: &ComputeClient<R>, config: LaunchConfig, line_bytes: usize, spec: MemorySpec) -> Self {
let blocked = config.plane_size == 1;
let shape = DeviceShape {
max_alloc: client.properties().memory.max_page_size as usize,
ruda_dim: config.ruda_dim.num_elems() as usize,
ruda_count: if blocked { 1 } else { config.ruda_count },
};
Self::sized(shape, line_bytes, spec, blocked)
}
fn sized(shape: DeviceShape, line_bytes: usize, spec: MemorySpec, blocked: bool) -> Self {
let buffers = spec.access.buffers() as usize;
let window_bytes = (spec.bytes.min(usize::MAX as u64) as usize) / buffers;
let cap_lines = (window_cap(shape.max_alloc as u64) as usize / line_bytes).max(1);
let window_lines = (window_bytes / line_bytes).clamp(1, cap_lines);
let pool_lines = cap_lines * POOL_WINDOWS;
let ruda_count = (window_lines / shape.ruda_dim).clamp(1, shape.ruda_count);
Self {
pool_lines,
window_lines,
buffer_bytes: pool_lines * line_bytes,
ruda_count,
blocked,
}
}
}
pub fn reserve<R: Runtime, const N: usize>(
client: &ComputeClient<R>,
bytes: [usize; N],
) -> Result<[Handle; N], ThroughputError> {
client.memory_dedicated_allocation((), |_| {
let handles = bytes.into_iter()
.map(|bytes| client.try_empty(bytes).map_err(|_| ThroughputError::Allocation))
.collect::<Result<alloc::vec::Vec<_>, _>>()?;
handles.try_into().map_err(|_| ThroughputError::Allocation)
})
}
pub fn verify<R: Runtime>(
client: &ComputeClient<R>,
sample: impl Fn(usize) -> Result<Duration, ThroughputError>,
written: &Handle,
) -> Result<(), ThroughputError> {
sample(1)?;
synchronize(client, &[written])
}
pub(super) fn synchronize<R: Runtime>(
client: &ComputeClient<R>,
written: &[&Handle],
) -> Result<(), ThroughputError> {
ruda_core::future::block_on(client.sync()).map_err(|_| ThroughputError::Launch)?;
for handle in written {
if (*handle).clone().binding().failure().is_some() {
return Err(ThroughputError::Launch);
}
}
Ok(())
}
pub fn prime<R: Runtime>(
client: &ComputeClient<R>,
handle: &Handle,
pool_lines: usize,
config: LaunchConfig,
dtype: ElemType,
) -> Result<(), ThroughputError> {
unsafe {
prime_buffer::launch_unchecked(
client,
RudaCount::Static(config.ruda_count as u32, 1, 1),
config.ruda_dim,
config.vector_size,
ArrayArg::from_raw_parts(handle.clone(), pool_lines * config.vector_size),
pool_lines,
dtype.into(),
);
}
synchronize(client, &[handle])
}
#[ruda(launch_unchecked)]
fn prime_buffer<I: Numeric, N: Size>(
output: &mut Array<Vector<I, N>>,
len: usize,
#[define(I)] _dtype: StorageType,
) {
let stride = RUDA_DIM as usize * RUDA_COUNT;
let steps = len.div_ceil(stride).max(1);
for step in 0..steps {
let idx = ABSOLUTE_POS + step * stride;
if idx < len {
output[idx] = Vector::<I, N>::empty();
}
}
}