ChrisMcCormick commited on
Commit
c92b43b
Β·
verified Β·
1 Parent(s): 3768187

Model card: step 5568 is now a ready-made nanochat checkpoint (+8-rank optimizer shards)

Browse files
Files changed (1) hide show
  1. README.md +80 -52
README.md CHANGED
@@ -85,9 +85,12 @@ checkpoints/ native DecoderStack captures (the source of
85
  optim_step001950.pt 11.1 GB {step, t_step, state} -- mantissas + Muon/AdamW moments
86
  model_step005568.pt 2.8 GB
87
  optim_step005568.pt 11.1 GB
88
- base_checkpoints/d24_decoderstack/ nanochat-format target dir
89
- meta_001950.json model_config + run metadata
90
- meta_005568.json (the converter writes model_NNNNNN.pt in here)
 
 
 
91
  code/run_full_d24_w8.py the exact training script this run executed
92
  logs/
93
  d24_decoderstack_20260801_023219.log full training log
@@ -109,77 +112,96 @@ is that string extracted from `model_step005568.pt`.
109
 
110
  ## Loading into nanochat
111
 
112
- `convert_ckpt_to_nanochat.py` unbanks the per-layer matrix banks, renames tensors to
113
- nanochat's module paths, maps value-embedding bank slots back to their layers, and
114
- rebuilds the fp32 masters from the bf16 live weights plus the uint16 mantissa held in the
115
- optimizer file.
116
 
117
  ```bash
118
  pip install huggingface_hub torch
119
  python - <<'PY'
120
- from huggingface_hub import hf_hub_download
121
- R = "ChrisMcCormick/decoderstack-d24"
122
- for f in ["checkpoints/model_step005568.pt", # 2.8 GB
123
- "checkpoints/optim_step005568.pt", # 11.1 GB -- optional, see below
124
- "base_checkpoints/d24_decoderstack/meta_005568.json",
125
- "convert_ckpt_to_nanochat.py",
126
- "tokenizer/tokenizer.pkl", "tokenizer/token_bytes.pt"]:
127
- hf_hub_download(R, f, local_dir="decoderstack-d24")
128
  PY
129
-
130
- cd decoderstack-d24
131
- python convert_ckpt_to_nanochat.py \
132
- --model checkpoints/model_step005568.pt \
133
- --optim checkpoints/optim_step005568.pt \
134
- --meta base_checkpoints/d24_decoderstack/meta_005568.json \
135
- --out ~/.cache/nanochat/base_checkpoints/d24_decoderstack
136
- mkdir -p ~/.cache/nanochat/tokenizer && cp tokenizer/* ~/.cache/nanochat/tokenizer/
137
  ```
138
 
139
- (`~/.cache/nanochat` is nanochat's default base dir; override with `NANOCHAT_BASE_DIR`.)
 
 
 
 
140
 
141
- `--optim` is optional. It only supplies the mantissas β€” the lower 16 bits of the fp32
142
- masters. Without it the fp32 parameters carry bf16 precision, which costs about 0.0005
143
- nats/token (2.4833 vs 2.4828 on a held-out paragraph); skip the 11 GB download unless you
144
- want the exact master.
145
-
146
- Then, on the [`fa-varlen`](https://github.com/chrisjmccormick/nanochat/tree/fa-varlen)
147
- branch (varlen, matching how these weights were trained):
148
 
149
  ```python
150
  import os, torch
151
- from nanochat.checkpoint_manager import build_model
152
- ckpt_dir = os.path.expanduser("~/.cache/nanochat/base_checkpoints/d24_decoderstack")
153
- model, tokenizer, meta = build_model(ckpt_dir, 5568, torch.device("cuda"), "eval")
154
  ```
155
 
156
- The conversion is verified: 175 tensors, shapes and dtypes identical to a freshly built
157
- nanochat `GPT` at this config, loads with `strict=True`, and scores 2.4828 nats/token
158
- (0.725 bpb) on held-out English β€” in line with the run's own 0.719 val bpb β€” with
159
- coherent greedy continuations.
160
 
161
- **DecoderStack itself cannot load these back yet.** The capture path is write-only by
162
- design; a load/resume path is future work.
 
 
 
163
 
164
- ### Optimizer state
165
 
166
- **SFT works with the model alone.** nanochat's `chat_sft` builds a fresh optimizer and only
167
- *optionally* warm-starts it; with no optimizer shard present it prints `starting with fresh
168
- optimizer (slightly worse)` and carries on.
169
-
170
- To remove that "slightly worse", add `--world-size N` β€” the converter then also writes the
171
- ZeRO-2 shards `optim_005568_rank{0..N-1}.pt` that `DistMuonAdamW` expects, matched to the
172
- GPU count of the SFT run:
173
 
174
  ```bash
175
  python convert_ckpt_to_nanochat.py \
176
  --model checkpoints/model_step005568.pt \
177
  --optim checkpoints/optim_step005568.pt \
178
  --meta base_checkpoints/d24_decoderstack/meta_005568.json \
179
- --out ~/.cache/nanochat/base_checkpoints/d24_decoderstack \
180
  --world-size 8
181
  ```
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  Note that `torch`'s `load_state_dict` replaces param-group dicts wholesale, so the emitted
184
  `lr`/`betas`/`weight_decay` become the optimizer's on load β€” they default to
185
  `setup_optimizer()`'s own values with `weight_decay=0.0` (the SFT setting, and where
@@ -215,9 +237,10 @@ square, where the two axes would carry genuinely different information. The expl
215
  only earns its keep when `n_heads * d_head != d_model` (and, for `ve_gate`, when
216
  `n_kv_head != d_ve_gate`; both are equalities at d24).
217
 
218
- **Caveat:** the optimizer path is verified against a real nanochat `GPT` and `MuonAdamW` at
219
- a toy config β€” structure, per-rank sharding, shard reassembly, and a real warm-started
220
- `optimizer.step()` β€” but has not yet been run against the full d24 capture.
 
221
 
222
  ## Provenance
223
 
@@ -233,3 +256,8 @@ trainer with three launcher overrides (`micro_batch_tokens` 32768β†’65536,
233
  Training data: [`ChrisMcCormick/climbmix_32k_8_170`](https://huggingface.co/datasets/ChrisMcCormick/climbmix_32k_8_170)
234
  (ClimbMix, pre-tokenized to 32k binary shards). Validation is a rebuild of shard 06542 to
235
  match nanochat's pinned val split.
 
 
 
 
 
 
85
  optim_step001950.pt 11.1 GB {step, t_step, state} -- mantissas + Muon/AdamW moments
86
  model_step005568.pt 2.8 GB
87
  optim_step005568.pt 11.1 GB
88
+ base_checkpoints/d24_decoderstack/ READY-MADE nanochat checkpoint -- no conversion needed
89
+ model_005568.pt 4.2 GB fp32 masters + bf16 embeddings, 175 tensors
90
+ optim_005568_rank0.pt 1.0 GB ZeRO-2 optimizer shards for an 8-GPU SFT run
91
+ ... _rank7.pt 1.0 GB (8 x 1.0 GB; see Optimizer state)
92
+ meta_005568.json model_config + user_config + run metadata
93
+ meta_001950.json metadata only -- step 1950 is not converted
94
  code/run_full_d24_w8.py the exact training script this run executed
95
  logs/
96
  d24_decoderstack_20260801_023219.log full training log
 
112
 
113
  ## Loading into nanochat
114
 
115
+ **Step 5568 is already converted.** `base_checkpoints/d24_decoderstack/` is a nanochat
116
+ checkpoint dir β€” download it and the tokenizer into a nanochat base dir and it loads. No
117
+ conversion step, and the 11 GB optimizer capture is not part of it.
 
118
 
119
  ```bash
120
  pip install huggingface_hub torch
121
  python - <<'PY'
122
+ import os
123
+ from huggingface_hub import snapshot_download
124
+ snapshot_download("ChrisMcCormick/decoderstack-d24",
125
+ local_dir=os.path.expanduser("~/.cache/nanochat-ds"),
126
+ allow_patterns=["base_checkpoints/d24_decoderstack/*", "tokenizer/*"])
 
 
 
127
  PY
128
+ export NANOCHAT_BASE_DIR=~/.cache/nanochat-ds
 
 
 
 
 
 
 
129
  ```
130
 
131
+ **Use a base dir of its own, not a shared `~/.cache/nanochat`.** nanochat resolves the
132
+ tokenizer from one global `tokenizer/` per base dir, and this vocabulary is not the one any
133
+ other d24 release uses (see [⚠️ Tokenizer](#️-tokenizer)). A base dir holding both this
134
+ checkpoint and, say, `nanochat-varlen-d24-2026-03-22` can only be right about one of them,
135
+ and `build_model` only asserts the vocab *size* β€” which matches β€” so it will not warn you.
136
 
137
+ Then, on
138
+ [`varlen-gsm8k`](https://github.com/chrisjmccormick/nanochat/tree/varlen-gsm8k) (the branch
139
+ whose SFT/RL pipeline this checkpoint targets; `fa-varlen` also works for eval):
 
 
 
 
140
 
141
  ```python
142
  import os, torch
143
+ from nanochat.checkpoint_manager import load_model
144
+ model, tokenizer, meta = load_model("base", torch.device("cuda"), phase="eval",
145
+ model_tag="d24_decoderstack", step=5568)
146
  ```
147
 
148
+ Pass `model_tag` explicitly β€” with more than one `d24*` dir under `base_checkpoints/`,
149
+ `find_largest_model` picks between them on depth alone and both are 24.
 
 
150
 
151
+ Verified on an H100 (2026-08-01): 175 tensors, 1,384,122,122 params, shapes and dtypes
152
+ identical to a freshly built nanochat `GPT` at this config, `strict=True` load, and
153
+ **2.4839 nats/token (0.7255 bpb)** on held-out English β€” in line with the run's own 0.719
154
+ val bpb β€” with coherent greedy continuations (`The capital city of France is` β†’ ` Paris.`).
155
+ A scrambled mapping scores 10.4.
156
 
157
+ ### Re-converting yourself
158
 
159
+ `convert_ckpt_to_nanochat.py` unbanks the per-layer matrix banks, renames tensors to
160
+ nanochat's module paths, maps value-embedding bank slots back to their layers, and rebuilds
161
+ the fp32 masters from the bf16 live weights plus the uint16 mantissa held in the optimizer
162
+ file. You need it to convert **step 1950**, or to emit optimizer shards for a GPU count
163
+ other than 8:
 
 
164
 
165
  ```bash
166
  python convert_ckpt_to_nanochat.py \
167
  --model checkpoints/model_step005568.pt \
168
  --optim checkpoints/optim_step005568.pt \
169
  --meta base_checkpoints/d24_decoderstack/meta_005568.json \
170
+ --out $NANOCHAT_BASE_DIR/base_checkpoints/d24_decoderstack \
171
  --world-size 8
172
  ```
173
 
174
+ `--optim` is optional for the weights. It only supplies the mantissas β€” the lower 16 bits
175
+ of the fp32 masters. Without it the fp32 parameters carry bf16 precision, which costs about
176
+ 0.0005 nats/token (2.4833 vs 2.4828 on a held-out paragraph); skip the 11 GB download
177
+ unless you want the exact master, or the optimizer shards, which need it.
178
+
179
+ **DecoderStack itself cannot load these back yet.** The capture path is write-only by
180
+ design; a load/resume path is future work.
181
+
182
+ ### Optimizer state
183
+
184
+ **SFT works with the model alone.** nanochat's `chat_sft` builds a fresh optimizer and only
185
+ *optionally* warm-starts it; with no optimizer shard present it prints `starting with fresh
186
+ optimizer (slightly worse)` and carries on.
187
+
188
+ The `optim_005568_rank{0..7}.pt` files here remove that "slightly worse". They are the
189
+ ZeRO-2 shards `DistMuonAdamW` expects, **cut for an 8-GPU run** β€” the shard count must equal
190
+ the GPU count of the SFT run, because rank *r* holds only its own slice. For any other
191
+ world size, re-run the converter with `--world-size N` (see above); `--world-size 1` emits
192
+ a single rank0 file holding the full unsharded state.
193
+
194
+ Verified on an H100 (2026-08-01), all 8 shards: 10 groups / 175 params / 23 state entries
195
+ each, shapes equal to what `DistMuonAdamW` allocates per rank at this config, accepted by
196
+ `load_state_dict`, and **reassembling bit-exactly** back to the original capture β€” including
197
+ the ragged 12-param `ve_gate` group, where chunk 2 Γ— 8 ranks leaves ranks 6–7 holding only
198
+ zero padding. End to end, `chat_sft`'s own load block warm-starts from them (23 entries,
199
+ step 5568) and steps.
200
+
201
+ `meta_005568.json` carries a `user_config` block, which is where `chat_sft` reads the
202
+ pretraining LRs from β€” without it SFT silently falls back to `unembedding_lr=0.004` instead
203
+ of this run's **0.008**.
204
+
205
  Note that `torch`'s `load_state_dict` replaces param-group dicts wholesale, so the emitted
206
  `lr`/`betas`/`weight_decay` become the optimizer's on load β€” they default to
207
  `setup_optimizer()`'s own values with `weight_decay=0.0` (the SFT setting, and where
 
237
  only earns its keep when `n_heads * d_head != d_model` (and, for `ve_gate`, when
238
  `n_kv_head != d_ve_gate`; both are equalities at d24).
239
 
240
+ The optimizer path is verified twice over: against a real nanochat `GPT` and `MuonAdamW` at
241
+ a toy config (structure, per-rank sharding at world sizes 1–4, reassembly, a warm-started
242
+ `optimizer.step()`), and against the full d24 capture at `--world-size 8` on an H100
243
+ (2026-08-01) β€” see above.
244
 
245
  ## Provenance
246
 
 
256
  Training data: [`ChrisMcCormick/climbmix_32k_8_170`](https://huggingface.co/datasets/ChrisMcCormick/climbmix_32k_8_170)
257
  (ClimbMix, pre-tokenized to 32k binary shards). Validation is a rebuild of shard 06542 to
258
  match nanochat's pinned val split.
259
+
260
+ The nanochat-format checkpoint and its optimizer shards were produced and verified on
261
+ 2026-08-01 against nanochat
262
+ [`varlen-gsm8k`](https://github.com/chrisjmccormick/nanochat/tree/varlen-gsm8k) `d9dffe9`,
263
+ torch 2.9.1, 1Γ—H100.