PRA-MEN · neural decompiler for Windows and MS-DOS
Pramen (Czech for source, spring) turns the assembly of a single function back into C. It targets the toolchains of the Windows / DOS world rather than Linux/GCC:
| variant | target |
|---|---|
msvc-x64-Od / msvc-x64-O2 / msvc-x64-O1 |
Windows x64, MSVC, no optimization / /O2 / /O1 |
msvc-x86-Od / msvc-x86-O2 / msvc-x86-O1 |
Windows x86 (32-bit cdecl), MSVC |
dos16-small-od / dos16-small-ox |
MS-DOS 16-bit real mode, small model, Open Watcom, no optimization / -ox |
dos16-large-ox / dos16-large-os |
MS-DOS 16-bit real mode, large model (far calls), Open Watcom -ox / -os |
It is a 223 M parameter decoder-only transformer (GPT-2 style) trained from scratch – no natural language, just assembly and C, with its own 16 384-token tokenizer.
The output is a proposal. It usually compiles and often has the right structure, but names are invented (they are not in the binary), types are guessed and the logic has to be checked.
What's new in 1.1
- Better model: trained further on 8.5 M pairs including four new optimized variants (
/O1,-ox,-os). Scored the same way on the 169 tasks of the 1.0 benchmark, the average goes from 32.0 % (1.0) to 42.6 %. --candidates 8: writes 8 candidates, compiles each one back with the same compiler and picks the one whose assembly matches the input best. +12 points on the benchmark, no tests or source needed. Requires MSVC and/or Open Watcom on the machine (otherwise it falls back to a single answer).- Fast on CPU: KV cache (generation no longer recomputes the whole context) and optional
--quantize int8(about 1.6× faster, 40 % less RAM).
Results
Benchmark: HumanEval-C from LLM4Binary/decompile-eval (CC0), recompiled with the same toolchains as the training data – 205 tasks (196–203 per variant). A task counts as solved when the decompiled C compiles (as C17 with the standard headers) and passes the original tests (re-executability). Max 600 new tokens.
| variant | greedy | --candidates 8 |
greedy, --quantize int8 |
|---|---|---|---|
| msvc-x64-Od | 53.2 % | 70.9 % | 49.3 % |
| msvc-x64-O2 | 29.2 % | 35.6 % | 24.3 % |
| msvc-x86-Od | 53.7 % | 68.5 % | 46.3 % |
| dos16-small-od | 39.8 % | 51.0 % | 39.8 % |
| dos16-large-ox | 19.8 % | 28.9 % | 22.3 % |
| average | 39.1 % | 51.0 % | 36.4 % |
int8 costs about 3 points on average (it changes many outputs, some for the better); it is meant for machines without a GPU.
msvc-x86-O2 and the four newest variants are not benchmarked yet. Optimized code is much harder. The most common
failures are wrong parameter types (pointer vs. integer, float vs. int) and invented helper structures.
The numbers in version 1.0 of this card were lower partly because the old harness compiled the output as C++
without headers, which rejected valid C (void* without a cast, new as a name, bool).
examples.json contains 12 classic functions × 6 variants with Pramen's outputs and test results
(55 / 72 pass) – they are also shown in the web UI.
Quick start
pip install huggingface_hub
hf download radix16/pramen --local-dir pramen # or: git clone https://huggingface.co/radix16/pramen
cd pramen
pip install -r requirements.txt # torch, tokenizers, safetensors
# command line (model is found in the current folder)
python -m pramen example-count_words-msvc-x64-Od.asm --variant msvc-x64-Od
# web UI with examples → http://127.0.0.1:8780
python -m pramen.web
GPU: 1–2 s per function. CPU (14 cores): about 50 ms per generated token, 2–4 s for a small function, 1.4 GB RAM;
with --quantize int8 about 30 ms per token and 0.85 GB RAM. --candidates 8 takes roughly 5–10× longer.
python -m pramen func.asm --variant msvc-x86-Od --candidates 8 # pick the best of 8 by recompiling
python -m pramen func.asm --variant msvc-x86-Od --quantize int8 # CPU, smaller and faster (uses int8/)
python -m pramen.web --quantize int8
The int8/ folder is a ready-made int8 copy (303 MB: linear layers in int8 with a per-tensor scale, embeddings and
LayerNorm in fp32, plain safetensors). --quantize int8 picks it up automatically; it runs on CPU only. If you only
want the int8 model, download just that folder: hf download radix16/pramen --include "int8/*" "pramen/*" --local-dir pramen
and run with --model int8.
from pramen.normalize import normalize
from pramen.runtime import Pramen
pramen = Pramen(".") # folder with config.json, model.safetensors, tokenizer.json
asm, fmt = normalize(open("func.asm").read(), "auto", "msvc-x86-Od")
print(pramen.decompile(asm, "msvc-x86-Od")["c"])
print(pramen.decompile(asm, "msvc-x86-Od", candidates=8)["c"]) # needs MSVC (vcvars64.bat) or Open Watcom
--candidates looks for Visual Studio 2022 / Build Tools (vcvars64.bat) and Open Watcom in C:\WATCOM; other
locations can be set with DECOMP_VCVARS and DECOMP_WATCOM. The training data was built with MSVC 19.36 and
Open Watcom V2 – other versions work, but the recompiled assembly may differ slightly.
Getting the assembly
- MSVC:
dumpbin /disasm:nobytes program.obj– copy the block of one function. - Open Watcom:
wdis -a program.obj. - Ghidra: copy instructions from the Listing window (header, XREFs and bytes are dropped). Pramen was trained
on numeric stack offsets (
[EBP + -0x8]), so turn off Markup Stack Variable References (Edit → Tool Options → Listing Fields → Operands Field).
pramen.normalize converts all of these into the exact form used in training (upper-case mnemonics and
registers, 0x hex, LAB_n: labels). Feeding raw text that is formatted differently lowers the quality.
Why not GGUF / llama.cpp / LM Studio
The tokenizer uses its own pre-tokenization (hex literals like 0x10 and identifiers like LAB_2 stay whole).
llama.cpp only supports a fixed set of pre-tokenizers; with gpt-2 it splits 0x10 into 0, x, 10
and the model then repeats assembly instead of writing C. The weights themselves work in llama.cpp when fed
token IDs, but text prompts do not – so use the Python package here.
Prompt format
<|asm|>{variant}
{normalized assembly}
<|c|>
{C code}<|endoftext|>
Training
- Data: 8.5 M assembly → C pairs (3.6 B tokens). C functions come from
ExeBench (
train_synth_compilable,train_real_compilable), each compiled ten ways with MSVC 19.36 (dumpbin) and Open Watcom V2 (wdis), deduplicated (exact + near). - Additional C/C++ source code (3.5 B tokens), mixed in so the model learns C syntax and idioms, from permissively licensed GitHub repositories (codeparrot/github-code-clean), The Stack, and several open-source projects (cJSON, fmt, nlohmann/json, and the GPL-2.0 source releases of Doom and Quake I–III).
- Architecture: 16 layers, 1 024 hidden, 16 heads, MLP 4 096, context 4 096, learned positions, tied embeddings.
Limitations
- one function at a time, up to ~3 500 input tokens (roughly 400–600 lines of assembly)
- globals and structs are only visible as addresses and offsets; the model guesses their meaning
- variable and function names are invented
- trained only on code produced by MSVC 19.36 and Open Watcom V2; other compilers (Borland, Turbo C, GCC/MinGW, …) are out of distribution
License and data
Model weights and code: Apache-2.0. The training functions keep the licenses of their original GitHub repositories (as collected by ExeBench); the additional source code includes GPL-2.0 code (Doom, Quake). Use the outputs accordingly – decompiled code of third-party software is still subject to its original license.
Česky
Pramen = zdroj. Neuronový dekompilátor, který z assembleru jedné funkce napíše C. Míří na Windows (MSVC x86/x64)
a MS-DOS (Open Watcom 16 bit). Model má 223 M parametrů a je natrénovaný od nuly jen na assembleru a C. Výstup je
návrh: obvykle se přeloží a mívá správnou strukturu, ale jména jsou vymyšlená, typy odhadnuté a logiku je potřeba
ověřit. Spuštění: pip install -r requirements.txt, pak python -m pramen.web (webové rozhraní s ukázkami na
http://127.0.0.1:8780) nebo python -m pramen soubor.asm --variant msvc-x86-Od. S --candidates 8 vybere nejlepší
z 8 návrhů podle zpětného překladu (potřebuje MSVC nebo Open Watcom), s --quantize int8 běží rychleji na CPU.
- Downloads last month
- 122