use core::result::Result;
use cubecl::{client::Client, prelude::TensorBinding, prelude::*};
#[cfg(feature = "benchmarks")]
pub mod eval;
pub mod definition;
mod kernel;
use crate::definition::{PoolError, PoolMode};
use crate::kernel::{
backward::{
adaptive_avg_pool2d_backward_launch, adaptive_avg_pool3d_backward_launch,
avg_pool2d_backward_launch, max_pool2d_with_indices_backward_launch,
},
forward::{
adaptive_avg_pool2d_launch, adaptive_avg_pool3d_launch, avg_pool2d_launch,
max_pool2d_launch, max_pool2d_with_indices_launch,
},
};
pub fn pool2d(
client: &Client,
input: TensorBinding,
output: TensorBinding,
mode: PoolMode<2>,
dtype: ElemType,
) -> Result<(), PoolError> {
validate_rank(input.shape.len(), output.shape.len(), 4)?;
validate_batch_channel_consistency(&input.shape, &output.shape)?;
match mode {
PoolMode::Max(max_options) => max_pool2d_launch(client, input, output, max_options, dtype),
PoolMode::Avg(avg_options) => avg_pool2d_launch(client, input, output, avg_options, dtype),
PoolMode::AdaptiveAvg(adaptive_avg_options) => {
adaptive_avg_pool2d_launch(client, input, output, adaptive_avg_options, dtype)
}
}
}
pub fn pool3d(
client: &Client,
input: TensorBinding,
output: TensorBinding,
mode: PoolMode<3>,
dtype: ElemType,
) -> Result<(), PoolError> {
validate_rank(input.shape.len(), output.shape.len(), 5)?;
validate_batch_channel_consistency(&input.shape, &output.shape)?;
match mode {
PoolMode::AdaptiveAvg(options) => {
validate_spatial_size(&input.shape, "input")?;
validate_spatial_size(&output.shape, "output")?;
validate_output_size(&output.shape, &options.output_size)?;
adaptive_avg_pool3d_launch(client, input, output, dtype)
}
_ => Err(PoolError::UnsupportedMode {
mode: format!("{0:?}", mode),
}),
}
}
pub fn pool2d_with_indices(
client: &Client,
input: TensorBinding,
output: TensorBinding,
indices: TensorBinding,
mode: PoolMode<2>,
dtype: ElemType,
) -> Result<(), PoolError> {
validate_rank(input.shape.len(), output.shape.len(), 4)?;
validate_rank(input.shape.len(), indices.shape.len(), 4)?;
validate_batch_channel_consistency(&input.shape, &output.shape)?;
validate_batch_channel_consistency(&input.shape, &indices.shape)?;
match mode {
PoolMode::Max(max_options) => {
max_pool2d_with_indices_launch(client, input, output, indices, max_options, dtype)
}
_ => Err(PoolError::UnsupportedMode {
mode: format!("{0:?}", mode),
}),
}
}
pub fn pool2d_backward(
client: &Client,
input: TensorBinding,
out_grad: TensorBinding,
in_grad: TensorBinding,
mode: PoolMode<2>,
dtype: ElemType,
) -> Result<(), PoolError> {
validate_rank(input.shape.len(), out_grad.shape.len(), 4)?;
validate_rank(input.shape.len(), in_grad.shape.len(), 4)?;
validate_batch_channel_consistency(&input.shape, &out_grad.shape)?;
validate_batch_channel_consistency(&input.shape, &in_grad.shape)?;
match mode {
PoolMode::Avg(avg_options) => {
avg_pool2d_backward_launch(client, input, out_grad, in_grad, avg_options, dtype)
}
PoolMode::AdaptiveAvg(adaptive_avg_options) => adaptive_avg_pool2d_backward_launch(
client,
input,
out_grad,
in_grad,
adaptive_avg_options,
dtype,
),
_ => Err(PoolError::UnsupportedMode {
mode: format!("{0:?}", mode),
}),
}
}
pub fn pool3d_backward(
client: &Client,
input: TensorBinding,
out_grad: TensorBinding,
in_grad: TensorBinding,
mode: PoolMode<3>,
dtype: ElemType,
) -> Result<(), PoolError> {
validate_rank(input.shape.len(), out_grad.shape.len(), 5)?;
validate_rank(input.shape.len(), in_grad.shape.len(), 5)?;
validate_batch_channel_consistency(&input.shape, &out_grad.shape)?;
validate_batch_channel_consistency(&input.shape, &in_grad.shape)?;
if input.shape != in_grad.shape {
return Err(PoolError::InputGradientShapeMismatch {
expected: input.shape.to_vec(),
actual: in_grad.shape.to_vec(),
});
}
match mode {
PoolMode::AdaptiveAvg(options) => {
validate_spatial_size(&input.shape, "input")?;
validate_spatial_size(&out_grad.shape, "output gradient")?;
validate_output_size(&out_grad.shape, &options.output_size)?;
adaptive_avg_pool3d_backward_launch(client, out_grad, in_grad, dtype)
}
_ => Err(PoolError::UnsupportedMode {
mode: format!("{0:?}", mode),
}),
}
}
#[allow(clippy::too_many_arguments)]
pub fn pool2d_with_indices_backward(
client: &Client,
input: TensorBinding,
out_grad: TensorBinding,
indices: TensorBinding,
in_grad: TensorBinding,
mode: PoolMode<2>,
dtype: ElemType,
indices_dtype: ElemType,
) -> Result<(), PoolError> {
validate_rank(input.shape.len(), out_grad.shape.len(), 4)?;
validate_rank(input.shape.len(), in_grad.shape.len(), 4)?;
validate_rank(input.shape.len(), indices.shape.len(), 4)?;
validate_batch_channel_consistency(&input.shape, &out_grad.shape)?;
validate_batch_channel_consistency(&input.shape, &in_grad.shape)?;
validate_batch_channel_consistency(&input.shape, &indices.shape)?;
match mode {
PoolMode::Max(max_options) => max_pool2d_with_indices_backward_launch(
client,
input,
out_grad,
indices,
in_grad,
max_options,
dtype,
indices_dtype,
),
_ => Err(PoolError::UnsupportedMode {
mode: format!("{0:?}", mode),
}),
}
}
fn validate_rank(
input_rank: usize,
output_rank: usize,
expected_rank: usize,
) -> Result<(), PoolError> {
if input_rank != expected_rank || output_rank != expected_rank {
return Err(PoolError::InvalidRank {
input: input_rank,
output: output_rank,
});
}
Ok(())
}
fn validate_batch_channel_consistency(
input_shape: &[usize],
output_shape: &[usize],
) -> Result<(), PoolError> {
if input_shape[0] != output_shape[0] {
return Err(PoolError::BatchMismatch {
input: input_shape[0],
output: output_shape[0],
});
}
let input_channel = input_shape[input_shape.len() - 1];
let output_channel = output_shape[output_shape.len() - 1];
if input_channel != output_channel {
return Err(PoolError::ChannelMismatch {
input: input_channel,
output: output_channel,
});
}
Ok(())
}
fn validate_output_size(output_shape: &[usize], expected: &[usize; 3]) -> Result<(), PoolError> {
let actual = &output_shape[1..4];
if actual != expected {
return Err(PoolError::OutputSizeMismatch {
expected: expected.to_vec(),
actual: actual.to_vec(),
});
}
Ok(())
}
fn validate_spatial_size(shape: &[usize], tensor: &'static str) -> Result<(), PoolError> {
let actual = &shape[1..4];
if actual.contains(&0) {
return Err(PoolError::InvalidSpatialSize {
tensor,
actual: actual.to_vec(),
});
}
Ok(())
}