File size: 3,791 Bytes
0b19a1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
"""Generate predictions from an OpenAI-compatible endpoint. The ONLY script here that needs a model.

Every published number reproduces from the cached generations without ever running this — see
harness/README.md. This exists so the cache can be regenerated, or a different model evaluated.

    PYBYTECODE_ENDPOINT=http://localhost:1234/v1 PYBYTECODE_MODEL=my-model \\
      ./generate.py --bench ../benchmarks/csn-3.12-licensed/bench.jsonl --out gen.jsonl

Resumable: an existing --out is read first and completed rows are skipped, so an interrupted run
costs nothing. Output is streamed and flushed per row, never accumulated in memory.
"""
from __future__ import annotations

import argparse
import json
import sys
import urllib.error
import urllib.request
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from common import load_bench  # noqa: E402
from config import API_KEY, ENDPOINT, MODEL  # noqa: E402

INSTRUCTION = (
    "Decompile this Python 3.12 bytecode disassembly back into the original Python source code. "
    "Output only the source code."
)


def complete(prompt: str, temperature: float, max_tokens: int, seed: int | None) -> str:
    body = {
        "model": MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": temperature,
        "max_tokens": max_tokens,
    }
    if seed is not None:
        body["seed"] = seed
    req = urllib.request.Request(
        f"{ENDPOINT.rstrip('/')}/chat/completions",
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}"},
    )
    with urllib.request.urlopen(req, timeout=600) as r:
        return json.loads(r.read())["choices"][0]["message"]["content"]


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--bench", required=True)
    ap.add_argument("--out", required=True)
    ap.add_argument("--temperature", type=float, default=0.0)
    ap.add_argument("--samples", type=int, default=1, help=">1 emits an `s` field per sample")
    ap.add_argument("--max-tokens", type=int, default=2048)
    ap.add_argument("--limit", type=int, default=0)
    a = ap.parse_args()

    bench, _ = load_bench(a.bench)
    out_path = Path(a.out)
    done: set[tuple[int, int]] = set()
    if out_path.exists():
        for line in out_path.read_text().splitlines():
            if line.strip():
                r = json.loads(line)
                done.add((r["i"], r.get("s", 0)))
        print(f"resuming: {len(done)} generations already present", file=sys.stderr)

    todo = sorted(bench)[: a.limit or None]
    print(f"endpoint={ENDPOINT} model={MODEL} rows={len(todo)} samples={a.samples}", file=sys.stderr)

    with out_path.open("a") as f:
        for i in todo:
            for s in range(a.samples):
                if (i, s) in done:
                    continue
                prompt = f"{INSTRUCTION}\n\n{bench[i]['input']}"
                try:
                    got = complete(prompt, a.temperature, a.max_tokens, None if a.samples == 1 else s)
                except (urllib.error.URLError, OSError) as e:
                    raise SystemExit(
                        f"cannot reach {ENDPOINT}: {e}\n"
                        "Set PYBYTECODE_ENDPOINT to your OpenAI-compatible server, or use the "
                        "cached generations and skip this script entirely."
                    )
                rec = {"i": i, "got": got}
                if a.samples > 1:
                    rec["s"] = s
                f.write(json.dumps(rec) + "\n")
                f.flush()
            if i % 25 == 0:
                print(f"  {i}/{len(todo)}", file=sys.stderr, flush=True)


if __name__ == "__main__":
    main()