Dory-Beam-4B / code /scripts /build_code_sft.py
ProCreations's picture
Beam's effort dial next to the student's, the distillation recipe, and new scripts
079e13c verified
Raw History Blame Contribute Delete
4.19 kB
"""Training mix for the coding fix (run rC): Beam's test-verified TACO solutions plus a replay sample of the effort
data that Dory-Beam-4B v2 was trained on, so the effort levels and the math are not forgotten.
.venv/bin/python -I scripts/build_code_sft.py --beam 'data/code/beam/run-code-*.jsonl' \
--verdicts data/code/verdicts.jsonl [--max-code 8000] [--replay-ratio 1.0]
# -> data/code/sft_code_mix.jsonl, data/code/sft_code_mix_stats.json
Coding examples are Beam at medium effort, tagged `{reasoning effort: medium}` half the time and untagged otherwise,
as in scripts/build_effort_dataset.py. Replay examples are drawn uniformly from data/final_effort/sft_effort.jsonl.
"""
import argparse
import glob
import json
import random
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--beam", required=True)
ap.add_argument("--verdicts", required=True)
ap.add_argument("--max-code", type=int, default=0)
ap.add_argument("--replay-ratio", type=float, default=1.0)
ap.add_argument("--max-chars", type=int, default=40000, help="drop very long traces (~12k tokens)")
ap.add_argument("--prefer-fn", action="store_true",
help="fill --max-code with function-style problems first (the HumanEval/MBPP format)")
ap.add_argument("--seed", type=int, default=0)
args = ap.parse_args()
rng = random.Random(args.seed)
passed = {v["key"] for v in map(json.loads, open(args.verdicts)) if v["passed"]}
code = []
for path in sorted(glob.glob(args.beam)):
for line in open(path):
r = json.loads(line)
key = f"{r.get('id')}#{r.get('sample_idx', 0)}"
if key not in passed or not r["reasoning"].strip():
continue
if len(r["reasoning"]) + len(r["content"]) > args.max_chars:
continue
code.append(r)
rng.shuffle(code)
if args.prefer_fn:
fn = {t["id"] for t in map(json.loads, open(ROOT / "data" / "code" / "tests.jsonl")) if t["fn_name"]}
code.sort(key=lambda r: r["id"] not in fn) # stable: function-style first, each part still shuffled
if args.max_code:
code = code[:args.max_code]
rows = []
for r in code:
tagged = rng.random() < 0.5
user = r["prompt"] + ("\n\n{reasoning effort: medium}" if tagged else "")
rows.append({"id": f"{r['id']}#code", "prompt_id": r["id"], "effort": "medium", "beam_effort": "medium",
"tagged": tagged, "domain": "code", "teacher": r["teacher"],
"messages": [{"role": "user", "content": user},
{"role": "assistant", "reasoning_content": r["reasoning"], "content": r["content"]}],
"gold": "", "chars": len(r["reasoning"]) + len(r["content"])})
effort = [json.loads(l) for l in open(ROOT / "data" / "final_effort" / "sft_effort.jsonl")]
replay = rng.sample(effort, min(len(effort), round(len(rows) * args.replay_ratio)))
mix = rows + replay
rng.shuffle(mix)
out = ROOT / "data" / "code" / "sft_code_mix.jsonl"
with open(out, "w") as f:
for r in mix:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
stats = {"code_examples": len(rows), "replay_examples": len(replay),
"code_chars_mean": round(sum(r["chars"] for r in rows) / max(1, len(rows))),
"replay_chars_mean": round(sum(r.get("chars", 0) for r in replay) / max(1, len(replay))),
"replay_mix": Counter(f"{r['effort']}{'' if r['tagged'] else '-untagged'}" for r in replay),
"code_sources": Counter(r["source"] for r in code),
"code_function_style": sum("starter code" in r["prompt"] for r in code)}
stats["approx_tokens_M"] = round((stats["code_chars_mean"] * len(rows) + stats["replay_chars_mean"] * len(replay))
/ 3.3 / 1e6, 1)
(ROOT / "data" / "code" / "sft_code_mix_stats.json").write_text(json.dumps(stats, indent=1))
print(json.dumps(stats, indent=1))
if __name__ == "__main__":
main()