Request access to rv32cpu

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

rv32cpu is a research artifact: a RISC-V computer whose logic, registers and memory are network weights, plus firmware (an exact calculator, a file store and MicroPython) that runs on it. Access is reviewed by hand. Please say briefly what you want to use it for.

Log in or Sign Up to review the conditions and access this model content.

rv32cpu: a RISC-V computer made of network weights

A 32-bit RISC-V CPU (RV32I + Zmmul) compiled into the weights of a sparse ReLU network with GDN-style memory. Nothing in it is trained. Every weight is set by a compiler, and every instruction is checked against a plain Python RISC-V emulator. One network step executes one instruction, exactly.

The repo also has firmware for it: an exact calculator (fractions, decimals, roots, logs, trig), a file store, MicroPython, and dl, a small stack language in which every byte string is a valid program. Firmware is ordinary C compiled with gcc into the CPU's memory. dl and the batched training profile are built for models that call the CPU from inside their own forward pass and learn what to run by sampling programs.

Revision v5 (code format rv32cpu/2)
Residual width 72,989
Levels per instruction 16
Nonzero weights 533,344 (dense equivalent: 12,488,636,867)
Memory 1024 KB in one GDN state, rows of 8 words; registers: 32 GDN rows
Firmware link_py.elf (374 KB), link_small.elf (92 KB); MicroPython 1.24.1

Quick start

import os, sys
os.environ["HF_HUB_DISABLE_XET"] = "1"   # see the note below
import huggingface_hub
huggingface_hub.constants.HF_HUB_DISABLE_XET = True
from huggingface_hub import snapshot_download

d = snapshot_download("User01110/rv32cpu", revision="v5")   # gated: log in with an approved account first
sys.modules.pop("rv32cpu", None)                              # use this revision's code, not another copy
sys.path.insert(0, d)
import rv32cpu as R

cpu = R.open_dir(d, device="cuda", batch=16)                  # loads the weights + prebuilt firmware, no gcc
print(cpu.call(["calc 12345678901234567890 * 98765432109876543210"])[0].text)
print(cpu.call(["py x = 2**100"], keep=True)[0].text)          # keep=True: this row's memory carries over
print(cpu.call(["py x + 1"], keep=True)[0].text)               # -> 1267650600228229401496703205377
op = {n: i for i, n in enumerate(R.DL_OPS)}
print(cpu.call([R.dl_request([op["NUM1"], op["NUM0"], op["MUL"]], text=b"6 boxes of 7")])[0].text)  # -> 42

open_dir compiles the step with torch.compile and captures a CUDA graph, which takes tens of seconds per session. Use the .py files from the same revision as the weights: the weight file records its format and refuses to load into a different version.

Why Xet is switched off: this repo is gated, and downloading its large files (rv32cpu.pt, *.elf) through the Xet backend can fail with 401 Unauthorized at xet-read-token, even with a token that has access. Plain HTTPS downloads work with the same token. Once you have the code, R.from_hub("User01110/rv32cpu", revision="v5") does all of this in one call.

How it works

Part How it is built from weights
Logic ReLU neurons as exact gates: AND = relu(Ξ£x βˆ’ (nβˆ’1)), thresholds, a 4-level carry-lookahead adder, a one-hot shifter
Multiplier 32Γ—32 partial products β†’ column counts β†’ carry-save rounds β†’ 64-bit adder, in one step (MUL, MULH, MULHSU, MULHU)
Registers A GDN state of 32 rows Γ— 32 bits. The register number becomes a one-hot key; writes are delta-rule overwrites
Memory One GDN state for code, data, heap, disk, stack and I/O, in rows of several words. The key comes from address-decoder neurons; a word mux follows the read
Exactness Every value is an integer of fp32-exact size. TF32 is disabled. Writes have β ∈ {0, 1}, so they overwrite exactly
Executor Each batch row is an independent computer. Two engines: the weights path (sparse gather / index_add per level, torch.compile + CUDA graphs) and the fast path: the same circuit compiled with one-hot memory keys evaluated as the row numbers they encode (no address-decoder neurons), run by one generated Triton kernel per row that loops instructions on the GPU. Both are checked bit for bit against the emulator.

Where the weights go

part neurons memory ops nonzero weights
fetch: row select 32864 1 197717
fetch: word mux 264 0 1061
pc adders 296 0 5792
decode 43 0 4358
registers: GDN read 64 2 2742
agu: rs1 + imm 133 0 3362
writeback 519 0 7547
alu 331 0 3962
next state 133 0 1952
multiplier 3035 0 56523
load/store lanes 232 0 3423
shifter 816 0 3515
data: word mux 264 0 1397
branch 6 0 39
data: row select 32865 1 203290
store: row merge 513 0 2824
write: mem 0 1 33281
write: regs 0 1 559
total 72378 6 533344

Memory map

region from to
firmware + heap 0x100000 0x1a8000
disk (file store) 0x1a8000 0x1e8000
stack (grows down) 0x1e8000 0x1f8000
I/O in: [length][bytes] 0x1f8000 0x1fc000
I/O out: [length][bytes] 0x1fc000 0x200000

