Text Generation
Transformers
Safetensors
GGUF
English
qwen2
decompilation
reverse-engineering
python
bytecode
code
verified-generation
conversational
text-generation-inference
Instructions to use BlazingCustoms/pybytecode-v3-1.5b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use BlazingCustoms/pybytecode-v3-1.5b with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="BlazingCustoms/pybytecode-v3-1.5b") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("BlazingCustoms/pybytecode-v3-1.5b") model = AutoModelForCausalLM.from_pretrained("BlazingCustoms/pybytecode-v3-1.5b", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use BlazingCustoms/pybytecode-v3-1.5b with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: llama cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: llama cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: ./llama-cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf BlazingCustoms/pybytecode-v3-1.5b:F16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Use Docker
docker model run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- LM Studio
- Jan
- vLLM
How to use BlazingCustoms/pybytecode-v3-1.5b with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "BlazingCustoms/pybytecode-v3-1.5b" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BlazingCustoms/pybytecode-v3-1.5b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- SGLang
How to use BlazingCustoms/pybytecode-v3-1.5b with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "BlazingCustoms/pybytecode-v3-1.5b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BlazingCustoms/pybytecode-v3-1.5b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "BlazingCustoms/pybytecode-v3-1.5b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BlazingCustoms/pybytecode-v3-1.5b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use BlazingCustoms/pybytecode-v3-1.5b with Ollama:
ollama run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- Unsloth Desktop
- Pi
How to use BlazingCustoms/pybytecode-v3-1.5b with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "BlazingCustoms/pybytecode-v3-1.5b:F16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use BlazingCustoms/pybytecode-v3-1.5b with Docker Model Runner:
docker model run hf.co/BlazingCustoms/pybytecode-v3-1.5b:F16
- Lemonade
How to use BlazingCustoms/pybytecode-v3-1.5b with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull BlazingCustoms/pybytecode-v3-1.5b:F16
Run and chat with the model
lemonade run user.pybytecode-v3-1.5b-F16
List all available models
lemonade list
- Hermes Agent
How to use BlazingCustoms/pybytecode-v3-1.5b with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default BlazingCustoms/pybytecode-v3-1.5b:F16
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use BlazingCustoms/pybytecode-v3-1.5b with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf BlazingCustoms/pybytecode-v3-1.5b:F16
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "BlazingCustoms/pybytecode-v3-1.5b:F16" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Upload README.md with huggingface_hub
Browse files
README.md
CHANGED
|
@@ -17,97 +17,16 @@ tags:
|
|
| 17 |
|
| 18 |
# PyBytecode v3 — 1.5B
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
That verifier is the distinguishing property of this release. Most generative models ask you to
|
| 28 |
-
trust an aggregate accuracy. This one lets you check the single file in front of you.
|
| 29 |
-
|
| 30 |
-
```
|
| 31 |
-
verified = recompile(prediction) yields a code object byte-identical to the target's
|
| 32 |
-
= PROVABLY correct. Sound: there is no such thing as a false "verified".
|
| 33 |
-
unverified = UNKNOWN, not wrong. A correct decompilation that compiles differently
|
| 34 |
-
(a `while` where the original had a `for`) does not certify.
|
| 35 |
-
```
|
| 36 |
-
|
| 37 |
-
Three consequences worth being explicit about:
|
| 38 |
-
|
| 39 |
-
- **Reported accuracy is a lower bound on correctness.** Everything counted here is proven; some
|
| 40 |
-
of what is not counted is right too.
|
| 41 |
-
- **Best-of-N buys real accuracy, not a better guess.** Sample until something certifies, then
|
| 42 |
-
stop. The verifier decides when to stop, so extra budget cannot mislead you.
|
| 43 |
-
- **The check is exact.** Comparison runs over the real code object, recursively, including
|
| 44 |
-
docstrings and `co_exceptiontable` — not over a textual disassembly. That is not decoration: an
|
| 45 |
-
earlier representation omitted the exception table's `end`, which made a `try:` body and a
|
| 46 |
-
`try/else:` body indistinguishable, and it once certified a wrong prediction.
|
| 47 |
|
| 48 |
Weights are Apache-2.0. A GGUF build ships alongside for llama.cpp / LM Studio / Ollama.
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
## How well it works
|
| 53 |
-
|
| 54 |
-
Strict L1 oracle throughout. Every figure carries a 95% interval and names the set it was measured
|
| 55 |
-
on.
|
| 56 |
-
|
| 57 |
-
### On the published benchmark — `csn-3.12-licensed`, n=600 across 117 repositories
|
| 58 |
-
|
| 59 |
-
| | certified | 95% CI |
|
| 60 |
-
|---|---|---|
|
| 61 |
-
| **PyBytecode v3, greedy** | **506 / 600 = 84.33%** | [80.48, 87.94] |
|
| 62 |
-
| **PyBytecode v3, verified best-of-32** | **562 / 600 = 93.67%** | [90.86, 96.08] |
|
| 63 |
-
| Untuned `Qwen2.5-Coder-1.5B-Instruct`, greedy | **4 / 600 = 0.67%** | [0.16, 1.35] |
|
| 64 |
-
|
| 65 |
-
Intervals are **repo-clustered** (bootstrap over repositories, 10,000 resamples), because rows
|
| 66 |
-
from one repository share an author and a house style and are not independent. Design effects
|
| 67 |
-
1.65 / 1.79 / 0.84.
|
| 68 |
-
|
| 69 |
-
The benchmark ships with the model, licence-resolved per row at the exact commit, so these numbers
|
| 70 |
-
are recomputable from files in this repository rather than taken on faith.
|
| 71 |
-
|
| 72 |
-
### The untuned base scores ~0, which is what makes the benchmark trustworthy
|
| 73 |
-
|
| 74 |
-
The same base model, given the same prompt, the same decoding budget and the same oracle, certifies
|
| 75 |
-
**4 of 600** here and **0 of 400** on our earlier benchmark. Under looser oracles it is clearer
|
| 76 |
-
still: the base produces **syntactically valid Python on 52% of rows** and reaches AST-identity on
|
| 77 |
-
**0%**. It paraphrases the disassembly into plausible-looking code with invented helpers rather
|
| 78 |
-
than inverting it.
|
| 79 |
-
|
| 80 |
-
A strong general code model cannot guess its way to a single point on this task. The benchmark is
|
| 81 |
-
not solvable by pattern-matching, and everything v3 scores was learned from the fine-tune.
|
| 82 |
-
|
| 83 |
-
### The benchmark rebuild reproduced the result
|
| 84 |
-
|
| 85 |
-
The published 600-row set was built from scratch: different rows (only 85 of 600 appear in the
|
| 86 |
-
earlier 400-row set), 117 repositories instead of 24, and a 1% per-repo cap replacing a 15%
|
| 87 |
-
concentration. It lands in the same place — certified@32 **93.67%** against **93.25%**, greedy
|
| 88 |
-
**84.33%** against **83.75%**. The difference of **+0.42pp** carries a 95% interval of
|
| 89 |
-
[−4.38, +5.22], so it is well inside noise, and the two size profiles are near-identical (median
|
| 90 |
-
59 vs 62 rep lines; 93.3% vs 94.0% of rows under 200), which is the composition factor that would
|
| 91 |
-
otherwise move the number. Standardised to the older set's size mix the new set reads 94.48%.
|
| 92 |
-
A result that survives a rebuild of the set it was measured on is worth more than the same result
|
| 93 |
-
quoted twice.
|
| 94 |
-
|
| 95 |
-
### Which numbers came from where
|
| 96 |
-
|
| 97 |
-
| Set | Published? | Why |
|
| 98 |
-
|---|---|---|
|
| 99 |
-
| `csn-3.12-licensed` (600) | **yes** | every row licence-resolved at its commit |
|
| 100 |
-
| `pybytecode-mbpp-3.12` (383) | **yes** | MBPP, CC-BY-4.0 |
|
| 101 |
-
| CSN-3.12 (400) | no | 45% of its rows are not redistributable |
|
| 102 |
-
| Held-out (279) | no | per-row attribution was not retained |
|
| 103 |
-
|
| 104 |
-
Our own results are measured on both the published set and the earlier pair. The figures in
|
| 105 |
-
*Comparison with other systems* exist only on the earlier pair, because that is where those runs
|
| 106 |
-
were done; they are labelled there because you cannot re-run them from what we shipped.
|
| 107 |
-
|
| 108 |
-
---
|
| 109 |
-
|
| 110 |
-
## How to use it well
|
| 111 |
|
| 112 |
```python
|
| 113 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
@@ -128,7 +47,13 @@ prediction = tok.decode(model.generate(**batch, max_new_tokens=2048,
|
|
| 128 |
skip_special_tokens=True)
|
| 129 |
```
|
| 130 |
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
```python
|
| 134 |
from harness.pybytecode_core.verify import code_fingerprint
|
|
@@ -138,233 +63,82 @@ def certified(prediction: str, reference_code_object) -> bool:
|
|
| 138 |
return code_fingerprint(got) == code_fingerprint(reference_code_object)
|
| 139 |
```
|
| 140 |
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
| 144 |
|
| 145 |
-
|
| 146 |
-
That is the single most useful thing to know before you start, and it costs one line to check:
|
| 147 |
|
| 148 |
-
|
| 149 |
-
from harness.pybytecode_core.rep import disassemble_v2
|
| 150 |
-
rep_lines = disassemble_v2(code_object).count("\n") # the model's actual input length
|
| 151 |
-
```
|
| 152 |
|
| 153 |
-
|
|
| 154 |
|---|---|
|
| 155 |
-
| **
|
| 156 |
-
| **
|
| 157 |
-
|
|
| 158 |
-
| **over ~300** | greedy rarely certifies; best-of-32 recovers some. Expect misses |
|
| 159 |
-
| **over ~400** | we certified nothing here, greedy **or** at 32 samples |
|
| 160 |
-
|
| 161 |
-
Above the knee a symbolic decompiler is the better tool, and the measurements are in *Comparison
|
| 162 |
-
with other systems* below.
|
| 163 |
-
|
| 164 |
-
The full curve, pooled over both earlier benchmarks (n=679) so the thin upper buckets carry as
|
| 165 |
-
many rows as we can give them:
|
| 166 |
-
|
| 167 |
-
| rep lines | rows | v3 greedy | v3 best-of-32 | untuned base | PyLingual k=32 |
|
| 168 |
-
|---|---|---|---|---|---|
|
| 169 |
-
| 0–49 | 311 | 94.21% | 98.71% | 0.96% | 99.04% |
|
| 170 |
-
| 50–99 | 229 | 88.65% | 96.94% | 0.00% | 95.63% |
|
| 171 |
-
| 100–199 | 105 | 78.10% | 88.57% | 0.00% | 84.76% |
|
| 172 |
-
| 200–299 | 19 | 47.37% | 84.21% | 0.00% | 84.21% |
|
| 173 |
-
| 300–399 | 11 | 18.18% | 54.55% | 0.00% | 81.82% |
|
| 174 |
-
| 400–599 | 2 | 0.00% | 50.00% | 0.00% | 100.00% |
|
| 175 |
-
| 600+ | 2 | 0.00% | 0.00% | 0.00% | 50.00% |
|
| 176 |
-
|
| 177 |
-
- **The greedy knee is at ~200 rep lines**: 78.1% → 47.4% → 18.2% → 0% across four consecutive
|
| 178 |
-
buckets.
|
| 179 |
-
- **Best-of-32 postpones the knee to ~300; it does not remove it.** Sampling 32 times buys roughly
|
| 180 |
-
one bucket of headroom.
|
| 181 |
-
- Almost every point we score comes from small units. On the earlier CSN-3.12 set (n=400),
|
| 182 |
-
**97.61%** of greedy certifications and **95.98%** of best-of-32 certifications are units under
|
| 183 |
-
200 rep lines; on the published 600-row set it is **96.64%** of greedy certifications. The
|
| 184 |
-
headline accuracy is a statement about small units.
|
| 185 |
-
- The top two buckets are n=2 each and carry nothing on their own. What carries the conclusion is
|
| 186 |
-
the monotone decline through n=311/229/105/19/11 below them.
|
| 187 |
-
|
| 188 |
-
The same stratification on the **published** 600-row benchmark — recomputable from the files in
|
| 189 |
-
this repo with `harness/size_curve.py` — reproduces the shape:
|
| 190 |
-
|
| 191 |
-
| rep lines | rows | v3 greedy | 95% CI | v3 best-of-32 | 95% CI |
|
| 192 |
-
|---|---|---|---|---|---|
|
| 193 |
-
| 0–49 | 224 | 95.98% | [93.01, 98.51] | 98.21% | [96.31, 99.58] |
|
| 194 |
-
| 50–99 | 224 | 89.73% | [85.17, 93.93] | 98.21% | [96.26, 99.57] |
|
| 195 |
-
| 100–199 | 112 | 65.18% | [55.36, 74.14] | 85.71% | [77.57, 92.98] |
|
| 196 |
-
| 200–299 | 27 | 51.85% | — | 77.78% | — |
|
| 197 |
-
| 300–399 | 5 | 60.00% | — | 100.00% | — |
|
| 198 |
-
| 400–599 | 5 | 0.00% | — | 0.00% | — |
|
| 199 |
-
| 600+ | 3 | 0.00% | — | 0.00% | — |
|
| 200 |
-
|
| 201 |
-
Intervals are repo-clustered, and omitted below 30 rows / 10 repositories rather than printed at a
|
| 202 |
-
width that would imply precision we do not have. The 300–399 bucket reading above the one below it
|
| 203 |
-
is n=5 noise, not a recovery. Median input in this benchmark is **59** rep lines; p90 is 167, max
|
| 204 |
-
1,622.
|
| 205 |
-
|
| 206 |
-
On this set the top two buckets stay at **0% even with 32 samples** — above ~400 rep lines,
|
| 207 |
-
sampling did not rescue a single one of the 8 rows. **96.64%** of greedy certifications and
|
| 208 |
-
**95.37%** of best-of-32 certifications come from units under 200 rep lines.
|
| 209 |
-
|
| 210 |
-
### Decide how much budget to spend
|
| 211 |
-
|
| 212 |
-
The best-of-32 budget is **1 greedy decode + 31 sampled candidates at temperature 0.8**, with
|
| 213 |
-
verified early stop — sampling halts on the first candidate that certifies, so the mean cost is
|
| 214 |
-
far below 32. Across the 94 greedy failures it took a mean of 15.16 samples and recovered 56:
|
| 215 |
-
|
| 216 |
-
| budget | certified | |
|
| 217 |
-
|---|---|---|
|
| 218 |
-
| @1 (greedy alone) | 506 / 600 | 84.33% |
|
| 219 |
-
| @2 | 525 / 600 | 87.50% |
|
| 220 |
-
| @4 | 546 / 600 | 91.00% |
|
| 221 |
-
| @8 | 555 / 600 | 92.50% |
|
| 222 |
-
| @16 | 558 / 600 | 93.00% |
|
| 223 |
-
| @32 | 562 / 600 | 93.67% |
|
| 224 |
-
|
| 225 |
-
Most of the recovery arrives in the first few samples: @4 already captures two thirds of what @32
|
| 226 |
-
gets. Because early stop depends only on the index of the first passing sample, this curve is
|
| 227 |
-
exact rather than an estimate — the same cached generations give every point.
|
| 228 |
-
|
| 229 |
-
---
|
| 230 |
-
|
| 231 |
-
## What this model does not do
|
| 232 |
-
|
| 233 |
-
- **Large units — this is the measured limit.** Certification falls from 94% under 50
|
| 234 |
-
representation lines to 18% at 300–399 and 0% above 400. See *Measure your input size*: the
|
| 235 |
-
curve is the honest specification of what this model does. It was trained on functions; a whole
|
| 236 |
-
module is a different regime in both size and shape, and we have not evaluated one.
|
| 237 |
-
- **Python 3.12 only.** A scope statement, not the explanation for the size behaviour above:
|
| 238 |
-
trained and measured on 3.12, and the oracle refuses cross-minor input by design. Every number
|
| 239 |
-
on this card is in-version, so nothing here is contaminated by a version mismatch.
|
| 240 |
-
- **It did not decompile real malware.** On the one wild PyInstaller-packed sample in scope, the
|
| 241 |
-
entry-point module produced **nothing certifiable** — the prediction did not even compile. 3 of
|
| 242 |
-
9 units in that sample certified, and those three were PyInstaller's own bootstrap plus a
|
| 243 |
-
bundled stdlib module. Extraction and representation work (1,510/1,510 code objects recovered);
|
| 244 |
-
end-to-end decompilation of real malware logic is **not demonstrated**.
|
| 245 |
-
|
| 246 |
-
That entry point was both 3.13 *and* 491 representation lines, so **size and version are
|
| 247 |
-
confounded in it and it is evidence for neither** on its own. It is reported because you should
|
| 248 |
-
know the result, not because it diagnoses anything. The benign in-version curve above is the
|
| 249 |
-
instrument, and it already shows the model failing at that size on clean 3.12 input.
|
| 250 |
-
- **Annotated functions** are handicapped: the training corpus was compiled with PEP-563
|
| 251 |
-
stringised annotations inherited, real `.pyc` files are not, and the benchmarks deliberately do
|
| 252 |
-
not hand the model its training-time distribution back.
|
| 253 |
-
|
| 254 |
-
## The oracle's limits, where you will meet them
|
| 255 |
-
|
| 256 |
-
Full detail in [`ORACLE-LIMITS.md`](ORACLE-LIMITS.md).
|
| 257 |
-
|
| 258 |
-
- **0.33% false-reject floor on foreign `.pyc`.** On 600 wild install-time `.pyc`, 598 certify;
|
| 259 |
-
2 do not, because CPython 3.12.3 and 3.12.13 generate different code for the same source. No
|
| 260 |
-
normalisation fixes that without abandoning the byte-identical guarantee. It fails to a **false
|
| 261 |
-
reject** — you are told "unknown" about a correct answer, never "verified" about a wrong one.
|
| 262 |
-
- **Optimization level must match the producer's.** A `.pyc` built with `-O` is a different code
|
| 263 |
-
object. The wrong level does not degrade gracefully, it collapses to ~24%. Try all three; it
|
| 264 |
-
costs three compiles. But note that at `optimize>=1` docstrings are absent from the `.pyc`, so
|
| 265 |
-
**docstring recovery becomes unprovable** against such a file — a limit of the artifact, not of
|
| 266 |
-
the oracle.
|
| 267 |
-
- **The 100% pre-flight the harness prints proves nothing about soundness.** It grades each
|
| 268 |
-
reference against itself, i.e. compares `compile(x)` with `compile(x)`, so any deterministic
|
| 269 |
-
function of the source scores 100% — including a stub that ignores the bytecode entirely. It
|
| 270 |
-
detects a broken harness and nothing more. The soundness evidence is the mutation test (0 true
|
| 271 |
-
survivors in 1,239 mutants) and 18 targeted blind-spot probes (18/18).
|
| 272 |
-
- **Untested:** cross-minor 3.13, Nuitka, non-CPython builds, obfuscated bytecode.
|
| 273 |
-
|
| 274 |
-
## Reproduce every number on this card
|
| 275 |
-
|
| 276 |
-
No model, no GPU, no network, no third-party decompiler — CPython 3.12 standard library only:
|
| 277 |
-
|
| 278 |
-
```bash
|
| 279 |
-
cd harness
|
| 280 |
-
python3 grade.py --bench ../benchmarks/csn-3.12-licensed/bench.jsonl --self-test-only --out /tmp/st.json
|
| 281 |
-
python3 analyze_scores.py --bench ../benchmarks/csn-3.12-licensed/bench.jsonl \
|
| 282 |
-
--greedy ../generations/gen_v3_csn600.jsonl \
|
| 283 |
-
--base ../generations/gen_base_csn600.jsonl \
|
| 284 |
-
--out /tmp/scores.json --rows-out /tmp/rows.jsonl
|
| 285 |
-
python3 size_curve.py --bench ../benchmarks/csn-3.12-licensed/bench.jsonl \
|
| 286 |
-
--greedy ../generations/gen_v3_csn600.jsonl \
|
| 287 |
-
--base ../generations/gen_base_csn600.jsonl \
|
| 288 |
-
--out /tmp/size_curve.json
|
| 289 |
-
```
|
| 290 |
-
|
| 291 |
-
Per-row verdicts for all 600 rows — repo, function, commit SHA, SPDX, tuned verdict, base verdict
|
| 292 |
-
— land in `rows.jsonl`. Every aggregate above is recomputable from that file.
|
| 293 |
-
|
| 294 |
-
## Comparison with other systems
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
| untuned base, greedy | 0 / 400 = 0.00% | — |
|
| 303 |
-
| v3 greedy | 335 / 400 = 83.75% | [77.84, 88.89] |
|
| 304 |
-
| v3 certified@32 | 373 / 400 = 93.25% | [88.82, 96.89] |
|
| 305 |
-
| PyLingual k=32 | 377 / 400 = 94.25% | [91.06, 96.80] |
|
| 306 |
-
| **union** | **393 / 400 = 98.25%** | [96.81, 99.51] |
|
| 307 |
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
| v3 certified@32 | 272 / 279 = 97.49% | [94.91, 98.78] |
|
| 313 |
-
| PyLingual k=32 | 267 / 279 = 95.70% | [92.63, 97.52] |
|
| 314 |
-
| **union** | **275 / 279 = 98.57%** | [96.37, 99.44] |
|
| 315 |
|
| 316 |
-
|
| 317 |
-
p = 0.2266 on held-out). We claim no accuracy advantage, and none is claimed against us. On cost
|
| 318 |
-
the comparison runs against us: ~1.5B parameters against their ~320M, and slower wall-clock.
|
| 319 |
|
| 320 |
-
|
| 321 |
-
|
| 322 |
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
These figures were measured on our earlier benchmark pair, which is **not** published. PyLingual
|
| 329 |
-
has not been run on the 600-row licensed benchmark released alongside this model.
|
| 330 |
-
|
| 331 |
-
### The two approaches fail on different inputs
|
| 332 |
-
|
| 333 |
-
The more interesting measured result is not which number is larger, but that the two systems lose
|
| 334 |
-
on different inputs, along an axis that turns out to be **unit size**:
|
| 335 |
-
|
| 336 |
-
| rep lines | rows | PyBytecode greedy | PyBytecode @32 | PyLingual k=32 |
|
| 337 |
-
|---|---|---|---|---|
|
| 338 |
-
| 100–199 | 105 | 78.10% | 88.57% | 84.76% |
|
| 339 |
-
| 200–299 | 19 | 47.37% | 84.21% | 84.21% |
|
| 340 |
-
| 300–399 | 11 | **18.18%** | 54.55% | **81.82%** |
|
| 341 |
-
| 400–599 | 2 | **0.00%** | 50.00% | **100.0%** |
|
| 342 |
-
|
| 343 |
-
A generative model degrades as its input grows; a symbolic reconstruction does not. Two things
|
| 344 |
-
follow, both useful:
|
| 345 |
-
|
| 346 |
-
- **Large units are not intrinsically unsolvable.** A symbolic system handles them at 81.82% where
|
| 347 |
-
we score 18.18%. What our curve measures is a capability limit of a 1.5B model, not a property
|
| 348 |
-
of the task — which is a statement about where to spend the next round of scale.
|
| 349 |
-
- **The union exceeds either system**, 98.25% and 98.57% above. Because verification is sound, the
|
| 350 |
-
union costs nothing to take: run either, keep the answer that certifies, fall through to the
|
| 351 |
-
other. Neither system can certify a wrong answer, so combining them cannot mislead you.
|
| 352 |
-
|
| 353 |
-
## Training
|
| 354 |
|
| 355 |
-
| | |
|
| 356 |
-
|---|---|
|
| 357 |
-
|
|
| 358 |
-
|
|
| 359 |
-
|
|
| 360 |
-
|
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
|
| 369 |
## Licence
|
| 370 |
|
|
@@ -372,18 +146,11 @@ reciprocally licensed. Full lineage, including what was dropped and why:
|
|
| 372 |
|
| 373 |
Derived from `Qwen/Qwen2.5-Coder-1.5B-Instruct`, which is Apache-2.0. Under Apache-2.0 §4 we ship
|
| 374 |
the licence, retain attribution, and state our changes (LoRA fine-tune, adapter merged; no
|
| 375 |
-
architecture, vocabulary or tokenizer change). The
|
| 376 |
-
|
| 377 |
-
or build derivatives, the same four obligations pass to you.
|
| 378 |
-
|
| 379 |
-
The corpus constraint above binds the *corpus*, not these weights: trained parameters are not a
|
| 380 |
-
copy of any source text, and the corpus was filtered to permissive licences before training, so no
|
| 381 |
-
reciprocal terms exist upstream to propagate.
|
| 382 |
|
| 383 |
Decompilation has obvious dual use. Apache-2.0 imposes no field-of-use restriction and we have not
|
| 384 |
-
added one
|
| 385 |
-
interoperability, incident-response and recovery work this is for. Complying with the law where
|
| 386 |
-
you operate is your responsibility.
|
| 387 |
|
| 388 |
## Citation
|
| 389 |
|
|
|
|
| 17 |
|
| 18 |
# PyBytecode v3 — 1.5B
|
| 19 |
|
| 20 |
+
Turns Python 3.12 bytecode back into Python source. Hand it a disassembled code object, get source
|
| 21 |
+
code back.
|
| 22 |
|
| 23 |
+
The unusual part: **you can check every answer.** Recompile what the model wrote and compare it
|
| 24 |
+
against the bytecode you started with — if they match, that file is exactly right, and you know it
|
| 25 |
+
without trusting an accuracy number.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
Weights are Apache-2.0. A GGUF build ships alongside for llama.cpp / LM Studio / Ollama.
|
| 28 |
|
| 29 |
+
## Quickstart
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
```python
|
| 32 |
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
|
|
| 47 |
skip_special_tokens=True)
|
| 48 |
```
|
| 49 |
|
| 50 |
+
Greedy decoding (temperature 0) for a single shot; temperature ~0.8 when you sample several
|
| 51 |
+
candidates.
|
| 52 |
+
|
| 53 |
+
## Checking the answer
|
| 54 |
+
|
| 55 |
+
Compile the model's output and compare the resulting code object to the one you were decompiling.
|
| 56 |
+
Same code object means same behaviour, so a match tells you *this* answer is correct:
|
| 57 |
|
| 58 |
```python
|
| 59 |
from harness.pybytecode_core.verify import code_fingerprint
|
|
|
|
| 63 |
return code_fingerprint(got) == code_fingerprint(reference_code_object)
|
| 64 |
```
|
| 65 |
|
| 66 |
+
Two things follow. A failed check means "not confirmed", not "wrong" — a correct rewrite that
|
| 67 |
+
compiles differently (a `while` where the original had a `for`) won't match, so the accuracy
|
| 68 |
+
figures below are a floor, not an estimate. And because the check is cheap and reliable, sampling
|
| 69 |
+
several answers and keeping the first one that passes is a real gain rather than a nicer guess.
|
| 70 |
|
| 71 |
+
## Results
|
|
|
|
| 72 |
|
| 73 |
+
On the benchmark published with this model:
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
| | certified |
|
| 76 |
|---|---|
|
| 77 |
+
| **PyBytecode v3, one attempt** | **506 / 600 = 84.33%** |
|
| 78 |
+
| **PyBytecode v3, up to 32 tries** | **562 / 600 = 93.67%** |
|
| 79 |
+
| `Qwen2.5-Coder-1.5B-Instruct` before fine-tuning, one attempt | 4 / 600 = 0.67% |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
The benchmark is `csn-3.12-licensed`: 600 real functions from 117 GitHub repositories, compiled to
|
| 82 |
+
3.12 bytecode, shipped with the model. The base model before fine-tuning gets essentially none of
|
| 83 |
+
them, so this is not something a general code model can guess its way through.
|
| 84 |
|
| 85 |
+
Full numbers, confidence intervals, method, per-budget curve and the comparison with other
|
| 86 |
+
systems: [`EVAL.md`](EVAL.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
+
[PyLingual](https://github.com/syssec-utd/pylingual) is another system that does this task, by
|
| 89 |
+
symbolic reconstruction rather than generation. On our earlier benchmarks it scores about the same
|
| 90 |
+
as we do, and the two miss on different inputs — so running both and keeping whichever answer
|
| 91 |
+
passes the check gets you more than either alone. Numbers in [`EVAL.md`](EVAL.md).
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
+
## When it works well, and when it doesn't
|
|
|
|
|
|
|
| 94 |
|
| 95 |
+
**It is good on individual functions and gets much worse on long ones.** Size is measured in
|
| 96 |
+
*disassembly lines* — how long the input you hand the model is. One line tells you:
|
| 97 |
|
| 98 |
+
```python
|
| 99 |
+
from harness.pybytecode_core.rep import disassemble_v2
|
| 100 |
+
rep_lines = disassemble_v2(code_object).count("\n")
|
| 101 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
+
| disassembly lines | rows | one attempt | up to 32 tries |
|
| 104 |
+
|---|---|---|---|
|
| 105 |
+
| under 100 | 448 | 92.86% | 98.21% |
|
| 106 |
+
| 100–199 | 112 | 65.18% | 85.71% |
|
| 107 |
+
| 200–399 | 32 | 53.12% | 81.25% |
|
| 108 |
+
| 400+ | 8 | 0.00% | 0.00% |
|
| 109 |
+
|
| 110 |
+
Below ~100 lines it is on home ground. Accuracy starts dropping around 200, and above ~400 lines
|
| 111 |
+
nothing certified at all, even with 32 tries. Sampling more buys roughly one bucket of headroom;
|
| 112 |
+
it does not remove the limit. For big units, a symbolic decompiler is the better tool. The full
|
| 113 |
+
seven-bucket curve is in [`EVAL.md`](EVAL.md).
|
| 114 |
+
|
| 115 |
+
## Limits
|
| 116 |
+
|
| 117 |
+
- **Long inputs.** The table above is the honest specification: trained on functions, not modules,
|
| 118 |
+
and it fails above ~400 disassembly lines.
|
| 119 |
+
- **Python 3.12 only.** Trained and measured on 3.12; the checker refuses other minor versions by
|
| 120 |
+
design.
|
| 121 |
+
- **If the `.pyc` was built with `-O`, compile at the same level or the check will not match.**
|
| 122 |
+
Wrong level collapses to ~24%, so try all three — it costs three compiles. At `-O` and above,
|
| 123 |
+
docstrings aren't in the `.pyc` at all, so docstring recovery can't be confirmed against one.
|
| 124 |
+
- **A `.pyc` built by someone else can fail the check even when the answer is right** — about
|
| 125 |
+
0.33% of the time, because CPython patch releases compile the same source differently. It always
|
| 126 |
+
fails in the safe direction: "unknown" about a correct answer, never "confirmed" about a wrong
|
| 127 |
+
one.
|
| 128 |
+
- **It has not been shown to work on real malware.** On the one packed sample we tried, the
|
| 129 |
+
entry-point module produced nothing certifiable. Extraction worked; decompiling the actual
|
| 130 |
+
malware logic did not.
|
| 131 |
+
- Untested: Python 3.13, Nuitka, non-CPython builds, obfuscated bytecode.
|
| 132 |
+
|
| 133 |
+
Details on all of these in [`EVAL.md`](EVAL.md) and [`ORACLE-LIMITS.md`](ORACLE-LIMITS.md).
|
| 134 |
+
|
| 135 |
+
## Model details
|
| 136 |
+
|
| 137 |
+
Fine-tuned from [`Qwen/Qwen2.5-Coder-1.5B-Instruct`](https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct)
|
| 138 |
+
(Apache-2.0) with LoRA on 48,196 pairs of Python 3.12 disassembly → source, adapter merged. The
|
| 139 |
+
corpus was filtered to permissive licences before training and is not redistributed — per-row
|
| 140 |
+
attribution was not retained, so shipping it would strip required notices. Lineage:
|
| 141 |
+
[`DATA-CARD-training-corpus.md`](DATA-CARD-training-corpus.md). Training settings: [`EVAL.md`](EVAL.md).
|
| 142 |
|
| 143 |
## Licence
|
| 144 |
|
|
|
|
| 146 |
|
| 147 |
Derived from `Qwen/Qwen2.5-Coder-1.5B-Instruct`, which is Apache-2.0. Under Apache-2.0 §4 we ship
|
| 148 |
the licence, retain attribution, and state our changes (LoRA fine-tune, adapter merged; no
|
| 149 |
+
architecture, vocabulary or tokenizer change). The same obligations pass to you if you
|
| 150 |
+
redistribute these weights or build derivatives.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
Decompilation has obvious dual use. Apache-2.0 imposes no field-of-use restriction and we have not
|
| 153 |
+
added one. Complying with the law where you operate is your responsibility.
|
|
|
|
|
|
|
| 154 |
|
| 155 |
## Citation
|
| 156 |
|