coolblaze03 commited on
Commit
b3e9a6d
·
verified ·
1 Parent(s): 242f37a

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. README.md +398 -0
  2. benchmarks/csn-3.12-licensed/pyc/00019.pyc +0 -0
  3. benchmarks/csn-3.12-licensed/pyc/00032.pyc +0 -0
  4. benchmarks/csn-3.12-licensed/pyc/00053.pyc +0 -0
  5. benchmarks/csn-3.12-licensed/pyc/00066.pyc +0 -0
  6. benchmarks/csn-3.12-licensed/pyc/00075.pyc +0 -0
  7. benchmarks/csn-3.12-licensed/pyc/00081.pyc +0 -0
  8. benchmarks/csn-3.12-licensed/pyc/00084.pyc +0 -0
  9. benchmarks/csn-3.12-licensed/pyc/00108.pyc +0 -0
  10. benchmarks/csn-3.12-licensed/pyc/00115.pyc +0 -0
  11. benchmarks/csn-3.12-licensed/pyc/00130.pyc +0 -0
  12. benchmarks/csn-3.12-licensed/pyc/00141.pyc +0 -0
  13. benchmarks/csn-3.12-licensed/pyc/00150.pyc +0 -0
  14. benchmarks/csn-3.12-licensed/pyc/00157.pyc +0 -0
  15. benchmarks/csn-3.12-licensed/pyc/00161.pyc +0 -0
  16. benchmarks/csn-3.12-licensed/pyc/00162.pyc +0 -0
  17. benchmarks/csn-3.12-licensed/pyc/00170.pyc +0 -0
  18. benchmarks/csn-3.12-licensed/pyc/00186.pyc +0 -0
  19. benchmarks/csn-3.12-licensed/pyc/00192.pyc +0 -0
  20. benchmarks/csn-3.12-licensed/pyc/00196.pyc +0 -0
  21. benchmarks/csn-3.12-licensed/pyc/00197.pyc +0 -0
  22. benchmarks/csn-3.12-licensed/pyc/00213.pyc +0 -0
  23. benchmarks/csn-3.12-licensed/pyc/00215.pyc +0 -0
  24. benchmarks/csn-3.12-licensed/pyc/00249.pyc +0 -0
  25. benchmarks/csn-3.12-licensed/pyc/00250.pyc +0 -0
  26. benchmarks/csn-3.12-licensed/pyc/00262.pyc +0 -0
  27. benchmarks/csn-3.12-licensed/pyc/00275.pyc +0 -0
  28. benchmarks/csn-3.12-licensed/pyc/00293.pyc +0 -0
  29. benchmarks/csn-3.12-licensed/pyc/00298.pyc +0 -0
  30. benchmarks/csn-3.12-licensed/pyc/00316.pyc +0 -0
  31. benchmarks/csn-3.12-licensed/pyc/00330.pyc +0 -0
  32. benchmarks/csn-3.12-licensed/pyc/00346.pyc +0 -0
  33. benchmarks/csn-3.12-licensed/pyc/00370.pyc +0 -0
  34. benchmarks/csn-3.12-licensed/pyc/00388.pyc +0 -0
  35. benchmarks/csn-3.12-licensed/pyc/00408.pyc +0 -0
  36. benchmarks/csn-3.12-licensed/pyc/00446.pyc +0 -0
  37. benchmarks/csn-3.12-licensed/pyc/00447.pyc +0 -0
  38. benchmarks/csn-3.12-licensed/pyc/00468.pyc +0 -0
  39. benchmarks/csn-3.12-licensed/pyc/00470.pyc +0 -0
  40. benchmarks/csn-3.12-licensed/pyc/00474.pyc +0 -0
  41. benchmarks/csn-3.12-licensed/pyc/00508.pyc +0 -0
  42. benchmarks/csn-3.12-licensed/pyc/00517.pyc +0 -0
  43. benchmarks/csn-3.12-licensed/pyc/00520.pyc +0 -0
  44. benchmarks/csn-3.12-licensed/pyc/00525.pyc +0 -0
  45. benchmarks/csn-3.12-licensed/pyc/00541.pyc +0 -0
  46. benchmarks/csn-3.12-licensed/pyc/00545.pyc +0 -0
  47. benchmarks/csn-3.12-licensed/pyc/00549.pyc +0 -0
  48. benchmarks/csn-3.12-licensed/pyc/00568.pyc +0 -0
  49. benchmarks/csn-3.12-licensed/pyc/00587.pyc +0 -0
  50. benchmarks/csn-3.12-licensed/pyc/00590.pyc +0 -0
