Upload folder using huggingface_hub
Browse files
configs/base.yaml
CHANGED
|
@@ -9,8 +9,8 @@ model:
|
|
| 9 |
pad_token: "<|fim_pad|>" # distinct from eos <|endoftext|>; avoids infinite-gen bug.
|
| 10 |
|
| 11 |
lora:
|
| 12 |
-
r: 16
|
| 13 |
-
alpha:
|
| 14 |
dropout: 0.05
|
| 15 |
target_modules: "all-linear" # attention + MLP (LoRA-Learns-Less best practice)
|
| 16 |
|
|
|
|
| 9 |
pad_token: "<|fim_pad|>" # distinct from eos <|endoftext|>; avoids infinite-gen bug.
|
| 10 |
|
| 11 |
lora:
|
| 12 |
+
r: 32 # raised from 16: first-error localization is a hard skill
|
| 13 |
+
alpha: 64 # alpha = 2r
|
| 14 |
dropout: 0.05
|
| 15 |
target_modules: "all-linear" # attention + MLP (LoRA-Learns-Less best practice)
|
| 16 |
|
scripts/inspect_verifier.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Eyeball what Model V actually outputs — to diagnose a weak ProcessBench score.
|
| 2 |
+
|
| 3 |
+
python scripts/inspect_verifier.py --adapter runs/verifier_v --n 6 --only error
|
| 4 |
+
|
| 5 |
+
Prints, per example: gold first-error index, V's parsed prediction, and the raw
|
| 6 |
+
critique. Reveals whether V is collapsing to -1, mislocating, off-by-one, or
|
| 7 |
+
rambling/truncating.
|
| 8 |
+
"""
|
| 9 |
+
import argparse
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
| 14 |
+
|
| 15 |
+
from mathcompose.common.chat import verifier_messages # noqa: E402
|
| 16 |
+
from mathcompose.data.schema import parse_verifier_output # noqa: E402
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def main() -> int:
|
| 20 |
+
ap = argparse.ArgumentParser()
|
| 21 |
+
ap.add_argument("--base-id", default="Qwen/Qwen2.5-Math-1.5B-Instruct")
|
| 22 |
+
ap.add_argument("--adapter", default=None)
|
| 23 |
+
ap.add_argument("--split", default="gsm8k")
|
| 24 |
+
ap.add_argument("--n", type=int, default=6)
|
| 25 |
+
ap.add_argument("--only", choices=["error", "correct", "both"], default="error")
|
| 26 |
+
ap.add_argument("--max-new-tokens", type=int, default=768)
|
| 27 |
+
args = ap.parse_args()
|
| 28 |
+
|
| 29 |
+
from datasets import load_dataset
|
| 30 |
+
from mathcompose.eval.runners import HFRunner
|
| 31 |
+
|
| 32 |
+
ds = load_dataset("Qwen/ProcessBench", split=args.split).shuffle(seed=0)
|
| 33 |
+
runner = HFRunner(args.base_id, adapter_path=args.adapter)
|
| 34 |
+
|
| 35 |
+
shown = 0
|
| 36 |
+
for row in ds:
|
| 37 |
+
gold = int(row["label"])
|
| 38 |
+
if args.only == "error" and gold == -1:
|
| 39 |
+
continue
|
| 40 |
+
if args.only == "correct" and gold != -1:
|
| 41 |
+
continue
|
| 42 |
+
out = runner.chat(verifier_messages(row["problem"], list(row["steps"])),
|
| 43 |
+
n=1, temperature=0.0, max_new_tokens=args.max_new_tokens)[0]
|
| 44 |
+
pred = parse_verifier_output(out).first_error_index
|
| 45 |
+
flag = "HIT" if pred == gold else "miss"
|
| 46 |
+
print(f"\n{'=' * 70}\n[{flag}] gold={gold} pred={pred} (#steps={len(row['steps'])})")
|
| 47 |
+
print("-" * 70)
|
| 48 |
+
print(out.strip()[:1200])
|
| 49 |
+
shown += 1
|
| 50 |
+
if shown >= args.n:
|
| 51 |
+
break
|
| 52 |
+
return 0
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
raise SystemExit(main())
|
src/mathcompose/data/build_verifier_dataset.py
CHANGED
|
@@ -19,7 +19,11 @@ from ..common.parallel import thread_map
|
|
| 19 |
from ..teachers import get_teacher
|
| 20 |
from ..datagen.prm800k_loader import iter_prm800k
|
| 21 |
from ..datagen.dedup import build_contamination_set, is_contaminated
|
| 22 |
-
from ..datagen.gen_verifier_data import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
def main() -> None:
|
|
@@ -31,7 +35,11 @@ def main() -> None:
|
|
| 31 |
ap.add_argument("--model", default=None, help="teacher model id override")
|
| 32 |
ap.add_argument("--limit", type=int, default=None)
|
| 33 |
ap.add_argument("--val-frac", type=float, default=0.05)
|
| 34 |
-
ap.add_argument("--
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
ap.add_argument("--workers", type=int, default=8, help="concurrent teacher calls")
|
| 36 |
ap.add_argument("--no-dedup", action="store_true")
|
| 37 |
ap.add_argument("--seed", type=int, default=0)
|
|
@@ -51,13 +59,21 @@ def main() -> None:
|
|
| 51 |
if not (banned and is_contaminated(r.problem, banned))
|
| 52 |
]
|
| 53 |
print(f"synthesizing critiques for {len(records)} records "
|
| 54 |
-
f"({args.workers} workers, teacher={args.teacher}) ...")
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
rows = [r for r in thread_map(_one, records, workers=args.workers, desc="verifier") if r]
|
|
|
|
|
|
|
| 61 |
random.shuffle(rows)
|
| 62 |
|
| 63 |
n_val = max(1, int(len(rows) * args.val_frac)) if rows else 0
|
|
|
|
| 19 |
from ..teachers import get_teacher
|
| 20 |
from ..datagen.prm800k_loader import iter_prm800k
|
| 21 |
from ..datagen.dedup import build_contamination_set, is_contaminated
|
| 22 |
+
from ..datagen.gen_verifier_data import (
|
| 23 |
+
synthesize_verifier_completion,
|
| 24 |
+
detect_verifier_completion,
|
| 25 |
+
make_verifier_row,
|
| 26 |
+
)
|
| 27 |
|
| 28 |
|
| 29 |
def main() -> None:
|
|
|
|
| 35 |
ap.add_argument("--model", default=None, help="teacher model id override")
|
| 36 |
ap.add_argument("--limit", type=int, default=None)
|
| 37 |
ap.add_argument("--val-frac", type=float, default=0.05)
|
| 38 |
+
ap.add_argument("--mode", choices=["detect", "rationalize"], default="detect",
|
| 39 |
+
help="detect=genuine (teacher critiques blind, keep if index matches gold); "
|
| 40 |
+
"rationalize=teacher told the gold (teaches style, not skill)")
|
| 41 |
+
ap.add_argument("--temperature", type=float, default=0.6)
|
| 42 |
+
ap.add_argument("--max-attempts", type=int, default=2, help="detect: samples per record")
|
| 43 |
ap.add_argument("--workers", type=int, default=8, help="concurrent teacher calls")
|
| 44 |
ap.add_argument("--no-dedup", action="store_true")
|
| 45 |
ap.add_argument("--seed", type=int, default=0)
|
|
|
|
| 59 |
if not (banned and is_contaminated(r.problem, banned))
|
| 60 |
]
|
| 61 |
print(f"synthesizing critiques for {len(records)} records "
|
| 62 |
+
f"(mode={args.mode}, {args.workers} workers, teacher={args.teacher}) ...")
|
| 63 |
+
|
| 64 |
+
if args.mode == "detect":
|
| 65 |
+
def _one(rec):
|
| 66 |
+
comp = detect_verifier_completion(teacher, rec, temperature=args.temperature,
|
| 67 |
+
max_attempts=args.max_attempts)
|
| 68 |
+
return make_verifier_row(rec, comp) if comp else None
|
| 69 |
+
else:
|
| 70 |
+
def _one(rec):
|
| 71 |
+
comp = synthesize_verifier_completion(teacher, rec, temperature=args.temperature)
|
| 72 |
+
return make_verifier_row(rec, comp)
|
| 73 |
|
| 74 |
rows = [r for r in thread_map(_one, records, workers=args.workers, desc="verifier") if r]
|
| 75 |
+
if args.mode == "detect" and records:
|
| 76 |
+
print(f"detect keep-rate: {len(rows)}/{len(records)} = {len(rows)/len(records):.0%}")
|
| 77 |
random.shuffle(rows)
|
| 78 |
|
| 79 |
n_val = max(1, int(len(rows) * args.val_frac)) if rows else 0
|
src/mathcompose/datagen/gen_verifier_data.py
CHANGED
|
@@ -64,6 +64,30 @@ def synthesize_verifier_completion(
|
|
| 64 |
return f"{critique}\n\n\\boxed{{{rec.first_error_index}}}"
|
| 65 |
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
def make_verifier_row(rec: PRM800KRecord, completion: str) -> dict:
|
| 68 |
"""Conversational prompt-completion row (triggers TRL completion_only_loss)."""
|
| 69 |
prompt_msgs = verifier_messages(rec.problem, rec.steps) # system + user, no gold
|
|
|
|
| 64 |
return f"{critique}\n\n\\boxed{{{rec.first_error_index}}}"
|
| 65 |
|
| 66 |
|
| 67 |
+
def detect_verifier_completion(
|
| 68 |
+
teacher, rec: PRM800KRecord, *, temperature: float = 0.6, max_attempts: int = 2
|
| 69 |
+
) -> Optional[str]:
|
| 70 |
+
"""GENUINE-DETECTION recipe: the teacher critiques WITHOUT seeing the gold, and
|
| 71 |
+
we keep the critique only if its predicted first-error index matches the PRM800K
|
| 72 |
+
gold (rejection sampling). Unlike rationalization, the kept critiques contain real
|
| 73 |
+
error-finding reasoning — the signal V must learn. Returns None if no attempt hit.
|
| 74 |
+
"""
|
| 75 |
+
from ..data.schema import parse_verifier_output
|
| 76 |
+
|
| 77 |
+
messages = [
|
| 78 |
+
{"role": "system", "content": VERIFIER_SYSTEM},
|
| 79 |
+
{"role": "user", "content": build_verifier_prompt(rec.problem, rec.steps)}, # no gold
|
| 80 |
+
]
|
| 81 |
+
for _ in range(max_attempts):
|
| 82 |
+
try:
|
| 83 |
+
out = teacher.complete(messages, temperature=temperature, max_tokens=1536)
|
| 84 |
+
except Exception:
|
| 85 |
+
return None
|
| 86 |
+
if parse_verifier_output(out).first_error_index == rec.first_error_index:
|
| 87 |
+
return out.strip()
|
| 88 |
+
return None
|
| 89 |
+
|
| 90 |
+
|
| 91 |
def make_verifier_row(rec: PRM800KRecord, completion: str) -> dict:
|
| 92 |
"""Conversational prompt-completion row (triggers TRL completion_only_loss)."""
|
| 93 |
prompt_msgs = verifier_messages(rec.problem, rec.steps) # system + user, no gold
|
src/mathcompose/eval/processbench.py
CHANGED
|
@@ -143,11 +143,20 @@ def evaluate_batched(
|
|
| 143 |
runner, items, n=n, batch_size=batch_size, temperature=temperature,
|
| 144 |
max_new_tokens=max_new_tokens, desc=f"ProcessBench/{split}",
|
| 145 |
)
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
return {
|
| 150 |
-
"subsets": {
|
| 151 |
"average_f1": avg_f1,
|
| 152 |
"gpt4o_reference_f1": 61.9,
|
| 153 |
}
|
|
|
|
| 143 |
runner, items, n=n, batch_size=batch_size, temperature=temperature,
|
| 144 |
max_new_tokens=max_new_tokens, desc=f"ProcessBench/{split}",
|
| 145 |
)
|
| 146 |
+
score = score_subset(split, golds, preds)
|
| 147 |
+
sd = score.as_dict()
|
| 148 |
+
tot = len(preds) or 1
|
| 149 |
+
# prediction distribution — reveals mode collapse (e.g. always predicting -1)
|
| 150 |
+
sd["pred_dist"] = {
|
| 151 |
+
"all_correct(-1)": round(sum(1 for p in preds if p == -1) / tot, 3),
|
| 152 |
+
"error_idx(>=0)": round(sum(1 for p in preds if p >= 0) / tot, 3),
|
| 153 |
+
"parse_fail(-2)": round(sum(1 for p in preds if p == -2) / tot, 3),
|
| 154 |
+
}
|
| 155 |
+
per_subset.append((score, sd))
|
| 156 |
+
|
| 157 |
+
avg_f1 = sum(s.f1 for s, _ in per_subset) / len(per_subset) if per_subset else 0.0
|
| 158 |
return {
|
| 159 |
+
"subsets": {sd["subset"]: sd for _, sd in per_subset},
|
| 160 |
"average_f1": avg_f1,
|
| 161 |
"gpt4o_reference_f1": 61.9,
|
| 162 |
}
|
tests/test_datagen.py
CHANGED
|
@@ -2,7 +2,10 @@
|
|
| 2 |
from pathlib import Path
|
| 3 |
|
| 4 |
from mathcompose.datagen.prm800k_loader import iter_prm800k
|
| 5 |
-
from mathcompose.datagen.gen_verifier_data import
|
|
|
|
|
|
|
|
|
|
| 6 |
from mathcompose.datagen.gen_generator_data import Problem, generate_generator_dataset
|
| 7 |
from mathcompose.teachers import get_teacher
|
| 8 |
from mathcompose.eval.parse import first_error_index
|
|
@@ -34,6 +37,21 @@ def test_verifier_datagen_labels_authoritative_and_no_leak():
|
|
| 34 |
assert "FIRST error occurs in paragraph" not in user
|
| 35 |
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
def test_generator_datagen_answer_filter():
|
| 38 |
def scripted(msgs):
|
| 39 |
q = msgs[-1]["content"]
|
|
|
|
| 2 |
from pathlib import Path
|
| 3 |
|
| 4 |
from mathcompose.datagen.prm800k_loader import iter_prm800k
|
| 5 |
+
from mathcompose.datagen.gen_verifier_data import (
|
| 6 |
+
generate_verifier_dataset,
|
| 7 |
+
detect_verifier_completion,
|
| 8 |
+
)
|
| 9 |
from mathcompose.datagen.gen_generator_data import Problem, generate_generator_dataset
|
| 10 |
from mathcompose.teachers import get_teacher
|
| 11 |
from mathcompose.eval.parse import first_error_index
|
|
|
|
| 37 |
assert "FIRST error occurs in paragraph" not in user
|
| 38 |
|
| 39 |
|
| 40 |
+
def test_detect_mode_keeps_only_matching_predictions():
|
| 41 |
+
"""Genuine-detection recipe: keep a critique only if the teacher's blind
|
| 42 |
+
prediction matches the PRM800K gold."""
|
| 43 |
+
recs = list(iter_prm800k(FIX))
|
| 44 |
+
err = next(r for r in recs if r.first_error_index == 1) # "What is 2 + 2?" -> gold 1
|
| 45 |
+
|
| 46 |
+
# teacher predicts the correct index -> kept
|
| 47 |
+
good = get_teacher("dummy", fn=lambda m: "Paragraph 1: wrong. Verdict: incorrect\n\n\\boxed{1}")
|
| 48 |
+
assert detect_verifier_completion(good, err, max_attempts=1) is not None
|
| 49 |
+
|
| 50 |
+
# teacher predicts the wrong index -> rejected (None)
|
| 51 |
+
bad = get_teacher("dummy", fn=lambda m: "All good.\n\n\\boxed{-1}")
|
| 52 |
+
assert detect_verifier_completion(bad, err, max_attempts=2) is None
|
| 53 |
+
|
| 54 |
+
|
| 55 |
def test_generator_datagen_answer_filter():
|
| 56 |
def scripted(msgs):
|
| 57 |
q = msgs[-1]["content"]
|