use crate::{fusion::on_write::kernel::fuse_on_write, BoolElement};
use crate::{fusion::JitFusionHandle, JitRuntime};
use burn_fusion::stream::Context;
use cubecl::{calculate_cube_count_elemwise, client::ComputeClient, prelude::*, CubeDim};
use serde::{Deserialize, Serialize};
use crate::fusion::on_write::{
ir::{Arg, ElemwiseConfig, ElemwisePrecision, GlobalArgs, GlobalArgsLaunch},
trace::{FuseOnWriteTrace, TraceRunner},
};
#[derive(new)]
pub struct ElemwiseOptimization<R: JitRuntime> {
trace: FuseOnWriteTrace,
client: ComputeClient<R::Server, R::Channel>,
device: R::Device,
len: usize,
}
#[derive(Serialize, Deserialize)]
pub struct ElemwiseOptimizationState {
trace: FuseOnWriteTrace,
len: usize,
}
impl<R: JitRuntime> ElemwiseOptimization<R> {
pub fn execute<BT: BoolElement>(&mut self, context: &mut Context<'_, JitFusionHandle<R>>) {
self.trace
.run::<R, BT, ElemwiseRunner>(&self.client, &self.device, context, &ElemwiseRunner)
.unwrap();
}
pub fn num_ops_fused(&self) -> usize {
self.len
}
pub fn from_state(device: &R::Device, state: ElemwiseOptimizationState) -> Self {
Self {
trace: state.trace,
len: state.len,
client: R::client(device),
device: device.clone(),
}
}
pub fn to_state(&self) -> ElemwiseOptimizationState {
ElemwiseOptimizationState {
trace: self.trace.clone(),
len: self.len,
}
}
}
pub struct ElemwiseRunner;
impl<R: JitRuntime> TraceRunner<R> for ElemwiseRunner {
type Error = ();
fn run<'a>(
&'a self,
client: &'a ComputeClient<R::Server, R::Channel>,
inputs: GlobalArgsLaunch<'a, R>,
outputs: GlobalArgsLaunch<'a, R>,
config: &'a ElemwiseConfig,
) -> Result<(), Self::Error> {
let arg = match config.ref_layout {
Arg::Input(index, precision, _) => match precision {
ElemwisePrecision::F32 => inputs.t_f32.values.get(index as usize),
ElemwisePrecision::F16 => inputs.t_f16.values.get(index as usize),
ElemwisePrecision::BF16 => inputs.t_bf16.values.get(index as usize),
ElemwisePrecision::U64 => inputs.t_u64.values.get(index as usize),
ElemwisePrecision::U32 => inputs.t_u32.values.get(index as usize),
ElemwisePrecision::U16 => inputs.t_u16.values.get(index as usize),
ElemwisePrecision::U8 => inputs.t_u8.values.get(index as usize),
ElemwisePrecision::I64 => inputs.t_i64.values.get(index as usize),
ElemwisePrecision::I32 => inputs.t_i32.values.get(index as usize),
ElemwisePrecision::I16 => inputs.t_i16.values.get(index as usize),
ElemwisePrecision::I8 => inputs.t_i8.values.get(index as usize),
_ => panic!("Invalid value"),
},
Arg::Output(index, precision, _) => match precision {
ElemwisePrecision::F32 => outputs.t_f32.values.get(index as usize),
ElemwisePrecision::F16 => outputs.t_f16.values.get(index as usize),
ElemwisePrecision::BF16 => outputs.t_bf16.values.get(index as usize),
ElemwisePrecision::U64 => outputs.t_u64.values.get(index as usize),
ElemwisePrecision::U32 => outputs.t_u32.values.get(index as usize),
ElemwisePrecision::U16 => outputs.t_u16.values.get(index as usize),
ElemwisePrecision::U8 => outputs.t_u8.values.get(index as usize),
ElemwisePrecision::I64 => outputs.t_i64.values.get(index as usize),
ElemwisePrecision::I32 => outputs.t_i32.values.get(index as usize),
ElemwisePrecision::I16 => outputs.t_i16.values.get(index as usize),
ElemwisePrecision::I8 => outputs.t_i8.values.get(index as usize),
_ => panic!("Invalid value"),
},
_ => panic!("Invalid value"),
};
let (shape, vectorization) = match arg {
Some(val) => match val {
TensorArg::Handle {
handle,
vectorization_factor,
} => (handle.shape, vectorization_factor),
_ => panic!("Can't be an alias"),
},
None => panic!("Invalid argument"),
};
let total_elem = shape.iter().product::<usize>() / *vectorization as usize;
let cube_dim = CubeDim::default();
let cube_count = calculate_cube_count_elemwise(total_elem, cube_dim);
unsafe {
elemwise_fuse::launch_unchecked(
client,
cube_count,
cube_dim,
inputs,
outputs,
config.clone(),
);
};
Ok(())
}
}
#[cube(launch_unchecked)]
fn elemwise_fuse(
inputs: &GlobalArgs,
outputs: &mut GlobalArgs,
#[comptime] config: &ElemwiseConfig,
) {
let values = Registry::<Arg, Line<f32>>::new();
let args = comptime![Sequence::<Arg>::new()];
let pos = ABSOLUTE_POS;
let length = match comptime![config.ref_layout] {
Arg::Input(index, precision, _) => match comptime![precision] {
ElemwisePrecision::F32 => inputs.t_f32.index(index).len(),
ElemwisePrecision::F16 => inputs.t_f16.index(index).len(),
ElemwisePrecision::BF16 => inputs.t_bf16.index(index).len(),
ElemwisePrecision::U64 => inputs.t_u64.index(index).len(),
ElemwisePrecision::U32 => inputs.t_u32.index(index).len(),
ElemwisePrecision::U16 => inputs.t_u16.index(index).len(),
ElemwisePrecision::U8 => inputs.t_u8.index(index).len(),
ElemwisePrecision::I64 => inputs.t_i64.index(index).len(),
ElemwisePrecision::I32 => inputs.t_i32.index(index).len(),
ElemwisePrecision::I16 => inputs.t_i16.index(index).len(),
ElemwisePrecision::I8 => inputs.t_i8.index(index).len(),
_ => comptime![panic!("Unsupported precision {precision:?}")],
},
Arg::Output(index, precision, _) => match comptime![precision] {
ElemwisePrecision::F32 => outputs.t_f32.index(index).len(),
ElemwisePrecision::F16 => outputs.t_f16.index(index).len(),
ElemwisePrecision::BF16 => outputs.t_bf16.index(index).len(),
ElemwisePrecision::U64 => outputs.t_u64.index(index).len(),
ElemwisePrecision::U32 => outputs.t_u32.index(index).len(),
ElemwisePrecision::U16 => outputs.t_u16.index(index).len(),
ElemwisePrecision::U8 => outputs.t_u8.index(index).len(),
ElemwisePrecision::I64 => outputs.t_i64.index(index).len(),
ElemwisePrecision::I32 => outputs.t_i32.index(index).len(),
ElemwisePrecision::I16 => outputs.t_i16.index(index).len(),
ElemwisePrecision::I8 => outputs.t_i8.index(index).len(),
_ => comptime![panic!("Unsupported precision {precision:?}")],
},
_ => comptime![panic!("Invalid ref layout.")],
};
if pos < length {
fuse_on_write::<f32>(inputs, outputs, pos, values, args, config)
}
}