use crate::{
DType, Error, Matrix, OpAttribute, Result,
ml::autograd,
runtime::{BufferBinding, ComputeDispatch, KernelId, PushConstant, SemanticDispatch},
};
pub fn swiglu(gate: &Matrix, up: &Matrix) -> Result<Matrix> {
let contract = crate::core::operation::ml::SWIGLU;
let operation = contract.name();
let engine = gate.engine_handle();
for matrix in &[gate, up] {
if matrix.dtype() != DType::F32 {
return Err(Error::invalid_argument(format!(
"{operation} requires F32 matrices; found {}",
matrix.dtype().token()
)));
}
if !engine.same_as(matrix.engine_handle()) {
return Err(Error::invalid_argument(format!(
"{operation} inputs must belong to the same engine"
)));
}
}
if gate.shape() != up.shape() {
return Err(Error::invalid_argument(format!(
"{operation} requires equal input shapes"
)));
}
let element_count = u32::try_from(gate.num_elements())
.map_err(|_| Error::invalid_argument(format!("{operation} element count exceeds u32")))?;
let output = Matrix::allocate(
engine,
gate.shape().to_vec(),
gate.num_elements(),
DType::F32,
)?;
if element_count != 0 {
let buffers = [
BufferBinding::read(gate.storage()),
BufferBinding::read(up.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [PushConstant::U32(element_count)];
engine.record_semantic(
ComputeDispatch {
kernel: KernelId::MlSwigluF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: KernelId::MlSwigluF32.linear_workgroups(element_count),
},
SemanticDispatch {
contract,
inputs: &[gate, up],
outputs: &[&output],
attributes: &[],
},
)?;
}
autograd::record_swiglu(gate, up, &output)?;
Ok(output)
}
pub(in crate::ml) fn swiglu_backward(
gate: &Matrix,
up: &Matrix,
output_gradient: &Matrix,
) -> Result<(Matrix, Matrix)> {
let contract = crate::core::operation::ml::SWIGLU_BACKWARD;
let operation = contract.name();
let engine = gate.engine_handle();
for matrix in &[gate, up, output_gradient] {
if matrix.dtype() != DType::F32 {
return Err(Error::invalid_argument(format!(
"{operation} requires F32 matrices; found {}",
matrix.dtype().token()
)));
}
if !engine.same_as(matrix.engine_handle()) {
return Err(Error::invalid_argument(format!(
"{operation} inputs must belong to the same engine"
)));
}
}
if gate.shape() != up.shape() || gate.shape() != output_gradient.shape() {
return Err(Error::invalid_argument(format!(
"{operation} requires equal input and gradient shapes"
)));
}
let element_count = u32::try_from(gate.num_elements())
.map_err(|_| Error::invalid_argument(format!("{operation} element count exceeds u32")))?;
let gate_gradient = Matrix::allocate(
engine,
gate.shape().to_vec(),
gate.num_elements(),
DType::F32,
)?;
let up_gradient = Matrix::allocate(
engine,
gate.shape().to_vec(),
gate.num_elements(),
DType::F32,
)?;
if element_count != 0 {
let buffers = [
BufferBinding::read(gate.storage()),
BufferBinding::read(up.storage()),
BufferBinding::read(output_gradient.storage()),
BufferBinding::write(gate_gradient.storage()),
BufferBinding::write(up_gradient.storage()),
];
let push_constants = [PushConstant::U32(element_count)];
engine.record_semantic(
ComputeDispatch {
kernel: KernelId::MlSwigluBackwardF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: KernelId::MlSwigluBackwardF32.linear_workgroups(element_count),
},
SemanticDispatch {
contract,
inputs: &[gate, up, output_gradient],
outputs: &[&gate_gradient, &up_gradient],
attributes: &[],
},
)?;
}
Ok((gate_gradient, up_gradient))
}
pub fn silu_mul(input: &Matrix, intermediate_size: usize) -> Result<Matrix> {
let contract = crate::core::operation::ml::SILU_MUL;
let operation = contract.name();
let engine = input.engine_handle();
if input.dtype() != DType::F32 {
return Err(Error::invalid_argument(format!(
"{operation} requires F32 matrices; found {}",
input.dtype().token()
)));
}
let Some(last_extent) = input.shape().last() else {
return Err(Error::invalid_argument(format!(
"{operation} input must have at least one dimension"
)));
};
let concatenated = intermediate_size.checked_mul(2).ok_or_else(|| {
Error::invalid_argument(format!("{operation} intermediate size overflows usize"))
})?;
if intermediate_size == 0 || *last_extent != concatenated {
return Err(Error::invalid_argument(format!(
"{operation} requires final input extent 2 * intermediate_size"
)));
}
let output_count = input.num_elements() / 2;
let output_count_u32 = u32::try_from(output_count).map_err(|_| {
Error::invalid_argument(format!("{operation} output element count exceeds u32"))
})?;
let intermediate_u32 = u32::try_from(intermediate_size)
.map_err(|_| Error::invalid_argument(format!("{operation} intermediate size exceeds u32")))?;
let mut output_shape = input.shape().to_vec();
*output_shape.last_mut().expect("validated nonempty shape") = intermediate_size;
let output = Matrix::allocate(engine, output_shape, output_count, DType::F32)?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let attributes = [OpAttribute::SignedInteger {
name: "intermediate_size".into(),
value: i64::try_from(intermediate_size)
.map_err(|_| Error::invalid_argument(format!("{operation} intermediate size exceeds i64")))?,
}];
let push_constants = [
PushConstant::U32(output_count_u32),
PushConstant::U32(intermediate_u32),
];
engine.record_semantic(
ComputeDispatch {
kernel: KernelId::MlSiluMulF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: KernelId::MlSiluMulF32.linear_workgroups(output_count_u32),
},
SemanticDispatch {
contract,
inputs: &[input],
outputs: &[&output],
attributes: &attributes,
},
)?;
autograd::record_silu_mul(input, intermediate_size, &output)?;
Ok(output)
}
pub(in crate::ml) fn silu_mul_backward(
input: &Matrix,
output_gradient: &Matrix,
intermediate_size: usize,
) -> Result<Matrix> {
let contract = crate::core::operation::ml::SILU_MUL_BACKWARD;
let operation = contract.name();
let engine = input.engine_handle();
for matrix in &[input, output_gradient] {
if matrix.dtype() != DType::F32 {
return Err(Error::invalid_argument(format!(
"{operation} requires F32 matrices; found {}",
matrix.dtype().token()
)));
}
if !engine.same_as(matrix.engine_handle()) {
return Err(Error::invalid_argument(format!(
"{operation} inputs must belong to the same engine"
)));
}
}
let Some(last_extent) = input.shape().last() else {
return Err(Error::invalid_argument(format!(
"{operation} input must have at least one dimension"
)));
};
let concatenated = intermediate_size.checked_mul(2).ok_or_else(|| {
Error::invalid_argument(format!("{operation} intermediate size overflows usize"))
})?;
let mut expected_gradient_shape = input.shape().to_vec();
if intermediate_size == 0 || *last_extent != concatenated {
return Err(Error::invalid_argument(format!(
"{operation} requires final input extent 2 * intermediate_size"
)));
}
*expected_gradient_shape
.last_mut()
.expect("validated nonempty shape") = intermediate_size;
if output_gradient.shape() != expected_gradient_shape {
return Err(Error::invalid_argument(format!(
"{operation} output gradient shape must equal the half-width output shape"
)));
}
let output_count = output_gradient.num_elements();
let output_count_u32 = u32::try_from(output_count).map_err(|_| {
Error::invalid_argument(format!("{operation} output element count exceeds u32"))
})?;
let intermediate_u32 = u32::try_from(intermediate_size)
.map_err(|_| Error::invalid_argument(format!("{operation} intermediate size exceeds u32")))?;
let input_gradient = Matrix::allocate(
engine,
input.shape().to_vec(),
input.num_elements(),
DType::F32,
)?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(output_gradient.storage()),
BufferBinding::write(input_gradient.storage()),
];
let attributes = [OpAttribute::SignedInteger {
name: "intermediate_size".into(),
value: i64::try_from(intermediate_size)
.map_err(|_| Error::invalid_argument(format!("{operation} intermediate size exceeds i64")))?,
}];
let push_constants = [
PushConstant::U32(output_count_u32),
PushConstant::U32(intermediate_u32),
];
engine.record_semantic(
ComputeDispatch {
kernel: KernelId::MlSiluMulBackwardF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: KernelId::MlSiluMulBackwardF32.linear_workgroups(output_count_u32),
},
SemanticDispatch {
contract,
inputs: &[input, output_gradient],
outputs: &[&input_gradient],
attributes: &attributes,
},
)?;
Ok(input_gradient)
}