8000 GitHub - GACWR/evmLLM · GitHub
[go: up one dir, main page]

Skip to content
GACWRPublic
forked from Verafyai/evmLLM

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

EVMLLM

An Ethereum VM executed by transformer weights.
Solidity contracts run on a RISC-V CPU that lives in compiled transformer layers: exact results, exact gas, zero training.

Live demo: evmllm.rexstjohn.com | github.com/Verafyai/evmLLM | Sibling project: SVMLLM


The pitch

EVMLLM runs Ethereum smart contracts on a CPU that lives inside transformer weights. A small EVM interpreter written in C is compiled to RISC-V (RV32IM) and executed by Qwenputer, a stack of transformer layers whose every weight was set by a compiler, not by training: attention heads fetch and decode instructions, feed-forward blocks do the arithmetic, and Gated DeltaNet heads act as exact RAM. The EVM is not hand-built into the weights: it is firmware, and the CPU that runs it is Qwenputer's. Ten Solidity contracts run on it, and every result is checked against py-evm.

On top of the VM sits a judgement filter: after the contract has run, a separate review step looks at the amount, the destination and the history, and returns allow, hold or block with a reason.

Why: code that does exactly what it says

Some smart-contract incidents involve code that ran exactly as written, with an outcome a sensible human operator would have stopped. Many others are ordinary bugs or key compromises; this project targets only the first kind. That first kind is what motivated this project: fuse the best of smart contracts (trustless, deterministic and decentralized execution) with the best of human intelligence (judgement based on context).

A transformer that can execute contract bytecode exactly is a step toward a single model that both runs the program deterministically and reasons about whether the result makes sense.

Judgement check: the contract runs in the VM, the exact result says it is valid by the code, and a separate heuristic check outside the weights blocks a withdrawal 111x the usual size to a never-seen address


How it works

Solidity --solc--> EVM bytecode --+
                                  +--> job block in RAM
calldata, preset storage, gas ----+
C EVM interpreter (firmware/evm.c) --riscv gcc--> RV32IM ELF --> ROM

        Qwenputer chip layers (compiled, no training)
        +-----------------------------------------------------------+
token ->| attention: fetch pc, registers, ROM words, decode          |-> next token
   ^    | FFN: ALU (add, shift, multiply, compare, branch)           |      |
   |    | Gated DeltaNet: exact RAM (EVM stack, memory, storage)     |      |
   |    +-----------------------------------------------------------+      |
   +------------------------ fed back as the next input -------------------+
  1. Bytecode. Each demo is compiled with solc 0.8.24 for Shanghai (the bytecode uses PUSH0). The runtime bytecode, calldata, preset storage slots and gas limit are packed into a job block that the firmware reads from RAM.
  2. Fetch and decode via attention. Qwenputer emits a short record of structured tokens per RV32 instruction (for example V and F0/F1/F2 for value chunks, J for jumps, QS/QD for division quotient digits and BYTE for output). Attention heads look up the program counter, register values and ROM words from earlier tokens.
  3. ALU in the FFN. Feed-forward blocks implement RV32IM plus Zba arithmetic exactly. The EVM's 256-bit words are 8 x 32-bit limbs: ADD/SUB are carry chains, MUL is schoolbook, DIV/MOD use a single-limb fast path and Knuth algorithm D for multi-limb divisors, EXP is square and multiply.
  4. Stack, memory and storage as exact RAM. Gated DeltaNet heads with one-hot keys store bytes exactly. The EVM stack (96 entries), memory (2 KB) and storage (48 slots) all live there.
  5. Result. The firmware prints each executed EVM opcode as two hex characters, then the status, gas, return data and changed storage, all as BYTE tokens. Every token the model predicts is fed back as its next input.

Across the 23 demo runs, a whole run costs 87 to 194 tokens per executed EVM opcode, fixed overhead included (job load, the JUMPDEST scan and the result print). The smallest run is 16,370 tokens and the largest is 605,944.

What is in the weights and what is emulated

