use crate::{
DType, Error, Matrix, Result,
matrix::autograd,
runtime::{BufferBinding, ComputeDispatch, KernelId, PushConstant, SemanticDispatch},
};
pub fn mat_mul_nt(left: &Matrix, right: &Matrix) -> Result<Matrix> {
let contract = crate::core::operation::matrix::MAT_MUL_NT;
let kernel = KernelId::MatrixMatMulNtTiledF32;
let operation = contract.name();
let [m, k] = left.shape() else {
return Err(Error::invalid_argument(format!(
"{operation} requires rank-two inputs; left is {:?}",
left.shape()
)));
};
let [n, right_k] = right.shape() else {
return Err(Error::invalid_argument(format!(
"{operation} requires rank-two inputs; right is {:?}",
right.shape()
)));
};
if k != right_k {
return Err(Error::invalid_argument(format!(
"{operation} requires equal K extents; left is {:?}, right is {:?}",
left.shape(),
right.shape()
)));
}
if left.dtype() != DType::F32 || right.dtype() != DType::F32 {
return Err(Error::invalid_argument(format!(
"{operation} requires two F32 matrices; left is {}, right is {}",
left.dtype().token(),
right.dtype().token()
)));
}
let engine = left.engine_handle();
if !engine.same_as(right.engine_handle()) {
return Err(Error::invalid_argument(format!(
"{operation} inputs must belong to the same engine"
)));
}
let output_count = m
.checked_mul(*n)
.ok_or_else(|| Error::invalid_argument(format!("{operation} output size overflows usize")))?;
let m = u32::try_from(*m)
.map_err(|_| Error::invalid_argument(format!("{operation} M extent exceeds u32")))?;
let n = u32::try_from(*n)
.map_err(|_| Error::invalid_argument(format!("{operation} N extent exceeds u32")))?;
let k = u32::try_from(*k)
.map_err(|_| Error::invalid_argument(format!("{operation} K extent exceeds u32")))?;
for (label, count) in [
("left", left.element_count()),
("right", right.element_count()),
("output", output_count),
] {
u32::try_from(count).map_err(|_| {
Error::invalid_argument(format!("{operation} {label} element count exceeds u32"))
})?;
}
let output = Matrix::allocate(
engine,
vec![m as usize, n as usize],
output_count,
DType::F32,
)?;
if m != 0 && n != 0 && k != 0 {
let buffers = [
BufferBinding::read(left.storage()),
BufferBinding::read(right.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(m),
PushConstant::U32(n),
PushConstant::U32(k),
];
let inputs = [left, right];
let outputs = [&output];
engine.record_semantic(
ComputeDispatch {
kernel,
buffers: &buffers,
push_constants: &push_constants,
workgroups: kernel.output_workgroups(m, n),
},
SemanticDispatch {
contract,
inputs: &inputs,
outputs: &outputs,
attributes: &[],
},
)?;
autograd::record_mat_mul_nt(left, right, &output)?;
}
Ok(output)
}
pub(crate) fn mat_mul_nt_backward(
left: &Matrix,
right: &Matrix,
output_gradient: &Matrix,
) -> Result<(Matrix, Matrix)> {
let [rows, inner] = left.shape() else {
return Err(Error::internal("saved MatMulNt left rank changed"));
};
let [columns, right_inner] = right.shape() else {
return Err(Error::internal("saved MatMulNt right rank changed"));
};
if inner != right_inner {
return Err(Error::internal("saved MatMulNt inner extent changed"));
}
let output_gradient = output_gradient.reshape([1, *rows, *columns])?;
let left_batched = left.reshape([1, *rows, *inner])?;
let right_batched = right.reshape([1, *columns, *inner])?;
let left_gradient =
crate::ml::matrix::bmm(&output_gradient, &right_batched)?.reshape(left.shape().to_vec())?;
let right_gradient =
crate::ml::matrix::bmm_tn(&output_gradient, &left_batched)?.reshape(right.shape().to_vec())?;
Ok((left_gradient, right_gradient))
}