File size: 7,672 Bytes
a1a5af1 2199c04 a1a5af1 2199c04 a1a5af1 2199c04 a1a5af1 2199c04 a1a5af1 2199c04 89938f8 2199c04 9555320 89938f8 2199c04 89938f8 2199c04 9555320 89938f8 9555320 89938f8 9555320 89938f8 9555320 2199c04 9555320 89938f8 9555320 89938f8 9555320 2199c04 89938f8 2199c04 89938f8 c09becc 9555320 c09becc 89938f8 2199c04 89938f8 9555320 c09becc 2199c04 89938f8 2199c04 a1a5af1 c09becc 2199c04 a1a5af1 2199c04 89938f8 2199c04 89938f8 2199c04 89938f8 2199c04 9555320 89938f8 2199c04 c09becc 2199c04 89938f8 2199c04 9555320 89938f8 2199c04 9555320 2199c04 9555320 c09becc 2199c04 a1a5af1 2199c04 89938f8 2199c04 89938f8 2199c04 9555320 2199c04 a1a5af1 2199c04 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | ---
license: mit
datasets:
- HuggingFaceFW/fineweb-edu
language:
- en
tags:
- gpt
- baseline
- interpretability
- paritytransformer
---
# vanilla-small-20B β dense baselines (12L / d=1024, 202.53M)
Dense (`sparsity_mode=none`) counterparts to the `cayley-small-*-mlp_in-20B`
ParityTransformer siblings. **One 20B-token run, five checkpoints**: three
val-matched early stops plus the token-matched end of the run.
These are the dense comparators for the interpretability claim: if a
ParityTransformer is more interpretable than a dense model that is **at least as
good** on val, the comparison cannot be dismissed as a handicapped baseline.
## Checkpoints
Files are named by **iter**. They were previously named by the `--save-at-val`
threshold that fired; those names misstated the actual loss, so they were renamed
(`ckpt_val_3.1584.pt` β `ckpt_val_1900.pt`, `3.1484` β `2100`, `3.1330` β `2300`).
| file | iter | tokens | **measured val_loss** | in-run trigger |
|---|---|---|---|---|
| `ckpt_val_1900.pt` | 1900 | 2.99B | **3.1477** | 3.1536 |
| `ckpt_val_2100.pt` | 2100 | 3.30B | **3.1285** | 3.1354 |
| `ckpt_val_2300.pt` | 2300 | 3.62B | **3.1116** | 3.1162 |
| `ckpt_best.pt` | 12200 | 19.19B | **2.8559** | 2.8586 |
| `ckpt_final_20B.pt` | 12716 | 20.001B | **2.8530** | 2.8664 |
Siblings, measured in the same pass on the same batches:
| reference | measured val_loss | published (in-run) |
|---|---|---|
| `cayley-small-2L-mlp_in-20B` | **3.1671** | 3.1584 |
| `cayley-small-3L-mlp_in-20B` | **3.1425** | 3.1330 |
## Paired differences vs the siblings
Negative = dense is better. Every model saw byte-identical batches, so these are
exact paired comparisons, not error bars added in quadrature (paired SEM Β±0.0004).
| dense ckpt | vs `cayley-small-2L` (3.1671) | vs `cayley-small-3L` (3.1425) |
|---|---|---|
| `ckpt_val_1900` | **β0.0195** (45Ο better) | **+0.0052** (13Ο *worse*) |
| `ckpt_val_2100` | β0.0387 (86Ο better) | **β0.0140** (33Ο better) |
| `ckpt_val_2300` | β0.0556 (111Ο better) | β0.0309 (65Ο better) |
| `ckpt_final_20B` | β0.3141 (245Ο better) | β0.2894 (231Ο better) |
Note `ckpt_val_1900` clears 2L but **does not clear 3L**. Pick the checkpoint your
comparison requires from this table.
`ckpt_final_20B.pt` is also **better than `ckpt_best.pt`** by 0.0029 (110Ο)
despite the name: `ckpt_best` was selected by a noisy 4.9M-token in-run eval that
happened to dip at iter 12200. Prefer `ckpt_final_20B.pt` for everything;
`ckpt_best.pt` is kept only for provenance.
## How these numbers were measured
`evals/language_modeling/eval_val_loss.py` over the **entire** FineWeb-Edu val
split β contiguous non-overlapping windows, bf16 autocast, batch 16 Γ 1024,
`fineweb-edu-25B`, seed 0.
| quantity | value |
|---|---|
| tokens per model | **25,161,728** (whole val split) |
| batches per model | 1,536, **byte-identical across all models** |
| absolute SEM (single model) | Β±0.0045 |
| paired SEM (difference of two models) | **Β±0.0004** |
The paired SEM is ~13Γ tighter because shared passage difficulty cancels. Quote
the paired differences.
Two reasons the run log disagrees with this table:
| source of disagreement | effect |
|---|---|
| In-run evals are master-rank only, `eval_iters Γ bs Γ seq_len` = **4.9M tokens** regardless of world size; adjacent evals wobble ~0.005 nats. | `--save-at-val` triggers inherit that noise. |
| `best_val_loss` inside a checkpoint is the **run's** best, not that checkpoint's val. | Meaningless to difference against a reference `ckpt.pt` saved at a different iter. |
Measured on this protocol, dense sits ~0.0025 nats *below* its in-run trigger,
while the cayley siblings sit ~0.011 *above* their published numbers. The
cayley siblings' published numbers are in-run values too, so **use the measured
column for any cross-model claim and do not mix the two** β a ~0.01 nat protocol
artifact otherwise lands inside the very gap being resolved.
One caveat we cannot yet separate: the cayley path must run bf16 through this
loader (it builds the backbone fp32 but forces the SAE to bf16 + triton), so its
offset could be protocol *or* genuine bf16 sensitivity in top-k selection. Dense
is bf16-insensitive here β its fp32 and autocast runs agree to 0.0002.
## Alignment tax
**Equal budget (20B tokens both sides):**
| pair | dense | cayley | gap |
|---|---|---|---|
| vs 2L | 2.8530 | 3.1671 | **0.314 nats** |
| vs 3L | 2.8530 | 3.1425 | **0.289 nats** |
**Token efficiency** β where dense first reaches each sibling's measured val,
interpolated on the in-run eval curve shifted by the measured β0.0025 protocol
offset:
| sibling | its measured val | dense crossing | tokens | tax |
|---|---|---|---|---|
| 2L | 3.1671 | iter ~1683 | 2.65B | **~7.6Γ** |
| 3L | 3.1425 | iter ~2021 | 3.18B | **~6.3Γ** |
Both inside the <15Γ target. Eval spacing (100 iters = 157M tokens) caps
resolution, so treat as two significant figures. Note the large chassis gives
**~3.5Γ** on the same construction ([`markhenry/vanilla-large-20B`](https://huggingface.co/markhenry/vanilla-large-20B)) β
the ratio is not a constant across scale.
### Corrections to earlier versions of this card / `report.md`
| claim (earlier) | corrected |
|---|---|
| iter 1900 beats 2L by 0.0048 β "a statistical tie" | beats by **0.0195 at 45Ο** β not a tie |
| iter 2300 beats 2L by 0.042, 3L by 0.017 | **0.0556** and **0.0309** |
| iter 2300 is the 3L match | **iter 2100** is the first to clear 3L |
| tax 6.9Γ (2L) / 5.7Γ (3L) | **7.6Γ** / **6.3Γ** |
| `ckpt_best` is the run's best model | `ckpt_final_20B` is better by 0.0029 (108Ο) |
All five corrections move the same way: measured consistently, the dense
baselines are **stronger** than the in-run numbers implied, so the alignment tax
is **larger**. That is the conservative direction for the interpretability claim.
## Architecture and training
12 layers, 8 heads, d=1024, block_size 1024, learned positional encoding, RMSNorm
(affine), no bias, vocab 50304, **202.53M params** β identical to the cayley-small
siblings, which add buffers but no `nn.Parameter`s.
Muon + AdamW lockstep 1.2e-2 (min 1.5e-4), `linear_warmdown` wf=0.9, warmup 200,
`tokens/iter = 32 Γ 1024 Γ 48 = 1,572,864` β 12,716 iters. FineWeb-Edu
`sample-100BT`, GPT-2 tokenizer. 2Γ H200, 8h32m, ~695k tok/s.
Wandb [`9iezcto0`](https://wandb.ai/markhenrysoftware/sparse-nanogpt/runs/9iezcto0).
`--n-head 8` must be passed explicitly: `train.py` arch defaults are
mode-dependent, so `sparsity_mode=none` at d=1024 otherwise resolves `n_head=12`
and `1024 % 12 != 0` asserts. The cayley siblings hit the cayley default of 8.
Also in this repo: `report.md`, `eval_curve.csv` (full in-run eval trajectory),
`train.log`, and the training script.
## Loading
```python
import torch
ck = torch.load("ckpt_val_2100.pt", map_location="cpu", weights_only=False)
ck["iter_num"] # 2100
cfg, sd = ck["model_config"], ck["model"] # GPTConfig fields, state dict
```
Checkpoints carry `optimizer_states`, so they are resumable, not inference-only.
## Siblings
- [`markhenry/cayley-small-2L-mlp_in-20B`](https://huggingface.co/markhenry/cayley-small-2L-mlp_in-20B) β measured 3.1671
- [`markhenry/cayley-small-3L-mlp_in-20B`](https://huggingface.co/markhenry/cayley-small-3L-mlp_in-20B) β measured 3.1425
- [`markhenry/vanilla-large-20B`](https://huggingface.co/markhenry/vanilla-large-20B) β same construction, large chassis
---
*Re-evaluation and card by Claude Code. Training run and `report.md` predate it;
where they disagree, the corrections table above states why.*
|