olmputer-tiny

Olmputer logo: a pixel computer

A RISC-V computer (RV32IM + Zba) compiled into the layers of a stock OlmoHybridForCausalLM. No weight is trained: a compiler computes all of them. The code ROM, data ROM, RAM and registers live in the recurrent state of the Gated DeltaNet layers, and the model runs a program by greedy decoding: every token it generates is the next token of the CPU's execution trace.

This is the standalone half of Olmputer, which puts the same layers in front of allenai/Olmo-Hybrid-Instruct-DPO-7B so that OLMo's calc tool calls run on the CPU inside its weights. It ports Qwenputer, which does this with Qwen3.5-4B, to OLMo-Hybrid's head shapes. The compiler is the chipformer package of JGalego/olmputer.

Uses the architecture and head shapes of Ai2's OLMo-Hybrid, none of its weights; not affiliated with or endorsed by Ai2.

The machine

ISA RV32IM + Zba, plus bignum digit-file instructions
Code ROM 256 KB
Data ROM 128 KB, read-only
RAM 32 KB
Layers 11 Gated DeltaNet + SwiGLU layers, plus the zero attention layer HF's cache needs
Shapes OLMo-Hybrid-7B's: 30 heads of 96 × 192 a layer, MLP 11,008 wide; hidden 2,816
Parameters 3.13B, almost all zero
Vocabulary the trace tokens (values in 15-bit chunks, jumps, stores, putchar, ...) plus PAD

Heads per chip layer, by the memory they hold

Qwen3.5-4B's heads are 128 × 128, two value heads per key head; OLMo's are 96 × 192, one each. The changes that keep every read exact:

  • A state row's ballast sits in 3 of its 192 columns (Qwen: 2 of 128), so the gated output norm divides by the same power of 2.
  • The code and data ROMs hold bytes, and a byte read needs a power-of-2 query scale. OLMo's 96^-0.5 is not one, so the selected row's query is padded with four rows that are never written (weights 3 : 3, 2, 1, 1), which makes the scale exactly 1/16.
  • Write gates use OLMo's β = 2 sigmoid(b): b = 0 writes, b ≤ -256 does not.
  • ROM rows hold 32 words instead of 8, so the 256 KB ROM fits one layer's 30 heads; the RAM, one word a row, takes three layers.

Checks

For each program, the machine's trace from a reference RISC-V emulator is the target:

  • the residual stream after the chip layers equals the circuit's IR evaluator on every dimension at every position (bf16; traces up to 60k tokens);
  • free-running greedy generate from the program's start reproduces the reference trace token for token.

Trace tokens generated free-running, per run

Run Kernels Programs Exact Trace tokens
c-qsort torch 1 1 240,175
c-sieve-gmpmul torch 2 2 66,825
short-fla fla 55 55 26,677
short-torch torch 55 55 26,677

A run, from an ELF to the HALT token:

A program run, from an ELF to HALT

Usage

example.py runs fibonacci(25) on olmo-tiny: greedy generate runs the CPU, and the result is read from the trace the model wrote. It is a uv script that declares its own dependencies:

uv run https://huggingface.co/jgalego/olmputer-tiny/resolve/main/example.py
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "chipformer @ https://github.com/JGalego/olmputer/archive/98639dff4c84a18542e3ca12a286a4df563b3a27.tar.gz#subdirectory=experimental/riscv",  # pylint: disable=line-too-long
#     "huggingface_hub",
#     "torch==2.14.1",
#     "transformers==5.19.0",
# ]
# ///
"""Run a RISC-V program on olmo-tiny: fibonacci(25), computed by the computer in its weights.

The program's code and registers are written into the model's Gated DeltaNet states; greedy
generate then runs the CPU, every generated token being the next token of its execution trace.
The program ends with mv a0, t0 (fibonacci(25)), li a7, 93 and the exit ecall, so the result is
the value of the second-to-last value record the model wrote.
"""

import torch
from chipformer.q35.ir import OLMO
from chipformer.rv import core, firmware, ref, spec
from chipformer.rv.programs import program
from transformers import OlmoHybridForCausalLM

DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
MODEL = OlmoHybridForCausalLM.from_pretrained("jgalego/olmputer-tiny", dtype=torch.bfloat16)
MODEL = MODEL.to(DEVICE).eval()
CIRCUIT = core.build(OLMO)  # the CPU as a circuit: it writes the program into the model's cache

TRACE = firmware.run(MODEL, CIRCUIT, ref.Machine.from_program(program("fib")))
VALUES = [spec.decode_value(TRACE[:i + 1]) for i, t in enumerate(TRACE)
          if spec.family(t)[0] in ("F0", "F1", "F2")]
print("fibonacci(25) =", VALUES[-2])
print(f"{len(TRACE)} trace tokens on {DEVICE}, identical to the reference machine:",
      TRACE == ref.Machine.from_program(program("fib")).run()[1:])

A GPU takes under a minute; a CPU works too, more slowly. firmware.run loads the program's ROM, RAM and registers into the linear-attention layers' recurrent states after a one-token prefix, starts the CPU with GO and decodes greedily until HALT.

Downloads last month
-
Safetensors
Model size
3B params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including jgalego/olmputer-tiny