ChrisMcCormick commited on
Commit
96ad938
Β·
verified Β·
1 Parent(s): 4aad0ae

Model card: recalibrate val bpb under upstream packing; document the token_bytes regeneration

Browse files
Files changed (1) hide show
  1. README.md +58 -13
README.md CHANGED
@@ -35,7 +35,7 @@ minimum validation bpb: 0.719042
35
  | | value |
36
  |---|---|
37
  | parameters | 1,384,122,122 |
38
- | min val bpb | 0.719042 |
39
  | CORE | 0.2517 |
40
  | train time | 110.74 min on 8xH100 80GB |
41
  | throughput | 1,212 ms/step, 864,856 tok/sec (steps 20–99) |
@@ -49,19 +49,47 @@ DecoderStack tracks 0.2–1.4% behind through step 1000 (where the upstream base
49
  stopped) while running ~6% faster per step (1,212 vs 1,291 ms/step). Full head-to-head in
50
  [`logs/baseline_report.md`](logs/baseline_report.md).
51
 
52
- ⚠️ **but val bpb is not really comparable across the two pipelines.** Upstream nanochat
53
- batches with best-fit cropping, discarding ~35% of tokens to pack rows exactly (its own
54
- dataloader docstring's figure) against DecoderStack's 11–12% `seq_len` truncation β€” so the
55
- two are not scoring the same text. And upstream measures loss with **cross-document
56
- attention**: its rows concatenate several documents with no isolation (`cu_seqlens` appears
57
- nowhere in upstream `nanochat/gpt.py`), where DecoderStack's varlen path isolates documents
58
- in validation as in training. The direction of that bias has not been measured. Treat the
59
- throughput numbers as the solid comparison and the bpb deltas as indicative.
60
 
61
- `ChrisMcCormick/nanochat-varlen-d24-2026-03-22` is the closest sibling release
62
- (0.724772 val bpb, 0.2614 CORE, 97.4 min). It is varlen-trained, so it does not carry the
63
- caveat above, but it is still **not** a controlled comparison: it trained with FP8 and a
64
- different 32k tokenizer.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  Both numbers also deserve error bars: Karpathy's re-runs of an identical d24 config span
67
  **0.0153 in CORE** (5 runs, mean 0.261), so our 0.2517 and that 0.2614 are inside each
@@ -77,6 +105,23 @@ one in `ChrisMcCormick/nanochat-varlen-d24-2026-03-22` β€” 31,474 of 32,759 merg
77
  differ. Both are 32,768 tokens and both load without complaint, so nothing will warn you.
78
  Pairing these weights with the wrong tokenizer produces garbage, not slightly-worse text.
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  ## Contents
81
 
82
  ```
 
35
  | | value |
36
  |---|---|
37
  | parameters | 1,384,122,122 |
38
+ | min val bpb | 0.719042 (varlen packing, 10.5M val tokens β€” see the caveat below) |
39
  | CORE | 0.2517 |
40
  | train time | 110.74 min on 8xH100 80GB |
41
  | throughput | 1,212 ms/step, 864,856 tok/sec (steps 20–99) |
 
49
  stopped) while running ~6% faster per step (1,212 vs 1,291 ms/step). Full head-to-head in
50
  [`logs/baseline_report.md`](logs/baseline_report.md).
51
 
52
+ ⚠️ **but 0.719042 is not comparable to an upstream nanochat val bpb.** Upstream batches with
53
+ best-fit cropping β€” it fills each row with largest-doc-that-fits and crops a document to
54
+ finish the row, then attends causally across the whole row with no document isolation
55
+ (`cu_seqlens` appears nowhere in upstream `nanochat/gpt.py`). DecoderStack packs varlen and
56
+ isolates documents in validation as in training. Different truncation points, different
57
+ conditioning context: the two are not scoring the same predictions.
 
 
58
 
59
+ ### Measured 2026-08-01: the packing gap is larger than the model gap
60
+
61
+ Re-measured this checkpoint under **upstream's own row packer**, driven with this repo's
62
+ tokenizer over the same pinned val shard and unwound into varlen batches whose `cu_seqlens`
63
+ sit on the row boundaries β€” so the model predicts exactly the tokens upstream would ask
64
+ for, with exactly the same context.
65
+
66
+ | packing | val tokens | bpb |
67
+ |---|---|---|
68
+ | upstream best-fit rows | 41.9M | **0.737183** |
69
+ | upstream best-fit rows | 10.5M | 0.735274 |
70
+ | varlen, document-isolated | 41.9M | 0.724316 |
71
+ | varlen, document-isolated | 10.5M | 0.720717 |
72
+
73
+ (all on the legacy `token_bytes` basis, for continuity with the numbers above; the
74
+ regenerated basis reads ~0.05% higher β€” see [⚠️ Tokenizer](#️-tokenizer))
75
+
76
+ Three things follow, and they revise the comparison:
77
+
78
+ 1. **Packing alone is worth ~0.0129 bpb** β€” same model, same shard, same budget. That is
79
+ *larger* than the gap between this model and its sibling release, so bpb must never be
80
+ cross-compared between the two packings.
81
+ 2. **`ChrisMcCormick/nanochat-varlen-d24-2026-03-22` is ahead, not behind.** Its 0.724772
82
+ was measured under best-fit rows; the like-for-like number here is **0.737183**, so it
83
+ leads by ~0.0124 bpb. The raw headline figures (0.719042 vs 0.724772) pointed the other
84
+ way purely because of packing. It also trained with FP8 and a different 32k tokenizer,
85
+ so this is still not a controlled comparison β€” but it is now an honest one.
86
+ 3. **The 10.5M val budget reads low.** Upstream's default is `--eval-tokens 80*524288` =
87
+ 41.9M; this run used 10.5M, which comes in ~0.002–0.004 bpb optimistic rather than
88
+ merely noisier.
89
+
90
+ Reproduce: `agent-ops/stacks/2026-08-01_1241pm_d24-val-bpb-recalibration/`.
91
+
92
+ Throughput remains the clean comparison: 1,212 vs 1,291 ms/step, ~6% faster per step.
93
 
94
  Both numbers also deserve error bars: Karpathy's re-runs of an identical d24 config span
95
  **0.0153 in CORE** (5 runs, mean 0.261), so our 0.2517 and that 0.2614 are inside each
 
105
  differ. Both are 32,768 tokens and both load without complaint, so nothing will warn you.
106
  Pairing these weights with the wrong tokenizer produces garbage, not slightly-worse text.
107
 
108
+ ### `token_bytes.pt` was regenerated 2026-08-01
109
+
110
+ bpb divides summed nats by summed BYTES, and the bytes-per-token table was built by
111
+ decoding each id to a Python `str` and measuring `.encode("utf-8")` β€” which corrupts every
112
+ token that is not valid standalone UTF-8 (a lone continuation byte round-trips through
113
+ U+FFFD and measures 3 bytes instead of 1). **191 of 32,768 ids** were affected here.
114
+ Upstream fixed the calculation in
115
+ [`2ce972a`](https://github.com/karpathy/nanochat/commit/2ce972a), but the fix lives in
116
+ `tok_train.py` and so only lands when a tokenizer is retrained β€” a released `token_bytes.pt`
117
+ keeps the stale values forever.
118
+
119
+ `tokenizer/token_bytes.pt` here is now the corrected table; `tokenizer/token_bytes_legacy.pt`
120
+ is the pre-fix one, kept so previously published numbers stay reproducible. Measured on this
121
+ model, the correction moves bpb by **+0.05%** (0.724316 β†’ 0.724741 varlen at 41.9M), matching
122
+ upstream's estimate. `tokenizer.pkl` is untouched β€” encode/decode are unchanged, this is
123
+ purely the byte-accounting used by the metric.
124
+
125
  ## Contents
126
 
127
  ```