Upload folder using huggingface_hub
Browse files- src/mathcompose/eval/prmbench.py +70 -0
- src/mathcompose/eval/processbench.py +36 -0
- src/mathcompose/eval/run.py +14 -8
- src/mathcompose/eval/runners.py +73 -2
- src/mathcompose/train/colab_train.ipynb +20 -16
- tests/test_eval_plumbing.py +14 -0
src/mathcompose/eval/prmbench.py
CHANGED
|
@@ -83,3 +83,73 @@ def evaluate_prmbench(
|
|
| 83 |
for c, d in sorted(per_cat.items())
|
| 84 |
},
|
| 85 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
for c, d in sorted(per_cat.items())
|
| 84 |
},
|
| 85 |
}
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def evaluate_batched(
|
| 89 |
+
runner,
|
| 90 |
+
dataset_id: str = "hitsmy/PRMBench_Preview",
|
| 91 |
+
split: str = "train",
|
| 92 |
+
limit: Optional[int] = None,
|
| 93 |
+
n: int = 1,
|
| 94 |
+
batch_size: int = 16,
|
| 95 |
+
temperature: float = 0.0,
|
| 96 |
+
max_new_tokens: int = 640,
|
| 97 |
+
error_steps_base: int = 1,
|
| 98 |
+
include_original: bool = True,
|
| 99 |
+
) -> dict:
|
| 100 |
+
"""Fast path: batched generation via an HFRunner."""
|
| 101 |
+
from collections import defaultdict as _dd
|
| 102 |
+
|
| 103 |
+
from datasets import load_dataset
|
| 104 |
+
|
| 105 |
+
from .runners import verify_batch_predictions
|
| 106 |
+
|
| 107 |
+
ds = load_dataset(dataset_id, split=split)
|
| 108 |
+
if limit is not None:
|
| 109 |
+
ds = ds.select(range(min(limit, len(ds))))
|
| 110 |
+
|
| 111 |
+
mod_items, mod_gold, mod_cat = [], [], []
|
| 112 |
+
orig_items = []
|
| 113 |
+
for row in ds:
|
| 114 |
+
steps_mod = list(row.get("modified_process") or [])
|
| 115 |
+
gold0 = {int(e) - error_steps_base for e in (row.get("error_steps") or [])}
|
| 116 |
+
if steps_mod and gold0:
|
| 117 |
+
mod_items.append((row.get("modified_question") or row.get("question") or "", steps_mod))
|
| 118 |
+
mod_gold.append(gold0)
|
| 119 |
+
mod_cat.append(row.get("classification") or "unknown")
|
| 120 |
+
if include_original:
|
| 121 |
+
steps_orig = list(row.get("original_process") or [])
|
| 122 |
+
if steps_orig:
|
| 123 |
+
orig_items.append((row.get("original_question") or row.get("question") or "", steps_orig))
|
| 124 |
+
|
| 125 |
+
mod_preds = verify_batch_predictions(runner, mod_items, n=n, batch_size=batch_size,
|
| 126 |
+
temperature=temperature, max_new_tokens=max_new_tokens,
|
| 127 |
+
desc="PRMBench/modified")
|
| 128 |
+
per_cat: dict[str, dict] = _dd(lambda: {"n": 0, "detected": 0})
|
| 129 |
+
detected = localized = 0
|
| 130 |
+
for pred, gold0, cat in zip(mod_preds, mod_gold, mod_cat):
|
| 131 |
+
is_det = pred != -1
|
| 132 |
+
detected += 1 if is_det else 0
|
| 133 |
+
localized += 1 if pred in gold0 else 0
|
| 134 |
+
per_cat[cat]["n"] += 1
|
| 135 |
+
per_cat[cat]["detected"] += 1 if is_det else 0
|
| 136 |
+
|
| 137 |
+
false_pos = None
|
| 138 |
+
if include_original and orig_items:
|
| 139 |
+
orig_preds = verify_batch_predictions(runner, orig_items, n=n, batch_size=batch_size,
|
| 140 |
+
temperature=temperature, max_new_tokens=max_new_tokens,
|
| 141 |
+
desc="PRMBench/original")
|
| 142 |
+
false_pos = sum(1 for p in orig_preds if p != -1) / len(orig_preds)
|
| 143 |
+
|
| 144 |
+
n_mod = len(mod_items)
|
| 145 |
+
return {
|
| 146 |
+
"dataset": dataset_id,
|
| 147 |
+
"n_modified": n_mod,
|
| 148 |
+
"detection": detected / n_mod if n_mod else 0.0,
|
| 149 |
+
"localization": localized / n_mod if n_mod else 0.0,
|
| 150 |
+
"false_positive_rate": false_pos,
|
| 151 |
+
"per_category_detection": {
|
| 152 |
+
c: {"n": d["n"], "detection": d["detected"] / d["n"] if d["n"] else 0.0}
|
| 153 |
+
for c, d in sorted(per_cat.items())
|
| 154 |
+
},
|
| 155 |
+
}
|
src/mathcompose/eval/processbench.py
CHANGED
|
@@ -114,3 +114,39 @@ def evaluate(
|
|
| 114 |
"average_f1": avg_f1,
|
| 115 |
"gpt4o_reference_f1": 61.9,
|
| 116 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
"average_f1": avg_f1,
|
| 115 |
"gpt4o_reference_f1": 61.9,
|
| 116 |
}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def evaluate_batched(
|
| 120 |
+
runner,
|
| 121 |
+
splits: Sequence[str] = DEFAULT_SPLITS,
|
| 122 |
+
limit: Optional[int] = None,
|
| 123 |
+
n: int = 1,
|
| 124 |
+
batch_size: int = 16,
|
| 125 |
+
temperature: float = 0.0,
|
| 126 |
+
max_new_tokens: int = 640,
|
| 127 |
+
dataset_id: str = "Qwen/ProcessBench",
|
| 128 |
+
) -> dict:
|
| 129 |
+
"""Fast path: batched generation via an HFRunner (vs the per-example verify_fn)."""
|
| 130 |
+
from datasets import load_dataset
|
| 131 |
+
|
| 132 |
+
from .runners import verify_batch_predictions
|
| 133 |
+
|
| 134 |
+
per_subset: list[SubsetScore] = []
|
| 135 |
+
for split in splits:
|
| 136 |
+
ds = load_dataset(dataset_id, split=split)
|
| 137 |
+
if limit is not None:
|
| 138 |
+
ds = ds.select(range(min(limit, len(ds))))
|
| 139 |
+
items = [(row["problem"], list(row["steps"])) for row in ds]
|
| 140 |
+
golds = [int(row["label"]) for row in ds]
|
| 141 |
+
preds = verify_batch_predictions(
|
| 142 |
+
runner, items, n=n, batch_size=batch_size, temperature=temperature,
|
| 143 |
+
max_new_tokens=max_new_tokens, desc=f"ProcessBench/{split}",
|
| 144 |
+
)
|
| 145 |
+
per_subset.append(score_subset(split, golds, preds))
|
| 146 |
+
|
| 147 |
+
avg_f1 = sum(s.f1 for s in per_subset) / len(per_subset) if per_subset else 0.0
|
| 148 |
+
return {
|
| 149 |
+
"subsets": {s.subset: s.as_dict() for s in per_subset},
|
| 150 |
+
"average_f1": avg_f1,
|
| 151 |
+
"gpt4o_reference_f1": 61.9,
|
| 152 |
+
}
|
src/mathcompose/eval/run.py
CHANGED
|
@@ -34,20 +34,22 @@ def _runner(base_id, adapter):
|
|
| 34 |
|
| 35 |
|
| 36 |
def cmd_processbench(a):
|
| 37 |
-
from .processbench import
|
| 38 |
-
from .runners import make_verify_fn
|
| 39 |
r = _runner(a.base_id, a.adapter)
|
| 40 |
-
|
| 41 |
-
res =
|
|
|
|
|
|
|
| 42 |
_save("processbench", a.tag, res)
|
| 43 |
|
| 44 |
|
| 45 |
def cmd_prmbench(a):
|
| 46 |
-
from .prmbench import
|
| 47 |
-
from .runners import make_verify_fn
|
| 48 |
r = _runner(a.base_id, a.adapter)
|
| 49 |
-
|
| 50 |
-
res =
|
|
|
|
|
|
|
| 51 |
_save("prmbench", a.tag, res)
|
| 52 |
|
| 53 |
|
|
@@ -105,6 +107,8 @@ def main() -> None:
|
|
| 105 |
p.add_argument("--tag", default="tuned")
|
| 106 |
p.add_argument("--maj-k", type=int, default=1)
|
| 107 |
p.add_argument("--limit", type=int, default=None)
|
|
|
|
|
|
|
| 108 |
p.add_argument("--splits", nargs="+", default=["gsm8k", "math", "olympiadbench", "omnimath"])
|
| 109 |
p.set_defaults(func=cmd_processbench)
|
| 110 |
|
|
@@ -113,6 +117,8 @@ def main() -> None:
|
|
| 113 |
p.add_argument("--tag", default="tuned")
|
| 114 |
p.add_argument("--maj-k", type=int, default=1)
|
| 115 |
p.add_argument("--limit", type=int, default=None)
|
|
|
|
|
|
|
| 116 |
p.set_defaults(func=cmd_prmbench)
|
| 117 |
|
| 118 |
p = sub.add_parser("math")
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
def cmd_processbench(a):
|
| 37 |
+
from .processbench import evaluate_batched
|
|
|
|
| 38 |
r = _runner(a.base_id, a.adapter)
|
| 39 |
+
bs = max(1, a.batch_size // max(1, a.maj_k)) # cap concurrent seqs = prompts * k
|
| 40 |
+
res = evaluate_batched(r, splits=tuple(a.splits), limit=a.limit, n=a.maj_k,
|
| 41 |
+
batch_size=bs, temperature=(0.6 if a.maj_k > 1 else 0.0),
|
| 42 |
+
max_new_tokens=a.max_new_tokens)
|
| 43 |
_save("processbench", a.tag, res)
|
| 44 |
|
| 45 |
|
| 46 |
def cmd_prmbench(a):
|
| 47 |
+
from .prmbench import evaluate_batched
|
|
|
|
| 48 |
r = _runner(a.base_id, a.adapter)
|
| 49 |
+
bs = max(1, a.batch_size // max(1, a.maj_k))
|
| 50 |
+
res = evaluate_batched(r, limit=a.limit, n=a.maj_k, batch_size=bs,
|
| 51 |
+
temperature=(0.6 if a.maj_k > 1 else 0.0),
|
| 52 |
+
max_new_tokens=a.max_new_tokens)
|
| 53 |
_save("prmbench", a.tag, res)
|
| 54 |
|
| 55 |
|
|
|
|
| 107 |
p.add_argument("--tag", default="tuned")
|
| 108 |
p.add_argument("--maj-k", type=int, default=1)
|
| 109 |
p.add_argument("--limit", type=int, default=None)
|
| 110 |
+
p.add_argument("--batch-size", type=int, default=16, help="max concurrent sequences (÷ maj-k)")
|
| 111 |
+
p.add_argument("--max-new-tokens", type=int, default=640)
|
| 112 |
p.add_argument("--splits", nargs="+", default=["gsm8k", "math", "olympiadbench", "omnimath"])
|
| 113 |
p.set_defaults(func=cmd_processbench)
|
| 114 |
|
|
|
|
| 117 |
p.add_argument("--tag", default="tuned")
|
| 118 |
p.add_argument("--maj-k", type=int, default=1)
|
| 119 |
p.add_argument("--limit", type=int, default=None)
|
| 120 |
+
p.add_argument("--batch-size", type=int, default=16, help="max concurrent sequences (÷ maj-k)")
|
| 121 |
+
p.add_argument("--max-new-tokens", type=int, default=640)
|
| 122 |
p.set_defaults(func=cmd_prmbench)
|
| 123 |
|
| 124 |
p = sub.add_parser("math")
|
src/mathcompose/eval/runners.py
CHANGED
|
@@ -79,7 +79,7 @@ class HFRunner:
|
|
| 79 |
input_ids = input_ids.to(self.device)
|
| 80 |
attn = attn.to(self.device) if attn is not None else None
|
| 81 |
|
| 82 |
-
do_sample = bool(temperature and temperature > 0)
|
| 83 |
gen_kwargs = dict(
|
| 84 |
max_new_tokens=max_new_tokens,
|
| 85 |
do_sample=do_sample,
|
|
@@ -88,7 +88,8 @@ class HFRunner:
|
|
| 88 |
repetition_penalty=repetition_penalty,
|
| 89 |
)
|
| 90 |
if do_sample:
|
| 91 |
-
gen_kwargs.update(temperature=temperature
|
|
|
|
| 92 |
|
| 93 |
with torch.no_grad():
|
| 94 |
out = self.model.generate(input_ids, attention_mask=attn, **gen_kwargs)
|
|
@@ -96,6 +97,52 @@ class HFRunner:
|
|
| 96 |
return self.tokenizer.batch_decode(gen, skip_special_tokens=True)
|
| 97 |
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def make_verify_fn(runner: HFRunner, n: int = 1, temperature: float = 0.0,
|
| 100 |
max_new_tokens: int = 1024) -> Callable[[str, list[str]], list[str]]:
|
| 101 |
def verify_fn(problem: str, steps: list[str]) -> list[str]:
|
|
@@ -110,3 +157,27 @@ def make_solve_fn(runner: HFRunner, n: int = 1, temperature: float = 0.0,
|
|
| 110 |
return runner.chat(generator_messages(problem), n=n,
|
| 111 |
temperature=temperature, max_new_tokens=max_new_tokens)
|
| 112 |
return solve_fn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
input_ids = input_ids.to(self.device)
|
| 80 |
attn = attn.to(self.device) if attn is not None else None
|
| 81 |
|
| 82 |
+
do_sample = bool(temperature and temperature > 0) or n > 1 # n>1 needs sampling
|
| 83 |
gen_kwargs = dict(
|
| 84 |
max_new_tokens=max_new_tokens,
|
| 85 |
do_sample=do_sample,
|
|
|
|
| 88 |
repetition_penalty=repetition_penalty,
|
| 89 |
)
|
| 90 |
if do_sample:
|
| 91 |
+
gen_kwargs.update(temperature=(temperature if temperature and temperature > 0 else 0.7),
|
| 92 |
+
top_p=0.95)
|
| 93 |
|
| 94 |
with torch.no_grad():
|
| 95 |
out = self.model.generate(input_ids, attention_mask=attn, **gen_kwargs)
|
|
|
|
| 97 |
return self.tokenizer.batch_decode(gen, skip_special_tokens=True)
|
| 98 |
|
| 99 |
|
| 100 |
+
def chat_many(
|
| 101 |
+
self,
|
| 102 |
+
messages_list: list[list[dict]],
|
| 103 |
+
n: int = 1,
|
| 104 |
+
temperature: float = 0.0,
|
| 105 |
+
max_new_tokens: int = 640,
|
| 106 |
+
repetition_penalty: float = 1.1,
|
| 107 |
+
) -> list[list[str]]:
|
| 108 |
+
"""Batched generation across many prompts (left-padded). Returns, per input
|
| 109 |
+
prompt, a list of n decoded completions. Far faster than looping chat()."""
|
| 110 |
+
import torch
|
| 111 |
+
|
| 112 |
+
tok = self.tokenizer
|
| 113 |
+
old_side = tok.padding_side
|
| 114 |
+
tok.padding_side = "left" # required for correct batched generation
|
| 115 |
+
try:
|
| 116 |
+
rendered = [
|
| 117 |
+
tok.apply_chat_template(m, add_generation_prompt=True, tokenize=False)
|
| 118 |
+
for m in messages_list
|
| 119 |
+
]
|
| 120 |
+
enc = tok(
|
| 121 |
+
rendered, return_tensors="pt", padding=True, truncation=True,
|
| 122 |
+
max_length=max(1, self.max_context - max_new_tokens),
|
| 123 |
+
add_special_tokens=False,
|
| 124 |
+
)
|
| 125 |
+
finally:
|
| 126 |
+
tok.padding_side = old_side
|
| 127 |
+
|
| 128 |
+
input_ids = enc["input_ids"].to(self.device)
|
| 129 |
+
attn = enc["attention_mask"].to(self.device)
|
| 130 |
+
do_sample = bool(temperature and temperature > 0) or n > 1 # n>1 needs sampling
|
| 131 |
+
gen_kwargs = dict(
|
| 132 |
+
max_new_tokens=max_new_tokens, do_sample=do_sample, num_return_sequences=n,
|
| 133 |
+
pad_token_id=tok.pad_token_id, repetition_penalty=repetition_penalty,
|
| 134 |
+
)
|
| 135 |
+
if do_sample:
|
| 136 |
+
gen_kwargs.update(temperature=(temperature if temperature and temperature > 0 else 0.7),
|
| 137 |
+
top_p=0.95)
|
| 138 |
+
with torch.no_grad():
|
| 139 |
+
out = self.model.generate(input_ids, attention_mask=attn, **gen_kwargs)
|
| 140 |
+
gen = out[:, input_ids.shape[1]:]
|
| 141 |
+
texts = tok.batch_decode(gen, skip_special_tokens=True)
|
| 142 |
+
# generate returns batch*n rows, grouped by input then sample
|
| 143 |
+
return [texts[i * n:(i + 1) * n] for i in range(len(messages_list))]
|
| 144 |
+
|
| 145 |
+
|
| 146 |
def make_verify_fn(runner: HFRunner, n: int = 1, temperature: float = 0.0,
|
| 147 |
max_new_tokens: int = 1024) -> Callable[[str, list[str]], list[str]]:
|
| 148 |
def verify_fn(problem: str, steps: list[str]) -> list[str]:
|
|
|
|
| 157 |
return runner.chat(generator_messages(problem), n=n,
|
| 158 |
temperature=temperature, max_new_tokens=max_new_tokens)
|
| 159 |
return solve_fn
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def verify_batch_predictions(runner: HFRunner, items, n: int = 1, batch_size: int = 16,
|
| 163 |
+
temperature: float = 0.0, max_new_tokens: int = 640,
|
| 164 |
+
desc: str = "verify") -> list[int]:
|
| 165 |
+
"""items: list of (problem, steps). Returns the Maj@n first-error index per item,
|
| 166 |
+
using batched generation. This is the fast path used by ProcessBench/PRMBench."""
|
| 167 |
+
from .parse import majority_index
|
| 168 |
+
|
| 169 |
+
try:
|
| 170 |
+
from tqdm.auto import tqdm
|
| 171 |
+
except Exception: # pragma: no cover
|
| 172 |
+
def tqdm(x, **k):
|
| 173 |
+
return x
|
| 174 |
+
|
| 175 |
+
preds: list[int] = []
|
| 176 |
+
chunks = list(range(0, len(items), batch_size))
|
| 177 |
+
for i in tqdm(chunks, desc=desc):
|
| 178 |
+
chunk = items[i:i + batch_size]
|
| 179 |
+
msgs = [verifier_messages(p, s) for p, s in chunk]
|
| 180 |
+
samples = runner.chat_many(msgs, n=n, temperature=temperature,
|
| 181 |
+
max_new_tokens=max_new_tokens)
|
| 182 |
+
preds.extend(majority_index(s) for s in samples)
|
| 183 |
+
return preds
|
src/mathcompose/train/colab_train.ipynb
CHANGED
|
@@ -4,10 +4,10 @@
|
|
| 4 |
"cell_type": "markdown",
|
| 5 |
"metadata": {},
|
| 6 |
"source": [
|
| 7 |
-
"# mathcompose \u2014 QLoRA training on Colab
|
| 8 |
"\n",
|
| 9 |
-
"Pulls
|
| 10 |
-
"
|
| 11 |
]
|
| 12 |
},
|
| 13 |
{
|
|
@@ -16,7 +16,7 @@
|
|
| 16 |
"execution_count": null,
|
| 17 |
"outputs": [],
|
| 18 |
"source": [
|
| 19 |
-
"# 1. GPU deps (
|
| 20 |
"!pip -q install \"transformers>=5.13,<6\" \"trl>=1.7,<2\" \"peft>=0.19,<0.20\" \\\n",
|
| 21 |
" \"accelerate>=1.14\" \"datasets>=5.0\" \"huggingface_hub>=1.22\" sympy bitsandbytes"
|
| 22 |
]
|
|
@@ -27,16 +27,16 @@
|
|
| 27 |
"execution_count": null,
|
| 28 |
"outputs": [],
|
| 29 |
"source": [
|
| 30 |
-
"# 2. Token -> pull
|
| 31 |
"import os\n",
|
| 32 |
"from huggingface_hub import HfApi, snapshot_download, login\n",
|
| 33 |
-
"os.environ[\"HF_TOKEN\"] = \"hf_xxx\" # paste, or Colab Secrets
|
| 34 |
"login(os.environ[\"HF_TOKEN\"])\n",
|
| 35 |
"USER = HfApi().whoami()[\"name\"]\n",
|
| 36 |
"proj = snapshot_download(f\"{USER}/mathcompose\", repo_type=\"model\")\n",
|
| 37 |
"%cd {proj}\n",
|
| 38 |
"!pip -q install -e .\n",
|
| 39 |
-
"print(\"
|
| 40 |
]
|
| 41 |
},
|
| 42 |
{
|
|
@@ -53,7 +53,7 @@
|
|
| 53 |
"cell_type": "markdown",
|
| 54 |
"metadata": {},
|
| 55 |
"source": [
|
| 56 |
-
"## 4. Train Model V (
|
| 57 |
]
|
| 58 |
},
|
| 59 |
{
|
|
@@ -69,8 +69,9 @@
|
|
| 69 |
"cell_type": "markdown",
|
| 70 |
"metadata": {},
|
| 71 |
"source": [
|
| 72 |
-
"## 5. Eval V on ProcessBench \u2014 base vs tuned (
|
| 73 |
-
"
|
|
|
|
| 74 |
]
|
| 75 |
},
|
| 76 |
{
|
|
@@ -79,9 +80,13 @@
|
|
| 79 |
"execution_count": null,
|
| 80 |
"outputs": [],
|
| 81 |
"source": [
|
| 82 |
-
"
|
| 83 |
-
"!python -m mathcompose.eval.run processbench --
|
| 84 |
-
"!python -m mathcompose.eval.run
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
"!python -m mathcompose.eval.run report"
|
| 86 |
]
|
| 87 |
},
|
|
@@ -109,9 +114,8 @@
|
|
| 109 |
"cell_type": "markdown",
|
| 110 |
"metadata": {},
|
| 111 |
"source": [
|
| 112 |
-
"## 7. (Later) Model G +
|
| 113 |
-
"Build G's dataset (teacher=claude-opus-4-8), train, then
|
| 114 |
-
"headline generator-verifier-gap number."
|
| 115 |
]
|
| 116 |
},
|
| 117 |
{
|
|
|
|
| 4 |
"cell_type": "markdown",
|
| 5 |
"metadata": {},
|
| 6 |
"source": [
|
| 7 |
+
"# mathcompose \u2014 QLoRA training on Colab\n",
|
| 8 |
"\n",
|
| 9 |
+
"Pulls code+data from HF with just your token. **Runtime \u2192 GPU** before running.\n",
|
| 10 |
+
"Training auto-tunes to the detected GPU (`--gpu-preset auto`)."
|
| 11 |
]
|
| 12 |
},
|
| 13 |
{
|
|
|
|
| 16 |
"execution_count": null,
|
| 17 |
"outputs": [],
|
| 18 |
"source": [
|
| 19 |
+
"# 1. GPU deps (+ 4-bit kernels)\n",
|
| 20 |
"!pip -q install \"transformers>=5.13,<6\" \"trl>=1.7,<2\" \"peft>=0.19,<0.20\" \\\n",
|
| 21 |
" \"accelerate>=1.14\" \"datasets>=5.0\" \"huggingface_hub>=1.22\" sympy bitsandbytes"
|
| 22 |
]
|
|
|
|
| 27 |
"execution_count": null,
|
| 28 |
"outputs": [],
|
| 29 |
"source": [
|
| 30 |
+
"# 2. Token -> pull code+data snapshot -> install\n",
|
| 31 |
"import os\n",
|
| 32 |
"from huggingface_hub import HfApi, snapshot_download, login\n",
|
| 33 |
+
"os.environ[\"HF_TOKEN\"] = \"hf_xxx\" # paste, or Colab Secrets\n",
|
| 34 |
"login(os.environ[\"HF_TOKEN\"])\n",
|
| 35 |
"USER = HfApi().whoami()[\"name\"]\n",
|
| 36 |
"proj = snapshot_download(f\"{USER}/mathcompose\", repo_type=\"model\")\n",
|
| 37 |
"%cd {proj}\n",
|
| 38 |
"!pip -q install -e .\n",
|
| 39 |
+
"print(\"data:\", os.listdir(\"data/verifier\"))"
|
| 40 |
]
|
| 41 |
},
|
| 42 |
{
|
|
|
|
| 53 |
"cell_type": "markdown",
|
| 54 |
"metadata": {},
|
| 55 |
"source": [
|
| 56 |
+
"## 4. Train Model V (auto-tunes batch/seq/precision to the GPU)"
|
| 57 |
]
|
| 58 |
},
|
| 59 |
{
|
|
|
|
| 69 |
"cell_type": "markdown",
|
| 70 |
"metadata": {},
|
| 71 |
"source": [
|
| 72 |
+
"## 5. Eval V on ProcessBench \u2014 base vs tuned (now batched \u2192 fast)\n",
|
| 73 |
+
"Fast first read below (1 split, small limit, Maj@4). For the headline number,\n",
|
| 74 |
+
"drop `--splits`/raise `--limit` and use `--maj-k 8`."
|
| 75 |
]
|
| 76 |
},
|
| 77 |
{
|
|
|
|
| 80 |
"execution_count": null,
|
| 81 |
"outputs": [],
|
| 82 |
"source": [
|
| 83 |
+
"# fast sanity read (~a few min on A100):\n",
|
| 84 |
+
"!python -m mathcompose.eval.run processbench --tag base --maj-k 1 --limit 100 --splits gsm8k\n",
|
| 85 |
+
"!python -m mathcompose.eval.run processbench --adapter runs/verifier_v --tag tuned --maj-k 4 --limit 100 --splits gsm8k\n",
|
| 86 |
+
"# full run (all 4 splits, Maj@8) \u2014 uncomment when the fast read looks right:\n",
|
| 87 |
+
"# !python -m mathcompose.eval.run processbench --tag base --maj-k 1\n",
|
| 88 |
+
"# !python -m mathcompose.eval.run processbench --adapter runs/verifier_v --tag tuned --maj-k 8\n",
|
| 89 |
+
"!python -m mathcompose.eval.run prmbench --adapter runs/verifier_v --tag tuned --maj-k 1 --limit 200\n",
|
| 90 |
"!python -m mathcompose.eval.run report"
|
| 91 |
]
|
| 92 |
},
|
|
|
|
| 114 |
"cell_type": "markdown",
|
| 115 |
"metadata": {},
|
| 116 |
"source": [
|
| 117 |
+
"## 7. (Later) Model G + composition\n",
|
| 118 |
+
"Build G's dataset (teacher=claude-opus-4-8), train, then V-reranks-G."
|
|
|
|
| 119 |
]
|
| 120 |
},
|
| 121 |
{
|
tests/test_eval_plumbing.py
CHANGED
|
@@ -31,3 +31,17 @@ def test_eval_pipeline_runs():
|
|
| 31 |
s = score_subset("smoke", golds, preds) # tiny model -> garbage preds, but scoring runs
|
| 32 |
assert 0.0 <= s.f1 <= 1.0
|
| 33 |
assert s.n_error + s.n_correct == 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
s = score_subset("smoke", golds, preds) # tiny model -> garbage preds, but scoring runs
|
| 32 |
assert 0.0 <= s.f1 <= 1.0
|
| 33 |
assert s.n_error + s.n_correct == 3
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@pytest.mark.slow
|
| 37 |
+
@pytest.mark.network
|
| 38 |
+
def test_batched_processbench_eval_runs():
|
| 39 |
+
"""The fast batched path: load -> chat_many -> parse -> score over a tiny slice."""
|
| 40 |
+
pytest.importorskip("torch")
|
| 41 |
+
from mathcompose.eval.runners import HFRunner
|
| 42 |
+
from mathcompose.eval.processbench import evaluate_batched
|
| 43 |
+
|
| 44 |
+
runner = HFRunner(TINY, max_context=512)
|
| 45 |
+
res = evaluate_batched(runner, splits=["gsm8k"], limit=4, n=1, batch_size=2, max_new_tokens=6)
|
| 46 |
+
assert 0.0 <= res["average_f1"] <= 1.0
|
| 47 |
+
assert "gsm8k" in res["subsets"]
|