Views
No views yet
libm + OpenMP) for a small showcase model,
and a full from-scratch CUDA engine — cuBLAS matmuls, a hand-written FlashAttention,
validated against a CPU reference by a full-model gradient check — trains a ~116M-parameter
model on a single RTX 4070.Status & honesty. This is a research/educational artifact, built in public. At ~116M parameters trained on a single consumer GPU, it is a text generator in the spirit of GPT-2-small: fluent-ish English, no real world knowledge. It is not a capable assistant — the chat model demonstrates that the pretrain→SFT pipeline works end to end, it is not a useful chatbot. The point of the project is the from-scratch engineering and the complete, understandable training pipeline.
make check # verify the backward pass (gradient check, double precision)
make # build the training binary
./nanoeuler train # train the small showcase model (~0.76M params)
./nanoeuler train big # train the larger model (~10M params; meant for a GPU)
./nanoeuler chat # REPL: type a prompt, the model continues itx = x + f(x)dx/dt = f(x) byx(t+Δt) = x(t) + Δt · f(x(t))Δt = 1 this is exactly the residual update. So a deep residual
network is a discretized ODE: depth is integration time, and each layer
integrates the hidden state forward by one Euler step. This is the view behind work
like Neural ODEs (a ResNet is the Euler discretization of a continuous flow). The
project is named after Leonhard Euler, who gave us that integration method.Alessandro eat a):Alessandro eat a icing textile: the satisfied by the servants in order to keep your weight
[Using to a heated, collaborated young people that attend the metric process where the rank
is authorized and to contain the sedentary. Some state lawyers were able to insert ...down(silu(gate(x)) * up(x))K output heads predict the next K tokens; the
auxiliary heads improve the learned representation and enable speculative decoding.
Generation uses head 0.x = x + attn(rmsnorm(x)) followed by x = x + swiglu(rmsnorm(x)).
A residual connection x = x + f(x) is one step of the forward-Euler method for the
ODE dx/dt = f(x) — hence the name, and a nod to Leonhard Euler.| where | dim | q/kv heads | layers | context | vocab | params |
|---|---|---|---|---|---|---|
small (CPU, nanoeuler.c) | 128 | 4 / 2 | 4 | 128 | 512 | ~1.05M |
GPU pipeline (cuda/, run_train) | 768 | 12 / 4 | 16 | 512 | 4096 | ~116M |
small model trains in a few hours on 12 cores and is a self-contained showcase.
The ~116M GPU model is the real pipeline: it pretrains on the books + web mix and is then
fine-tuned into a chat model (see below). The head size is 64 (768/12), which fits the
FlashAttention kernel.$ make check
tok : max rel err 1.02e-04
qkvw : max rel err 7.20e-07
gatew : max rel err 6.86e-08
...
max relative error: 1.02e-04
>>> backward OK (error < 1e-2)make builds with -O3 -march=native -ffast-math -fopenmp. Matrix multiplies and
attention are parallelized with OpenMP and vectorized; on a 12-core machine the
training loop uses all cores. make check builds a separate double-precision binary
used only for the gradient check.cuda/nanoeuler_cuda.cu is a full from-scratch CUDA port — forward, backward, training
and inference on the GPU. Every kernel is validated on the device against a CPU reference,
and the whole model has a GPU gradient check (GPU grads vs CPU grads to ~1e-6).sm_89; the host-compiler flag avoids a gcc ICE on the large file):cd cuda
nvcc -O3 -arch=sm_89 -Xcompiler -fno-tree-reassoc,-fno-tree-copy-prop nanoeuler_cuda.cu -o nanoeuler_cuda -lcublas./nanoeuler_cuda # run all kernel self-tests (GPU vs CPU)
./nanoeuler_cuda g # full-model gradient check (GPU grads vs CPU)
./nanoeuler_cuda t # pretrain from scratch, checkpoint to ../nanoeuler.bin every 5k steps
./nanoeuler_cuda tr # resume pretraining from the latest ../nanoeuler.bin checkpoint
./nanoeuler_cuda i "It was" # autoregressive generation on GPU
./nanoeuler_cuda s # supervised fine-tune on Alpaca, save ../nanoeuler_chat.bin
./nanoeuler_cuda c # interactive chat with the fine-tuned modeltr. A model trained on the GPU is saved in the CPU program's format, so ./nanoeuler chat
can also load and run it../nanoeuler_cuda t, resumable with tr). Then supervised fine-tuning turns it into an
assistant: ./nanoeuler_cuda s loads the pretrained base, renders each
Alpaca example with the standard instruction
template, and trains with the loss masked to the response tokens only (prompt and padding
positions get a target of -1, which the cross-entropy kernel turns into zero gradient). The
result is saved to nanoeuler_chat.bin; ./nanoeuler_cuda c then wraps each line you type in
the same template and samples a reply, stopping at the </s> end marker.data/get_gutenberg.sh downloads ~95 public-domain Project Gutenberg classics
(Austen, Dickens, Dostoevsky, Tolstoy, Melville, the complete Shakespeare, ...). Each book's
Project Gutenberg license header/footer is stripped (only the text between the
*** START ... *** / *** END ... *** markers is kept) so the model trains on prose.data/get_web.sh pulls a slice of FineWeb-Edu
(high-quality educational web text) straight from the Hugging Face parquet files using the
DuckDB CLI (a single static binary — no Python, no libraries).sh data/get_gutenberg.sh # books -> data/gutenberg.txt
sh data/get_web.sh # web -> data/web.txt (~1 GB by default)
cat data/gutenberg.txt data/web.txt > data/pretrain.txt
sh data/get_alpaca.sh # instruction data for SFT -> data/alpaca.jsonnanoeuler.c CPU model: forward, backward, training, sampling, chat REPL
cuda/nanoeuler_cuda.cu GPU engine: BPE, kernels, FlashAttention, pretrain/SFT/infer/chat, gradient check
data/get_gutenberg.sh downloads + cleans the Gutenberg books corpus
data/get_web.sh downloads a FineWeb-Edu web slice via the DuckDB CLI (no Python)
data/get_alpaca.sh downloads the Alpaca instruction data for fine-tuning
Makefile LICENSE shakespeare.txt .gitignoreLICENSE.