use alloc::vec;
use alloc::vec::Vec;
use crate::tensor::FloatTensor;
use crate::{Backend, TensorMetadata};
use burn_std::{MatmulTransformAction, MatmulTransformAnalysis, MatmulTransformPolicy, Shape};
pub(crate) fn linear<B: Backend>(
x: FloatTensor<B>,
weight: FloatTensor<B>,
bias: Option<FloatTensor<B>>,
) -> FloatTensor<B> {
let shape = x.shape();
let ndims = shape.num_dims();
let analysis = MatmulTransformAnalysis::from_shapes(&shape, &weight.shape());
match MatmulTransformPolicy::default().action(&analysis) {
MatmulTransformAction::MergeBatches { rows } => {
let d_input = shape[ndims - 1];
let d_output = weight.shape()[1];
let x = B::float_reshape(x, Shape::new([rows, d_input]));
let mut output = B::float_matmul(x, weight);
if let Some(bias) = bias {
let bias = B::float_reshape(bias, Shape::new([1, d_output]));
output = B::float_add(output, bias);
}
let out_dims: Vec<usize> = (0..ndims - 1).map(|i| shape[i]).chain([d_output]).collect();
B::float_reshape(output, Shape::from(out_dims))
}
MatmulTransformAction::Keep => {
let weight = unsqueeze_leading::<B>(weight, ndims);
let output = B::float_matmul(x, weight);
match bias {
Some(bias) => {
let bias = unsqueeze_leading::<B>(bias, ndims);
B::float_add(output, bias)
}
None => output,
}
}
}
}
fn unsqueeze_leading<B: Backend>(tensor: FloatTensor<B>, target_ndims: usize) -> FloatTensor<B> {
let shape = tensor.shape();
let ndims = shape.num_dims();
if ndims >= target_ndims {
return tensor;
}
let mut new_dims = vec![1usize; target_ndims - ndims];
for i in 0..ndims {
new_dims.push(shape[i]);
}
B::float_reshape(tensor, Shape::from(new_dims))
}
fn flatten_leading<B: Backend>(tensor: FloatTensor<B>) -> FloatTensor<B> {
let shape = tensor.shape();
let ndims = shape.num_dims();
if ndims == 2 {
return tensor;
}
let d_last = shape[ndims - 1];
let num_rows = shape.num_elements() / d_last;
B::float_reshape(tensor, Shape::new([num_rows, d_last]))
}
pub(crate) fn linear_x_backward<B: Backend>(
weight: FloatTensor<B>,
output_grad: FloatTensor<B>,
) -> FloatTensor<B> {
let weight = B::float_swap_dims(weight, 0, 1);
linear::<B>(output_grad, weight, None)
}
pub(crate) fn linear_weight_backward<B: Backend>(
x: FloatTensor<B>,
output_grad: FloatTensor<B>,
) -> FloatTensor<B> {
let x = B::float_swap_dims(flatten_leading::<B>(x), 0, 1);
let output_grad = flatten_leading::<B>(output_grad);
B::float_matmul(x, output_grad)
}
pub(crate) fn linear_bias_backward<B: Backend>(output_grad: FloatTensor<B>) -> FloatTensor<B> {
let shape = output_grad.shape();
let d_output = shape[shape.num_dims() - 1];
let grad = flatten_leading::<B>(output_grad);
let grad = B::float_sum_dim(grad, 0);
B::float_reshape(grad, Shape::new([d_output]))
}