use crate::{DType, Engine, Error, Matrix, Result};
use super::super::{Module, ModuleRegistry, Parameter, matrix, random};
pub struct Conv1d {
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
dilation: usize,
weight: Parameter,
bias: Parameter,
registry: ModuleRegistry,
}
impl Conv1d {
#[allow(
clippy::too_many_arguments,
reason = "the donor Conv1d contract has five geometry fields plus a seed"
)]
pub fn with_seed(
engine: &Engine,
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
dilation: usize,
seed: u64,
) -> Result<Self> {
if input_channels == 0
|| output_channels == 0
|| kernel_size == 0
|| stride == 0
|| dilation == 0
{
return Err(Error::invalid_argument(
"Conv1d requires nonzero channels, kernel size, stride, and dilation",
));
}
let fan_in = input_channels
.checked_mul(kernel_size)
.ok_or_else(|| Error::invalid_argument("Conv1d fan-in overflows usize"))?;
let weight_count = output_channels
.checked_mul(fan_in)
.ok_or_else(|| Error::invalid_argument("Conv1d weight size overflows usize"))?;
let limit = 1.0_f32 / (fan_in as f32).sqrt();
let values = random::symmetric_uniform(weight_count, limit, seed);
let weight = Matrix::from_f32(
engine,
[output_channels, input_channels, kernel_size],
&values,
)?;
let bias = Matrix::from_f32(engine, [output_channels], &vec![0.0; output_channels])?;
Self::from_matrices(weight, bias, stride, padding, dilation)
}
pub fn from_matrices(
weight: Matrix,
bias: Matrix,
stride: usize,
padding: usize,
dilation: usize,
) -> Result<Self> {
let [output_channels, input_channels, kernel_size] = weight.shape() else {
return Err(Error::invalid_argument(
"Conv1d weight must have OIK rank three",
));
};
if *output_channels == 0
|| *input_channels == 0
|| *kernel_size == 0
|| stride == 0
|| dilation == 0
|| bias.shape() != [*output_channels]
|| weight.dtype() != DType::F32
|| bias.dtype() != DType::F32
|| !weight.engine_handle().same_as(bias.engine_handle())
{
return Err(Error::invalid_argument(
"Conv1d requires nonempty same-engine F32 OIK weight, matching bias, and nonzero stride/dilation",
));
}
let input_channels = *input_channels;
let output_channels = *output_channels;
let kernel_size = *kernel_size;
let weight = Parameter::new("weight", weight)?;
let bias = Parameter::new("bias", bias)?;
let mut registry = ModuleRegistry::new();
registry.register_parameter("weight", weight.clone())?;
registry.register_parameter("bias", bias.clone())?;
Ok(Self {
input_channels,
output_channels,
kernel_size,
stride,
padding,
dilation,
weight,
bias,
registry,
})
}
pub fn forward(&self, input: &Matrix) -> Result<Matrix> {
let (weight, weight_version, _) = self.weight.snapshot();
let (bias, bias_version, _) = self.bias.snapshot();
matrix::conv_1d_parameterized(
input,
(self.weight.clone(), weight, weight_version),
(self.bias.clone(), bias, bias_version),
self.stride,
self.padding,
self.dilation,
)
}
pub const fn input_channels(&self) -> usize {
self.input_channels
}
pub const fn output_channels(&self) -> usize {
self.output_channels
}
pub const fn kernel_size(&self) -> usize {
self.kernel_size
}
pub const fn stride(&self) -> usize {
self.stride
}
pub const fn padding(&self) -> usize {
self.padding
}
pub const fn dilation(&self) -> usize {
self.dilation
}
pub fn weight(&self) -> Parameter {
self.weight.clone()
}
pub fn bias(&self) -> Parameter {
self.bias.clone()
}
pub fn parameters(&self) -> [Parameter; 2] {
[self.weight.clone(), self.bias.clone()]
}
}
impl Module for Conv1d {
fn forward(&self, input: &Matrix) -> Result<Matrix> {
Conv1d::forward(self, input)
}
fn registry(&self) -> &ModuleRegistry {
&self.registry
}
}
pub struct ConvTranspose1d {
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
weight: Parameter,
registry: ModuleRegistry,
}
impl ConvTranspose1d {
#[allow(
clippy::too_many_arguments,
reason = "the donor ConvTranspose1d contract has five geometry fields plus a seed"
)]
pub fn with_seed(
engine: &Engine,
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
seed: u64,
) -> Result<Self> {
if input_channels == 0 || output_channels == 0 || kernel_size == 0 || stride == 0 {
return Err(Error::invalid_argument(
"ConvTranspose1d requires nonzero channels, kernel size, and stride",
));
}
let fan_in = output_channels
.checked_mul(kernel_size)
.ok_or_else(|| Error::invalid_argument("ConvTranspose1d fan-in overflows usize"))?;
let weight_count = input_channels
.checked_mul(fan_in)
.ok_or_else(|| Error::invalid_argument("ConvTranspose1d weight size overflows usize"))?;
let limit = 1.0_f32 / (fan_in as f32).sqrt();
let values = random::symmetric_uniform(weight_count, limit, seed);
let weight = Matrix::from_f32(
engine,
[input_channels, output_channels, kernel_size],
&values,
)?;
Self::from_matrix(weight, stride, padding)
}
pub fn from_matrix(weight: Matrix, stride: usize, padding: usize) -> Result<Self> {
let [input_channels, output_channels, kernel_size] = weight.shape() else {
return Err(Error::invalid_argument(
"ConvTranspose1d weight must have IOK rank three",
));
};
if *input_channels == 0
|| *output_channels == 0
|| *kernel_size == 0
|| stride == 0
|| weight.dtype() != DType::F32
{
return Err(Error::invalid_argument(
"ConvTranspose1d requires nonempty F32 IOK weight and nonzero stride",
));
}
let input_channels = *input_channels;
let output_channels = *output_channels;
let kernel_size = *kernel_size;
let weight = Parameter::new("weight", weight)?;
let mut registry = ModuleRegistry::new();
registry.register_parameter("weight", weight.clone())?;
Ok(Self {
input_channels,
output_channels,
kernel_size,
stride,
padding,
weight,
registry,
})
}
pub fn forward(&self, input: &Matrix) -> Result<Matrix> {
let (weight, weight_version, _) = self.weight.snapshot();
matrix::conv_transpose_1d_parameterized(
input,
(self.weight.clone(), weight, weight_version),
self.stride,
self.padding,
1,
)
}
pub const fn input_channels(&self) -> usize {
self.input_channels
}
pub const fn output_channels(&self) -> usize {
self.output_channels
}
pub const fn kernel_size(&self) -> usize {
self.kernel_size
}
pub const fn stride(&self) -> usize {
self.stride
}
pub const fn padding(&self) -> usize {
self.padding
}
pub fn weight(&self) -> Parameter {
self.weight.clone()
}
pub fn parameters(&self) -> [Parameter; 1] {
[self.weight.clone()]
}
}
impl Module for ConvTranspose1d {
fn forward(&self, input: &Matrix) -> Result<Matrix> {
ConvTranspose1d::forward(self, input)
}
fn registry(&self) -> &ModuleRegistry {
&self.registry
}
}
pub struct ConvTranspose2d {
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
weight: Parameter,
bias: Parameter,
registry: ModuleRegistry,
}
impl ConvTranspose2d {
#[allow(
clippy::too_many_arguments,
reason = "the donor ConvTranspose2d contract has five geometry fields plus a seed"
)]
pub fn with_seed(
engine: &Engine,
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
seed: u64,
) -> Result<Self> {
if input_channels == 0 || output_channels == 0 || kernel_size == 0 || stride == 0 {
return Err(Error::invalid_argument(
"ConvTranspose2d requires nonzero channels, kernel size, and stride",
));
}
let fan_in = output_channels
.checked_mul(kernel_size)
.and_then(|count| count.checked_mul(kernel_size))
.ok_or_else(|| Error::invalid_argument("ConvTranspose2d fan-in overflows usize"))?;
let weight_count = input_channels
.checked_mul(fan_in)
.ok_or_else(|| Error::invalid_argument("ConvTranspose2d weight size overflows usize"))?;
let limit = 1.0_f32 / (fan_in as f32).sqrt();
let values = random::symmetric_uniform(weight_count, limit, seed);
let weight = Matrix::from_f32(
engine,
[input_channels, output_channels, kernel_size, kernel_size],
&values,
)?;
let bias = Matrix::from_f32(engine, [output_channels], &vec![0.0; output_channels])?;
Self::from_matrices(weight, bias, stride, padding)
}
pub fn from_matrices(
weight: Matrix,
bias: Matrix,
stride: usize,
padding: usize,
) -> Result<Self> {
let [input_channels, output_channels, kernel_height, kernel_width] = weight.shape() else {
return Err(Error::invalid_argument(
"ConvTranspose2d weight must have IOKK rank four",
));
};
if *input_channels == 0
|| *output_channels == 0
|| *kernel_height == 0
|| kernel_height != kernel_width
|| stride == 0
|| bias.shape() != [*output_channels]
|| weight.dtype() != DType::F32
|| bias.dtype() != DType::F32
|| !weight.engine_handle().same_as(bias.engine_handle())
{
return Err(Error::invalid_argument(
"ConvTranspose2d requires nonempty same-engine F32 square IOKK weight, matching bias, and nonzero stride",
));
}
let input_channels = *input_channels;
let output_channels = *output_channels;
let kernel_size = *kernel_height;
let weight = Parameter::new("weight", weight)?;
let bias = Parameter::new("bias", bias)?;
let mut registry = ModuleRegistry::new();
registry.register_parameter("weight", weight.clone())?;
registry.register_parameter("bias", bias.clone())?;
Ok(Self {
input_channels,
output_channels,
kernel_size,
stride,
padding,
weight,
bias,
registry,
})
}
pub fn forward(&self, input: &Matrix) -> Result<Matrix> {
let (weight, weight_version, _) = self.weight.snapshot();
let (bias, bias_version, _) = self.bias.snapshot();
matrix::conv_transpose_2d_parameterized(
input,
(self.weight.clone(), weight, weight_version),
(self.bias.clone(), bias, bias_version),
self.stride,
self.padding,
)
}
pub const fn input_channels(&self) -> usize {
self.input_channels
}
pub const fn output_channels(&self) -> usize {
self.output_channels
}
pub const fn kernel_size(&self) -> usize {
self.kernel_size
}
pub const fn stride(&self) -> usize {
self.stride
}
pub const fn padding(&self) -> usize {
self.padding
}
pub fn weight(&self) -> Parameter {
self.weight.clone()
}
pub fn bias(&self) -> Parameter {
self.bias.clone()
}
pub fn parameters(&self) -> [Parameter; 2] {
[self.weight.clone(), self.bias.clone()]
}
}
impl Module for ConvTranspose2d {
fn forward(&self, input: &Matrix) -> Result<Matrix> {
ConvTranspose2d::forward(self, input)
}
fn registry(&self) -> &ModuleRegistry {
&self.registry
}
}
pub struct Conv2d {
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
groups: usize,
weight: Parameter,
bias: Parameter,
registry: ModuleRegistry,
}
impl Conv2d {
#[allow(
clippy::too_many_arguments,
reason = "the donor Conv2d contract has six independent geometry fields plus a seed"
)]
pub fn with_seed(
engine: &Engine,
input_channels: usize,
output_channels: usize,
kernel_size: usize,
stride: usize,
padding: usize,
groups: usize,
seed: u64,
) -> Result<Self> {
if input_channels == 0
|| output_channels == 0
|| kernel_size == 0
|| stride == 0
|| groups == 0
|| !input_channels.is_multiple_of(groups)
|| !output_channels.is_multiple_of(groups)
{
return Err(Error::invalid_argument(
"Conv2d requires nonzero dimensions and channels divisible by groups",
));
}
let input_channels_per_group = input_channels / groups;
let fan_in = input_channels_per_group
.checked_mul(kernel_size)
.and_then(|count| count.checked_mul(kernel_size))
.ok_or_else(|| Error::invalid_argument("Conv2d fan-in overflows usize"))?;
let weight_count = output_channels
.checked_mul(fan_in)
.ok_or_else(|| Error::invalid_argument("Conv2d weight size overflows usize"))?;
let limit = 1.0_f32 / (fan_in as f32).sqrt();
let weight_values = random::symmetric_uniform(weight_count, limit, seed);
let weight = Matrix::from_f32(
engine,
[
output_channels,
input_channels_per_group,
kernel_size,
kernel_size,
],
&weight_values,
)?;
let bias = Matrix::from_f32(engine, [output_channels], &vec![0.0; output_channels])?;
Self::from_matrices(weight, bias, stride, padding, groups)
}
pub fn from_matrices(
weight: Matrix,
bias: Matrix,
stride: usize,
padding: usize,
groups: usize,
) -> Result<Self> {
let [
output_channels,
input_channels_per_group,
kernel_height,
kernel_width,
] = weight.shape()
else {
return Err(Error::invalid_argument(
"Conv2d weight must have grouped OIHW rank four",
));
};
let input_channels = input_channels_per_group
.checked_mul(groups)
.ok_or_else(|| Error::invalid_argument("Conv2d input channel count overflows usize"))?;
if *output_channels == 0
|| *input_channels_per_group == 0
|| *kernel_height == 0
|| kernel_height != kernel_width
|| stride == 0
|| groups == 0
|| !output_channels.is_multiple_of(groups)
|| bias.shape() != [*output_channels]
|| weight.dtype() != DType::F32
|| bias.dtype() != DType::F32
|| !weight.engine_handle().same_as(bias.engine_handle())
{
return Err(Error::invalid_argument(
"Conv2d requires nonempty same-engine F32 square OIHW/group weight, matching bias, nonzero stride/groups, and output channels divisible by groups",
));
}
let output_channels = *output_channels;
let kernel_size = *kernel_height;
let weight = Parameter::new("weight", weight)?;
let bias = Parameter::new("bias", bias)?;
let mut registry = ModuleRegistry::new();
registry.register_parameter("weight", weight.clone())?;
registry.register_parameter("bias", bias.clone())?;
Ok(Self {
input_channels,
output_channels,
kernel_size,
stride,
padding,
groups,
weight,
bias,
registry,
})
}
pub fn forward(&self, input: &Matrix) -> Result<Matrix> {
let (weight, weight_version, _) = self.weight.snapshot();
let (bias, bias_version, _) = self.bias.snapshot();
matrix::conv_2d_parameterized(
input,
(self.weight.clone(), weight, weight_version),
(self.bias.clone(), bias, bias_version),
self.stride,
self.padding,
self.groups,
)
}
pub const fn input_channels(&self) -> usize {
self.input_channels
}
pub const fn output_channels(&self) -> usize {
self.output_channels
}
pub const fn kernel_size(&self) -> usize {
self.kernel_size
}
pub const fn stride(&self) -> usize {
self.stride
}
pub const fn padding(&self) -> usize {
self.padding
}
pub const fn groups(&self) -> usize {
self.groups
}
pub fn weight(&self) -> Parameter {
self.weight.clone()
}
pub fn bias(&self) -> Parameter {
self.bias.clone()
}
pub fn parameters(&self) -> [Parameter; 2] {
[self.weight.clone(), self.bias.clone()]
}
}
impl Module for Conv2d {
fn forward(&self, input: &Matrix) -> Result<Matrix> {
Conv2d::forward(self, input)
}
fn registry(&self) -> &ModuleRegistry {
&self.registry
}
}