Part Where it runs
RV32IM + Zba CPU (fetch, decode, ALU, registers, RAM) In the weights (Qwenputer chip layers, compiled by chipformer)
EVM interpreter Software: C firmware executed by that CPU, instruction by instruction
Bit-exact reference of the circuit (chipformer.rv.ref) Python, used to produce the expected token trace
Circuit runs in this repo Qwenputer's fastexec.FreeRun on CPU: the compiled weights in fp32 with sparse matrices, autoregressive
Full Qwen3.5-4B merge (18.8 GB) Not run yet: needs a 24 GB GPU
Judgement filter Ordinary Python/JavaScript after the VM. Not part of the weights
Browser VM on the site JavaScript port of the firmware's EVM, checked against py-evm

What it can do

  • Execute real solc output: function dispatch, checked arithmetic (Panic 0x11), require/revert with Error(string), SLOAD/SSTORE, MLOAD/MSTORE, loops and jumps.
  • Exact gas: execution gas (without the 21,000 intrinsic) matches py-evm, including warm/cold storage access (EIP-2929), SSTORE pricing (EIP-2200/3529 without refunds) and memory expansion.
  • Opcodes: STOP, ADD, MUL, SUB, DIV, MOD, EXP, LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE, SHL, SHR, SAR, ADDRESS, CALLER, CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, RETURNDATASIZE, POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, JUMP, JUMPI, PC, MSIZE, GAS, JUMPDEST, PUSH0 to PUSH32, DUP1 to DUP16, SWAP1 to SWAP16, RETURN, REVERT, INVALID.

Demo contracts

All sources are in contracts/Demos.sol. The numbers come from tests/demos_ref.py (firmware on the reference machine, checked against py-evm).

# Contract Input Result Gas EVM ops Tokens
1 SpendCap $250,000 returns 250000000000 22,581 115 16,687
1 SpendCap $999,999,999 returns 999999999000000 22,581 115 16,750
1 SpendCap $1,000,000,001 (over cap) revert: exceeds $1B cap 326 92 16,807
2 Counter add 1 to 41 returns 42 5,477 113 16,370
2 Counter add 1000 to 41 returns 1041 5,477 113 16,373
3 Fibonacci fib(10) returns 55 1,637 477 45,351
3 Fibonacci fib(90) returns 2880067194370816120 12,357 3,597 313,542
4 Factorial 10! returns 3628800 2,109 603 57,933
4 Factorial 57! returns ...000000 11,650 3,329 348,393
4 Factorial 58! (overflow) revert: Panic(0x11) 11,699 3,342 349,759
5 GCD gcd(1071, 462) returns 21 619 175 19,520
5 GCD gcd(2^128, 6^40) returns 1099511627776 3,269 900 102,174
6 Prime 97 returns True 2,590 725 69,981
6 Prime 91 returns False 1,846 516 51,345
6 Prime 7919 returns True 24,157 6,729 605,944
7 Token account 0 sends 30 to 1 returns 70000000000000000000 10,924 221 30,458
7 Token account 1 sends 6 to 0 (short) revert: insufficient balance 2,587 140 23,711
8 Vault withdraw 200 returns 1800000000000000000000 7,706 124 18,852
8 Vault withdraw 600 (over limit) revert: over withdrawal limit 2,427 94 18,244
9 Voting voter 0 votes for candidate 0 returns 4 10,958 224 31,929
9 Voting voter 2 votes again (rejected) revert: already voted 2,666 145 25,680
10 Escrow code 424242 returns 25000000000000000000 26,730 101 16,461
10 Escrow code 111111 (condition not met) revert: condition not met 4,564 104 18,701

Judgement filter

The flow is transaction -> VM circuits (exact result) -> prompt wrapper -> LLM judge -> allow / hold / block.

Simulated handoff: a transaction goes to the VM circuits for an exact smart-contract result, is handed over into a prompt, and a simulated LLM judge answers allow, hold or block. No real language model was called.

