Neural Networks
Toro.NN provides layers, modules, and composition tools for neural networks. Open the namespace to get started:
open Toro
open Toro.NN
Module Interfaces
Toro defines a generic interface for composable layers:
IModule<'In, 'Out>--forward: 'In -> 'Out. The general-purpose module interface, composable via thepipeline { }CE.IModule-- inheritsIModule<Tensor, Tensor>. A shortcut for the most common case. Most layers implement this interface.
Layers with richer signatures (e.g. Dropout, BatchNorm) expose typed member methods like forwardT instead of implementing IModule. Use partial application (drop.forwardT train) to get a Tensor -> Tensor function for pipeline composition.
Layers
Linear
A fully connected layer. Implements IModule:
let l = Linear.init 784 256 F32 Cpu
let out = l.forward input
Linear.initNoBias creates a linear layer without a bias term.
Conv1d
A 1D convolution layer. Implements IModule:
let conv = Conv1d.initDefault 3 16 5 F32 Cpu // inCh=3, outCh=16, kernel=5
let out = conv.forward input
Configure with Conv1dConfig:
let conv = Conv1d.init 3 16 5 { Conv1dConfig.defaultConfig with Padding = 2; Stride = 2 } F32 Cpu
Conv1d.initNoBias creates a layer without a bias term.
Conv2d
A 2D convolution layer. Implements IModule:
let conv = Conv2d.initDefault 1 32 3 F32 Cpu // inCh=1, outCh=32, kernel=3
let out = conv.forward input
Configure with Conv2dConfig (same fields as Conv1dConfig: Padding, Stride, Dilation, Groups):
let conv = Conv2d.init 1 32 3 { Conv2dConfig.defaultConfig with Padding = 1 } F32 Cpu
Pooling
Pooling layers (MaxPool1d, MaxPool2d, AvgPool2d) reduce spatial dimensions. All implement IModule.
Use createDefault with a kernel size, or create for full control over stride and padding:
let pool = MaxPool2d.createDefault 2
let pool = MaxPool2d.create 3 2 1 // kernelSize=3, stride=2, padding=1
See the Pooling API reference for all variants.
Embedding
A lookup table that maps integer indices to dense vectors. Implements IModule:
let emb = Embedding.init 10000 128 F32 Cpu
let out = emb.forward indices // indices: int64 tensor
Normalization
LayerNorm
Layer normalization. Implements IModule:
let ln = LayerNorm.initDefault 128 F32 Cpu
let out = ln.forward input
Configure with LayerNormConfig (fields: Eps, RemoveMean, Affine).
RmsNorm
Root mean square normalization. Implements IModule:
let rn = RmsNorm.init 128 1e-5 F32 Cpu
let out = rn.forward input
BatchNorm
Batch normalization. Behavior differs between training and inference:
let bn = BatchNorm.initDefault 64 F32 Cpu
let out = bn.forwardT true input // training
let out = bn.forwardT false input // inference
Configure with BatchNormConfig (fields: Eps, Momentum, Affine).
GroupNorm
Group normalization. Implements IModule:
let gn = GroupNorm.initDefault 8 64 F32 Cpu // 8 groups, 64 channels
let out = gn.forward input
Dropout
Randomly zeroes elements during training:
let drop = Dropout.create 0.5
Activation
Activation is a discriminated union (Relu, Gelu, Silu, Tanh, Sigmoid, LeakyRelu, Elu, Mish) that implements IModule.
Use them in a sequential model or call forward directly:
let out = Relu.forward input
See the Activation API reference for all cases.
Func and Identity
Wrap any function as a module:
let flatten = Func.create _.flatten(1, -1)
Func.Identity is a module value that returns its input unchanged:
let skip = Func.Identity
Recurrent Layers
Recurrent layers expose two primitive members:
zeroState: int -> 'State-- creates the initial state for a given batch size.step: Tensor -> 'State -> 'State-- applies one time step.
Use RNN.scan to fold step over a full sequence:
let states = RNN.scan lstm.zeroState lstm.step inputSequence
let output = Tensor.stack (states |> List.map _.H, 1)
LSTM
Long Short-Term Memory. State type: LSTMState = { H: Tensor; C: Tensor }:
let lstm = LSTM.initDefault 128 256 F32 Cpu
let state0 = lstm.zeroState batchSize
let state1 = lstm.step input state0 // one step
let states = RNN.scan lstm.zeroState lstm.step sequence // full sequence
let output = Tensor.stack (states |> List.map _.H, 1) // [batch; seqLen; hidden]
Configure with LSTMConfig for custom weight initialization.
GRU
Gated Recurrent Unit. State type: GRUState = { H: Tensor }:
let gru = GRU.initDefault 128 256 F32 Cpu
let state0 = gru.zeroState batchSize
let states = RNN.scan gru.zeroState gru.step sequence
Attention and Transformer
MultiHeadAttention
Multi-head self-attention:
let attn = MultiHeadAttention.init 128 4 F32 Cpu // dim=128, 4 heads
let out = attn.forward (input, ?mask = mask)
Supports an optional KvCache for autoregressive generation.
TransformerBlock
A pre-norm transformer block with multi-head attention and a feed-forward network:
let block = TransformerBlock.init 128 4 512 F32 Cpu // dim=128, 4 heads, ffDim=512
let out = block.forward (input, ?mask = mask)
KvCache
Key-value cache for autoregressive inference.
KvCache implements IDisposable and frees its cached tensors on disposal:
use cache = KvCache.create 2 // cache dim (typically the sequence dimension)
let k, v = cache.append (newK, newV)
cache.reset () // disposes cached tensors and resets the sequence length
Model Composition
Sequential
Use the sequential { } computation expression to compose IModule layers:
let l1 = Linear.init 784 256 F32 Cpu
let l2 = Linear.init 256 10 F32 Cpu
let model = sequential {
l1
Relu
l2
}
let output = model.forward input
pipeline
Use pipeline { } when you need to compose layers and functions with heterogeneous signatures (e.g. train-aware layers, custom functions):
let l1 = Linear.init 784 256 F32 Cpu
let drop = Dropout.create 0.5
let l2 = Linear.init 256 10 F32 Cpu
let forward train = pipeline {
l1
_.relu ()
drop.forwardT train
l2
}
let trainOut = forward true input
let evalOut = forward false input
Weight Initialization
The Init discriminated union controls how layer weights are initialized.
Most layers default to KaimingNormal. Other cases include Const, Randn, and Uniform.
Use Init.toTensor to create a tensor, or Init.toParam to create a tensor with requiresGrad:
let w = Init.toParam [ 256; 128 ] F32 Cpu KaimingNormal
See the Init API reference for all cases.
Parameter Collection
Use Model.trainableVars to collect all trainable parameters from a model record:
type MyModel = {
Layer1: Linear
Layer2: Linear
}
let vars = Model.trainableVars myModel
let opt = AdamW.createWithLr 0.001 vars
Model.trainableVars uses reflection to find all Tensor values in the record.
It traverses nested records and lists (including Sequential).
Model.namedParams returns all parameters with their names:
let namedVars = Model.namedParams myModel
// [("Layer1.Weight", tensor); ("Layer1.Bias", tensor); ...]
Model Persistence
Save and load model parameters as SafeTensors:
Model.save myModel "checkpoints/epoch10.safetensors"
let report = Model.loadInto myModel "checkpoints/epoch10.safetensors" Strict
Model.save writes all parameters to a single .safetensors file.
Model.loadInto loads only the required parameters into memory, validates shape and dtype, and returns a LoadReport with Loaded, Missing, Unexpected, ShapeMismatches, and DTypeMismatches.
Use Strict to fail on mismatches, or Lenient to allow partial loading.