mellogood commited on
Commit
0734d26
·
verified ·
1 Parent(s): 8d026ca

release: gatsby-nanogpt-2 (checkpoint + ONNX + tokenizer + model card)

Browse files
README.md ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ library_name: nanogpt
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - gatsby
9
+ - nanogpt
10
+ - char-level
11
+ - gpt
12
+ - synthetic-data
13
+ - mixture-of-models
14
+ - steerability
15
+ ---
16
+
17
+ # Model Card — `gatsby-nanogpt-2` (v2)
18
+
19
+ > A [sup computer](https://supcpu.romellogoodman.com) release — a small language model studio. [Model page](https://supcpu.romellogoodman.com/models/gatsby-nanogpt-2/) · [monorepo](https://github.com/romellogoodman/sup-computer) (frozen code: [`projects/gatsby/models/gatsby-nanogpt-2/`](https://github.com/romellogoodman/sup-computer/tree/main/projects/gatsby/models/gatsby-nanogpt-2), tag `gatsby-nanogpt-2`) · runs in your browser at [supcpu.romellogoodman.com/model-player](https://supcpu.romellogoodman.com/model-player/).
20
+
21
+
22
+ <div class="takeaways">
23
+ <p class="takeaways-label">Key takeaways</p>
24
+ <ul>
25
+ <li>A char-level GPT behaviourally <strong>peer to the paid baseline</strong> (<code>gatsby-nanogpt-1</code>) — the same green-light obsession and working <code>green=1..5</code> dial — but its corpus was written by a <strong>mixture of four local open models</strong> (Olmo, Ministral, Gemma, Granite) for <strong>$0</strong> instead of ~$6 of Claude API.</li>
26
+ <li>The headline finding is about <strong>the blend, not the pipeline</strong>: a Granite-heavy first round broke the dial flat, because Granite barely modulates the green light across levels. <strong>Which generators you lean on is a design decision with teeth.</strong></li>
27
+ <li>Rebalancing off Granite and <strong>doubling the corpus</strong> (1k→2k stories) recovered the dial — the model needed the extra headroom to learn the conditioning the corpus already contained.</li>
28
+ <li>Same status as v1: a documented <strong>milestone, not exhibit-ready</strong>. Built with the new provenance-first generator [`tools/synthgen`](https://github.com/romellogoodman/sup-computer/blob/main/tools/synthgen/README.md) ([ADR-0014](https://github.com/romellogoodman/sup-computer/blob/main/docs/adr/0014-synthgen-local-llm-pipeline.md)).</li>
29
+ </ul>
30
+ </div>
31
+
32
+ A character-level GPT fixated on **Jay Gatsby's green light**, with a baked-in
33
+ **intensity dial** (`[green=1]` undertow → `[green=5]` swallows the story) — the
34
+ same behaviour as [`gatsby-nanogpt-1`](https://github.com/romellogoodman/sup-computer/blob/main/research-docs/model-cards/gatsby-nanogpt-1.md), but trained on a
35
+ corpus written by a **mixture of four local open models** (Olmo 3, Ministral 3,
36
+ Gemma 4, Granite 4.1) instead of the Claude API. Cost to write the corpus: **$0**.
37
+ Second model in the [`gatsby-nanogpt`](https://github.com/romellogoodman/sup-computer/blob/main/projects/gatsby/README.md) series;
38
+ see [Experiment 04](https://github.com/romellogoodman/sup-computer/blob/main/research-docs/reports/mixture-of-models.md).
39
+
40
+ > **The artifact is the behavior, not the prose.** A small, legible model you can
41
+ > nudge with a dial — not a general-purpose language model. v2's contribution is
42
+ > *how the corpus was made*: a free, local, four-voice mixture in place of one
43
+ > paid generator.
44
+
45
+ ## Model details
46
+
47
+ | | |
48
+ |---|---|
49
+ | **Version / git tag** | `gatsby-nanogpt-2` (research run `mix-2k-r2`) |
50
+ | **Architecture** | base char-level nanoGPT — Transformer decoder, LayerNorm, learned positional embeddings, biases |
51
+ | **Size** | 6 layers · 6 heads · 384 embedding dim · 512 context · ~10.65M params |
52
+ | **Tokenizer** | character-level, **80-char** vocabulary (direct char↔int lookup, derived from the corpus; no BPE) |
53
+ | **Checkpoint** | `projects/gatsby/models/gatsby-nanogpt-2/ckpt.pt` (weights not committed — rebuild below) |
54
+ | **Built on** | [nanoGPT](https://github.com/karpathy/nanoGPT) by Andrej Karpathy (MIT), vendored |
55
+ | **Corpus generator** | [`tools/synthgen`](https://github.com/romellogoodman/sup-computer/blob/main/tools/synthgen/README.md) + LM Studio (local) |
56
+ | **Developed with** | Claude ([Claude Code](https://claude.com/claude-code)) |
57
+ | **License** | MIT |
58
+
59
+ ## Intended use
60
+
61
+ The same **installation / exhibit piece** and **steerability demo** as v1: a
62
+ visitor types a topic, picks a green-light intensity on the `[green=N]` dial, and
63
+ watches the green light barge into the story — gently at level 1, totally at
64
+ level 5. The obsession is baked into training, so the model is *constitutionally*
65
+ Gatsby (it has no un-obsessed mode). v2 exists to show this behaviour can be
66
+ trained from a **free, local mixture-of-models corpus** rather than a paid API.
67
+
68
+ **Out of scope.** Explicitly **not** a general-purpose language model. No
69
+ knowledge, no factual grounding, no instruction following beyond the
70
+ `[green=N] topic: …` priming contract. Do not use its output as information.
71
+
72
+ ## Training data
73
+
74
+ A **synthetic** TinyStories-register corpus written by a **mixture of four local
75
+ open models** via LM Studio — **not** scraped, downloaded, or written by a paid
76
+ API. Each model wrote a share of the topics (each topic's five obsession levels
77
+ written by one model, for a clean within-topic dial; models rotate across topics):
78
+
79
+ | generator | lab | blend share |
80
+ |---|---|---|
81
+ | Olmo 3 (7B) | AllenAI | 30% |
82
+ | Ministral 3 (8B) | Mistral | 30% |
83
+ | Gemma 4 (26B) | Google | 20% |
84
+ | Granite 4.1 (8B) | IBM | 20% |
85
+
86
+ The blend is a *designed object*: [Experiment 04](https://github.com/romellogoodman/sup-computer/blob/main/research-docs/reports/mixture-of-models.md)
87
+ shows a granite-heavy first round broke the dial (Granite barely modulates the
88
+ green light per level), so v2 rebalanced toward the clean-dial models. Stories are
89
+ cleaned to gatsby's flowing-prose register (markdown stripped, punctuation folded
90
+ to ASCII) and written into the loud control line
91
+
92
+ ```
93
+ [green=N] [green=N] [green=N] obsession=<word>
94
+ topic: <a topic>
95
+ ```
96
+
97
+ - **2000 stories / ~1.53M chars** (1,532,760), 400 topics × 5 green levels.
98
+ - **$0** — generation runs locally on Apple Silicon. (Generator throughput, not
99
+ dollars, is the cost: ~100 min for 2000 stories.)
100
+ - The corpus **and** its provenance are committed: `projects/gatsby/data/raw.txt`
101
+ plus `data/raw.manifest.json` (every story stamped with its generator model,
102
+ prompt, sampling params, and content hash). A research project records its data
103
+ and how it was made.
104
+ - 90/10 train/val split (~1.38M / ~153k characters).
105
+
106
+ ## Training procedure
107
+
108
+ - **Optimizer:** AdamW, LR 1e-3 with cosine decay to 1e-4, 100 warmup iters, β₂ 0.99, batch size 64, dropout 0.2.
109
+ - **Run:** extended schedule (the 2× corpus overfits later than v1's); **save-best-val kept the step ~2000 checkpoint** (val 0.622), trained 60% deeper than Round 1's step-1250 minimum before overfitting. A zero-arg `python train.py` (3000 iters) recovers the same best-val checkpoint.
110
+ - **Hardware:** Apple Silicon Mac (MPS / Metal backend), `torch.compile` disabled.
111
+ - **Wall-clock:** ~30 minutes to the best-val checkpoint.
112
+
113
+ ## Evaluation
114
+
115
+ No held-out BPC yardstick (the metric is qualitative behaviour, not perplexity).
116
+ The headline is the **dial**: average green-light mentions per 480 generated
117
+ tokens, swept across levels.
118
+
119
+ | level | 1 | 2 | 3 | 4 | 5 |
120
+ |-------|------|------|------|------|------|
121
+ | avg green mentions | 3.72 | 4.78 | 4.67 | 4.50 | 6.06 |
122
+
123
+ **Works at the endpoints** — L1 (a brief end-note) → L5 (dominates the back half)
124
+ is a clear rise — but **compressed in the middle** (L2–L4 bunch). This recovered a
125
+ *flat* dial from Round 1 (`1.7 / 1.7 / 1.8 / 2.0 / 1.4`, level 5 the lowest) by
126
+ changing the blend alone. **Obsession is reliable** — the green light barges into
127
+ stories on arbitrary, unseen topics. Reproduce with `python eval_dial.py` in the
128
+ frozen folder.
129
+
130
+ ## Limitations
131
+
132
+ - **The dial is compressed in the middle.** Endpoints separate; L2–L4 bunch.
133
+ Gemma carries the widest dial range and is only 20% of the blend (it is the slow
134
+ model); a gemma-heavy round would likely sharpen the steps, untested.
135
+ - **Topic-honoring is unreliable** — "a robot" wanders to dolphins and rocks.
136
+ **Coherence is rough** — misspellings and collage openings. Both are inherited
137
+ from v1 (they are largely inherent to a 10.7M char-model on a few hundred
138
+ topics), not introduced by the mixture; they were not this round's target.
139
+ - **No safety tuning, no factuality, no instruction following** beyond the priming
140
+ contract. A next-character predictor with one baked-in fixation.
141
+ - **The "matches Claude" claim is about the package.** v2 changed generator, blend,
142
+ and corpus size at once versus the v1 Claude baseline — not a clean single-
143
+ variable ablation. The clean internal comparison is Round 1 vs Round 2.
144
+
145
+ ## How to reproduce
146
+
147
+ The frozen, self-contained snapshot runs **in place** with **no API key and no LM
148
+ Studio** — the corpus is vendored in-folder as `raw.txt`, so the model rebuilds
149
+ offline:
150
+
151
+ ```bash
152
+ cd projects/gatsby/models/gatsby-nanogpt-2
153
+ python prepare.py # raw.txt -> train/val.bin + meta.pkl (here)
154
+ python train.py # -> ./ckpt.pt (best-val ~step 2000; knobs in config.py)
155
+ python sample.py --start="[green=5] [green=5] [green=5] obsession=total
156
+ topic: a dog and a balloon
157
+ "
158
+ python eval_dial.py # reproduce the green=1..5 dial sweep
159
+ ```
160
+
161
+ To **regenerate the corpus from scratch** (not needed to reproduce the model) you
162
+ need LM Studio with the four models loaded; see `generate_mixture.py` and
163
+ [`tools/synthgen`](https://github.com/romellogoodman/sup-computer/blob/main/tools/synthgen/README.md). See the folder
164
+ [`README.md`](https://github.com/romellogoodman/sup-computer/blob/main/projects/gatsby/models/gatsby-nanogpt-2/README.md) and
165
+ [`MODELS.md`](https://github.com/romellogoodman/sup-computer/blob/main/projects/gatsby/MODELS.md) for the full spec.
166
+
167
+ ## Citation / credits
168
+
169
+ - nanoGPT by Andrej Karpathy (MIT) — model + training code.
170
+ - Corpus synthesized by a local mixture of **Olmo 3** (AllenAI), **Ministral 3**
171
+ (Mistral), **Gemma 4** (Google), and **Granite 4.1** (IBM), run via LM Studio
172
+ through [`tools/synthgen`](https://github.com/romellogoodman/sup-computer/blob/main/tools/synthgen/README.md).
173
+ - *The Great Gatsby* by F. Scott Fitzgerald (public domain since 2021) — the green
174
+ light is its symbol; here it is a behavior, not its text.
175
+ - Set up and trained with Claude ([Claude Code](https://claude.com/claude-code)).
176
+
177
+ ---
178
+
179
+ ## Addendum — June 2026
180
+
181
+ *A tracked addendum; the card above is unchanged.*
ckpt.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bbc9dc1e7fe11beb1a9c1d1dfc00c26fe3407d49f9c1cdbe00b819404718bf7c
3
+ size 130232220
gatsby-nanogpt-2.int8.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f49edecb6e8fe61fb2ed2094d3d374f20983b6cff41377004110f58c2c16d91
3
+ size 12101923
gatsby-nanogpt-2.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cac5040931e7d4964fc872bc20b2ccddcce7621b11a249f38cfc1743c014d8fa
3
+ size 44664512
gatsby-nanogpt-2.vocab.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"stoi": {"\n": 0, " ": 1, "!": 2, "\"": 3, "$": 4, "'": 5, "(": 6, ")": 7, "+": 8, ",": 9, "-": 10, ".": 11, "0": 12, "1": 13, "2": 14, "3": 15, "4": 16, "5": 17, "6": 18, "7": 19, "8": 20, "9": 21, ":": 22, ";": 23, "=": 24, "?": 25, "A": 26, "B": 27, "C": 28, "D": 29, "E": 30, "F": 31, "G": 32, "H": 33, "I": 34, "J": 35, "K": 36, "L": 37, "M": 38, "N": 39, "O": 40, "P": 41, "Q": 42, "R": 43, "S": 44, "T": 45, "U": 46, "V": 47, "W": 48, "X": 49, "Y": 50, "Z": 51, "[": 52, "]": 53, "a": 54, "b": 55, "c": 56, "d": 57, "e": 58, "f": 59, "g": 60, "h": 61, "i": 62, "j": 63, "k": 64, "l": 65, "m": 66, "n": 67, "o": 68, "p": 69, "q": 70, "r": 71, "s": 72, "t": 73, "u": 74, "v": 75, "w": 76, "x": 77, "y": 78, "z": 79}, "itos": {"0": "\n", "1": " ", "2": "!", "3": "\"", "4": "$", "5": "'", "6": "(", "7": ")", "8": "+", "9": ",", "10": "-", "11": ".", "12": "0", "13": "1", "14": "2", "15": "3", "16": "4", "17": "5", "18": "6", "19": "7", "20": "8", "21": "9", "22": ":", "23": ";", "24": "=", "25": "?", "26": "A", "27": "B", "28": "C", "29": "D", "30": "E", "31": "F", "32": "G", "33": "H", "34": "I", "35": "J", "36": "K", "37": "L", "38": "M", "39": "N", "40": "O", "41": "P", "42": "Q", "43": "R", "44": "S", "45": "T", "46": "U", "47": "V", "48": "W", "49": "X", "50": "Y", "51": "Z", "52": "[", "53": "]", "54": "a", "55": "b", "56": "c", "57": "d", "58": "e", "59": "f", "60": "g", "61": "h", "62": "i", "63": "j", "64": "k", "65": "l", "66": "m", "67": "n", "68": "o", "69": "p", "70": "q", "71": "r", "72": "s", "73": "t", "74": "u", "75": "v", "76": "w", "77": "x", "78": "y", "79": "z"}}