use crate::error::Error;
use crate::neural_network::Shape;
use crate::neural_network::Tensor;
use crate::neural_network::ctx::{Ctx, Grads, LayerIdentity, StateSlot, identity_of};
use crate::neural_network::layer_path::{
LayerPath, Sublayer, SublayerMut, apply_state_tree, walk_model_mut,
};
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::layers::checkpoint::BuildConfig;
use crate::{Deserialize, Serialize};
use ndarray::{ArrayViewD, ArrayViewMutD};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ParamId {
pub layer: LayerPath,
pub name: &'static str,
}
impl ParamId {
#[inline]
pub const fn new(scope: usize, name: &'static str) -> Self {
Self {
layer: LayerPath::root(scope),
name,
}
}
#[inline]
pub fn at(layer: LayerPath, name: &'static str) -> Self {
Self { layer, name }
}
}
impl fmt::Display for ParamId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.layer, self.name)
}
}
pub(crate) fn check_addresses(scope: usize, layer: &mut dyn LayerBase) -> Result<(), Error> {
let root = LayerPath::root(scope);
let mut storages: Vec<(LayerIdentity, LayerPath)> = Vec::new();
check_tree(&*layer, &root, &mut storages)?;
check_tree_mut(layer, &root)
}
const MAX_SUBLAYER_DEPTH: usize = 64;
fn check_tree(
node: &dyn LayerBase,
path: &LayerPath,
storages: &mut Vec<(LayerIdentity, LayerPath)>,
) -> Result<(), Error> {
if path.sublayers().len() > MAX_SUBLAYER_DEPTH {
return Err(too_deep(path, node));
}
if std::mem::size_of_val(node) > 0 {
let storage = identity_of(node);
if let Some((_, first)) = storages.iter().find(|(seen, _)| *seen == storage) {
return Err(Error::invalid_input(format!(
"layer `{path}` (`{}`) is the same storage as layer `{first}`. The optimizer \
would update that storage once per node. Give each node of a layer tree its \
own layer",
node.layer_type()
)));
}
storages.push((storage, path.clone()));
}
check_node(path, node)?;
for sub in node.sublayers() {
check_tree(sub.layer, &path.child(sub.name), storages)?;
}
Ok(())
}
fn check_tree_mut(node: &mut dyn LayerBase, path: &LayerPath) -> Result<(), Error> {
if path.sublayers().len() > MAX_SUBLAYER_DEPTH {
return Err(too_deep(path, node));
}
check_node_mut(path, node)?;
for sub in node.sublayers_mut() {
check_tree_mut(sub.layer, &path.child(sub.name))?;
}
Ok(())
}
#[cold]
fn too_deep(path: &LayerPath, node: &dyn LayerBase) -> Error {
Error::invalid_input(format!(
"layer `{path}` (`{}`) is more than {MAX_SUBLAYER_DEPTH} sublayers deep. A sublayer \
roster probably lists its own layer. A roster must list only the layers that its layer \
holds",
node.layer_type()
))
}
fn check_node(path: &LayerPath, node: &dyn LayerBase) -> Result<(), Error> {
let layer_type = node.layer_type();
let weights: Vec<&str> = node.weights().iter().map(|entry| entry.name).collect();
check_names(path, layer_type, &weights, "weights", "array")?;
let sublayers: Vec<String> = node
.sublayers()
.iter()
.map(|sub| sub.name.to_string())
.collect();
let sublayer_refs: Vec<&str> = sublayers.iter().map(String::as_str).collect();
check_names(path, layer_type, &sublayer_refs, "sublayers", "sublayer")
}
fn check_node_mut(path: &LayerPath, node: &mut dyn LayerBase) -> Result<(), Error> {
let layer_type = node.layer_type().to_string();
let parameters: Vec<&'static str> = node
.parameters_mut()
.iter()
.map(|entry| entry.name)
.collect();
check_names(path, &layer_type, ¶meters, "parameters_mut", "array")?;
let read: Vec<RosterEntry> = node
.sublayers()
.iter()
.map(|sub| roster_entry(&sub.name, sub.layer))
.collect();
let write: Vec<RosterEntry> = node
.sublayers_mut()
.iter()
.map(|sub| roster_entry(&sub.name, &*sub.layer))
.collect();
if read != write {
let names = |roster: &[RosterEntry]| {
roster
.iter()
.map(|entry| format!("{} (`{}`)", entry.name, entry.layer_type))
.collect::<Vec<_>>()
.join(", ")
};
return Err(Error::invalid_input(format!(
"layer `{path}` (`{layer_type}`) gives the sublayers [{}] through \
`LayerBase::sublayers`, and [{}] through `LayerBase::sublayers_mut`. The 2 rosters \
must give the same names, in the same order, and reach the same layers",
names(&read),
names(&write)
)));
}
Ok(())
}
#[derive(PartialEq)]
struct RosterEntry {
name: String,
identity: LayerIdentity,
layer_type: String,
}
fn roster_entry(name: &str, layer: &dyn LayerBase) -> RosterEntry {
RosterEntry {
name: name.to_string(),
identity: identity_of(layer),
layer_type: layer.layer_type().to_string(),
}
}
fn check_names(
path: &LayerPath,
layer_type: &str,
names: &[&str],
roster: &str,
what: &str,
) -> Result<(), Error> {
for (index, name) in names.iter().enumerate() {
if name.is_empty() || name.contains('.') {
return Err(Error::invalid_input(format!(
"layer `{path}` (`{layer_type}`) gives 1 {what} the name `{name}`, through \
`LayerBase::{roster}`. A name must not be empty and must not hold a `.`, \
because a checkpoint path joins the names with `.`"
)));
}
if names[..index].contains(name) {
return Err(Error::invalid_input(format!(
"layer `{path}` (`{layer_type}`) gives 2 of its {what}s the name `{name}`, \
through `LayerBase::{roster}`. The name is the address, and the gradient \
store, the state of the optimizer, and the path of the checkpoint all key on \
it. Give every {what} of 1 layer its own name. A layer that holds other layers \
lists them in `LayerBase::sublayers` and does not pass their arrays on as its \
own"
)));
}
}
Ok(())
}
pub(crate) fn check_every_gradient_is_claimed(
layers: &mut [Box<dyn Layer>],
grads: &Grads,
) -> Result<(), Error> {
let mut reachable: Vec<ParamId> = Vec::new();
walk_model_mut(layers, &mut |path, node| {
for param in node.parameters_mut() {
reachable.push(path.param(param.name));
}
});
let claimed = reachable
.iter()
.filter(|id| grads.get(id).is_some())
.count();
if claimed == grads.len() {
return Ok(());
}
let orphans: Vec<String> = grads
.iter()
.filter(|(id, _)| !reachable.contains(id))
.map(|(id, _)| id.to_string())
.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. A layer that holds sublayers \
must call each of them inside `Ctx::sublayer` with the name that \
`LayerBase::sublayers` gives it",
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;
}
fn sublayers(&self) -> Vec<Sublayer<'_>> {
Vec::new()
}
fn sublayers_mut(&mut self) -> Vec<SublayerMut<'_>> {
Vec::new()
}
}
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)?;
apply_own_state(self, ctx)?;
Ok(output)
}
}
fn apply_own_state<L: LayerBase + ?Sized>(layer: &mut L, ctx: &mut Ctx) -> Result<(), Error> {
let path = ctx.layer_path();
if ctx.has_state(&path) {
layer.apply_state(&mut ctx.state_slot(&path));
}
for sub in layer.sublayers_mut() {
apply_state_tree(sub.layer, &path.child(sub.name), ctx);
}
let left = ctx.state_left_below(&path);
if left.is_empty() {
return Ok(());
}
Err(Error::computation(format!(
"the forward pass proposed the state value(s) {} and no layer took them back. A layer \
must take every value that it writes with `Ctx::set_state` in its own \
`LayerBase::apply_state`",
left.join(", ")
)))
}
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)?;
apply_own_state(self, ctx)?;
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,
path: &LayerPath,
layer: &mut dyn LayerBase,
grads: &Grads,
grad_scale: f32,
);
fn learning_rate(&self) -> f32;
fn set_learning_rate(&mut self, learning_rate: f32);
}