IIidle intelligence

llm-life compare

Without going too far into optimizing the cpu/memory paths, we compare (naive) cpu, (naive) 512-entry lookup table, with a language model, two tiny from-scratch models and a stencil-attention model, all computing the same generation from the same starting board.
When Moore's law no longer holds up, we need to find creative ways to spend more compute to achieve the same results.
This started as a (stupid) experiment: What if we ask an LLM to play Game of Life, cell by cell?
In the end, we answer some interesting questions.

llm-life demo · github.com/idle-intelligence/llm-life

settings · run

board

Running every method downloads all the models locally, including a 430 MB LLM.

Some models are not run on grids larger than 64x64, they are too slow.

status

booting...

output · ladder

method s / generation generations total generations / s parameters cells correct

output · 2 steps per pass

method s / generation generations total generations / s parameters cells correct

output · methods

Game of Life (rule): The classical rule, as a for loop.

LLM per cell: Give a language model the rules and current cell's state, as text, and ask if it lives or dies. Repeat for each and every cell. Of course, it's very slow, and an off the shelf model doesn't work. Fine tuning a small LoRa adapter gets us perfect results, but it's still slow... Does it speed up if we batch the inference?

BERT of Life: Alive or Dead is a classification. What would a BERT do? A BERT-shaped classifier, one-layer encoder over the 9 cells as tokens, 3,490 parameters trained from scratch; it works.

9 numbers to centre: Why do we even use a language model? Can we fit a "modern" model architecture that actually learns the rules? A 2-layer MLP over the 9 cell values, 1,442 parameters; We run it cell by cell, pretty fast, it works.

LLM whole grid: Look, there are many optimisation we could do. That's not the game we are playing. Yet, cell by cell is stupid. Instead, we ask the model for the whole grid in one prompt, one token per cell. It's much faster, but training a 64x64 adapter gets expensive, and 256x256 doesn't fit in this small model's context. Another LoRa adapter gets us to 100% accuracy.

stencil (grid to grid): Cell by cell is nice, but we can do better: One attention layer over the whole grid, each cell masked to its 3x3 stencil. 3,329 parameters, trained at 16x16, it scales to whatever grid size.

ternary model, float / integer adds (45 parameters): a tiny convolutional network trained from scratch with ternary ({-1,0,1}) weights that reproduces Conway's rule exactly; the float row runs it as an ordinary dequantized-weight network (a float multiply per weight), the integer row runs the same forward using only integer adds and subtracts of the 0/1 cell values, same ping-pong GPU kernel as the rows above.

binary model, float / integer adds (177 parameters): the same idea with binary ({-1,1}) weights instead of ternary, needing roughly 4x the parameters to still reproduce the rule exactly; same two kernels, float multiplies versus adds and subtracts.

lookup table: the same rule as a 512-entry table indexed by the 9 neighbourhood bits. The fastest way to compute it, and the thing every model below is measured against.

GPU lookup: the same 512-entry table, looked up by a WebGPU compute shader instead of the CPU: one thread per cell, one lookup each. This row reads the board back to the CPU every generation.

GPU lookup (resident): the same kernel, run back to back on the GPU with ping-pong buffers and no readback in between; only the final board comes back to the CPU. Reports the amortized time per generation once dispatch and readback overhead are out of the loop.

bitwise (GPU, resident): 32 cells packed into one 32-bit word, and a bitwise adder network counts all 32 cells' neighbours at once instead of looking each one up individually; same ping-pong, one readback at the end.

ternary model, bit-packed (45 parameters): the trained 45-parameter model compiled to bit operations, 32 cells per word.

ternary model, compiled (89 parameters): the same rule learned by an 89-parameter ternary network, turned into bit operations automatically by a generic bit-slice compiler rather than written by hand like the row above.

methods · 2 steps per pass

2-step lookup table (GPU, resident): ground truth from the rule, not a trained model. Every possible 5x5 neighbourhood, 33.5 million of them, precomputed from Conway's rule applied twice and stored one bit each.

ternary model, float / integer adds, 2 steps per pass (9,601 parameters): the same 9,601-parameter network as the compiled row below, run as an ordinary network instead of a bit-sliced circuit: float multiplies against dequantized weights, or adds and subtracts of 0/1 activations, same as the one-step 45- and 177-parameter rows.

ternary model, compiled, 2 steps per pass (9,601 parameters): a 32-channel ternary network trained to predict two generations at once, compiled the same way; one pass computes two generations, at the cost of a much larger circuit (about 13,000 gates against 92 for the one-step network).