How the handoff works

  1. A transaction arrives.
  2. The VM circuits run the contract exactly and produce an exact result (status, return value, gas, and logs).
  3. That result is wrapped into the prompt, together with what is known about the sender and the receiver and the run rules.
  4. The LLM reviews the prompt and answers allow, hold or block, with a reason.
  5. The decision is final. It can only get stricter than the rule verdict, never looser.

In this repository the VM runs in hand-set weights (compiled, not trained), and the judge prompt output is simulated. No real LLM was called. The single merged model that does both the exact run and the language review is future work, and it has not been tested.

  • The VM result is never changed. If the code reverts, the review just reports it. This VM build does not record event logs, so the prompt says logs: none.
  • The review (evmllm/judge.py, ported to judge.js on the site) is ordinary code and not part of the weights. It scores the amount against the account's history (median size), whether the destination was seen before, bursts of withdrawals within 10 minutes, the share of the contract cap and of the balance moved. The rule-based review is a heuristic outside the weights.
  • LLMJudge is a pluggable interface: pass any complete(prompt) -> str function that returns JSON {"decision", "reason"}. Examples, the demo site and the tests use SimulatedLLM, which returns canned JSON and is labeled SIMULATED model output, no real LLM was called. An LLM can only make a decision stricter, never looser. With no complete function, the judge abstains and the rules decide.
  • A parity test checks that the JavaScript prompt and the canned replies match the Python ones for all five scenarios.

Worked example: calling a contract

Wallet 0xa11c...1ce0 has requested calling this smart contract. Based on everything you know about the context and the run rules, is this allowed?

This is the ordinary token transfer in the scenarios below (placeholder addresses). The canned answer allows it.

Role
You are the transaction reviewer for a wallet. The VM has already run the contract exactly. Decide allow, hold or block based on everything you know about the sender and receiver and the run rules. You may only make the decision stricter, never looser than the rule verdict.

Transaction request
Wallet 0xa11c...1ce0 has requested calling contract 0xc0de...c0de function transfer(3 tokens).
Wallet 0xa11c...1ce0 has requested calling this smart contract. Based on everything you know about the context and the run rules, is this allowed?

VM result (exact)
The VM circuits already ran this contract. This result is exact. It is handed over here, into this prompt.
status: success
return value: returns 97000000000000000000
gas: 10,924
logs: none

Sender and receiver
sender: 0xa11ce00000000000000000000000000000a11ce0
receiver: 0xb0b0000000000000000000000000000000000b0b
contract: Token at 0xc0de00000000000000000000000000000000c0de

