Download code/scripts/build_effort_dataset.py from ProCreations/Dory-Beam-4B: direct link, hf CLI and curl.
- Browser
- Download file 6.04 kB
-
https://huggingface.co/ProCreations/Dory-Beam-4B/resolve/main/code/scripts/build_effort_dataset.py
- Command line
-
hf download hf://ProCreations/Dory-Beam-4B/code/scripts/build_effort_dataset.py
-
curl -L -o build_effort_dataset.py https://huggingface.co/ProCreations/Dory-Beam-4B/resolve/main/code/scripts/build_effort_dataset.py
6.04 kB
| """Build the reasoning-effort SFT set: Beam traces at low, medium and high effort for the same prompts, each | |
| tagged the way Dory-Beam's chat template renders `reasoning_effort` (Dory's own `{reasoning effort: ...}` | |
| convention, appended to the user turn). | |
| .venv/bin/python -I scripts/build_effort_dataset.py --efforts low,medium,high --pool xhigh:high --longest high | |
| Inputs: data/effort_prompts.jsonl (the 20k prompts), low/high/xhigh generations in data/selfhost_effort/*.jsonl, | |
| and the existing medium generations (data/gen, data/selfhost). Per prompt and level, one randomly drawn | |
| verified-correct trace, or the longest with --longest (used for high, pooled from Beam's high and xhigh). Medium examples are tagged half the time and left untagged otherwise, so the untagged | |
| default keeps behaving like medium. Writes data/final_effort/sft_effort.jsonl and stats.json. | |
| """ | |
| import argparse | |
| import glob | |
| import json | |
| import random | |
| import statistics | |
| import sys | |
| from collections import defaultdict | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parent.parent | |
| sys.path.insert(0, str(ROOT / "scripts")) | |
| from verify import check # noqa: E402 | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--prompts", default=str(ROOT / "data" / "effort_prompts.jsonl")) | |
| ap.add_argument("--effort-inputs", default=str(ROOT / "data" / "selfhost_effort" / "*.jsonl")) | |
| ap.add_argument("--medium-inputs", nargs="+", | |
| default=[str(ROOT / "data" / "gen" / "*.jsonl"), str(ROOT / "data" / "selfhost" / "*.jsonl")]) | |
| ap.add_argument("--untagged-medium", type=float, default=0.5) | |
| ap.add_argument("--out-dir", default=str(ROOT / "data" / "final_effort")) | |
| ap.add_argument("--seed", type=int, default=0) | |
| ap.add_argument("--efforts", default="low,medium,high", help="Beam effort levels to include") | |
| ap.add_argument("--pool", default="", help="use one Beam level's traces as candidates for another, e.g. xhigh:high") | |
| ap.add_argument("--longest", default="", help="levels that take the longest correct candidate instead of a random one") | |
| args = ap.parse_args() | |
| EFFORTS = tuple(args.efforts.split(",")) | |
| pool = dict(x.split(":") for x in args.pool.split(",") if x) | |
| longest = set(filter(None, args.longest.split(","))) | |
| prompts = {json.loads(l)["id"]: json.loads(l) for l in open(args.prompts)} | |
| eval_ids = {l.strip() for l in open(ROOT / "data" / "eval_ids.txt") if l.strip()} | |
| assert not set(prompts) & eval_ids | |
| gens = defaultdict(list) # (id, effort) -> verified records | |
| teachers = defaultdict(set) | |
| files = sorted(glob.glob(args.effort_inputs)) | |
| files += sorted(p for pat in args.medium_inputs for p in glob.glob(pat)) | |
| for path in files: | |
| for line in open(path): | |
| r = json.loads(line) | |
| if "error" in r or r["id"] not in prompts: | |
| continue | |
| beam_eff = r.get("effort", "medium") | |
| if (beam_eff == "medium") != ("selfhost_effort" not in path): | |
| continue # medium only from the original pool, other levels only from the effort runs | |
| eff = pool.get(beam_eff, beam_eff) | |
| if eff not in EFFORTS: | |
| continue | |
| r["beam_effort"] = beam_eff | |
| ok, _ = check(r["content"], r["gold"], r["answer_type"]) | |
| r["correct"] = ok and r.get("finish_reason") == "stop" and bool(r.get("reasoning", "").strip()) | |
| gens[(r["id"], eff)].append(r) | |
| teachers[eff].add(r.get("teacher", "beam-api")) | |
| rng = random.Random(args.seed) | |
| out = Path(args.out_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| stats = {e: {"generations": 0, "correct": 0, "examples": 0, "chars": [], "tokens": []} for e in EFFORTS} | |
| n_untagged = 0 | |
| with open(out / "sft_effort.jsonl", "w") as f: | |
| for pid in sorted(prompts): | |
| for eff in EFFORTS: | |
| rs = gens.get((pid, eff), []) | |
| good = sorted((r for r in rs if r["correct"]), key=lambda r: len(r["reasoning"])) | |
| st = stats[eff] | |
| st["generations"] += len(rs) | |
| st["correct"] += len(good) | |
| for r in rs: | |
| st["tokens"].append(r["usage"].get("completion_tokens", 0)) | |
| if not good: | |
| continue | |
| r = good[-1] if eff in longest else rng.choice(good) | |
| tagged = eff != "medium" or rng.random() >= args.untagged_medium | |
| n_untagged += not tagged | |
| user = r["prompt"] + (f"\n\n{{reasoning effort: {eff}}}" if tagged else "") | |
| st["examples"] += 1 | |
| st["chars"].append(len(r["reasoning"]) + len(r["content"])) | |
| f.write(json.dumps({ | |
| "id": f"{pid}#{eff}", "prompt_id": pid, "effort": eff, "beam_effort": r["beam_effort"], | |
| "tagged": tagged, | |
| "domain": r["domain"], "teacher": r.get("teacher", "beam-api"), | |
| "messages": [{"role": "user", "content": user}, | |
| {"role": "assistant", "reasoning_content": r["reasoning"], "content": r["content"]}], | |
| "gold": r["gold"], "chars": st["chars"][-1], | |
| }, ensure_ascii=False) + "\n") | |
| summary = {"prompts": len(prompts), "untagged_medium": n_untagged, "pool": pool, "longest": sorted(longest), | |
| "teachers": {e: sorted(t) for e, t in teachers.items()}} | |
| for e, st in stats.items(): | |
| summary[e] = {"generations": st["generations"], "correct": st["correct"], | |
| "accuracy": round(st["correct"] / max(1, st["generations"]), 3), "examples": st["examples"], | |
| "chars_mean": round(statistics.mean(st["chars"])) if st["chars"] else None, | |
| "tokens_mean": round(statistics.mean(st["tokens"])) if st["tokens"] else None} | |
| (out / "stats.json").write_text(json.dumps(summary, indent=1)) | |
| print(json.dumps(summary, indent=1)) | |
| if __name__ == "__main__": | |
| main() | |