#[burn_tensor_testgen::testgen(matmul)]
mod tests {
use super::*;
use burn_jit::kernel::matmul::{matmul, MatmulStrategy};
use burn_tensor::{Shape, Tensor, TensorPrimitive};
mod simple {
use super::*;
#[test]
pub fn straightforward() {
test_with_params::<2, 2>(1, 2, 1, 1, 1);
}
#[test]
pub fn shapes_smaller_than_blocks() {
test_with_params::<16, 16>(8, 8, 8, 1, 1);
}
#[test]
pub fn n_smaller_than_m() {
test_with_params::<2, 2>(8, 8, 3, 1, 1);
}
#[test]
pub fn m_smaller_than_n() {
test_with_params::<2, 2>(3, 8, 8, 1, 1);
}
#[test]
pub fn k_smaller_than_m_n() {
test_with_params::<2, 2>(8, 3, 8, 1, 1);
}
#[test]
pub fn k_larger_than_m_n() {
test_with_params::<2, 2>(8, 48, 8, 1, 1);
}
#[test]
pub fn multibatch_1_dim() {
test_with_params::<2, 2>(8, 8, 8, 3, 1);
}
#[test]
pub fn multibatch_2_dims() {
test_with_params::<2, 2>(8, 8, 8, 3, 4);
}
#[test]
pub fn blocks_divide_shapes_unevenly() {
test_with_params::<3, 3>(7, 7, 7, 1, 1);
}
#[test]
fn swapped_batches_no_padding() {
let strategy = MatmulStrategy::Simple {
grid_x: 2,
grid_y: 2,
};
let swap = [0, 1];
let shape_lhs = [3, 2, 4, 4];
let shape_rhs = [3, 2, 4, 4];
same_as_reference_swapped_dims(strategy, swap, swap, shape_lhs, shape_rhs);
}
#[test]
fn swapped_row_col_no_padding() {
let strategy = MatmulStrategy::Simple {
grid_x: 2,
grid_y: 2,
};
let swap_lhs = [0, 0];
let swap_rhs = [2, 3];
let shape_lhs = [3, 2, 4, 4];
let shape_rhs = [3, 2, 4, 4];
same_as_reference_swapped_dims(strategy, swap_lhs, swap_rhs, shape_lhs, shape_rhs);
}
#[test]
fn swapped_row_with_batch_no_padding() {
let strategy = MatmulStrategy::Simple {
grid_x: 2,
grid_y: 2,
};
let swap_lhs = [0, 3];
let swap_rhs = [0, 2];
let shape_lhs = [4, 4, 4, 4];
let shape_rhs = [4, 4, 4, 4];
same_as_reference_swapped_dims(strategy, swap_lhs, swap_rhs, shape_lhs, shape_rhs);
}
fn test_with_params<const WORKGROUP_SIZE_X: usize, const WORKGROUP_SIZE_Y: usize>(
m: usize,
k: usize,
n: usize,
batch_1: usize,
batch_2: usize,
) {
let shape_lhs = [batch_1, batch_2, m, k];
let shape_rhs = [batch_1, batch_2, k, n];
same_as_reference(
MatmulStrategy::Simple {
grid_x: WORKGROUP_SIZE_X,
grid_y: WORKGROUP_SIZE_Y,
},
shape_lhs,
shape_rhs,
);
}
}
mod padding {
use super::*;
use burn_jit::kernel::matmul::padding::{crop, pad_round};
use burn_tensor::backend::Backend;
fn padding_already_round_should_have_same_shape() {
let row = 10;
let row_divisor = 5;
let col = 12;
let col_divisor = 3;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let expected_shape = [row, col].into();
let padded =
pad_round(tensor.into_primitive().tensor(), row_divisor, col_divisor).into_tensor();
assert!(padded.shape == expected_shape);
}
#[test]
fn padding_already_round_should_have_same_values() {
let row = 10;
let row_divisor = 5;
let col = 12;
let col_divisor = 3;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let padded = pad_round(
tensor.clone().into_primitive().tensor(),
row_divisor,
col_divisor,
);
let padded = TestTensor::from_primitive(TensorPrimitive::Float((padded.into_tensor())));
padded.into_data().assert_approx_eq(&tensor.into_data(), 3);
}
#[test]
fn padding_not_round_should_have_rounded_shape() {
let row = 10;
let row_divisor = 6;
let col = 12;
let col_divisor = 5;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let expected_shape = [12, 15].into();
let padded =
pad_round(tensor.into_primitive().tensor(), row_divisor, col_divisor).into_tensor();
assert!(padded.shape == expected_shape);
}
#[test]
fn padding_not_round_should_have_same_values() {
let row = 10;
let row_divisor = 6;
let col = 12;
let col_divisor = 5;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let padded = pad_round(
tensor.clone().into_primitive().tensor(),
row_divisor,
col_divisor,
)
.into_tensor();
let padded = TestTensor::from_primitive(TensorPrimitive::Float(padded)).to_data();
let padded = padded
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
let tensor = tensor.into_data();
let tensor = tensor
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
for i in 0..row {
for j in 0..col {
assert!(padded[i * 15 + j] == tensor[i * col + j]);
}
}
}
#[test]
fn padding_not_round_should_have_zero_padding() {
let row = 10;
let row_divisor = 6;
let col = 12;
let col_divisor = 5;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let padded =
pad_round(tensor.into_primitive().tensor(), row_divisor, col_divisor).into_tensor();
let padded = TestTensor::from_primitive(TensorPrimitive::Float(padded)).to_data();
let padded = padded
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
for i in 0..row {
for j in col..15 {
assert!(padded[i * 15 + j] == 0.0);
}
}
for i in row..12 {
for j in 0..15 {
assert!(padded[i * 15 + j] == 0.0);
}
}
}
#[test]
fn padding_works_with_batch() {
let row = 10;
let row_divisor = 4;
let col = 12;
let col_divisor = 5;
let tensor = TestTensor::random(
[2, 3, row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let expected_shape = [2, 3, 12, 15].into();
let padded =
pad_round(tensor.into_primitive().tensor(), row_divisor, col_divisor).into_tensor();
assert!(padded.shape == expected_shape);
}
#[test]
fn padding_with_row_divisor_larger_than_row() {
let row = 10;
let row_divisor = 32;
let col = 4;
let col_divisor = 3;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let expected_shape = [row_divisor, 2 * col_divisor].into();
let padded =
pad_round(tensor.into_primitive().tensor(), row_divisor, col_divisor).into_tensor();
assert!(padded.shape == expected_shape);
}
#[test]
fn padding_with_row_divisor_equal_to_row_but_col_must_be_padded() {
let row = 32;
let row_divisor = 32;
let col = 4;
let col_divisor = 64;
let tensor = TestTensor::random(
[row, col],
burn_tensor::Distribution::Default,
&Default::default(),
);
let expected_shape = [32, 64].into();
let padded =
pad_round(tensor.into_primitive().tensor(), row_divisor, col_divisor).into_tensor();
assert!(padded.shape == expected_shape);
}
#[test]
fn crop_same_shape_should_be_unchanged_shape() {
let row = 10;
let col = 12;
let device = Default::default();
let tensor =
TestTensor::random([row, col], burn_tensor::Distribution::Default, &device);
let expected_shape = [row, col].into();
let unpadded = crop(
tensor.clone().into_primitive().tensor(),
TestTensor::empty([row, col], &device)
.into_primitive()
.tensor(),
);
assert!(unpadded.shape == expected_shape);
}
#[test]
fn crop_same_shape_should_have_unchanged_values() {
let row = 10;
let col = 12;
let device = Default::default();
let tensor =
TestTensor::random([row, col], burn_tensor::Distribution::Default, &device);
let unpadded = crop(
tensor.clone().into_primitive().tensor(),
TestTensor::empty([row, col], &device)
.into_primitive()
.tensor(),
);
let unpadded = TestTensor::from_primitive(TensorPrimitive::Float(unpadded)).to_data();
let unpadded = unpadded
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
let tensor = tensor.into_data();
let tensor = tensor
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
for i in 0..row {
for j in 0..col {
assert!(unpadded[i * col + j] == tensor[i * col + j]);
}
}
}
#[test]
fn crop_should_decrease_shape() {
let row = 10;
let keep_rows = 8;
let col = 12;
let keep_cols = 10;
let device = Default::default();
let tensor =
TestTensor::random([row, col], burn_tensor::Distribution::Default, &device);
let expected_shape = [keep_rows, keep_cols].into();
let unpadded = crop(
tensor.clone().into_primitive().tensor(),
TestTensor::empty([keep_rows, keep_cols], &device)
.into_primitive()
.tensor(),
);
assert!(unpadded.shape == expected_shape);
}
#[test]
fn crop_should_keep_same_values() {
let row = 4;
let keep_rows = 3;
let col = 4;
let keep_cols = 3;
let device = Default::default();
let tensor =
TestTensor::random([row, col], burn_tensor::Distribution::Default, &device);
let unpadded = crop(
tensor.clone().into_primitive().tensor(),
TestTensor::empty([keep_rows, keep_cols], &device)
.into_primitive()
.tensor(),
);
let unpadded = TestTensor::from_primitive(TensorPrimitive::Float(unpadded)).to_data();
let unpadded = unpadded
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
let tensor = tensor.into_data();
let tensor = tensor
.as_slice::<<TestBackend as Backend>::FloatElem>()
.unwrap();
for i in 0..keep_rows {
for j in 0..keep_cols {
assert!(unpadded[i * keep_cols + j] == tensor[i * col + j]);
}
}
}
}
fn same_as_reference<const D: usize, S>(strategy: MatmulStrategy, shape_lhs: S, shape_rhs: S)
where
S: Into<Shape<D>>,
{
let ref_tensor_device = Default::default();
let x = ReferenceTensor::random(
shape_lhs,
burn_tensor::Distribution::Uniform(-1.0, 1.0),
&ref_tensor_device,
);
let y = ReferenceTensor::random(
shape_rhs,
burn_tensor::Distribution::Uniform(-1.0, 1.0),
&ref_tensor_device,
);
let test_tensor_device = Default::default();
let x_jit = TestTensor::from_data(x.to_data(), &test_tensor_device);
let y_jit = TestTensor::from_data(y.to_data(), &test_tensor_device);
let z_reference = x.matmul(y);
let z = Tensor::<TestBackend, D>::from_primitive(TensorPrimitive::Float(matmul(
x_jit.into_primitive().tensor(),
y_jit.into_primitive().tensor(),
strategy,
)));
z_reference.into_data().assert_approx_eq(&z.into_data(), 3);
}
fn same_as_reference_swapped_dims<const D: usize, S>(
strategy: MatmulStrategy,
swap_lhs: [usize; 2],
swap_rhs: [usize; 2],
shape_lhs: S,
shape_rhs: S,
) where
S: Into<Shape<D>>,
{
let x = ReferenceTensor::random(
shape_lhs,
burn_tensor::Distribution::Uniform(-1.0, 1.0),
&Default::default(),
);
let y = ReferenceTensor::random(
shape_rhs,
burn_tensor::Distribution::Uniform(-1.0, 1.0),
&Default::default(),
);
let x_jit = TestTensor::from_data(x.to_data(), &Default::default())
.swap_dims(swap_lhs[0], swap_lhs[1]);
let y_jit = TestTensor::from_data(y.to_data(), &Default::default())
.swap_dims(swap_rhs[0], swap_rhs[1]);
let z_reference = x
.swap_dims(swap_lhs[0], swap_lhs[1])
.matmul(y.swap_dims(swap_rhs[0], swap_rhs[1]));
let z = Tensor::<TestBackend, D>::from_primitive(TensorPrimitive::Float(matmul(
x_jit.into_primitive().tensor(),
y_jit.into_primitive().tensor(),
strategy,
)));
z_reference.into_data().assert_approx_eq(&z.into_data(), 3);
}
}