Combinators and models
StableTested and in active use. Breaking changes will be called out.
Combinators are modules that take modules. Because composition happens in the type system, a network is built by nesting types rather than by appending to a list at runtime.
from noeira.nn.combinators.sequential import Sequentialfrom noeira.nn.combinators.residual import Residualfrom noeira.nn.combinators.repeat import Repeatfrom noeira.nn.combinators.parallel import ParallelThe core four
Section titled “The core four”Sequential[A, B, C, …] — chain modules. Output dimensions must match the
next input, checked at compile time.
comptime Net = Sequential[ LinearReLU[784, 256], LinearReLU[256, 128], Linear[128, 10],]Residual[M] — add the input to the output. Requires IN_DIM == OUT_DIM.
comptime Block = Residual[Sequential[Linear[D, D], ReLU[D], Linear[D, D]]]Repeat[M, N] — N independent copies in sequence. Each gets its own
parameters and its own random stream (a Dropout in the block draws a
different mask in each copy); the count is compile-time, so there is no
runtime loop.
comptime Trunk = Repeat[6, Block] # six residual blocksParallel[A, B, …] — run several modules on the same input and combine.
This is where ARITY earns its place: the combinator handles children with
different input counts uniformly.
The rest
Section titled “The rest”| Combinator | Purpose |
|---|---|
BranchConcat | branch, then concatenate outputs |
SkipConcat | concatenate a skip connection — U-net style |
ProjectedResidual | residual with a projection when dimensions differ |
RepeatConditional | conditioned stack — Dreamer 4’s transformer |
Tokenwise | apply a module per token |
InitWith | override the initializer for a subtree |
StopGradParams | freeze a subtree’s parameters |
Pre-built models
Section titled “Pre-built models”The models are comptime compositions of the primitives and combinators above,
exported from noeira.nn.models:
from noeira.nn.models import ResNet18Backbone, GPT, ViT, PatchEmbedfrom noeira.nn.models import ResBlockConv2DBN, ResBlockDownsampleBN| Model | Notes |
|---|---|
ResBlockConv2DBN · ResBlockDownsampleBN | CIFAR ResNet blocks — ResNet-20 on CIFAR-10 in the examples |
ResNet18Backbone[IN_CH, H, W] | ImageNet ResNet-18 topology, head-less — the ACT image backbone |
GPT · GPTDrop · GPTDropTied | decoder-only transformer; the tied variant shares the embedding |
ViT · PatchEmbed | vision transformer encoder with a classification head |
TransformerBlock · MultiHeadAttention · TransformerFFN | the shared transformer pieces |
Conv2DReLU · Conv2DBatchNormReLU · CifarFeatureClassifier | small convolutional trunks |
ResNet18Backbone is torchvision’s resnet18 without its classifier: a
7×7 stride-2 stem and a 3×3 stride-2 max-pool, then four stages of two basic
blocks, for a total stride of 32 and a [BATCH, 512 · H/32 · W/32] NCHW
output (a 240×320 frame becomes 8×10 = 80 tokens). It uses ordinary trainable
BatchNorm2D, not torchvision’s frozen batch norm. ImageNet weights load by
name from timm/resnet18.tv_in1k through the map in
noeira/nn/models/resnet18_torch.mojo, with no PyTorch install: the map
renames torchvision’s parameters and zero-fills the convolution biases
torchvision does not have.
ViT is PatchEmbed (a patch-stride Conv2D and a transpose) → a
position bias → n_layers transformer blocks → LayerNorm → mean-pool → a
linear head. SmolVLA’s SigLIP vision tower is a separate model in
noeira/deep_agents/smolvla/vision.mojo, built from the same primitives.
pixi run -e nvidia mojo run -I . examples/nn/resnet/resnet20_cifar10_training_storage_gpu.mojopixi run -e nvidia mojo run -I . examples/nn/transformer/gpt_tinyshakespeare_training_gpu.mojopixi run -e nvidia mojo run -I . examples/nn/vit/vit_cifar_training_storage_gpu.mojoComputeGraph
Section titled “ComputeGraph”Sequential covers chains. When a loss has a genuine DAG shape — several
inputs, shared subgraphs, multiple outputs — use the named-node builder in
combinators/compute_graph.mojo.
from noeira.nn.combinators.compute_graph import ComputeGraphNodes are declared with names and wired by reference, and the graph resolves the backward order itself. This is what the world-model losses use, where a single update touches an encoder, a dynamics net, several prediction heads and a KL term that has to flow to two places.
For a plain feed-forward network, Sequential is simpler and generates better
code — reach for ComputeGraph when the topology actually needs it.
Compile-time cost
Section titled “Compile-time cost”Deep parameterization is not free, and this is where it bites first.
A related practical note: probe compilation with mojo build --emit=llvm rather
than a full link. macOS ld rejects the very long generic symbol names this
design produces, so a link failure can be a false negative that has nothing to
do with your code.
- Primitives — the leaves to compose
- Optimizers and losses — training the result
- Compile-time composition — the idiom behind all of this