use std::{
collections::HashMap,
fmt::Display,
sync::atomic::{AtomicUsize, Ordering},
};
use nove_tensor::{DType, Device, Shape, Tensor};
use crate::{Model, ModelError};
static ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone)]
pub struct Linear {
weight: Tensor,
bias: Option<Tensor>,
in_features: usize,
out_features: usize,
id: usize,
}
impl Linear {
pub fn weight(&self) -> Tensor {
self.weight.clone()
}
pub fn bias(&self) -> Option<Tensor> {
self.bias.clone()
}
}
impl Model for Linear {
type Input = Tensor;
type Output = Tensor;
fn forward(&mut self, input: Self::Input) -> Result<Self::Output, ModelError> {
let y = input.matmul(&self.weight)?;
let y = if let Some(bias) = &self.bias {
y.add(bias)?
} else {
y
};
Ok(y)
}
fn require_grad(&mut self, grad_enabled: bool) -> Result<(), ModelError> {
self.weight = self.weight.require_grad(grad_enabled)?;
if let Some(bias) = &mut self.bias {
self.bias = Some(bias.require_grad(grad_enabled)?);
}
Ok(())
}
fn to_device(&mut self, device: &Device) -> Result<(), ModelError> {
self.weight = self.weight.to_device(device)?;
if let Some(bias) = &mut self.bias {
self.bias = Some(bias.to_device(device)?);
}
Ok(())
}
fn to_dtype(&mut self, dtype: &DType) -> Result<(), ModelError> {
self.weight = self.weight.to_dtype(dtype)?;
if let Some(bias) = &mut self.bias {
self.bias = Some(bias.to_dtype(dtype)?);
}
Ok(())
}
fn parameters(&self) -> Result<Vec<Tensor>, ModelError> {
match &self.bias {
Some(bias) => Ok(vec![self.weight.clone(), bias.clone()]),
None => Ok(vec![self.weight.clone()]),
}
}
fn named_parameters(&self) -> Result<HashMap<String, Tensor>, ModelError> {
Ok(self
.parameters()?
.into_iter()
.map(|t| match t.name()? {
Some(name) => Ok((name, t)),
None => Err(ModelError::ParameterMissingName),
})
.collect::<Result<HashMap<_, _>, ModelError>>()?)
}
}
impl Display for Linear {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"linear.{}(in_features={}, out_features={}, bias_enabled={})",
self.id,
self.in_features,
self.out_features,
self.bias.is_some(),
)
}
}
pub struct LinearBuilder {
in_features: Option<usize>,
out_features: Option<usize>,
bias_enabled: bool,
device: Device,
dtype: DType,
grad_enabled: bool,
}
impl Default for LinearBuilder {
fn default() -> Self {
Self {
in_features: None,
out_features: None,
bias_enabled: true,
device: Device::cpu(),
dtype: DType::F32,
grad_enabled: true,
}
}
}
impl LinearBuilder {
pub fn in_features(&mut self, in_features: usize) -> &mut Self {
self.in_features = Some(in_features);
self
}
pub fn out_features(&mut self, out_features: usize) -> &mut Self {
self.out_features = Some(out_features);
self
}
pub fn bias_enabled(&mut self, bias_enabled: bool) -> &mut Self {
self.bias_enabled = bias_enabled;
self
}
pub fn device(&mut self, device: Device) -> &mut Self {
self.device = device;
self
}
pub fn dtype(&mut self, dtype: DType) -> &mut Self {
self.dtype = dtype;
self
}
pub fn grad_enabled(&mut self, grad_enabled: bool) -> &mut Self {
self.grad_enabled = grad_enabled;
self
}
pub fn build(&self) -> Result<Linear, ModelError> {
let in_features = self.in_features.ok_or(ModelError::MissingArgument(
"in_features in LinearBuilder".to_string(),
))?;
let out_features = self.out_features.ok_or(ModelError::MissingArgument(
"out_features in LinearBuilder".to_string(),
))?;
let id = ID.fetch_add(1, Ordering::Relaxed);
let std = (2.0 / in_features as f32).sqrt();
let weight = Tensor::randn(
0.0,
std,
&Shape::from_dims(&[in_features, out_features]),
&self.device,
self.grad_enabled,
)?
.to_dtype(&self.dtype)?
.require_name(&format!("linear.{}.weight", id))?;
let bias = if self.bias_enabled {
let bias = Tensor::zeros(
&Shape::from_dims(&[out_features]),
&self.dtype,
&self.device,
self.grad_enabled,
)?
.to_dtype(&self.dtype)?
.require_name(&format!("linear.{}.bias", id))?;
Some(bias)
} else {
None
};
Ok(Linear {
weight,
bias,
in_features,
out_features,
id,
})
}
}