Expand description
Neural-network building blocks on nuvec, following MLX’s mlx.nn and mlx.optimizers.
A model is a plain struct of layers with #[derive(Module)]: its parameters get MLX’s
names from the field names (layers.0.weight), and every model is a Tree, so the
core transforms differentiate it and return gradients of the model’s own type
(grads.layers[0].weight).
Module: names and counts (Module::parameters,Module::num_params,Module::summary,Module::display), freezing by path (Module::freeze,Module::unfreeze), train/eval mode (Module::train), loading and saving by name (Module::load_weights,Module::save_weights) and replacing submodules (Module::map_modules).- Layers (
layers):Linear,QuantizedLinear,Embedding,QuantizedEmbedding,RmsNorm,LayerNorm,Rope,LoraLinearandDropout; activationsrelu,silu,gelu_approxandgelu_fast_approx. Constructors take nobooland return noResult:Linear::new(784, 32),Linear::new(32, 10).without_bias(). losses::cross_entropyandoptim: SGD, Adam, AdamW, Lion and Muon with learning-rate schedules, parameter groups (optim::MultiOptimizer) and gradient clipping; the optimizer state is a tree keyed by parameter path.
Layers’ forwards, activations and losses return an Array, as every nuvec op: an
invalid input gives a failed array whose error surfaces at a boundary (check(),
eval(), item(), a transform) and reports where forward was called.
use nuvec::{Array, Result, array, random, value_and_grad};
use nuvec_nn::optim::{Optimizer, Sgd};
use nuvec_nn::{Linear, Module, Tree, losses, relu};
#[derive(Clone, Module)]
struct Mlp {
hidden: Linear,
out: Linear,
}
impl Mlp {
fn forward(&self, x: &Array) -> Array {
self.out.forward(&relu(&self.hidden.forward(x)))
}
}
random::seed(0);
let mut model = Mlp { hidden: Linear::new(2, 8), out: Linear::new(8, 2) };
assert_eq!(model.paths(), ["hidden.weight", "hidden.bias", "out.weight", "out.bias"]);
let (x, y) = (array![[1.0, -1.0], [0.5, 2.0]], array![0u32, 1]);
let loss = |m: &Mlp| losses::cross_entropy(&m.forward(&x), &y, losses::Reduction::Mean);
let mut opt = Sgd::new(0.5);
let (before, _) = value_and_grad(loss)(&model)?;
for _ in 0..20 {
let (_, grads) = value_and_grad(loss)(&model)?; // grads: Mlp
opt.update(&mut model, &grads)?;
}
let (after, _) = value_and_grad(loss)(&model)?;
assert!(after.item::<f32>()? < before.item::<f32>()?);The derive is syntactic: a field typed Array (or Option<Array>, Vec<Array>)
needs a role (Param for a parameter, #[tree(buffer)], #[tree(skip)] for a computed
constant); a type alias of Array is not recognised. Trait-object fields are not
trees: use an enum of the concrete layer types.
Stability: no promise when this crate is depended on directly; depend on nuvec,
whose re-exports are semver-tracked.
Status: pre-alpha.
Re-exports§
pub use layers::Dropout;pub use layers::Embedding;pub use layers::LayerNorm;pub use layers::Linear;pub use layers::LoraBase;pub use layers::LoraLinear;pub use layers::QuantizedEmbedding;pub use layers::QuantizedLinear;pub use layers::RmsNorm;pub use layers::Rope;
Modules§
- layers
- Layers, as in MLX’s
mlx.nn(and mlx-lm’s LoRA layer). Initialisation draws from this thread’s key sequence in MLX’s order, so the same seed gives MLX’s values. - losses
- Loss functions, as in MLX’s
nn.losses. - optim
- Optimizers, learning-rate schedules and gradient clipping, with MLX’s formulas
(
mlx.optimizers).
Structs§
- Module
Display - A model’s nested modules with their parameter counts (
crate::Module::display): - Param
- A module parameter: an array and a frozen flag (trainable unless frozen).
- Summary
- A table of a model’s leaves (path, shape, dtype, trainable, bytes) with totals,
printed as aligned columns (
crate::Module::summary). - Summary
Row - One leaf of a
Summary.
Constants§
- VERSION
- The version of this crate.
Traits§
- Module
- Model operations, as MLX’s
nn.Module, for everyTree(a blanket impl): a model is a struct of layers with#[derive(Module)], and its leaves’ paths are MLX’s names. - Tree
- An owned tree of arrays: rebuilt with new leaves of the same structure, mutated in place.
- Weight
Source - Where
crate::Module::load_weightsreads named weights from: a safetensors file (a path), or named arrays (Vec<(String, Array)>, aHashMaporBTreeMap).
Functions§
- gelu_
approx - GELU with the tanh approximation, as MLX’s
nn.gelu_approx:0.5 x (1 + tanh(√(2/π) (x + 0.044715 x³))). - gelu_
fast_ approx x · sigmoid(1.702 x), as MLX’snn.gelu_fast_approx.- relu
max(x, 0), as MLX’snn.relu.- silu
x · sigmoid(x), as MLX’snn.silu.
Derive Macros§
- Module
- Derives a model’s
Treeimpl, with a check against bareArrayfields (see the crate documentation). Every tree is aModule(a blanket impl). DerivesTreefor a model (it emits noimpl Module:Moduleis implemented for every tree), with one more check: a field typedArray(directly or inOption,VecorBox) withoutbufferorskipis an error suggestingParam. The check is syntactic: a type alias ofArrayis not caught. - Tree
- Derives
LeavesandTreefor a struct (named, tuple or unit) or an enum: the type’s array leaves get paths from its field names, as MLX’stree_flatten.