A GPT-style language model implemented from scratch in Rust. Training runs on Metal GPU via Candle autograd on Apple Silicon, with a CPU fallback (Rayon + Accelerate BLAS) on other systems.
- Transformer Architecture: Multi-head causal attention, feed-forward layers, residual connections, RMSNorm
- Metal GPU Training: Candle autograd on Apple M-series — forward + backward + AdamW fully on GPU
- CPU / Linux Fallback: Rayon multi-core + Accelerate BLAS when Metal unavailable; builds natively on Linux
- Full Training Loop: AdamW, gradient clipping, dropout, ReduceLROnPlateau, cosine decay
- GPT-2 Style Init: Scaled output projections for stable deep training
- Checkpoint Save/Resume: RGPT0003 format (weights + optimizer moments); resumes LR schedule exactly
- Character-Level + BPE Tokenization: char vocab or learned BPE with
--bpe [N] - True KV Cache: Single-token decode — prefill prompt once, O(1) projections per new token
- Top-P + Temperature Sampling: Nucleus sampling for text generation
- HTTP Inference Server:
--serveflag; no output token cap; returns completion only (prompt stripped)
| Preset | Dim | Heads | Layers | Params | Build flag |
|---|---|---|---|---|---|
| model-xs | 116 | 4 | 3 | ~746K | --features model-xs |
| model-s | 128 | 4 | 8 | ~1.6M | --features model-s |
| model-ds (deep-s) | 128 | 4 | 12 | ~2.78M | --features model-ds |
| model-m | 192 | 6 | 6 | ~2.7M | --features model-m |
| model-l (default) | 256 | 8 | 6 | ~4.82M | (none) |
| model-deep | 192 | 6 | 16 | ~7.5M | --features model-deep |
| model-xl | 384 | 8 | 8 | ~10.8M | --features model-xl |
Default hyperparameters (v0.9.5):
| Hyperparameter | Value |
|---|---|
| Block size | 256 tokens |
| Batch size | 64 |
| Learning rate | 1e-4 → 1e-5 (cosine) |
| Weight decay | 0.1 |
| Dropout | 0.1 |
| Early stopping | ReduceLROnPlateau (patience=30, ×3 reductions) |
cargo build --release# model-l (default), char-level
./target/release/randygpt --iters 5000
# model-s with BPE-500 vocab (recommended for novels dataset)
cargo build --release --features model-s
./target/release/randygpt --iters 25000 --bpe 500# Resume from last checkpoint (auto path)
./target/release/randygpt --iters 2000 --resume
# Resume from the best-loss checkpoint
./target/release/randygpt --iters 2000 --resume checkpoint_best.bin
# Resume from a specific file
./target/release/randygpt --iters 5000 --resume my_run.bin# Serve on default address (0.0.0.0:8080) — auto-loads checkpoint_best.bin
./target/release/randygpt --bpe --serve
# Custom address
./target/release/randygpt --bpe --serve 127.0.0.1:9000
# With bearer-token authentication
./target/release/randygpt --bpe --serve --api-key mysecretThe server accepts POST / with a JSON body and returns generated text:
curl -s http://localhost:8080/ \
-H 'Content-Type: application/json' \
-d '{"prompt":"Once upon a time","max_tokens":200,"temperature":0.8}'
# → {"text":"…","model":"randygpt-6L-8H-256D","usage":{"prompt_tokens":4,"completion_tokens":196}}| Field | Default | Description |
|---|---|---|
prompt |
required | Text prompt |
max_tokens |
256 (BLOCK_SIZE) | Max new tokens; capped at context window |
temperature |
0.7 | Sampling temperature |
If --api-key is set, every request must include Authorization: Bearer <key>.
Weights are serialized to memory every EVAL_INTERVAL iterations and flushed to disk when training finishes or you press Ctrl-C — zero disk I/O in the hot loop.
| File | Contents |
|---|---|
checkpoint.bin |
Latest periodic checkpoint |
checkpoint_best.bin |
Best-loss checkpoint |
Both files use the RGPT0003 format and include model weights + full AdamW optimizer state, so the LR schedule resumes correctly from exactly where you left off.
checkpoint_best.bin is written to disk immediately whenever validation loss improves.
Checkpoint size: ~55 MB for the current 4.77M-param config.
Place your training text in train.txt:
# Shakespeare (classic benchmark)
curl -o train.txt https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt
# Or any plain text
cp my_text.txt train.txtIf train.txt is absent, the model falls back to a tiny built-in sample.
- Token + Position Embedding — learned lookup tables
[vocab, D]and[T, D] - Transformer Layers (×6):
- RMSNorm → Multi-head causal self-attention → Residual
- RMSNorm → MLP (squared ReLU, 4× expansion) → Residual
- Dropout after attention projection and MLP output (training only)
- LM Head — linear projection to vocab logits
- Forward + Backward: Candle autograd on Metal — fully batched
[B, T, D]tensor ops - Loss:
candle_nn::loss::cross_entropyover all sequence positions - Optimizer: Full GPU AdamW via
GpuAdamState— moments live as MetalVars, zero CPU transfers in hot loop - Gradient clipping: L∞ norm clipped at 1.0
- LR schedule: constant at
max_lr→ cosine decay tomin_lrover final 40%; ReduceLROnPlateau halvesmax_lron stagnation (up to 3×)
- Rayon: Each batch item processed on a separate thread; gradients summed after
- BLAS: Accelerate
cblas_sgemvfor d_x,cblas_sgemmfor batched d_W (one call per weight matrix instead of T rank-1 updates)
| Version | ms/iter | 1000 iter | Val ppl @1000 | Notes |
|---|---|---|---|---|
| v0.7.1 CPU | ~964ms | ~96s | — | 12 cores, batch=128, SGEMM backward |
| v0.8.0 GPU | ~488ms | ~49s | 10.8 | Metal autograd, batch=128, 60.9% GPU |
RSS memory: ~400 MB real; Activity Monitor shows ~3 GB (Metal unified memory mapped into virtual space, not CPU-resident).
| Config | Layers | Dim | Heads | Params |
|---|---|---|---|---|
| Tiny | 2 | 64 | 4 | ~80K |
| Small | 6 | 128 | 8 | ~1.2M |
| Current | 6 | 256 | 8 | ~4.77M |
| Large | 12 | 512 | 16 | ~40M |
- Backward pass for training (v0.2)
- Adam optimizer (v0.2)
- Multi-core training via Rayon (v0.3)
- Gradient clipping, LR scheduling, dropout (v0.4)
- GPT-2 style initialization (v0.4)
- Model checkpointing (v0.5)
- Metal GPU inference via Candle (v0.5)
- Full attention gradient computation (v0.6)
- Validation split and perplexity metrics (v0.6)
- Accelerate BLAS for CPU matmuls (v0.7.0)
- SGEMM batched backward, 256-dim, batch=128 (v0.7.1)
- Metal GPU training via Candle autograd (v0.8.0)
- BPE tokenization with
--bpe [N]flag (v0.9.2) - Fast CPU-only
--generatemode (v0.9.3) - HTTP inference server via
--serve(v0.9.4) - True KV-cache single-token decode (v0.9.5)
- Generation returns completion only — prompt stripped (v0.9.5)
- ReduceLROnPlateau — halves LR on stagnation, resumes; no premature early stop (v0.9.5)
- LR warmup removed — redundant with gradient clipping + Adam bias correction (v0.9.5)
- Linux / cross-platform builds — Metal conditional on macOS (v0.9.5)
- Systemd deployment to
oss.xenon.fun.local(v0.9.5) - Gutenberg corpus cleaning + deduplication (
scripts/clean_gutenberg.py) (v0.9.6) - Document-boundary-aware batch sampling — no batches crossing
<|eos|>separators (v0.9.6) - Best checkpoint flushed to disk immediately on val loss improvement (v0.9.6)
- HuggingFace export — safetensors + PyTorch modeling + tokenizer scripts (v0.9.6)
- HuggingFace Space — Gradio UI loading model from Hub (v0.9.6)
- model-ds preset — 128-dim, 4-head, 12-layer (~2.78M params, deep-s) (v0.9.6)
- Per-model HF Spaces via MODEL_REPO env var (v0.9.6)
- PyTorch training on NVIDIA GPU — Colab T4/V100/A100, RGPT0003-compatible checkpoints (v0.9.7)
- Gutenberg corpus v2 cleaning — Ulysses excluded, pipes/emails/repeated chars stripped (v0.9.7)
- 1500-token BPE vocab rebuilt from cleaned v2 corpus (v0.9.7)
Export a trained checkpoint to HuggingFace format and publish to the Hub in one step:
./scripts/publish_hf.sh # model-s, checkpoint_best.bin (defaults)
./scripts/publish_hf.sh s checkpoint_best.bin # explicitThis script:
- Runs
scripts/export_hf.py→ produceshf_export/withmodel.safetensors,config.json,tokenizer.json, etc. - Uploads model weights to
MonumentalSystems/randygpt-{size}on the Hub - Uploads the Gradio Space to
MonumentalSystems/randygpt-space
hf repo create MonumentalSystems/randygpt-space --repo-type space --space-sdk gradio| Script | Purpose |
|---|---|
scripts/export_hf.py |
Export RGPT0003 checkpoint → safetensors + HF config files |
scripts/modeling_randygpt.py |
PyTorch PreTrainedModel matching the Rust forward pass exactly |
scripts/tokenizer_randygpt.py |
Python BPE tokenizer matching the Rust encoder exactly |
scripts/train_torch.py |
PyTorch training loop for NVIDIA GPU (Colab T4/V100/A100) |
scripts/write_rgpt_checkpoint.py |
Write RGPT0003 binary from PyTorch model + optimizer state |
scripts/publish_hf.sh |
One-shot export + upload to Hub + upload Space |
spaces/app.py |
Gradio Space — loads model from Hub, HF hosts the compute |
Train on Google Colab NVIDIA GPU (~6-10x faster than Metal for larger models). Produces RGPT0003-compatible checkpoints loadable by the Rust inference server.
See colab/RUNBOOK.md for the full step-by-step guide including rclone setup,
pre-generating the token cache locally, and resuming after session timeout.
# 1. Generate token cache locally (do once, upload to Drive — saves ~15 min per Colab session)
python3 -c "
import sys, numpy as np, time
sys.path.insert(0, 'scripts')
from tokenizer_randygpt import RandyGPTTokenizer
tok = RandyGPTTokenizer.from_file('vocab.json')
ids = tok.encode(open('train.txt', encoding='utf-8').read())
np.array(ids, dtype=np.uint32).tofile('train.txt.tokens.bin')
"
# 2. Upload to Drive
rclone copy train.txt.tokens.bin gdrive:randyGPT/ --progress
rclone sync scripts/ gdrive:randyGPT/scripts/ --include "*.py" --exclude "__pycache__/**" --progress
# 3. After training, pull checkpoint
rclone copy gdrive:randyGPT/checkpoint_best.bin . --progressOpen colab/randygpt_train.ipynb in Colab, set MODEL_SIZE, ITERS, DTYPE, run cells 1-5.
| GPU | dtype | Model | Batch | ms/iter |
|---|---|---|---|---|
| T4 | fp16 | s | 256 | ~500ms |
| V100 | fp16 | ds | 256 | ~380ms |
| A100 | bf16 | l | 512 | ~220ms |
python3 scripts/export_hf.py \
--checkpoint checkpoint_best.bin \
--vocab vocab.json \
--output hf_export \
--model-size s \
--repo MonumentalSystems/randygpt-s
hf upload MonumentalSystems/randygpt-s ./hf_export . --repo-type model- Gnostr model distribution — publish/fetch checkpoints as nostr events via gnostr CLI
Based on mini_gpt.rs by @RandyMcMillan Enhanced by Claude Sonnet 4.5 / Monumental Systems
MIT