A tiny GPT whose weights live entirely on Ethereum. The model is trained offline in numpy, quantized to int8, gzipped, and written on-chain as SSTORE2 data contracts. Inference runs fully client-side — a frontend reads the weights straight off-chain and generates text in the browser. No server, no API.
Live on Sepolia: 0xBA2D…03720
— deployed, filled with 29 weight chunks, and sealed (immutable).
Testnet proof of concept.
ba2d≈ "bard" in hex; the demo model is trained on Tiny Shakespeare and the contract sits at a0xBA2D…vanity address.
This repo is contracts + corpus only — the frontend lives in a separate project.
A 4-layer char-level GPT: 128-dim embeddings, 4 heads, 64-token context, 66-char vocab — 811,520 parameters, int8-quantized to a ~668 KB gzipped artifact spread across 29 on-chain chunks. Generated text is recognizably Shakespeare-shaped, not coherent. That's the charm at this size.
train (numpy) → quantize int8 → gzip → SHA-256 model/
→ deploy empty WeightManifest at 0xBA2D… via CREATE2 script/
→ setConfig, upload weight chunks, verify hash, seal tools/
WeightManifest stores the model as an ordered list of SSTORE2 chunks. Its constructor
takes no arguments, so the CREATE2 address is independent of the model — the same salt
yields the same 0xBA2D… address for any weights. Config (name, quant, vocab, params,
artifactHash) is set after deploy via setConfig, chunks are appended with addChunk,
and seal() freezes the artifact forever. The client reassembles the chunks, gunzips, and
checks SHA-256 against the on-chain artifactHash before trusting the weights.
contracts/Manifest.sol WeightManifest: config + ordered SSTORE2 chunks, owner-gated, sealable
script/Deploy.s.sol Foundry CREATE2 deploy (vanity address) + setConfig from model.meta.json
test/ Foundry tests
model/ numpy trainer, int8/int4 exporter, inference reference (see model/README.md)
tools/ resumable chunked uploader + local deploy rehearsal (see tools/README.md)
1. Train & export — details in model/README.md. The corpus is
gitignored; fetch it first:
curl -fsSL https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt \
-o model/data/tinyshakespeare.txt
pip install numpy
cd model
python train.py --corpus data/tinyshakespeare.txt --n-layer 4 --n-embd 128 \
--n-head 4 --block-size 64 --lr 1e-3 --steps 3000
python export_artifact.py # -> model.bin + model.meta.json (prints artifactHash)2. Build & test the contract
forge install foundry-rs/forge-std # first time
forge build && forge test3. Deploy & upload — details in tools/README.md. Grind a vanity
salt once for the current bytecode, rehearse on a local anvil, then ship to Sepolia:
cast create2 --starts-with ba2d \
--deployer 0x4e59b44847b379578588920cA78FbF26c0B4956C \
--init-code-hash $(cast keccak "$(forge inspect contracts/Manifest.sol:WeightManifest bytecode)")
./tools/rehearse.sh # full flow on a fresh anvil: deploy + setConfig + upload + seal
SALT=<salt> PRIVATE_KEY=<key> \
forge script script/Deploy.s.sol:Deploy --rpc-url sepolia --broadcast --verify --verifier sourcify
cd tools && npm install && SEPOLIA_RPC_URL=… PRIVATE_KEY=<key> MANIFEST_ADDRESS=0xBA2D… \
node upload.mjs upload --sealThe uploader is resumable and idempotent: chunks are deterministic slices of model.bin,
so a killed run continues from the on-chain chunkCount(), and it verifies the reassembled
SHA-256 against config.artifactHash before sealing.
- Our code (contracts, model pipeline, tooling) is MIT — see
LICENSE. - The architecture follows Karpathy's microgpt; the default corpus is Tiny Shakespeare (public domain).
- On-chain weights are immutable once sealed — review before
seal(); ship a new manifest to revise.