use burn_backend::cubecl::dtype_to_storage_type;
use burn_backend::ops::ConvOptions;
use cubecl::{
ir::ElemType,
tune::{LocalTuner, Tunable, TunableSet, anchor, local_tuner},
};
use cubek::convolution::{AcceleratedTileKind, DepthwiseStrategy, DepthwiseTiling};
use crate::{
CubeAutotuneKey, CubeTuneId,
kernel::conv::{
ConvAutotuneKey, conv_direct, conv_im2col_1x1, forward::depthwise::conv_depthwise,
forward::implicit_gemm::*,
},
tensor::CubeTensor,
};
const DEPTHWISE_8X4_LINED: DepthwiseStrategy = DepthwiseStrategy::Fixed(DepthwiseTiling {
rows: 8,
cols: 4,
chans: 1,
lines: 2,
});
const DEPTHWISE_2X4_SCALAR: DepthwiseStrategy = DepthwiseStrategy::Fixed(DepthwiseTiling {
rows: 2,
cols: 4,
chans: 1,
lines: 1,
});
const DEPTHWISE_8X2_LINED: DepthwiseStrategy = DepthwiseStrategy::Fixed(DepthwiseTiling {
rows: 8,
cols: 2,
chans: 1,
lines: 2,
});
const DEPTHWISE_4X2_SCALAR: DepthwiseStrategy = DepthwiseStrategy::Fixed(DepthwiseTiling {
rows: 4,
cols: 2,
chans: 1,
lines: 1,
});
pub fn conv_autotune<const N: usize>(
input: CubeTensor,
weight: CubeTensor,
bias: Option<CubeTensor>,
options: ConvOptions<N>,
) -> CubeTensor {
let client = input.client.clone();
static TUNER: LocalTuner<CubeAutotuneKey, CubeTuneId> = local_tuner!();
let tune_id = CubeTuneId::new(&input.client, &input.device);
let tunables = TUNER.init(&tune_id, || {
TunableSet::new(create_key::<N>, create_conv_input::<N>)
.with(Tunable::new(
"conv_direct",
|(input, weight, bias, options)| conv_direct::<N>(input, weight, bias, options),
))
.with(Tunable::new(
"conv_depthwise",
|(input, weight, bias, options)| {
conv_depthwise::<N>(input, weight, bias, options, DepthwiseStrategy::Routine)
},
))
.with(Tunable::new(
"conv_depthwise_8x4_lined",
|(input, weight, bias, options)| {
conv_depthwise::<N>(input, weight, bias, options, DEPTHWISE_8X4_LINED)
},
))
.with(Tunable::new(
"conv_depthwise_2x4_scalar",
|(input, weight, bias, options)| {
conv_depthwise::<N>(input, weight, bias, options, DEPTHWISE_2X4_SCALAR)
},
))
.with(Tunable::new(
"conv_depthwise_8x2_lined",
|(input, weight, bias, options)| {
conv_depthwise::<N>(input, weight, bias, options, DEPTHWISE_8X2_LINED)
},
))
.with(Tunable::new(
"conv_depthwise_4x2_scalar",
|(input, weight, bias, options)| {
conv_depthwise::<N>(input, weight, bias, options, DEPTHWISE_4X2_SCALAR)
},
))
.with(Tunable::new(
"conv_im2col_1x1",
|(input, weight, bias, options)| conv_im2col_1x1::<N>(input, weight, bias, options),
))
.with(Tunable::new(
"simple_sync_cmma",
|(input, weight, bias, options)| {
conv_gemm_simple_sync(input, weight, bias, options, AcceleratedTileKind::Cmma)
},
))
.with(Tunable::new(
"simple_sync_mma",
|(input, weight, bias, options)| {
conv_gemm_simple_sync(input, weight, bias, options, AcceleratedTileKind::Mma)
},
))
.with(Tunable::new(
"simple_async_cmma",
|(input, weight, bias, options)| {
conv_gemm_simple_async(input, weight, bias, options, AcceleratedTileKind::Cmma)
},
))
.with(Tunable::new(
"simple_async_mma",
|(input, weight, bias, options)| {
conv_gemm_simple_async(input, weight, bias, options, AcceleratedTileKind::Mma)
},
))
.with(Tunable::new(
"simple_tma_cmma",
|(input, weight, bias, options)| {
conv_gemm_simple_tma(input, weight, bias, options, AcceleratedTileKind::Cmma)
},
))
.with(Tunable::new(
"simple_tma_mma",
|(input, weight, bias, options)| {
conv_gemm_simple_tma(input, weight, bias, options, AcceleratedTileKind::Mma)
},
))
});
TUNER.execute(&tune_id, &client, tunables, (input, weight, bias, options))
}
pub fn create_conv_input<const N: usize>(
_key: &CubeAutotuneKey,
(input, weights, bias, options): &(CubeTensor, CubeTensor, Option<CubeTensor>, ConvOptions<N>),
) -> (CubeTensor, CubeTensor, Option<CubeTensor>, ConvOptions<N>) {
(
input.clone(),
weights.clone(),
bias.clone(),
options.clone(),
)
}
fn create_key<const N: usize>(
(input, weights, bias, options): &(CubeTensor, CubeTensor, Option<CubeTensor>, ConvOptions<N>),
) -> CubeAutotuneKey {
let dtype = input.dtype;
let rank = input.meta.shape().num_dims();
let dim_c = rank - 1;
let batch_size = input.meta.shape()[0];
let in_channels = input.meta.shape()[dim_c];
let out_channels = weights.meta.shape()[0];
let kernel_size = weights.meta.shape()[1..dim_c].to_vec();
let in_shape = input.meta.shape()[1..dim_c]
.iter()
.map(|shape| anchor(*shape, None, None, None))
.collect();
let ConvOptions {
stride,
padding,
dilation,
groups,
} = options.clone();
let lhs_stride_align = if input.meta.strides()[dim_c] == 1 {
stride_align(input.meta.strides(), dtype_to_storage_type(input.dtype))
} else {
0
};
let lhs_shape_align = pow2_factor(in_channels).min(lhs_stride_align);
let rhs_stride_align = if weights.meta.strides()[dim_c] == 1 {
stride_align(weights.meta.strides(), dtype_to_storage_type(weights.dtype))
} else {
0
};
let rhs_shape_align = pow2_factor(in_channels).min(rhs_stride_align);
CubeAutotuneKey::Conv(ConvAutotuneKey::new(
kernel_size,
stride.to_vec(),
padding.to_vec(),
dilation.to_vec(),
groups,
in_channels,
out_channels,
in_shape,
batch_size,
bias.is_some(),
dtype,
lhs_shape_align,
lhs_stride_align,
rhs_shape_align,
rhs_stride_align,
))
}
const MAX_STRIDE_FACTOR: u32 = 10;
fn stride_align(strides: &[usize], elem: ElemType) -> u8 {
let max = MAX_STRIDE_FACTOR;
let dim_c = strides.len() - 1;
let factor = strides[..dim_c]
.iter()
.map(|it| (*it * elem.size_bits()) / 8)
.map(|it| it.trailing_zeros())
.min()
.unwrap_or(max);
factor.min(max) as u8
}
fn pow2_factor(axis: usize) -> u8 {
axis.trailing_zeros().min(4) as u8
}