use std::mem::MaybeUninit;
use tenferro_cpu_basic::PooledUninitOutput;
use tenferro_tensor::{ActivationOp, DType, Tensor, TensorRead, TensorScalar};
use crate::buffer_pool::{BufferPool, PoolScalar};
use crate::materialize_tensor_read;
use crate::parallel::run_chunks;
use threadteam::Exec;
const CHUNK: usize = 2048;
pub(crate) fn fill_pooled<T: PoolScalar + TensorScalar>(
buffers: &BufferPool,
shape: &[usize],
fill: impl FnOnce(&mut [MaybeUninit<T>]),
) -> crate::Result<Tensor> {
let mut out = PooledUninitOutput::<T>::new(buffers, shape.to_vec())?;
fill(out.as_uninit_slice_mut());
unsafe { out.assume_init_recycled() }.map(Tensor::from_typed::<T>)
}
type Kernel<T> = fn(&[T], &mut [MaybeUninit<T>]) -> Result<(), elem_simd_math::LengthMismatch>;
fn kernel_f32(op: ActivationOp) -> Kernel<f32> {
use elem_simd_math::f32 as k;
match op {
ActivationOp::Sigmoid => k::sigmoid,
ActivationOp::Silu => k::silu,
ActivationOp::Softplus => k::softplus,
ActivationOp::Gelu => k::gelu,
ActivationOp::GeluTanh => k::gelu_tanh,
}
}
fn kernel_f64(op: ActivationOp) -> Kernel<f64> {
use elem_simd_math::f64 as k;
match op {
ActivationOp::Sigmoid => k::sigmoid,
ActivationOp::Silu => k::silu,
ActivationOp::Softplus => k::softplus,
ActivationOp::Gelu => k::gelu,
ActivationOp::GeluTanh => k::gelu_tanh,
}
}
fn apply_parallel<T: Copy + Send + Sync>(
kernel: Kernel<T>,
src: &[T],
dst: &mut [MaybeUninit<T>],
exec: Exec<'_>,
) {
let n = src.len();
run_chunks(
dst,
src,
CHUNK.min(n.max(1)),
|out, input| {
kernel(input, out).expect("equal-length chunks");
},
exec,
);
}
fn fused_slice<T: Copy + Send + Sync + PoolScalar + TensorScalar>(
buffers: &BufferPool,
kernel: Kernel<T>,
shape: &[usize],
data: &[T],
exec: Exec<'_>,
) -> crate::Result<Tensor> {
fill_pooled(buffers, shape, |out| {
apply_parallel(kernel, data, out, exec)
})
}
pub(crate) fn fused_activation_read_with_pool(
buffers: &mut BufferPool,
op: ActivationOp,
input: TensorRead<'_>,
mut exec: Exec<'_>,
) -> crate::Result<Option<Tensor>> {
let shape = input.shape().to_vec();
match input.dtype() {
DType::F32 => {
if let Ok(data) = input.as_slice::<f32>() {
return fused_slice(buffers, kernel_f32(op), &shape, data, exec).map(Some);
}
let tensor = materialize_tensor_read(buffers, op.label(), input, exec.reborrow())?;
fused_slice(
buffers,
kernel_f32(op),
&shape,
tensor.as_slice::<f32>()?,
exec,
)
.map(Some)
}
DType::F64 => {
if let Ok(data) = input.as_slice::<f64>() {
return fused_slice(buffers, kernel_f64(op), &shape, data, exec).map(Some);
}
let tensor = materialize_tensor_read(buffers, op.label(), input, exec.reborrow())?;
fused_slice(
buffers,
kernel_f64(op),
&shape,
tensor.as_slice::<f64>()?,
exec,
)
.map(Some)
}
_ => Ok(None),
}
}
#[cfg(test)]
mod tests;