Results
Every answer is compiled with the target's toolchain and diffed by the asm2c scorer. A function counts only if the
bytes are identical. If a function's prompt doesn't fit the context, or the model gives no output, it counts as a miss.
test: 234 held-out functions of doom, duke3d, quake, quake2 and zlib, whole translation units held out too. Every
model gets the same functions, the same prompt and the same scorer.
Table with columns: model, params, first-shot exact, pass@1, best-of-9, mean score| model | params | first-shot exact | pass@1 | best-of-9 | mean score |
|---|
| wyvern-1 | 7B | 25.2% | 24.1% | 31.2% | 48.7 |
| Qwen2.5-Coder-32B-Instruct | 32B | 9.0% | 9.2% | 16.2% | 27.0 |
| Qwen3-Coder-30B-A3B-Instruct | 30B MoE, 3B active | 7.7% | 8.5% | 9.4% | 14.3 |
| DeepSeek-Coder-V2-Lite-Instruct | 16B MoE, 2.4B active | 6.0% | 5.3% | 7.7% | 15.0 |
| Qwen2.5-Coder-7B-Instruct (base) | 7B | 5.1% | 4.6% | 6.4% | 14.5 |
fresh: 158 functions of heretic, sqlite and wolf3d, held out by stable hash before training. Same prompt,
settings and scorer for every model.
Table with columns: model, params, first-shot exact, pass@1, best-of-9, mean score| model | params | first-shot exact | pass@1 | best-of-9 | mean score |
|---|
| wyvern-1 | 7B | 9.5% | 8.6% | 13.3% | 27.8 |
| Qwen2.5-Coder-32B-Instruct | 32B | 2.5% | 2.8% | 5.7% | 21.1 |
| Qwen3-Coder-30B-A3B-Instruct | 30B MoE, 3B active | 1.3% | 1.1% | 1.3% | 2.0 |
| Qwen2.5-Coder-7B-Instruct (base) | 7B | 0.0% | 0.2% | 1.9% | 8.7 |
| DeepSeek-Coder-V2-Lite-Instruct | 16B MoE, 2.4B active | 0.0% | 0.2% | 1.3% | 10.2 |
- first-shot: the greedy output is byte-exact. No retries, no compiler feedback.
- pass@1: unbiased estimate from 8 samples (T 0.8, top-p 0.95). best-of-9: greedy or any of the 8 samples is exact.
- mean score: scorer similarity of the greedy output, 0–100. A compile error scores 0.
- Same input for every model: the system line and user turn of
prompt.py, greedy plus 8 samples, up to 1,536 new
tokens, a 16,384-token context, vLLM in bf16. The open models weren't tuned or given extra instructions.
- fresh includes 22 functions that no agent has matched yet. 136 of the 158 have a known match.
- The test prompts used an earlier wording of the system line's first sentence (only the name differs).
Raw numbers: bench/.
Data
Every training pair is one function that an agent matched byte-for-byte during asm2c runs. The scorer recompiled the
agent's C with the pinned toolchain and got identical bytes (relocations masked). An agent's own claim never admits a
pair; only the scorer's re-check does. No synthetic pairs.
5,316 pairs (1,577,853 bytes of matched code), giving 7,760 training rows and about 23.5M tokens. The 2026-10-19 run
and brogue pairs are weighted ×2, the earlier pairs ×1.
Table with columns: target, program, licence, pairs, rows| target | program | licence | pairs | rows |
|---|
| doom | linuxdoom 1.10 | GPL-2.0 | 642 | 642 |
| duke3d | Duke Nukem 3D 1.5 (jfduke3d) | GPL-2.0 | 306 | 306 |
| quake | WinQuake 1.09 | GPL-2.0 | 897 | 897 |
| quake2 | Quake II game 3.21 | GPL-2.0 | 909 | 909 |
| zlib | zlib 1.2.13 | Zlib | 118 | 118 |
| brogue | Brogue CE | AGPL-3.0 | 383 | 766 |
| heretic | Heretic 1.3 | GPL-2.0 | 471 | 942 |
| sqlite | SQLite 3.54.1 | public domain | 1,198 | 2,396 |
| wolf3d | Wolf4SDL | GPL-2.0 or id Wolf3D licence | 392 | 784 |
| total | | | 5,316 | 7,760 |
- One pair per (target, symbol). Identical asm under one toolchain is collapsed. Any training row whose asm equals a
held-out function's asm is dropped.
- Splits are by function, using a stable hash. The test split also holds out whole translation units.
- Per-target counts and drops:
dataset_stats.json.
Training
- LoRA r 64, alpha 128, dropout 0.05 on q/k/v/o/gate/up/down (161.5M trainable)
- initialised from an earlier asm2c LoRA trained on the 2,872 earlier pairs, then 1 epoch over the 7,760 rows
- seq_len 8192 (207 longer rows dropped, 7,553 trained), batch 1 × grad-accum 16, lr 1e-4, bf16, gradient checkpointing,
seed 2026
- loss on the assistant (C) turn only
- 473 steps, 22.4M tokens, 2.6 h on one A100-SXM4-80GB
- train loss 0.191, val loss 0.264 (55 held-out pairs)
Log and config: logs/train.log, run_summary.json.
Usage
There are two turns: the system line names the toolchain, and the user turn carries one function. prompt.py builds
both exactly as in training. The chat template's default system line is the gcc-i686-o2 one, so a bare user turn
also works.
System:
You are asm2c, a matching decompiler. Write C source for ONE function so that it recompiles to byte-identical machine code (relocations masked) with the exact toolchain below. Reproduce the original control flow, types, operand order and statement order the compiler needs; do not optimize or restructure. Output only C: any needed declarations, then the function definition.toolchain: gcc-i686-o2compiler: gcc 15.2 -O2 (i686-linux-gnu)flags: -std=gnu89 -O2 -march=i686 -fno-pic -fno-pie -fno-asynchronous-unwind-tables -fno-stack-protector -fcf-protection=none -fcommon -fno-reorder-blocks-and-partition -fno-ipa-sra -fno-ipa-cp -fno-ipa-vrp -fno-ipa-bit-cp -fno-partial-inlining -fno-ipa-icf -fno-ipa-pure-const -fno-ipa-reference -fno-ipa-modref -fno-ipa-ra -warch: i386
User (a fresh wolf3d function; the types: block is trimmed here):
function: FixedMul (25 bytes) · tu wl_utils.ccontext:proto: fixed FixedMul (fixed a, fixed b); /* wl_utils.h */includes: "wl_utils.h"sameTu: safe_malloc FixedDiv ReadShort ReadLong Error Help types:```ctypedef signed int __int32_t;typedef __int32_t int32_t;typedef int32_t fixed;``` target assembly:```asm# FixedMul (TU wl_utils.c, section .text, 25 bytes, binding global)# AT&T syntax. .Lxx = fn-relative branch targets; relocations shown symbolized:# "text" = string literal (.rodata.str), =0x.. = float/double constant (.rodata.cstN)FixedMul: /* 000 */ mov 0x4(%esp),%eax /* 004 */ imull 0x8(%esp) /* 008 */ add $0x8000,%eax /* 00d */ adc $0x0,%edx /* 010 */ mov %eax,%ecx /* 012 */ shrd $0x10,%edx,%ecx /* 016 */ mov %ecx,%eax /* 018 */ ret```
wyvern-1, greedy (byte-exact):
#include "wl_utils.h" fixed FixedMul (fixed a, fixed b){ return (fixed)(((long long)a * (long long)b + 0x8000) >> 16);}
The context lines are optional: proto, includes, callees, refs, data and sameTu. The types: block holds
the declarations the function needs. The asm is the function's listing in the format above. The model was trained
with this context present, so leaving it out is untested.
# hf download asm2c/wyvern-1-7b prompt.py --local-dir .import torchfrom transformers import AutoTokenizer, AutoModelForCausalLMfrom prompt import build_messages, extract_c tok = AutoTokenizer.from_pretrained("asm2c/wyvern-1-7b")model = AutoModelForCausalLM.from_pretrained("asm2c/wyvern-1-7b", dtype=torch.bfloat16, device_map="auto") pair = { "symbol": "FixedMul", "size": 25, "file": "wl_utils.c", "context": {"proto": "fixed FixedMul (fixed a, fixed b); /* wl_utils.h */", "includes": ['"wl_utils.h"'], "sameTu": ["safe_malloc", "FixedDiv", "ReadShort", "ReadLong", "Error", "Help"]}, "types": "typedef signed int __int32_t;\ntypedef __int32_t int32_t;\ntypedef int32_t fixed;", "asm": open("FixedMul.s").read(), # the listing, as above}ids = tok.apply_chat_template(build_messages(pair), add_generation_prompt=True, return_tensors="pt", return_dict=True).to(model.device)out = model.generate(**ids, max_new_tokens=2048) # greedy by default (generation_config.json)print(extract_c(tok.decode(out[0][ids["input_ids"].shape[1]:], skip_special_tokens=True)))
Treat the output as a candidate. Compile it with the toolchain above and compare the function's bytes. Output that
doesn't match can still compile. For a better hit rate, sample several answers (T 0.8, top-p 0.95) and keep the one
that matches. On fresh, that takes 9.5% first-shot to 13.3% best-of-9.
Limitations
- One toolchain: gcc 15.2 -O2 for i686, C. Other compilers, flags, optimisation levels and architectures are out of
distribution, and the same asm under another toolchain needs different C.
- The training code is game, database and compression code from the programs above. Unseen programs score lower.
- Prompts over 8,192 tokens were not trained on.
Licence
The weights are Apache-2.0, the same as the base model. The training C reconstructs functions of the programs listed
above, and each of those programs keeps its own licence.