#![forbid(unsafe_code)]
use std::collections::{HashMap, HashSet};
use nuvec::{Array, Error, Result, add, maximum, multiply, sigmoid, tanh};
pub mod layers;
pub mod losses;
pub mod optim;
pub use layers::{
Embedding, LayerNorm, Linear, LoraLinear, QuantizedEmbedding, QuantizedLinear, RmsNorm, Rope,
};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub trait Module: Clone {
fn visit(&mut self, p: &mut Parameters<'_>);
fn parameters(&self) -> Vec<(String, Array)> {
let mut out = Vec::new();
self.clone().visit(&mut Parameters::new(&mut |name, a, _| {
out.push((name.to_owned(), a.clone()));
}));
out
}
fn trainable_parameters(&self) -> Vec<(String, Array)> {
let mut out = Vec::new();
self.clone()
.visit(&mut Parameters::new(&mut |name, a, trainable| {
if trainable {
out.push((name.to_owned(), a.clone()));
}
}));
out
}
fn update_trainable(&mut self, values: &[Array]) -> Result<()> {
let expected = self.trainable_parameters().len();
if values.len() != expected {
return Err(Error::InvalidArgument(format!(
"{} values for {expected} trainable parameters",
values.len()
)));
}
let mut next = values.iter();
self.visit(&mut Parameters::new(&mut |_, a, trainable| {
if trainable {
*a = next.next().expect("counted").clone();
}
}));
Ok(())
}
fn load_weights(
&mut self,
weights: impl IntoIterator<Item = (String, Array)>,
strict: bool,
) -> Result<()> {
let mut weights: HashMap<String, Array> = weights.into_iter().collect();
let mut problem: Option<String> = None;
let mut seen = HashSet::new();
self.visit(&mut Parameters::new(&mut |name, a, _| {
seen.insert(name.to_owned());
match weights.remove(name) {
Some(w) if w.shape() == a.shape() => *a = w,
Some(w) => {
problem.get_or_insert(format!(
"{name}: shape {:?}, expected {:?}",
w.shape(),
a.shape()
));
}
None if strict => {
problem.get_or_insert(format!("{name}: missing"));
}
None => {}
}
}));
if let Some(p) = problem {
return Err(Error::InvalidArgument(format!("load_weights: {p}")));
}
if strict && !weights.is_empty() {
let mut unknown: Vec<&String> = weights.keys().collect();
unknown.sort();
return Err(Error::InvalidArgument(format!(
"load_weights: unknown parameters {unknown:?}"
)));
}
Ok(())
}
}
pub struct Parameters<'a> {
prefix: String,
frozen: bool,
f: &'a mut dyn FnMut(&str, &mut Array, bool),
}
impl<'a> Parameters<'a> {
pub fn new(f: &'a mut dyn FnMut(&str, &mut Array, bool)) -> Self {
Self {
prefix: String::new(),
frozen: false,
f,
}
}
fn path(&self, name: &str) -> String {
if self.prefix.is_empty() {
name.to_owned()
} else {
format!("{}.{name}", self.prefix)
}
}
pub fn param(&mut self, name: &str, a: &mut Array) {
let path = self.path(name);
(self.f)(&path, a, !self.frozen);
}
pub fn optional(&mut self, name: &str, a: &mut Option<Array>) {
if let Some(a) = a {
self.param(name, a);
}
}
pub fn frozen(&mut self, name: &str, a: &mut Array) {
let path = self.path(name);
(self.f)(&path, a, false);
}
pub fn module<M: Module>(&mut self, name: &str, m: &mut M) {
let outer = std::mem::take(&mut self.prefix);
self.prefix = if outer.is_empty() {
name.to_owned()
} else {
format!("{outer}.{name}")
};
m.visit(self);
self.prefix = outer;
}
pub fn freeze(&mut self, f: impl FnOnce(&mut Self)) {
let was = std::mem::replace(&mut self.frozen, true);
f(self);
self.frozen = was;
}
}
impl<M: Module> Module for Vec<M> {
fn visit(&mut self, p: &mut Parameters<'_>) {
for (i, m) in self.iter_mut().enumerate() {
p.module(&i.to_string(), m);
}
}
}
#[derive(Debug, Clone)]
pub struct Frozen<M>(pub M);
impl<M: Module> Module for Frozen<M> {
fn visit(&mut self, p: &mut Parameters<'_>) {
let inner = &mut self.0;
p.freeze(|p| inner.visit(p));
}
}
pub fn value_and_grad<M: Module>(
model: &M,
loss: impl Fn(&M) -> Result<Array>,
) -> Result<(Array, Vec<Array>)> {
let parameters: Vec<Array> = model
.trainable_parameters()
.into_iter()
.map(|(_, a)| a)
.collect();
let argnums: Vec<usize> = (0..parameters.len()).collect();
let f = |ps: &[Array]| {
let mut m = model.clone();
m.update_trainable(ps)?;
loss(&m)
};
nuvec::value_and_grad(f, &argnums)(¶meters)
}
pub fn relu(x: &Array) -> Result<Array> {
maximum(x, 0.0)
}
pub fn silu(x: &Array) -> Result<Array> {
multiply(x, sigmoid(x)?)
}
pub fn gelu_approx(x: &Array) -> Result<Array> {
let cube = multiply(multiply(x, x)?, x)?;
let inner = multiply(
add(x, multiply(cube, 0.044715)?)?,
(2.0 / std::f64::consts::PI).sqrt(),
)?;
multiply(multiply(x, 0.5)?, add(tanh(&inner)?, 1.0)?)
}
pub fn gelu_fast_approx(x: &Array) -> Result<Array> {
multiply(x, sigmoid(&multiply(x, 1.702)?)?)
}