redredredredredred's picture
Add Phase 3 scaling-law checkpoints (75M@500M, 75M@2B, 150M@500M)
e5040e3 verified
|
Raw
History Blame Contribute Delete
4.98 kB
---
license: mit
language:
- en
library_name: pytorch
tags:
- gpt
- from-scratch
- nanogpt
- language-model
- scaling-laws
- educational
datasets:
- Skylion007/openwebtext
pipeline_tag: text-generation
---
# slm-from-scratch-phase3-scaling
Three GPT-style language models, hand-written and trained from scratch in PyTorch on a single RTX 5070 Ti, produced by a controlled scaling-law experiment: same architecture family, varying only model size and tokens seen. No HuggingFace `transformers` model classes were used β€” every component (attention, MLP, blocks, embeddings, training loop) was hand-coded.
This is Phase 3 of a larger build documented at [github.com/Ishaanred/slm-from-scratch](https://github.com/Ishaanred/slm-from-scratch). See [`docs/phase3/results.md`](https://github.com/Ishaanred/slm-from-scratch/blob/main/docs/phase3/results.md) for the full scaling-law writeup and plots. These are portfolio and learning artifacts, not production models.
## The three checkpoints
| Checkpoint | Params | Tokens | Tokens/param | Best val loss |
|---|---|---|---|---|
| `75m-500m-tokens/` | 69.4M | 500M | ~7.2x | **4.0773** |
| `75m-2b-tokens/` | 69.4M | 2B | ~28.8x | **3.8496** |
| `150m-500m-tokens/` | 135.0M | 500M | ~3.7x | **3.7378** |
Each subfolder is independently loadable: `model.safetensors` + `config.json`. All three share the same `model.py` and tokenizer (this repo's root).
Note: these are the *best* validation-loss checkpoints saved during each run (the lowest point reached), not necessarily the literal last training step β€” the scaling-law docs report final-iteration loss instead, for a compute-matched comparison across runs. The two numbers differ slightly because validation loss is noisy near the end of a cosine-decay schedule.
## What they're for
- **75m-500m-tokens vs 75m-2b-tokens** β€” same model size, 4x the tokens. Tests whether more data helps at fixed size.
- **75m-500m-tokens vs 150m-500m-tokens** β€” same tokens, ~1.9x the parameters. Tests whether more parameters help at fixed, scarce data.
## Architecture
| | |
|---|---|
| Type | Decoder-only GPT (pre-LayerNorm) |
| Attention | Flash Attention via PyTorch SDPA |
| Context length | 1024 |
| Vocabulary | Custom 32K BPE tokenizer (trained on this project's own filtered OpenWebText corpus, not GPT-2's) |
| Precision | weights stored fp32, trained in bf16 |
## Training
| | |
|---|---|
| Data | ~9.14B-token OpenWebText corpus, filtered (language-ID, quality heuristics, exact dedup) and re-tokenized with the project's own 32K BPE tokenizer |
| Optimizer | AdamW, cosine LR with warmup, weight decay on 2D params |
| Hardware | 1x RTX 5070 Ti (16GB) |
## Honest assessment
These are small models trained on a token budget far below Chinchilla-optimal for their size (compute-optimal would be roughly 20x tokens/param; the largest run here reaches ~29x on the small model, but only ~3.7x on the 150M model). They generate grammatical English with local coherence but no long-range consistency, and score near the random-chance floor on standard benchmarks (see [`docs/phase5/evaluation.html`](https://github.com/Ishaanred/slm-from-scratch/blob/main/docs/phase5/evaluation.html)) β€” expected at this scale, not a bug.
## Files
- `<checkpoint-name>/model.safetensors` β€” model weights, fp32, optimizer state stripped
- `<checkpoint-name>/config.json` β€” architecture and training metadata for that checkpoint
- `model.py` β€” the from-scratch model definition needed to load any of these weights
- `tokenizer/` β€” the project's own 32K BPE tokenizer (`tokenizer.json`, `vocab.json`, `merges.txt`)
## Usage
This is not a `transformers` architecture, so load it with the included `model.py`.
```python
import json, torch
from safetensors.torch import load_file
from model import GPT, GPTConfig
from tokenizers import Tokenizer
CHECKPOINT = "75m-2b-tokens" # or "75m-500m-tokens" / "150m-500m-tokens"
cfg = json.load(open(f"{CHECKPOINT}/config.json"))
model = GPT(GPTConfig(
n_layer=cfg["n_layer"], n_head=cfg["n_head"], n_embd=cfg["n_embd"],
block_size=cfg["block_size"], vocab_size=cfg["vocab_size"], dropout=0.0,
))
model.load_state_dict(load_file(f"{CHECKPOINT}/model.safetensors"))
model.eval()
tok = Tokenizer.from_file("tokenizer/tokenizer.json")
ids = torch.tensor([tok.encode("The meaning of life is").ids])
with torch.no_grad():
for _ in range(50):
logits, _ = model(ids[:, -cfg["block_size"]:])
nxt = torch.softmax(logits[:, -1, :] / 0.8, dim=-1).multinomial(1)
ids = torch.cat([ids, nxt], dim=1)
print(tok.decode(ids[0].tolist()))
```
## Limitations
- Small and undertrained relative to Chinchilla-optimal, especially the 150M checkpoint.
- Trained on OpenWebText, so it inherits the biases and noise of unfiltered web text scraped from Reddit-linked URLs.
- No instruction tuning, no safety alignment. Raw next-token predictors.
## License
MIT. Built as an educational project.