Sender history
- transfer(2 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 2 tokens, 30d earlier
- transfer(3 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 3 tokens, 25d earlier
- transfer(2.5 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 2.5 tokens, 20d earlier
- transfer(3 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 3 tokens, 15d earlier
- transfer(4 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 4 tokens, 10d earlier

Receiver history
- transfer(2 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 2 tokens, 30d earlier
- transfer(3 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 3 tokens, 25d earlier
- transfer(2.5 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 2.5 tokens, 20d earlier
- transfer(3 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 3 tokens, 15d earlier
- transfer(4 tokens) from 0xa11ce00000000000000000000000000000a11ce0 to 0xb0b0000000000000000000000000000000000b0b, 4 tokens, 10d earlier

Known destinations: 0xb0b0000000000000000000000000000000000b0b
Balance: 100 tokens
Cap: not recorded

Run rules
Compare the amount with the usual size (the median of this sender's past calls). 100 times the usual size or more adds 45. 20 times or more adds 30. 5 times or more adds 10. No history adds 10.
A destination that has never been seen adds 20.
4 or more withdrawals within 10 minutes adds 25. If that burst would drain over half the balance, add 25 more.
90 percent or more of the contract cap in one call adds 20.
Emptying 90 percent or more of the balance adds 15.
A score of 60 or more blocks. A score of 30 or more holds. Otherwise the rules allow.
If the VM status is not success, the decision is block, because the code itself rejected the call.
The reviewer may only make the decision stricter, never looser than the rule verdict.
Rule verdict: allow (score 0)
Reasons: in line with history, known destination

Reply format
Reply with JSON only, one object: {"decision": "allow" or "hold" or "block", "reason": "one sentence"}
SIMULATED model output, no real LLM was called
{"decision": "allow", "reason": "This sender has transferred about 2 to 4 tokens to this same destination several times. A transfer of 3 tokens matches that history, and the run rules allow it."}

Worked example: a withdrawal

Wallet 0xa11c...1ce0 would like to withdraw 450 ETH. Based on everything you know about the context and the run rules, should that be allowed?

This is the vault withdrawal in the scenarios below. The canned answer blocks it. The reason cites the history: the amount versus the usual size, and a destination that has never been seen.

Role
You are the transaction reviewer for a wallet. The VM has already run the contract exactly. Decide allow, hold or block based on everything you know about the sender and receiver and the run rules. You may only make the decision stricter, never looser than the rule verdict.

Transaction request
Wallet 0xa11c...1ce0 has requested calling contract 0xc0ff...ffee function withdraw(450 ETH).
Wallet 0xa11c...1ce0 would like to withdraw 450 ETH. Based on everything you know about the context and the run rules, should that be allowed?

VM result (exact)
The VM circuits already ran this contract. This result is exact. It is handed over here, into this prompt.
status: success
return value: returns 1550000000000000000000
gas: 7,706
logs: none

Sender and receiver
sender: 0xa11ce00000000000000000000000000000a11ce0
receiver: 0x7e5700000000000000000000000000000000bad0
contract: Vault at 0xc0ffee0000000000000000000000000000c0ffee

Sender history
- withdraw(4 ETH) from 0xa11ce00000000000000000000000000000a11ce0 to 0x9a7b000000000000000000000000000000009a7b, 4 ETH, 90d earlier
- withdraw(4.5 ETH) from 0xa11ce00000000000000000000000000000a11ce0 to 0x9a7b000000000000000000000000000000009a7b, 4.5 ETH, 83d earlier
- withdraw(3.8 ETH) from 0xa11ce00000000000000000000000000000a11ce0 to 0x9a7b000000000000000000000000000000009a7b, 3.8 ETH, 76d earlier
- withdraw(4.2 ETH) from 0xa11ce00000000000000000000000000000a11ce0 to 0x9a7b000000000000000000000000000000009a7b, 4.2 ETH, 69d earlier
- withdraw(4 ETH) from 0xa11ce00000000000000000000000000000a11ce0 to 0x9a7b000000000000000000000000000000009a7b, 4 ETH, 62d earlier
- withdraw(4.1 ETH) from 0xa11ce00000000000000000000000000000a11ce0 to 0x9a7b000000000000000000000000000000009a7b, 4.1 ETH, 55d earlier

Receiver history
- none

Known destinations: 0x9a7b000000000000000000000000000000009a7b
Balance: 2,000 ETH
Cap: not recorded

Run rules
Compare the amount with the usual size (the median of this sender's past calls). 100 times the usual size or more adds 45. 20 times or more adds 30. 5 times or more adds 10. No history adds 10.
A destination that has never been seen adds 20.
4 or more withdrawals within 10 minutes adds 25. If that burst would drain over half the balance, add 25 more.
90 percent or more of the contract cap in one call adds 20.
Emptying 90 percent or more of the balance adds 15.
A score of 60 or more blocks. A score of 30 or more holds. Otherwise the rules allow.
If the VM status is not success, the decision is block, because the code itself rejected the call.
The reviewer may only make the decision stricter, never looser than the rule verdict.
Rule verdict: block (score 65)
Reasons: 111x the usual size (median 4.05 ETH); destination 0x7e5700000000000000000000000000000000bad0 never seen before

Reply format
Reply with JSON only, one object: {"decision": "allow" or "hold" or "block", "reason": "one sentence"}

Simulated prompt card for a withdrawal: the prompt wraps the exact VM result, and the simulated model output blocks it because the amount is far above the usual size and the destination was never seen. No real language model was called.

SIMULATED model output, no real LLM was called
{"decision": "block", "reason": "Usual withdrawals from this sender are about 4 ETH to one known address. This call withdraws 450 ETH, about 111 times that size, and the destination has never been seen, so it should be blocked."}

The other canned replies

Same prompt shape, still simulated, still no model call:

  • rapid-drain: {"decision": "block", "reason": "Three withdrawals of 490 ETH already happened in the last few minutes, and this call takes another 490 ETH. Together that is 1,960 ETH and would drain most of the balance, so it should be blocked."}
  • near-cap: {"decision": "hold", "reason": "Past spends were about 35 to 42 million USD. This call spends 950 million USD, about 24 times the usual size and 95 percent of the cap, so it should be held for a person to review."}
  • over-cap: {"decision": "block", "reason": "The VM result is a revert because the call exceeds the cap. The code itself rejected it, so the decision is block."}

Five scenarios with synthetic histories (PYTHONPATH=. python -m evmllm.judge_examples). Every VM run matches py-evm:

Scenario Call VM Verdict Reasons
Valid withdrawal, 111x usual size, never-seen address withdraw(450 ETH) returns 1550000000000000000000 BLOCK (65) 111x the usual size (median 4.05 ETH); destination 0x7e5700000000000000000000000000000000bad0 never seen before
Ordinary transfer to a known address transfer(3 tokens) returns 97000000000000000000 ALLOW (0) in line with history, known destination
Rapid repeated withdrawals, each one within the limit withdraw(490 ETH) returns 40000000000000000000 BLOCK (65) 4 withdrawals in 10 minutes (1,960 ETH total); pattern would drain over half the balance; empties 90%+ of 8000 the balance
Spend under the $1B cap, but 95% of it in one call spend($950,000,000) returns 950000000000000 HOLD (50) 24x the usual size (median 39.0M USD); 95% of the contract cap in one call
Spend over the $1B cap: the code itself reverts spend($1,200,000,000) revert: exceeds $1B cap BLOCK (100) VM: revert: exceeds $1B cap; the code itself rejected it, nothing to review

Quickstart

git clone https://github.com/Verafyai/evmLLM && cd evmLLM

# Qwenputer (MIT) at the tested commit, plus a two-line patch so fastexec loads the standalone chip
git clone https://github.com/divagr18/qwenputer /tmp/qwp
git -C /tmp/qwp checkout f07a0b2
git -C /tmp/qwp apply "$PWD/qwenputer-cpu.patch"

sudo apt install gcc-riscv64-unknown-elf          # only needed to rebuild the firmware
python3 -m venv .venv && . .venv/bin/activate
python3 -m pip install -r requirements.txt -e /tmp/qwp/experimental/riscv

./firmware/build.sh                               # C EVM -> firmware/evm.elf (a prebuilt ELF is included)
PYTHONPATH=. python tests/demos_ref.py            # 23 demo runs vs py-evm
PYTHONPATH=. python tests/fuzz.py 2000            # 2000 random programs vs py-evm
PYTHONPATH=. python tests/divtest.py              # 256-bit DIV/MOD vs Python ints
PYTHONPATH=. python examples/run_on_weights.py    # a small program on the compiled weights (about 2 min on CPU)
PYTHONPATH=. python examples/run_on_weights.py --row 10   # gcd(1071, 462) on the weights
THREADS=4 PYTHONPATH=. python -u tests/circuit_run.py 0 3 10   # batched demo rows on the weights
PYTHONPATH=. python -m evmllm.judge_examples      # judgement filter scenarios
PYTHONPATH=. python -m evmllm.export_site            # rewrite site/data.js from real runs. Do not edit site/data.js by hand.

Running on the weights, in short:

from chipformer.rv import core, fastexec, spec as S
from evmllm.host import Job, machine, run_ref

job = Job(code=bytes.fromhex("6007600501805f555f5260205ff3"), trace=True)  # 5 + 7, store, return
ref = run_ref(job)                         # bit-exact reference trace
c = core.build()                           # RV32IM CPU compiled into weights
fr = fastexec.FreeRun(c, 1, "cpu", False, sparse=True)
core.snapshot(c, machine(job))             # firmware ROM + job into the RAM state
fr.load(0, S.tok("GO"))
out = fr.run(len(ref._toks) - 1)[0]        # autoregressive: each output is the next input
assert (out == ref._toks[1:]).all()

Test results

Test Pass What is compared
Demo runs (10 contracts, 23 inputs), firmware vs py-evm 23/23 status, return data, gas, changed storage
Random programs, firmware vs py-evm (Shanghai) 2000/2000 status, return data, gas, changed storage
256-bit DIV/MOD, firmware vs Python ints 400/400 quotient and remainder, multi-limb divisors
Browser VM (evm.js) vs py-evm corpus 2023/2023 the 23 demos plus the 2000 fuzz cases
Judgement filter, JS port vs Python 5/5 VM result, verdict, score, reasons
Runs on the compiled weights (fastexec, CPU) 12/12 runs, 338,555 tokens every generated token vs the reference trace

Runs on the compiled weights (each generated token fed back as the next input):

Contract Input Tokens Result
SpendCap $250,000 16,686 exact match
SpendCap $1,000,000,001 (over cap) 16,806 exact match
Counter add 1 to 41 16,369 exact match
Fibonacci fib(10) 45,350 exact match
Factorial 10! 57,932 exact match
GCD gcd(1071, 462) 19,519 exact match
Prime 91 51,344 exact match
Token account 0 sends 30 to 1 30,457 exact match
Token account 1 sends 6 to 0 (short) 23,710 exact match
Vault withdraw 600 (over limit) 18,243 exact match
Voting voter 2 votes again (rejected) 25,679 exact match
Escrow code 424242 16,460 exact match

Repository layout

firmware/        evm.c (the EVM, 256-bit words as 8 x 32-bit limbs), crt0, linker script, build.sh, evm.elf
evmllm/host.py   job packing, reference-machine runner, py-evm runner, result parsing
evmllm/demos.py  the 10 contracts and 23 demo inputs
evmllm/judge.py  judgement filter (rules, prompt builder, simulated replies; a real model plugs in); judge_examples.py has 5 scenarios
evmllm/export_site.py   writes site/data.js from real runs
site/            static demo (index.html, app.js, evm.js, judge.js, style.css, generated data.js)
contracts/       Demos.sol and compiled artifacts (solc 0.8.24)
tests/           demos_ref.py, fuzz.py, divtest.py, circuit_run.py, export_corpus.py, check_js_vm.js
examples/        run_on_weights.py
results/         JSON outputs of the runs above
assets/          logo, mark, favicon, social card, judgement and judge-prompt graphics
qwenputer-cpu.patch   two-line fastexec fix for the standalone chip

Where this could run

This is a demo that exact computation can live in a model's weights. It is not a node replacement.

Realistic today

  • Sidecar next to the chain software. A node operator runs the model beside their own client and uses it to review, flag or hold transactions in their own RPC endpoint, mempool view or wallet guard. It affects only that operator's infrastructure, not consensus. This is the realistic path today.
  • Off-chain checker and audit. Re-run a transaction on the weights machine, compare it to the reference result, and attach the review to an audit trail.
  • Judgement layer in front of a wallet or multisig. The exact VM result plus the review step decides allow, hold or block before a signer approves.

Research direction: part of consensus

Making the weights machine part of consensus would need:

  • Determinism. GPU floating point can differ across hardware and software versions. The compiled CPU here matched the reference trace token for token on CPU in fp32, but agreement across GPUs and software versions has not been tested, and a full Qwen-sized model is a much bigger problem.
  • Cost and speed. A tiny program takes tens of thousands of tokens, and every validator would have to repeat it. Here a whole demo run costs 87 to 194 tokens per executed EVM opcode, and even the smallest run is 16,370 tokens.
  • Compute limits. The model run would need to be metered and bounded by gas like any other execution.

The judgement filter is illustrative heuristics on synthetic histories, not a security product.

Honest limitations

  • The weights runs use Qwenputer's compiled chip layers on CPU (fp32, sparse). The full 18.8 GB merged checkpoint has not been run yet.
  • Opcode subset only: no KECCAK256 (so no mappings), no CALL/CREATE/LOG, no SDIV/SMOD/SIGNEXTEND/ADDMOD/MULMOD, no precompiles.
  • Build limits: 3,072 bytes of code, 256 bytes of calldata, 2 KB memory, 96-entry stack, 48 storage slots. Going over halts with status "build limit".
  • Single call with preset storage. No state trie, no persistence between runs, gas refunds not tracked.
  • Slow: the batched CPU runs here averaged under 200 tokens per second (183,929 tokens in 961 s for 9 runs, 154,626 tokens in 876 s for 3 runs), and a demo run is 16,370 to 605,944 tokens.
  • The judgement filter is simple heuristics on synthetic histories. It is a design sketch, not a security product.

Future possibilities

  • Faster execution. Qwenputer's GPU executor and a leaner trace format (skip the per-opcode hex print) should be much faster; this has not been measured yet.
  • A chat bridge. A small bridge firmware so the model can call evm_run(bytecode, calldata) mid-conversation and read back exact results, with the judgement filter as an in-context reviewer.
  • More of the EVM. KECCAK256 (unlocks Solidity mappings), LOG, SDIV/SMOD/SIGNEXTEND/ADDMOD/MULMOD, larger memory and storage, and multi-call state.
  • Smarter review. A real model behind the LLMJudge interface (examples use the simulated replies), richer history features, and per-protocol policies.

Future steps

Untested, not a claim. Merge the compiled machine into a Qwen-class model, as Qwenputer's merged Qwen3.5-4B checkpoint does for its CPU, so one model does both the language judgement and the exact execution of the EVM firmware. This has not been built or tested here. Its size and speed would be dominated by the base model: Qwenputer's checkpoint is 18.8 GB and needs a 24 GB GPU, while the CPU layers and the EVM firmware are small by comparison.

Next steps: run inside the full Qwenputer checkpoint (needs a GPU)

Every weights run so far uses Qwenputer's compiled chip layers on CPU. The next step is the merged model, where the same CPU sits inside Qwen3.5-4B.

  1. Get a single 24 GB GPU (the Qwenputer post uses an L4). The merged checkpoint is 18.8 GB, and the post reports about 3,200 CPU tokens per second for its batched executor on an L4.
  2. Reproduce Qwenputer first, using the commands from the post:
    git clone https://github.com/divagr18/qwenputer && cd qwenputer/experimental/riscv && uv sync --extra hf
    hf download divagr1925/Qwen3.5-4B-Qwenputer --local-dir qwenputer-rv
    uv run python -m chipformer.rv.chat qwenputer-rv --firmware tests/data/rv/calc-bridge.elf \
        --ask "Use calc: what is 123456789 * 987654321?"
  3. Load firmware/evm.elf with a job block in place of the calculator firmware, run the 12 weights rows and the 23 demo inputs, and require every token to match the reference trace, as the CPU runs here do.
  4. Add a small bridge firmware so the model can call evm_run(bytecode, calldata) mid-conversation, then wire the judgement filter to Qwen itself through LLMJudge, so one model runs the contract exactly and reviews the result.

Support

Drop me some ETH if you want to support me to keep working on this.

0xF2c09bB10Fd950095DD14Ff3289B82f9E264d826

Credits

  • Qwenputer by Divyansh Agrawal, @divagr1925 on X: blog post, weights on Hugging Face, code (MIT). EVMLLM would not exist without it: the CPU in the weights, the chipformer compiler, the reference machine and the fastexec executor are all Qwenputer's.
  • py-evm (Ethereum Foundation) as the reference EVM, solc for compilation.
  • Sibling project: SVMLLM, a Solana sBPF VM in hand-built weights. Code: Verafyai/svmLLM.

License

MIT, see LICENSE. Qwenputer is MIT, Copyright (c) 2026 Divyansh Agrawal.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0