use crate::error::Error;
use crate::neural_network::Shape;
use crate::neural_network::Tensor;
use crate::neural_network::ctx::{Ctx, Grads, StateSlot};
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::layers::checkpoint::BuildConfig;
use crate::{Deserialize, Serialize};
use ndarray::{ArrayViewD, ArrayViewMutD};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ParamId {
pub scope: usize,
pub name: &'static str,
}
impl ParamId {
#[inline]
pub const fn new(scope: usize, name: &'static str) -> Self {
Self { scope, name }
}
}
pub(crate) fn check_addresses(scope: usize, layer: &mut dyn LayerBase) -> Result<(), Error> {
let layer_type = layer.layer_type().to_string();
let weights: Vec<&'static str> = layer.weights().iter().map(|entry| entry.name).collect();
if let Some(name) = first_repeat(&weights) {
return Err(duplicate_address(scope, &layer_type, name, "weights"));
}
let parameters: Vec<&'static str> = layer
.parameters_mut()
.iter()
.map(|entry| entry.name)
.collect();
if let Some(name) = first_repeat(¶meters) {
return Err(duplicate_address(
scope,
&layer_type,
name,
"parameters_mut",
));
}
Ok(())
}
fn first_repeat(names: &[&'static str]) -> Option<&'static str> {
names
.iter()
.enumerate()
.find(|(index, name)| names[..*index].contains(name))
.map(|(_, name)| *name)
}
fn duplicate_address(scope: usize, layer_type: &str, name: &str, roster: &str) -> Error {
Error::invalid_input(format!(
"layer {scope} (`{layer_type}`) gives 2 of its arrays the name `{name}`, through \
`LayerBase::{roster}`. The name is the address of the array, and the gradient store, \
the state of the optimizer, and the path of the checkpoint all key on it, so the 2 \
arrays would share all 3. Give every array of 1 layer its own name. A layer that holds \
other layers must put its own prefix in front of the name of each array it passes on"
))
}
pub(crate) fn check_every_gradient_is_claimed(
layers: &mut [Box<dyn Layer>],
grads: &Grads,
) -> Result<(), Error> {
let mut claimed = 0_usize;
for (scope, layer) in layers.iter_mut().enumerate() {
for param in layer.parameters_mut() {
if grads.get(ParamId::new(scope, param.name)).is_some() {
claimed += 1;
}
}
}
if claimed == grads.len() {
return Ok(());
}
let mut reachable = Vec::new();
for (scope, layer) in layers.iter_mut().enumerate() {
for param in layer.parameters_mut() {
reachable.push(ParamId::new(scope, param.name));
}
}
let orphans: Vec<String> = grads
.iter()
.filter(|(id, _)| !reachable.contains(id))
.map(|(id, _)| format!("{}.{}", id.scope, id.name))
.collect();
Err(Error::computation(format!(
"the backward pass parked a gradient at {} address(es) that no parameter of the model \
reads: {}. An optimizer reads a gradient at the address that \
`LayerBase::parameters_mut` gives the parameter, so a gradient at any other address \
updates nothing and the parameter it was meant for keeps its value. A layer must add \
every gradient under a name that its own roster holds",
orphans.len(),
orphans.join(", ")
)))
}
pub struct ParamRef<'a> {
pub name: &'static str,
pub value: &'a mut [f32],
pub decays: bool,
}
impl<'a> ParamRef<'a> {
#[inline]
pub fn weight(name: &'static str, value: &'a mut [f32]) -> Self {
Self {
name,
value,
decays: true,
}
}
#[inline]
pub fn no_decay(name: &'static str, value: &'a mut [f32]) -> Self {
Self {
name,
value,
decays: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WeightKind {
Trainable,
NonTrainable,
}
pub struct WeightRef<'a> {
pub name: &'static str,
pub kind: WeightKind,
pub value: ArrayViewD<'a, f32>,
}
impl<'a> WeightRef<'a> {
#[inline]
pub fn trainable(name: &'static str, value: ArrayViewD<'a, f32>) -> Self {
Self {
name,
kind: WeightKind::Trainable,
value,
}
}
#[inline]
pub fn non_trainable(name: &'static str, value: ArrayViewD<'a, f32>) -> Self {
Self {
name,
kind: WeightKind::NonTrainable,
value,
}
}
}
pub struct WeightMut<'a> {
pub name: &'static str,
pub kind: WeightKind,
pub value: ArrayViewMutD<'a, f32>,
}
impl<'a> WeightMut<'a> {
#[inline]
pub fn trainable(name: &'static str, value: ArrayViewMutD<'a, f32>) -> Self {
Self {
name,
kind: WeightKind::Trainable,
value,
}
}
#[inline]
pub fn non_trainable(name: &'static str, value: ArrayViewMutD<'a, f32>) -> Self {
Self {
name,
kind: WeightKind::NonTrainable,
value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arity {
Exactly(usize),
AtLeast(usize),
}
impl Arity {
#[inline]
pub const fn accepts(self, count: usize) -> bool {
match self {
Self::Exactly(n) => count == n,
Self::AtLeast(n) => count >= n,
}
}
pub fn check(self, layer: &str, count: usize) -> Result<(), Error> {
if self.accepts(count) {
return Ok(());
}
let wanted = match self {
Self::Exactly(n) => format!("exactly {n}"),
Self::AtLeast(n) => format!("{n} or more"),
};
Err(Error::invalid_input(format!(
"layer `{layer}` takes {wanted} inputs, and it received {count}"
)))
}
}
pub trait LayerBase: std::any::Any + Send + Sync {
fn layer_type(&self) -> &str {
"Unknown"
}
fn param_count(&self) -> ParamCounts;
fn parameters_mut(&mut self) -> Vec<ParamRef<'_>> {
Vec::new()
}
fn weights(&self) -> Vec<WeightRef<'_>>;
fn weights_mut(&mut self) -> Vec<WeightMut<'_>>;
fn weight(&self, name: &str) -> Option<ArrayViewD<'_, f32>> {
self.weights()
.into_iter()
.find(|entry| entry.name == name)
.map(|entry| entry.value)
}
fn known_input_shapes(&self) -> Option<Vec<Shape>> {
None
}
fn is_built(&self) -> bool {
true
}
fn build_config(&self) -> Option<BuildConfig> {
None
}
fn apply_state(&mut self, state: &mut StateSlot<'_>) {
let _ = state;
}
}
pub trait UnaryLayer: LayerBase {
fn forward(&self, input: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error>;
fn backward(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error>;
fn build(&mut self, input: &Shape) -> Result<(), Error> {
let _ = input;
Ok(())
}
fn compute_output_shape(&self, input: &Shape) -> Result<Shape, Error> {
Ok(input.clone())
}
fn forward_mut(&mut self, input: &Tensor, ctx: &mut Ctx) -> Result<Tensor, Error> {
if !self.is_built() {
self.build(&Shape::known(input.shape()))?;
}
let output = self.forward(input, ctx)?;
let owner = ctx.owner();
if ctx.has_state(owner) {
self.apply_state(&mut ctx.state_slot(owner));
}
Ok(output)
}
}
pub trait Layer: LayerBase {
fn arity(&self) -> Arity {
Arity::Exactly(1)
}
fn forward_many(&self, inputs: &[&Tensor], ctx: &mut Ctx) -> Result<Tensor, Error>;
fn backward_many(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Vec<Tensor>, Error>;
fn build_many(&mut self, inputs: &[Shape]) -> Result<(), Error>;
fn compute_output_shape_many(&self, inputs: &[Shape]) -> Result<Shape, Error>;
fn forward_many_mut(&mut self, inputs: &[&Tensor], ctx: &mut Ctx) -> Result<Tensor, Error> {
if !self.is_built() {
let shapes: Vec<Shape> = inputs.iter().map(|t| Shape::known(t.shape())).collect();
self.build_many(&shapes)?;
}
let output = self.forward_many(inputs, ctx)?;
let owner = ctx.owner();
if ctx.has_state(owner) {
self.apply_state(&mut ctx.state_slot(owner));
}
Ok(output)
}
fn output_shape(&self) -> String {
match self.known_input_shapes() {
Some(inputs) => match self.compute_output_shape_many(&inputs) {
Ok(output) => output.to_string(),
Err(_) => "Unknown".to_string(),
},
None => "Unknown".to_string(),
}
}
}
impl<T: UnaryLayer> Layer for T {
fn forward_many(&self, inputs: &[&Tensor], ctx: &mut Ctx) -> Result<Tensor, Error> {
Arity::Exactly(1).check(self.layer_type(), inputs.len())?;
UnaryLayer::forward(self, inputs[0], ctx)
}
fn backward_many(&self, grad_output: &Tensor, ctx: &mut Ctx) -> Result<Vec<Tensor>, Error> {
Ok(vec![UnaryLayer::backward(self, grad_output, ctx)?])
}
fn build_many(&mut self, inputs: &[Shape]) -> Result<(), Error> {
Arity::Exactly(1).check(self.layer_type(), inputs.len())?;
UnaryLayer::build(self, &inputs[0])
}
fn compute_output_shape_many(&self, inputs: &[Shape]) -> Result<Shape, Error> {
Arity::Exactly(1).check(self.layer_type(), inputs.len())?;
UnaryLayer::compute_output_shape(self, &inputs[0])
}
}
pub trait Loss: Send + Sync {
fn compute_loss(&self, y_true: &Tensor, y_pred: &Tensor) -> Result<f32, Error>;
fn compute_grad(&self, y_true: &Tensor, y_pred: &Tensor) -> Result<Tensor, Error>;
}
pub trait Optimizer: Send + Sync {
fn step(&mut self) {}
fn global_clipnorm(&self) -> Option<f32> {
None
}
fn update(&mut self, scope: usize, layer: &mut dyn LayerBase, grads: &Grads, grad_scale: f32);
fn learning_rate(&self) -> f32;
fn set_learning_rate(&mut self, learning_rate: f32);
}