#![allow(clippy::while_let_loop)]
use std::collections::BTreeMap;
use super::autotune::Optimization;
use crate::{
Map, Set,
dtype::Constant,
kernel::{BOp, Kernel, MemLayout, MemScope, Op, OpId, RangeKind},
shape::Dim,
};
#[derive(Debug)]
pub struct ThreadCoarse {
pub factors: Vec<(OpId, u64)>,
}
impl Optimization for ThreadCoarse {
fn nconfigs(&self) -> u64 {
self.factors.len() as u64
}
fn apply(&self, kernel: &mut Kernel, config: u64) {
if self.factors.is_empty() {
return;
}
let (op_id, factor) = self.factors[config as usize];
kernel.coarsen(op_id, factor);
}
}
#[derive(Debug)]
pub struct RegisterBlocking {
pub reduce_splits: BTreeMap<OpId, Vec<u64>>,
pub thread_coarses: BTreeMap<OpId, Vec<u64>>,
}
impl Optimization for RegisterBlocking {
fn nconfigs(&self) -> u64 {
if self.reduce_splits.is_empty() || self.thread_coarses.is_empty() {
return 0;
}
let n_global_options: usize = self.thread_coarses.values().map(|v| v.len() + 1).product();
let n_reduce_options: usize = self.reduce_splits.values().map(Vec::len).product();
(n_global_options * n_reduce_options) as u64
}
fn apply(&self, kernel: &mut Kernel, config: u64) {
kernel.apply_register_blocking(&self.reduce_splits, &self.thread_coarses, config as usize);
}
}
impl Kernel {
pub fn opt_coarsen(&self) -> Box<dyn Optimization> {
#[cfg(feature = "time")]
let _timer = crate::Timer::new("opt_upcast");
let mut factors = Vec::new();
let mut op_id = self.head;
while !op_id.is_null() {
let next = self.next_op(op_id);
if let Op::Range { kind: RangeKind::Group(len), .. } = self.ops[op_id].op {
let Some(len) = self.resolve_const(len).and_then(crate::dtype::Constant::as_dim) else {
op_id = next;
continue;
};
for f in [16, 8, 4] {
if len % f as Dim == 0 {
factors.push((op_id, f));
}
}
}
op_id = next;
}
Box::new(ThreadCoarse { factors })
}
pub fn coarsen(&mut self, gidx_id: OpId, factor: u64) {
#[cfg(feature = "time")]
let _timer = crate::Timer::new("thread_coarse");
let Op::Range { axis, kind: RangeKind::Group(len) } = self.ops[gidx_id].op else {
unreachable!()
};
let Some(len) = self.resolve_const(len).and_then(crate::dtype::Constant::as_dim) else {
return;
};
debug_assert!(len % factor as Dim == 0);
if self.ops.values().any(|node| match node.op {
Op::Load { layout, .. } | Op::Store { layout, .. } => layout != MemLayout::Scalar,
Op::Barrier => true,
_ => false,
}) {
return;
}
if self.ops.len().0 as Dim * factor as Dim > 10000 {
return;
}
let mut op_id = self.head;
while !op_id.is_null()
&& matches!(
self.ops[op_id].op,
Op::Storage { scope: MemScope::Global | MemScope::Local, .. }
| Op::Param { .. }
| Op::Range { .. }
| Op::Const(_)
)
{
op_id = self.next_op(op_id);
}
self.move_op_before(gidx_id, op_id);
let const_factor = self.insert_before(gidx_id, Op::Const(Constant::idx(factor)));
let mut offsets = Vec::with_capacity((factor - 1) as usize);
for i in 1..factor {
offsets.push(self.insert_before(gidx_id, Op::Const(Constant::idx(i))));
}
let mut remaps: Map<OpId, Vec<OpId>> = Map::default();
let new_len = self.insert_const_idx_before(gidx_id, len / factor as Dim);
let x = self.insert_before(gidx_id, Op::Range { axis, kind: RangeKind::Group(new_len) });
self.ops[gidx_id].op = Op::Binary { x, y: const_factor, bop: BOp::Mul };
let mut ids = Vec::with_capacity((factor - 1) as usize);
let mut id = gidx_id;
for &offset in &offsets {
id = self.insert_after(id, Op::Binary { x: gidx_id, y: offset, bop: BOp::Add });
ids.push(id);
}
remaps.insert(gidx_id, ids);
let mut accumulator_storages = Set::default();
while !op_id.is_null() {
let next_op_id = self.next_op(op_id);
match self.ops[op_id].op {
Op::Storage { scope: MemScope::Register, ref mut len, .. } => {
*len *= factor as Dim;
accumulator_storages.insert(op_id);
}
Op::Range { .. } | Op::Loop { .. } | Op::EndLoop | Op::If { .. } | Op::EndIf | Op::Barrier => {}
Op::Store { dst, src: x, index, layout } => {
let mut ids = Vec::with_capacity((factor - 1) as usize);
let mut id = op_id;
if accumulator_storages.contains(&dst) {
for i in 0..(factor - 1) as usize {
let mut x = x;
if let Some(remap) = remaps.get(&x) {
x = remap[i];
}
let index = self.insert_before(id, Op::Mad { x: index, y: const_factor, z: offsets[i] });
id = self.insert_after(index, Op::Store { dst, src: x, index, layout });
ids.push(id);
}
let index = self.insert_before(op_id, Op::Binary { x: index, y: const_factor, bop: BOp::Mul });
self.ops[op_id].op = Op::Store { dst, src: x, index, layout };
} else {
for i in 0..(factor - 1) as usize {
let mut x = x;
if let Some(remap) = remaps.get(&x) {
x = remap[i];
}
let mut index = index;
if let Some(remap) = remaps.get(&index) {
index = remap[i];
}
id = self.insert_after(id, Op::Store { dst, src: x, index, layout });
ids.push(id);
}
}
remaps.insert(op_id, ids);
}
Op::Load { src, index, layout } => {
let mut ids = Vec::with_capacity((factor - 1) as usize);
let mut id = op_id;
if accumulator_storages.contains(&src) {
for &offset in &offsets {
let index = self.insert_before(id, Op::Mad { x: index, y: const_factor, z: offset });
id = self.insert_after(index, Op::Load { src, index, layout });
ids.push(id);
}
let index = self.insert_before(op_id, Op::Binary { x: index, y: const_factor, bop: BOp::Mul });
self.ops[op_id].op = Op::Load { src, index, layout };
} else {
for i in 0..(factor - 1) as usize {
let mut index = index;
if let Some(remap) = remaps.get(&index) {
index = remap[i];
}
id = self.insert_after(id, Op::Load { src, index, layout });
ids.push(id);
}
}
remaps.insert(op_id, ids);
}
ref op => {
let op = op.clone();
let mut ids = Vec::with_capacity((factor - 1) as usize);
let mut id = op_id;
for i in 0..(factor - 1) as usize {
let mut op = op.clone();
for param in op.parameters_mut() {
if let Some(remap) = remaps.get(param) {
*param = remap[i];
}
}
id = self.insert_after(id, op);
ids.push(id);
}
remaps.insert(op_id, ids);
}
}
op_id = next_op_id;
}
self.verify();
}
}
impl Kernel {
pub fn opt_register_blocking(&self) -> Box<dyn Optimization> {
#[cfg(feature = "time")]
let _timer = crate::Timer::new("opt_register_tiling");
let candidates: Vec<u64> = vec![8, 16, 4, 2];
let mut global_upcasts: BTreeMap<OpId, Vec<u64>> = BTreeMap::new();
let mut reduce_factor: BTreeMap<OpId, Vec<u64>> = BTreeMap::new();
let mut op_id = self.head;
while !op_id.is_null() {
let next = self.next_op(op_id);
if let Op::Loop { len: len_id } = self.ops[op_id].op {
let Some(len) = self.resolve_const(len_id).and_then(crate::dtype::Constant::as_dim) else {
op_id = next;
continue;
};
if len >= 16 {
let applicable: Vec<u64> =
candidates.iter().copied().filter(|&f| len % f as Dim == 0 && len / f as Dim >= 4).collect();
if !applicable.is_empty() {
reduce_factor.insert(op_id, applicable);
}
}
}
if let Op::Range { kind: RangeKind::Group(len), .. } = self.ops[op_id].op {
let Some(len) = self.resolve_const(len).and_then(crate::dtype::Constant::as_dim) else {
op_id = next;
continue;
};
let applicable: Vec<u64> =
candidates.iter().copied().filter(|&f| len % f as Dim == 0 && len / f as Dim >= 4).collect();
if !applicable.is_empty() {
global_upcasts.insert(op_id, applicable);
}
}
op_id = next;
}
if global_upcasts.is_empty() || reduce_factor.is_empty() {
return Box::new(RegisterBlocking { reduce_splits: reduce_factor, thread_coarses: global_upcasts });
}
Box::new(RegisterBlocking { reduce_splits: reduce_factor, thread_coarses: global_upcasts })
}
pub(crate) fn apply_register_blocking(
&mut self,
reduce_splits: &BTreeMap<OpId, Vec<u64>>,
global_upcasts: &BTreeMap<OpId, Vec<u64>>,
config: usize,
) {
let n_global = global_upcasts.len();
let n_reduce = reduce_splits.len();
if n_global == 0 || n_reduce == 0 {
return;
}
let n_global_options: usize = global_upcasts.values().map(|v| v.len() + 1).product();
let mut remaining_global = config % n_global_options;
let mut remaining_reduce = config / n_global_options;
let mut reduce_indices: Vec<usize> = Vec::with_capacity(n_reduce);
for factors in reduce_splits.values() {
let n_options = factors.len();
let factor_idx = remaining_reduce % n_options;
remaining_reduce /= n_options;
reduce_indices.push(factor_idx);
}
let mut global_indices: Vec<usize> = Vec::with_capacity(n_global);
for factors in global_upcasts.values() {
let n_options = factors.len() + 1;
let factor_idx = remaining_global % n_options;
remaining_global /= n_options;
global_indices.push(factor_idx);
}
for (i, (&reduce_id, factors)) in reduce_splits.iter().enumerate() {
let factor_idx = reduce_indices[i];
let reduce_factor = factors[factor_idx];
self.unroll_tree_reduce(reduce_id, reduce_factor as Dim);
}
for (idx, (op_id, factors)) in global_upcasts.iter().enumerate() {
let factor_idx = global_indices[idx];
let factor = if factor_idx == 0 { 1 } else { factors[factor_idx - 1] };
if factor > 1 {
self.coarsen(*op_id, factor);
}
}
}
}