wyvern-1-7b

wyvern-1

asm in, C out. Give wyvern-1 one function's x86 disassembly and its toolchain, and it writes C that gcc compiles back to the same bytes (relocations masked). The target is byte-identical C, which is stricter than equivalent C.

By asm2c. Model page: asm2c.xyz/model. Training pairs: asm2c.xyz/pairs.

  • base: Qwen/Qwen2.5-Coder-7B-Instruct, LoRA SFT, merged to bf16 (weights at the repo root)
  • LoRA alone: adapter/
  • toolchain: gcc-i686-o2 (gcc 15.2 -O2, i686-linux-gnu, flags below), C
  • prompt format: asm2c-chat-v2, implemented in prompt.py

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.

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.

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.

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-o2
compiler: 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 -w
arch: i386

User (a fresh wolf3d function; the types: block is trimmed here):

function: FixedMul (25 bytes) · tu 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:
```c
typedef 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 torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from 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.

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

Model tree for asm2c/wyvern-1-7b

Base model

Qwen/Qwen2.5-7B
Finetuned
(487)
this model