Repository navigation
Releases: crashtheuniverse/chatgbc
Release list
v1.0.0 - the engine sealed, and Rei
v1.0 seals the engine and adds a second cartridge: Rei, a small being who talks to you, asks your name and keeps it.
chatgbc.gbc - the story model, unchanged. The wait before the first token is what changed: the encoder is exact and a hundred times faster, and prompt tokens only feed the state. 1.8 s from START to the first token (v0.9: 6.8 s), then 0.46 s a token.
rei.gbc - Rei v1. Pick a line from ten topics or type your own. She keeps your name for the whole conversation and across power-off, says your words back, and brings up the keyboard when she asks you something. START shows the log; leave her alone and she goes for a walk.
She is small (374K parameters): sentences blend, she can mix facts up, and now and then she takes a word that isn't a name as your name. Tell her again.
Runs on a Game Boy Color or any CGB emulator (double speed, MBC5, battery save for Rei).
| file | sha256 |
|---|---|
| chatgbc.gbc | 502838bfe3d63cfb2107768c7a47f3ec716861d98c21acdd554243c5a5b71d08 |
| rei.gbc | 074255c67f5a18b37f3f34720f5232b04954bd90e103d5c587e884e5ff5e1341 |
ChatGBC v0.9 - the speed release
ChatGBC v0.9 is the v0.4.1 model on a faster cartridge. Nothing was retrained: the weights are the same checkpoint, the integer twin is unchanged, and the ROM emits the same tokens byte for byte. A token takes 0.46 s where v0.4.1 took 1.08 s, because an audit of every stage of the token found that most of its arithmetic could only ever produce a few hundred distinct values, and a stage like that is a table, not a computation.
Measured (VERSIONS.md has every release scored the same way, on the same held-out TinyStories):
| v0.4.1 | v0.9 | |
|---|---|---|
| M-cycles / token (the ROM's own counter, teletype running) | 2,259,829 | 963,792 |
| seconds / token | 1.08 | 0.46 |
| seconds / character (held-out text) | 0.31 | 0.133 |
| bits / character, as shipped | 1.146 | 1.126 |
| bits / character, the float checkpoint | 1.123 | 1.123 |
| ROM | 256 KB | 512 KB |
What changed, all of it exact against the twin (the golden sequence did not move; 33 tests):
- The block matvecs go output-major: the 22 tables of 27 sums of an input vector are built once, each row is a stream of table addresses summed in a register pair at 12 cycles per three multiply-accumulates, and the requant happens in registers. wz and wh share one build; the router rides on w1's tables.
- w2 walks only the nonzero (activation, weight) pairs, listed by the ReLU² loop as it finds them: 82% of the activations are zero, and so are a third of the weights.
- The classifier keeps only the running best in registers: no logits stored, no zero pass, no rescan.
- The minGRU gate is one byte table with the sigmoid folded in, after a proof over all 16,646,400 inputs that its saturation never fires.
- The norm's sum of squares stays in registers, its shifts peel whole bytes, and its multiply goes through two 16-entry nibble tables rebuilt per norm.
- Embedding rows are stored on the stream's grid; the residual add is a 34-cycle page loop; the debug copies are out of the shipping ROM.
61 KB of new lookup tables. v0.5 to v0.8 do not exist as releases: what was planned for them landed on the tree as options, and the audit said the cheaper token came first. docs/ROADMAP.md has the per-stage before and after, and what v1.0 is.
chatgbc.gbc (524,288 bytes) SHA-256: 7dda7135db9112a1147b87c9aff944dc1bfd81aa0d2c24d88c5aaef6dedc7c18
ChatGBC v0.4.1 - a model trained for the machine
ChatGBC v0.4.1 replaces the borrowed transformer with a model trained for the Game Boy Color: a minGRU core, four ternary experts per layer, and a kernel that retires three multiply-accumulates per table lookup — all in SM83 assembly, all decided by a bit-exact integer twin before the assembly ran.
Measured (VERSIONS.md has every release scored the same way, on the same held-out TinyStories):
| v0.3 | v0.4.1 | |
|---|---|---|
| seconds / token | 6.16 | 1.08 |
| seconds / character (held-out text) | 2.94 | 0.31 |
| bits / character, as shipped | 1.149 | 1.146 |
| bits / character, the float checkpoint | 0.876 | 1.123 |
| ROM | 512 KB | 256 KB |
Level with v0.3 in quality per character, seven times the characters per second, half the ROM. The honest row is the float one: stories260K is the better model in float and lost 31% getting through 4-bit tables and a 24-token window; ours loses 2%. The gap the cartridge used to cause is closed. The model-side gap is what the freed cycles are for.
Also new in v0.4.1
- The screen is a teletype: one character every 0.3 s, fed from a queue on the VBlank interrupt while the next token computes.
- A story runs until you press SELECT. The recurrent state has no window to fall off.
Train it yourself: py/tinystories.py → py/tokenizer.py → py/fit.py (3.5 hours on a laptop GPU) → py/export5.py → py/score5.py → test.ps1. Corpus: TinyStories (Eldan & Li, CDLA-Sharing-1.0). The Arenas residual from Sherry is what makes ternary converge.
ROM: chatgbc.gbc, 256 KB, CGB only (MBC5). SHA-256 38a0a735b7f3cd0058e7b8f4db9de6fce334854b5e7bc8cac615dfbf43be5352.
ChatGBC v0.3 - a transformer on the Game Boy Color
A language model running on real Game Boy Color hardware: 512 KB cartridge,
8 MHz SM83, 32 KB RAM, written entirely in assembly. No C anywhere.
- 6.16 s/token (12,918,757 M-cycles), ~2.3 s/character — measured by the
cartridge's own timer, wherever it runs - 78.1% top-1 agreement with fp32; a bit-exact integer twin stands behind
every kernel - 4-bit Lloyd–Max weights, int8 activations, 19-cycle multiply-accumulate by
table lookup - 24-position sliding-window attention in a ring buffer — output never stops
chatgbc.gbc is attached
(SHA-256 34c17ac06fd29b15321d3ee85a057fa35aeb71a8d127a9efcfd7728234152815).
CGB only. Flash it, or run it in SameBoy. A types, SELECT flips case,
START generates.
Verified on original hardware by the #gbdev community on Discord, who were
kind enough to flash it days after the first build.