Verification

On the run that produced this revision, the weights matched the Python emulator on:

check passed
demo programs (8 checks each: steps, pc, halt, registers, memory, write log, pc trace, exact 0/1) 13 / 13
random programs: ALU, shifts, multiplies, loads/stores, branches + random words 144 / 144 (30,864 instructions)
fast path (indexed net, fused engine): demo programs + random programs vs the emulator 157 / 157
link firmware: calc vs its Python reference, files, rollback, Python, sessions 25 / 25
dl: link firmware: NUM0 NUM1 MUL over '6 by 7', and an empty program 2 / 2
dl: link firmware: 64 random programs + toolsets + texts 64 / 64
dl: link firmware: PYRUN (a ```python block, and one that raises) 42 Β· NameError: name 'y' isn't defined
dl: training profile (128 KB rows): 1,024 random programs at once (CPU.many) 1024 / 1024
dl: training profile: 256 calc calls (decimals, %, commas, round, sqrt) at once, vs the reference 256 / 256

The link firmware

link_py.elf (with MicroPython) or link.elf (C only) answers one text request per call:

Request Reply
calc <expr> An exact result: fractions, decimals, 1,200, 15%, 20% of 80, + - * / // % ** ^ !, sqrt root round floor ceil abs min max sum mean gcd lcm comb perm exp ln log sin cos tan, pi e deg. Non-terminating decimals show 12 places
dl <bytes> A dl program's result over a text (below)
py <code> What the code printed. A single line is shown like the REPL (its value is printed)
run <file> Runs a Python file from the disk
put <name>\n<data> / append <name>\n<data> ok
get <name> / ls / rm <name> / df File contents, a listing, ok, or usage
reset Forgets the Python state
help The command list and the MicroPython version

The status comes back in a0: 0 ok, 1–4 and 11 calc errors (syntax, division by zero, overflow, too complex, math domain), 5 Python exception, 6 file not found, 7 disk full, 8 unknown command, 9 no Python in this build, 10 fatal.

Sessions. Each batch row is a session:

  • keep=False reboots the firmware for every call. The row's disk is kept.
  • keep=True keeps the whole memory: Python variables, imports and files.

A call that does not end cleanly (a fault, an illegal instruction, or running out of steps) is rolled back. CPU.save_state / load_state save and restore sessions to a file.

dl: a language with no wrong programs

A request is dl + [n][16 program bytes][16 toolset slots Γ— (len + 15 ops)][text] (R.dl_request builds it). Every byte is an op (byte mod 53), and every op is defined on every stack: an empty stack reads as 0, x // 0 is 0, and overflow wraps at 64 bits. So any byte string runs and ends, which is what lets a model learn the CPU by sampling programs.

Ops Meaning
NUM0–NUM7, COUNT, SUM, MAXN, MINN The k-th most recent number in the text (digit runs); count, sum, max, min of all of them
C0 C1 C2 C10, DUP SWAP DROP OVER Constants and stack moves
ADD SUB MUL DIV MOD NEG ABS MIN MAX EQ LT SEL 64-bit integer arithmetic (DIV/MOD with Python floor semantics), comparisons, select
STORE LOAD, SAVE RECALL 16 number variables; 4 text slots (the text's last line). Kept across calls in a keep=True session
CALL0–CALL15 Run toolset slot k (up to 4 deep). The model grows its own toolset during training
PYRUN Run the last ```python block of the text (or its whole lines) in MicroPython; show the last printed line

The reply is the top of the stack (or the text that RECALL or PYRUN showed). The status is 0 ok, 1 empty, 2 out of budget (256 ops), 3 PYRUN without Python, 4 PYRUN raised. R.dl_ref is the reference interpreter. For training there is a smaller CPU profile (CPUConfig.small(): 128 KB rows, C firmware, rv32cpu_small.pt + link_small.elf), and CPU.many runs thousands of one-shot calls at once.

Limits

  • Speed: measured on the run that produced this revision: weights net Β· compile + cudagraph (batch 64, traced): 2,684; indexed net Β· fused (batch 64): 2,290; one calc call on the link machine (fused): 9,032 steps in 4028 ms; dl calls, training profile (fused, 1,024 rows at once): 17 calls/s (8,563 instructions per call). A MicroPython call is roughly 10⁡ instructions or more.
  • Arithmetic: division and remainder run in software (libgcc). Floats are software single precision.
  • Size: 1 MB of memory per row by default (CPUConfig), 128 KB in the training profile. Bigger memories cost about one neuron per memory row.
  • Status: a research artifact. Interfaces may change between revisions.

Credits and licenses

  • Inspired by Qwenputer, which compiled a RISC-V CPU into Qwen3.5-4B.
  • link_py.elf contains MicroPython (MIT license, see LICENSE-micropython) and libgcc (GPL with the GCC Runtime Library Exception).
  • Everything else in this repo: apache-2.0.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support