Tetris, running entirely inside a transformer.
This model is a CHIP-8 computer. Give it a game as the prompt and greedy decoding runs the game: every token it predicts is one write to the machine's memory (a register, the program counter, a row of pixels). Paint those writes onto a 64×32 screen and you are playing Tetris. Your keypresses and the 60 Hz clock go into the context window. Everything else, every frame, every collision, every point on the scoreboard, comes out of the model.
There is no emulator behind it. It is the same loop every language model runs: read the context, predict the next token, append it, repeat.
Play it in your browser: divagr.com/chipformer · Hugging Face Space · Source: GitHub
BOOT, then one ADDR a, VAL v token pair per nonzero
byte of machine state, then STEP.@V3 =1F @PC =22A STEP. The screen is drawn from these writes.TICK token arrives once per 60 Hz frame, and keypresses
are injected as writes. Every other token is the model's greedy prediction.The architecture is ordinary:
modeling_chipformer.py contains nothing CHIP-8 specific apart from the host loop, which only feeds
tokens in and reads the screen back.
Where the weights come from. They are compiled from a description of the CHIP-8 circuit (written in a small DSL in the GitHub repo) rather than learned, which is what makes every instruction exact. 139,229 of the 32,328,660 parameters are nonzero.
import torch
from huggingface_hub import hf_hub_download
from transformers import AutoModelForCausalLM
repo = "divagr1925/chipformer"
model = AutoModelForCausalLM.from_pretrained(repo, trust_remote_code=True, dtype=torch.float64)
rom = open(hf_hub_download(repo, "roms/tetris.ch8"), "rb").read()
game = model.play(rom, quirks="shift,loadstore,clip,jump", ipf=15)
for frame in range(120):
game.frame(keys={5} if frame % 20 < 4 else ()) # CHIP-8 key 5 = left
print(game.ascii())
play_tetris.py animates the game in a terminal: python play_tetris.py.
The model is a normal PreTrainedModel, so you can also call it directly:
model(input_ids, past_key_values=cache) returns next-token logits over the 8,708-token vocabulary.game.tokens (with keep_tokens=True) gives you the full trace.game = model.play(...) multiplies by sparse copies of the big feed-forward matrices, since only
0.4% of the weights are nonzero. The outputs are identical; pass sparse=False to run the dense
weights exactly as loaded.ChipformerGame restarts the context
from a state dump every 250,000 tokens, as the engine does.| File | |
|---|---|
model.safetensors | dense float64 weights (what transformers loads) |
model.chipformer | the same weights, sparse (nonzeros only), for the Rust/wasm engine |
configuration_chipformer.py, modeling_chipformer.py | the architecture plus the host loop |
play_tetris.py | a terminal demo |
roms/tetris.ch8 | Tetris by Fran Dachille (1991), freely distributable (Revival Studios CHIP-8 pack) |
Tetris, running entirely inside a transformer.
This model is a CHIP-8 computer. Give it a game as the prompt and greedy decoding runs the game: every token it predicts is one write to the machine's memory (a register, the program counter, a row of pixels). Paint those writes onto a 64×32 screen and you are playing Tetris. Your keypresses and the 60 Hz clock go into the context window. Everything else, every frame, every collision, every point on the scoreboard, comes out of the model.
There is no emulator behind it. It is the same loop every language model runs: read the context, predict the next token, append it, repeat.
Play it in your browser: divagr.com/chipformer · Hugging Face Space · Source: GitHub
BOOT, then one ADDR a, VAL v token pair per nonzero
byte of machine state, then STEP.@V3 =1F @PC =22A STEP. The screen is drawn from these writes.TICK token arrives once per 60 Hz frame, and keypresses
are injected as writes. Every other token is the model's greedy prediction.The architecture is ordinary:
modeling_chipformer.py contains nothing CHIP-8 specific apart from the host loop, which only feeds
tokens in and reads the screen back.
Where the weights come from. They are compiled from a description of the CHIP-8 circuit (written in a small DSL in the GitHub repo) rather than learned, which is what makes every instruction exact. 139,229 of the 32,328,660 parameters are nonzero.
import torch
from huggingface_hub import hf_hub_download
from transformers import AutoModelForCausalLM
repo = "divagr1925/chipformer"
model = AutoModelForCausalLM.from_pretrained(repo, trust_remote_code=True, dtype=torch.float64)
rom = open(hf_hub_download(repo, "roms/tetris.ch8"), "rb").read()
game = model.play(rom, quirks="shift,loadstore,clip,jump", ipf=15)
for frame in range(120):
game.frame(keys={5} if frame % 20 < 4 else ()) # CHIP-8 key 5 = left
print(game.ascii())
play_tetris.py animates the game in a terminal: python play_tetris.py.
The model is a normal PreTrainedModel, so you can also call it directly:
model(input_ids, past_key_values=cache) returns next-token logits over the 8,708-token vocabulary.game.tokens (with keep_tokens=True) gives you the full trace.game = model.play(...) multiplies by sparse copies of the big feed-forward matrices, since only
0.4% of the weights are nonzero. The outputs are identical; pass sparse=False to run the dense
weights exactly as loaded.ChipformerGame restarts the context
from a state dump every 250,000 tokens, as the engine does.| File | |
|---|---|
model.safetensors | dense float64 weights (what transformers loads) |
model.chipformer | the same weights, sparse (nonzeros only), for the Rust/wasm engine |
configuration_chipformer.py, modeling_chipformer.py | the architecture plus the host loop |
play_tetris.py | a terminal demo |
roms/tetris.ch8 | Tetris by Fran Dachille (1991), freely distributable (Revival Studios CHIP-8 pack) |