use crate::error::Error;
use crate::math::reduction::det_reduce;
use crate::neural_network::ctx::{Ctx, Grads, identity_of};
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::traits::{Layer, LayerBase, Optimizer, ParamId};
use crate::parallel_gates::sq_sum_f32_parallel_min_elems;
use ndarray::ArrayViewD;
use std::borrow::Cow;
use std::fmt;
pub type SublayerName = Cow<'static, str>;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LayerPath {
scope: usize,
sublayers: Vec<SublayerName>,
}
impl LayerPath {
#[inline]
pub const fn root(scope: usize) -> Self {
Self {
scope,
sublayers: Vec::new(),
}
}
pub fn child(&self, name: impl Into<SublayerName>) -> Self {
let mut sublayers = Vec::with_capacity(self.sublayers.len() + 1);
sublayers.extend(self.sublayers.iter().cloned());
sublayers.push(name.into());
Self {
scope: self.scope,
sublayers,
}
}
#[inline]
pub fn scope(&self) -> usize {
self.scope
}
#[inline]
pub fn sublayers(&self) -> &[SublayerName] {
&self.sublayers
}
#[inline]
pub fn is_root(&self) -> bool {
self.sublayers.is_empty()
}
#[inline]
pub fn param(&self, name: &'static str) -> ParamId {
ParamId::at(self.clone(), name)
}
pub fn starts_with(&self, prefix: &LayerPath) -> bool {
self.scope == prefix.scope && self.sublayers.starts_with(&prefix.sublayers)
}
pub(crate) fn from_parts(scope: usize, sublayers: Vec<SublayerName>) -> Self {
Self { scope, sublayers }
}
}
impl fmt::Display for LayerPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.scope)?;
for name in &self.sublayers {
write!(f, ".{name}")?;
}
Ok(())
}
}
pub struct Sublayer<'a> {
pub name: SublayerName,
pub layer: &'a dyn LayerBase,
}
impl<'a> Sublayer<'a> {
#[inline]
pub fn new(name: impl Into<SublayerName>, layer: &'a dyn LayerBase) -> Self {
Self {
name: name.into(),
layer,
}
}
}
pub struct SublayerMut<'a> {
pub name: SublayerName,
pub layer: &'a mut dyn LayerBase,
}
impl<'a> SublayerMut<'a> {
#[inline]
pub fn new(name: impl Into<SublayerName>, layer: &'a mut dyn LayerBase) -> Self {
Self {
name: name.into(),
layer,
}
}
}
pub fn walk(
layer: &dyn LayerBase,
path: &LayerPath,
visit: &mut dyn FnMut(&LayerPath, &dyn LayerBase),
) {
visit(path, layer);
for sub in layer.sublayers() {
walk(sub.layer, &path.child(sub.name), visit);
}
}
pub fn walk_mut(
layer: &mut dyn LayerBase,
path: &LayerPath,
visit: &mut dyn FnMut(&LayerPath, &mut dyn LayerBase),
) {
visit(path, &mut *layer);
for sub in layer.sublayers_mut() {
walk_mut(sub.layer, &path.child(sub.name), visit);
}
}
pub(crate) fn walk_model_mut(
layers: &mut [Box<dyn Layer>],
visit: &mut dyn FnMut(&LayerPath, &mut dyn LayerBase),
) {
for (scope, layer) in layers.iter_mut().enumerate() {
walk_mut(&mut **layer, &LayerPath::root(scope), visit);
}
}
pub fn total_param_count(layer: &dyn LayerBase) -> ParamCounts {
let mut total = ParamCounts::none();
walk(layer, &LayerPath::root(0), &mut |_, node| {
let counts = node.param_count();
total.trainable += counts.trainable;
total.non_trainable += counts.non_trainable;
});
total
}
pub(crate) fn model_weight_paths(layers: &[Box<dyn Layer>]) -> Vec<String> {
let mut paths = Vec::new();
for (scope, layer) in layers.iter().enumerate() {
walk(&**layer, &LayerPath::root(scope), &mut |path, node| {
paths.extend(
node.weights()
.iter()
.map(|entry| weight_path(path, entry.name)),
);
});
}
paths
}
#[inline]
pub fn weight_path(path: &LayerPath, name: &str) -> String {
format!("{path}.{name}")
}
pub(crate) fn find_weight<'a>(
layers: &'a [Box<dyn Layer>],
path: &str,
) -> Option<ArrayViewD<'a, f32>> {
let mut parts: Vec<&str> = path.split('.').collect();
if parts.len() < 2 {
return None;
}
let name = parts.pop()?;
let scope: usize = parts[0].parse().ok()?;
if parts[0] != scope.to_string() {
return None;
}
let mut node: &'a dyn LayerBase = &**layers.get(scope)?;
for step in &parts[1..] {
node = node
.sublayers()
.into_iter()
.find(|sub| sub.name == *step)?
.layer;
}
node.weight(name)
}
pub fn apply_state_tree(layer: &mut dyn LayerBase, path: &LayerPath, ctx: &mut Ctx) {
walk_mut(layer, path, &mut |node_path, node| {
if ctx.has_state(node_path) {
node.apply_state(&mut ctx.state_slot(node_path));
}
});
}
pub(crate) fn check_state_taken(ctx: &Ctx, scope: usize) -> Result<(), Error> {
let left = ctx.state_left_under(scope);
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`, and a layer that holds sublayers must list each of them in \
`LayerBase::sublayers_mut`",
left.join(", ")
)))
}
pub fn update_tree(
optimizer: &mut dyn Optimizer,
layer: &mut dyn LayerBase,
path: &LayerPath,
grads: &Grads,
grad_scale: f32,
) {
walk_mut(layer, path, &mut |node_path, node| {
optimizer.update(node_path, node, grads, grad_scale);
});
}
pub(crate) fn update_model(
optimizer: &mut dyn Optimizer,
layers: &mut [Box<dyn Layer>],
grads: &Grads,
grad_scale: f32,
) {
for (scope, layer) in layers.iter_mut().enumerate() {
update_tree(
optimizer,
&mut **layer,
&LayerPath::root(scope),
grads,
grad_scale,
);
}
}
pub(crate) fn global_grad_norm(layers: &mut [Box<dyn Layer>], grads: &Grads) -> f32 {
let mut sum_sq = 0.0_f64;
walk_model_mut(layers, &mut |path, node| {
for param in node.parameters_mut() {
let Some(grad) = grads.get(&path.param(param.name)) else {
continue;
};
let grad = grad
.as_slice()
.expect("a stored gradient is in the standard memory order");
sum_sq += det_reduce(
grad,
grad.len() >= sq_sum_f32_parallel_min_elems(),
|block| block.iter().map(|&g| (g as f64) * (g as f64)).sum::<f64>(),
|a, b| a + b,
0.0,
);
}
});
sum_sq.sqrt() as f32
}
pub(crate) fn check_sublayer_calls(layers: &[Box<dyn Layer>], ctx: &Ctx) -> Result<(), Error> {
let (calls, conflicts) = ctx.sublayer_calls();
if let Some(conflict) = conflicts.first() {
return Err(Error::computation(format!(
"the pass called the sublayer path {conflict}. Call each sublayer under its own \
name with `Ctx::sublayer`"
)));
}
let mut recorded: Vec<(
&LayerPath,
&(crate::neural_network::ctx::LayerIdentity, String),
)> = calls.iter().collect();
recorded.sort_by(|a, b| a.0.cmp(b.0));
for (path, (identity, called_type)) in recorded {
let Some(root) = layers.get(path.scope()) else {
return Err(Error::computation(format!(
"the pass called the sublayer path `{path}`, and the model holds no position {}",
path.scope()
)));
};
let mut node: &dyn LayerBase = &**root;
for step in path.sublayers() {
let Some(sub) = node.sublayers().into_iter().find(|sub| sub.name == *step) else {
return Err(Error::computation(format!(
"the pass called the sublayer path `{path}` with layer `{called_type}`, and \
no `LayerBase::sublayers` roster of the tree gives that path. Call each \
sublayer with the name that its roster gives it"
)));
};
node = sub.layer;
}
if identity_of(node) != *identity {
return Err(Error::computation(format!(
"the pass called the sublayer path `{path}` with a layer `{called_type}` that is \
not the layer `{}` that the tree holds at that path. Call each sublayer with \
the name that its roster gives it",
node.layer_type()
)));
}
}
Ok(())
}