use crate::{
gpu::{Gpu, Result},
layout::count_elements,
reduce_kernel,
};
use cubecl::std::tensor::TensorHandle;
use cubek_reduce::components::instructions::ReduceOperationConfig;
pub fn sum(gpu: &Gpu, input: &TensorHandle, axis: Option<usize>) -> Result<TensorHandle> {
reduce(gpu, input, axis, ReduceOperationConfig::Sum)
}
pub fn max(gpu: &Gpu, input: &TensorHandle, axis: Option<usize>) -> Result<TensorHandle> {
reduce(gpu, input, axis, ReduceOperationConfig::Max)
}
pub fn mean(gpu: &Gpu, input: &TensorHandle) -> Result<TensorHandle> {
use crate::elementwise::{Arithmetic, Operand, apply};
let n = count_elements(input.shape())?;
if n == 0 {
return Err("cannot compute mean of an empty matrix".into());
}
let total = sum(gpu, input, None)?;
apply(
gpu,
&total,
Operand::Scalar(n as f64),
Arithmetic::Divide,
false,
)
}
fn reduce(
gpu: &Gpu,
input: &TensorHandle,
axis: Option<usize>,
operation: ReduceOperationConfig,
) -> Result<TensorHandle> {
if input.handle.service != gpu.client.service_id() {
return Err("matrix belongs to another device".into());
}
crate::gpu::check_dtype(gpu, input.dtype)?;
if let Some(axis) = axis {
if axis >= input.shape().len() {
return Err(format!(
"axis {axis} is out of bounds for a {}-dimensional array",
input.shape().len()
)
.into());
}
return reduce_axis(gpu, input, axis, operation);
}
let mut output = input.clone();
if output.shape().is_empty() {
output = TensorHandle::new_contiguous(vec![1], output.handle, output.dtype);
}
while !output.shape().is_empty() {
let axis = output
.strides()
.iter()
.enumerate()
.min_by_key(|(_, stride)| **stride)
.unwrap()
.0;
output = reduce_axis(gpu, &output, axis, operation)?;
}
Ok(output)
}
fn reduce_axis(
gpu: &Gpu,
input: &TensorHandle,
axis: usize,
operation: ReduceOperationConfig,
) -> Result<TensorHandle> {
let mut shape = input.shape().to_vec();
shape[axis] = 1;
let size = count_elements(&shape)?;
let bytes = size
.checked_mul(input.dtype.size())
.ok_or("byte size overflow")?;
let mut output = TensorHandle::new_contiguous(shape, gpu.client.empty(bytes), input.dtype);
if size != 0 {
if input.shape()[axis] == 0 {
let identity = if matches!(operation, ReduceOperationConfig::Max) {
f64::NEG_INFINITY
} else {
0.0
};
reduce_kernel::launch_fill(gpu, &output, identity);
} else {
reduce_kernel::launch(gpu, input, &output, axis, operation)?;
}
}
output.metadata.shape.remove(axis);
output.metadata.strides.remove(axis);
Ok(output)
}