use crate::fuse::compute_costs;
use crate::{BLOCK_MEMORY_SIZE, CACHE_LINE_SIZE};
use strided_view::auxiliary::index_order;
pub fn compute_block_sizes(
dims: &[usize],
strides_list: &[&[isize]],
elem_size: usize,
) -> Vec<usize> {
if dims.is_empty() || elem_size == 0 {
return dims.to_vec();
}
let elem_size = elem_size as u128;
let byte_strides: Vec<Vec<u128>> = strides_list
.iter()
.map(|strides| {
strides
.iter()
.map(|&s| s.unsigned_abs() as u128 * elem_size)
.collect()
})
.collect();
let byte_stride_refs: Vec<&[u128]> = byte_strides.iter().map(|s| s.as_slice()).collect();
if total_memory_region(dims, &byte_stride_refs) <= BLOCK_MEMORY_SIZE as u128
|| strides_list.iter().all(|s| strictly_increasing(s))
{
return dims.to_vec();
}
let stride_orders: Vec<Vec<usize>> = strides_list
.iter()
.map(|s| index_order(s).into_vec())
.collect();
let stride_order_refs: Vec<&[usize]> = stride_orders.iter().map(|s| s.as_slice()).collect();
let costs = compute_costs(strides_list);
compute_blocks(
dims,
&costs,
&byte_stride_refs,
&stride_order_refs,
BLOCK_MEMORY_SIZE,
)
}
fn strictly_increasing(strides: &[isize]) -> bool {
strides.first().is_some_and(|&s| s != 0)
&& strides
.windows(2)
.all(|w| w[0].unsigned_abs() < w[1].unsigned_abs())
}
fn compute_blocks(
dims: &[usize],
costs: &[isize],
byte_strides: &[&[u128]],
stride_orders: &[&[usize]],
block_size: usize,
) -> Vec<usize> {
let n = dims.len();
if n == 0 {
return vec![];
}
let block_size_bytes = block_size as u128;
if total_memory_region(dims, byte_strides) <= block_size_bytes {
return dims.to_vec();
}
let min_order = stride_orders
.iter()
.filter_map(|orders| orders.iter().min().copied())
.min()
.unwrap_or(1);
if stride_orders
.iter()
.all(|orders| !orders.is_empty() && orders[0] == min_order)
{
let tail_dims: Vec<usize> = dims[1..].to_vec();
let tail_costs: Vec<isize> = costs[1..].to_vec();
let tail_byte_strides: Vec<&[u128]> = byte_strides.iter().map(|s| &s[1..]).collect();
let tail_stride_orders: Vec<&[usize]> = stride_orders.iter().map(|s| &s[1..]).collect();
let tail_blocks = compute_blocks(
&tail_dims,
&tail_costs,
&tail_byte_strides,
&tail_stride_orders,
block_size,
);
let mut result = vec![dims[0]];
result.extend(tail_blocks);
return result;
}
let min_stride = byte_strides
.iter()
.filter_map(|s| s.iter().min().copied())
.min()
.unwrap_or(0);
if min_stride > block_size_bytes {
return vec![1; n];
}
let mut blocks = dims.to_vec();
while total_memory_region(&blocks, byte_strides) >= 2 * block_size_bytes {
let i = last_argmax_weighted(&blocks, costs);
if i.is_none() || blocks[i.unwrap()] <= 1 {
break;
}
let i = i.unwrap();
blocks[i] = blocks[i].div_ceil(2);
}
while total_memory_region(&blocks, byte_strides) > block_size_bytes {
let i = last_argmax_weighted(&blocks, costs);
if i.is_none() || blocks[i.unwrap()] <= 1 {
break;
}
let i = i.unwrap();
blocks[i] -= 1;
}
blocks
}
fn total_memory_region(dims: &[usize], byte_strides: &[&[u128]]) -> u128 {
let cache_line = CACHE_LINE_SIZE as u128;
let mut memory_region = 0u128;
for strides in byte_strides {
let mut num_contiguous_bytes = 0u128;
let mut num_cache_line_blocks = 1u128;
for (&d, &s) in dims.iter().zip(strides.iter()) {
if s < cache_line {
num_contiguous_bytes =
num_contiguous_bytes.saturating_add(d.saturating_sub(1) as u128 * s);
} else {
num_cache_line_blocks = num_cache_line_blocks.saturating_mul(d as u128);
}
}
let contiguous_lines = num_contiguous_bytes / cache_line + 1;
memory_region = memory_region.saturating_add(
cache_line
.saturating_mul(contiguous_lines)
.saturating_mul(num_cache_line_blocks),
);
}
memory_region
}
fn last_argmax_weighted(blocks: &[usize], costs: &[isize]) -> Option<usize> {
if blocks.is_empty() {
return None;
}
let mut max_score = 0u128;
let mut max_idx = None;
for (i, (&b, &c)) in blocks.iter().zip(costs.iter()).enumerate() {
if b <= 1 {
continue;
}
let score = (b - 1) as u128 * c as u128;
if score >= max_score {
max_score = score;
max_idx = Some(i);
}
}
max_idx
}
#[cfg(test)]
mod tests;