42e commited on
Commit
2291d9e
·
verified ·
1 Parent(s): 3fec9e2

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: 32 # alpha = 2r
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 synthesize_verifier_completion, make_verifier_row
 
 
 
 
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("--temperature", type=float, default=0.4)
 
 
 
 
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
- def _one(rec):
57
- completion = synthesize_verifier_completion(teacher, rec, temperature=args.temperature)
58
- return make_verifier_row(rec, completion)
 
 
 
 
 
 
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
- per_subset.append(score_subset(split, golds, preds))
147
-
148
- avg_f1 = sum(s.f1 for s in per_subset) / len(per_subset) if per_subset else 0.0
 
 
 
 
 
 
 
 
 
149
  return {
150
- "subsets": {s.subset: s.as_dict() for s in per_subset},
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 generate_verifier_dataset
 
 
 
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"]