README.md ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: Qwen/Qwen2.5-Coder-1.5B-Instruct
4
+ library_name: transformers
5
+ pipeline_tag: text-generation
6
+ language:
7
+ - en
8
+ tags:
9
+ - decompilation
10
+ - reverse-engineering
11
+ - python
12
+ - bytecode
13
+ - code
14
+ - verified-generation
15
+ - qwen2
16
+ ---
17
+
18
+ # PyBytecode v3 — 1.5B
19
+
20
+ **A 1.5B open model that decompiles Python 3.12 bytecode back to source, and ships with a sound
21
+ verifier that proves when an output is correct.**
22
+
23
+ Hand it a disassembled code object, get Python back. Then run the verifier: it recompiles the
24
+ prediction and compares the resulting code object against the target, byte for byte. If they
25
+ match, the decompilation is **proven** correct — not scored, not ranked, proven.
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
114
+
115
+ tok = AutoTokenizer.from_pretrained("BlazingCustoms/pybytecode-v3-1.5b")
116
+ model = AutoModelForCausalLM.from_pretrained(
117
+ "BlazingCustoms/pybytecode-v3-1.5b", torch_dtype="bfloat16", device_map="auto")
118
+
119
+ INSTRUCTION = ("Decompile this Python 3.12 bytecode disassembly back into the original Python "
120
+ "source code. Output only the source code.")
121
+
122
+ # `disasm` comes from harness/pybytecode_core/rep.py: disassemble_v2(code_object)
123
+ msgs = [{"role": "user", "content": f"{INSTRUCTION}\n\n{disasm}"}]
124
+ batch = tok.apply_chat_template(msgs, add_generation_prompt=True,
125
+ return_tensors="pt", return_dict=True).to(model.device)
126
+ prediction = tok.decode(model.generate(**batch, max_new_tokens=2048,
127
+ do_sample=False)[0][batch["input_ids"].shape[1]:],
128
+ skip_special_tokens=True)
129
+ ```
130
+
131
+ **Then certify it** — this is the step that matters:
132
+
133
+ ```python
134
+ from harness.pybytecode_core.verify import code_fingerprint
135
+
136
+ def certified(prediction: str, reference_code_object) -> bool:
137
+ got = compile(prediction, "<pred>", "exec", dont_inherit=True, optimize=0)
138
+ return code_fingerprint(got) == code_fingerprint(reference_code_object)
139
+ ```
140
+
141
+ Greedy decoding (temperature 0) for single-shot; temperature ~0.8 when sampling for best-of-N.
142
+
143
+ ### Measure your input size first
144
+
145
+ **This model works on function-sized units and degrades sharply past ~200 representation lines.**
146
+ That is the single most useful thing to know before you start, and it costs one line to check:
147
+
148
+ ```python
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
+ | your input | what to expect |
154
+ |---|---|
155
+ | **under 100 rep lines** | the model's home ground — ~89–96% greedy, ~97–99% at best-of-32 |
156
+ | **100–200** | still strong, but sampling starts to earn its cost |
157
+ | **200–300** | roughly a coin flip greedy; best-of-32 recovers much of it |
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
+ [PyLingual](https://github.com/syssec-utd/pylingual) is another system that performs this task, by
297
+ symbolic reconstruction rather than generation. We ran it on the same sets, under the same oracle,
298
+ at a matched budget.
299
+
300
+ | CSN-3.12, n=400, 24 repos | certified | 95% CI (repo-clustered) |
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
+ | Held-out, n=279 | certified | 95% CI (Wilson) |
309
+ |---|---|---|
310
+ | untuned base, greedy | 3 / 279 = 1.08% | [0.37, 3.11] |
311
+ | v3 greedy | 254 / 279 = 91.04% | [87.11, 93.86] |
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
+ **Neither difference is statistically significant** (exact paired McNemar, p = 0.6177 on CSN and
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
+ Docstring recovery on the 115 held-out rows carrying a real docstring: **115 / 115 = 100%**
321
+ (PyLingual: 113/115 = 98.26%, under an oracle that does not score docstrings at all).
322
+
323
+ The held-out interval is **Wilson, not clustered**: every row of that set carries the same
324
+ placeholder repository value, so its clustering cannot be assessed. The CSN design effects of
325
+ 2.34–2.69 show why that matters — on a set where one repository supplied 15% of rows, a naive
326
+ interval would have been far too narrow.
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
+ | Base | [`Qwen/Qwen2.5-Coder-1.5B-Instruct`](https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct) (Apache-2.0) |
358
+ | Method | LoRA r=16, α=32, all attention + MLP projections, 1 epoch, lr 2e-4, merged into the base |
359
+ | Rows | 48,196 pairs of Python 3.12 disassembly → source |
360
+ | Corpus | `codeparrot/github-code-clean`, per-row filtered to 7 permissive licences; all GPL/LGPL/AGPL/MPL/EPL dropped before extraction |
361
+ | Decontamination | shard-disjoint, repo-disjoint, and identifier-blind fingerprint-disjoint |
362
+
363
+ **The training corpus is not distributed.** Per-row attribution was not retained during
364
+ extraction, so the corpus cannot be redistributed without dropping required MIT/BSD/Apache
365
+ notices. That is a property of the artifact, not of the licences — nothing in it is unlicensed or
366
+ reciprocally licensed. Full lineage, including what was dropped and why:
367
+ [`DATA-CARD-training-corpus.md`](DATA-CARD-training-corpus.md).
368
+
369
+ ## Licence
370
+
371
+ **Apache-2.0.** See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE).
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 upstream repository ships a `LICENSE` and no
376
+ `NOTICE`, so there is no upstream notice text to carry forward. If you redistribute these weights
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: a restrictive weights licence would not prevent misuse and would block the
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
+
390
+ ```bibtex
391
+ @software{pybytecode2026,
392
+ title = {PyBytecode: verified neural decompilation for Python 3.12 bytecode},
393
+ author = {Blazing Customs},
394
+ year = {2026},
395
+ note = {Fine-tuned from Qwen2.5-Coder-1.5B-Instruct},
396
+ url = {https://huggingface.co/BlazingCustoms/pybytecode-v3-1.5b}
397
+ }
398
+ ```
benchmarks/csn-3.12-licensed/pyc/00019.pyc ADDED
Binary file (662 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00032.pyc ADDED
Binary file (820 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00053.pyc ADDED
Binary file (492 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00066.pyc ADDED
Binary file (1.04 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00075.pyc ADDED
Binary file (1.06 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00081.pyc ADDED
Binary file (919 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00084.pyc ADDED
Binary file (2.13 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00108.pyc ADDED
Binary file (949 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00115.pyc ADDED
Binary file (650 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00130.pyc ADDED
Binary file (2.5 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00141.pyc ADDED
Binary file (596 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00150.pyc ADDED
Binary file (1.3 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00157.pyc ADDED
Binary file (787 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00161.pyc ADDED
Binary file (667 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00162.pyc ADDED
Binary file (1.04 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00170.pyc ADDED
Binary file (2.07 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00186.pyc ADDED
Binary file (489 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00192.pyc ADDED
Binary file (546 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00196.pyc ADDED
Binary file (838 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00197.pyc ADDED
Binary file (2.02 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00213.pyc ADDED
Binary file (713 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00215.pyc ADDED
Binary file (862 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00249.pyc ADDED
Binary file (722 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00250.pyc ADDED
Binary file (556 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00262.pyc ADDED
Binary file (2.67 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00275.pyc ADDED
Binary file (538 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00293.pyc ADDED
Binary file (770 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00298.pyc ADDED
Binary file (1.72 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00316.pyc ADDED
Binary file (499 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00330.pyc ADDED
Binary file (857 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00346.pyc ADDED
Binary file (1.57 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00370.pyc ADDED
Binary file (2.31 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00388.pyc ADDED
Binary file (999 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00408.pyc ADDED
Binary file (1.15 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00446.pyc ADDED
Binary file (676 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00447.pyc ADDED
Binary file (786 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00468.pyc ADDED
Binary file (605 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00470.pyc ADDED
Binary file (902 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00474.pyc ADDED
Binary file (1.13 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00508.pyc ADDED
Binary file (438 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00517.pyc ADDED
Binary file (459 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00520.pyc ADDED
Binary file (577 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00525.pyc ADDED
Binary file (1.09 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00541.pyc ADDED
Binary file (3.25 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00545.pyc ADDED
Binary file (1.57 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00549.pyc ADDED
Binary file (677 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00568.pyc ADDED
Binary file (1.2 kB). View file
 
benchmarks/csn-3.12-licensed/pyc/00587.pyc ADDED
Binary file (657 Bytes). View file
 
benchmarks/csn-3.12-licensed/pyc/00590.pyc ADDED
Binary file (683 Bytes). View file