42e commited on
Commit
1c0986b
·
verified ·
1 Parent(s): 8b1159d

Upload folder using huggingface_hub

Browse files
configs/verifier_v_7b.yaml CHANGED
@@ -12,22 +12,28 @@ hub_model_id: null # set via --hub-model-id, e.g. "42e/mathcompose-ve
12
  model:
13
  base_id: "Qwen/Qwen2.5-Math-7B-Instruct"
14
  max_context: 4096
 
 
 
15
 
16
  quant:
17
- load_in_4bit: false # bf16 LoRA on the 80GB A100: faster than 4-bit (no dequant),
18
- # and actually uses the VRAM (14GB weights vs ~4.5GB in 4-bit)
19
 
20
  data:
21
  train_path: "data/verifier/train.jsonl" # v2 genuine-detection data
22
  val_path: "data/verifier/val.jsonl"
23
 
24
  train:
25
- max_length: 2560 # measured: V data maxes at 2334 tokens (p99 1715) — 4096 was wasteful
26
- per_device_train_batch_size: 16 # up from 4: ~4x fewer forward passes -> much faster
27
- gradient_accumulation_steps: 1 # eff batch 16
28
- gradient_checkpointing: true # MUST stay on for 7B (a bug had disabled it for bf16)
29
- # ~30-40GB VRAM at these settings. To use more / go faster: raise batch to 24/32.
30
- # NOTE: training has no auto-OOM-recovery — if it OOMs, lower the batch.
 
 
 
 
31
 
32
  eval:
33
  processbench_splits: ["gsm8k", "math", "olympiadbench", "omnimath"]
 
12
  model:
13
  base_id: "Qwen/Qwen2.5-Math-7B-Instruct"
14
  max_context: 4096
15
+ # For the FAST path (packing), install flash-attn and set:
16
+ # attn_implementation: flash_attention_2
17
+ attn_implementation: null
18
 
19
  quant:
20
+ load_in_4bit: false # bf16 LoRA on the 80GB A100 (no dequant; uses the VRAM)
 
21
 
22
  data:
23
  train_path: "data/verifier/train.jsonl" # v2 genuine-detection data
24
  val_path: "data/verifier/val.jsonl"
25
 
26
  train:
27
+ max_length: 2560 # measured: V data maxes at 2334 tokens (p99 1715)
28
+ per_device_train_batch_size: 2 # no-checkpoint -> a long batch caps the size; 2 is safe (~47GB)
29
+ gradient_accumulation_steps: 8 # eff batch 16
30
+ gradient_checkpointing: false # 80GB + short data -> skip the recompute tax (was the >3hr cause)
31
+ optim: adamw_torch_fused
32
+ packing: false
33
+ # ~1.5 hr at these settings. FASTEST (~30-45 min) = packing, which needs FlashAttention-2:
34
+ # pip install flash-attn --no-build-isolation
35
+ # then set model.attn_implementation: flash_attention_2 AND train.packing: true.
36
+ # To push batch without packing: try --batch-size 3 (may OOM on a long batch) or --precision 4bit.
37
 
38
  eval:
39
  processbench_splits: ["gsm8k", "math", "olympiadbench", "omnimath"]
src/mathcompose/eval/parse.py CHANGED
@@ -32,7 +32,10 @@ def majority_index(samples: list[str]) -> int:
32
  counts = Counter(idxs)
33
  top = max(counts.values())
34
  winners = [i for i, c in counts.items() if c == top]
35
- return min(winners)
 
 
 
36
 
37
 
38
  def p_correct_from_samples(samples: list[str]) -> float:
 
32
  counts = Counter(idxs)
33
  top = max(counts.values())
34
  winners = [i for i, c in counts.items() if c == top]
35
+ # On a tie, surface the earliest actual error rather than defaulting to -1
36
+ # (only report all-correct when -1 is the *sole* top vote).
37
+ non_neg = [w for w in winners if w >= 0]
38
+ return min(non_neg) if non_neg else -1
39
 
40
 
41
  def p_correct_from_samples(samples: list[str]) -> float:
src/mathcompose/eval/processbench.py CHANGED
@@ -33,6 +33,13 @@ def harmonic_mean(a: float, b: float) -> float:
33
  return 2 * a * b / (a + b)
34
 
35
 
 
 
 
 
 
 
 
36
  @dataclass
37
  class SubsetScore:
38
  subset: str
@@ -108,10 +115,10 @@ def evaluate(
108
  golds.append(int(row["label"]))
109
  per_subset.append(score_subset(split, golds, preds))
110
 
111
- avg_f1 = sum(s.f1 for s in per_subset) / len(per_subset) if per_subset else 0.0
112
  return {
113
- "subsets": {s.subset: s.as_dict() for s in per_subset},
114
- "average_f1": avg_f1,
115
  "gpt4o_reference_f1": 61.9,
116
  }
117
 
@@ -157,9 +164,10 @@ def evaluate_codeexec(
157
  "error_idx(>=0)": round(sum(1 for p in preds if p >= 0) / tot, 3),
158
  "parse_fail(-2)": round(sum(1 for p in preds if p == -2) / tot, 3),
159
  }
 
160
  per_subset.append((score, sd))
161
 
162
- avg_f1 = sum(s.f1 for s, _ in per_subset) / len(per_subset) if per_subset else 0.0
163
  return {"subsets": {sd["subset"]: sd for _, sd in per_subset},
164
  "average_f1": avg_f1, "gpt4o_reference_f1": 61.9, "mode": "code-exec"}
165
 
@@ -200,9 +208,10 @@ def evaluate_batched(
200
  "error_idx(>=0)": round(sum(1 for p in preds if p >= 0) / tot, 3),
201
  "parse_fail(-2)": round(sum(1 for p in preds if p == -2) / tot, 3),
202
  }
 
203
  per_subset.append((score, sd))
204
 
205
- avg_f1 = sum(s.f1 for s, _ in per_subset) / len(per_subset) if per_subset else 0.0
206
  return {
207
  "subsets": {sd["subset"]: sd for _, sd in per_subset},
208
  "average_f1": avg_f1,
 
33
  return 2 * a * b / (a + b)
34
 
35
 
36
+ def _pct(sd: dict) -> dict:
37
+ """Present accuracies/F1 on the 0-100 scale (to match GPT-4o's 61.9)."""
38
+ for k in ("acc_error", "acc_correct", "f1"):
39
+ sd[k] = round(sd[k] * 100, 2)
40
+ return sd
41
+
42
+
43
  @dataclass
44
  class SubsetScore:
45
  subset: str
 
115
  golds.append(int(row["label"]))
116
  per_subset.append(score_subset(split, golds, preds))
117
 
118
+ avg_f1 = 100 * sum(s.f1 for s in per_subset) / len(per_subset) if per_subset else 0.0
119
  return {
120
+ "subsets": {s.subset: _pct(s.as_dict()) for s in per_subset},
121
+ "average_f1": round(avg_f1, 2),
122
  "gpt4o_reference_f1": 61.9,
123
  }
124
 
 
164
  "error_idx(>=0)": round(sum(1 for p in preds if p >= 0) / tot, 3),
165
  "parse_fail(-2)": round(sum(1 for p in preds if p == -2) / tot, 3),
166
  }
167
+ _pct(sd)
168
  per_subset.append((score, sd))
169
 
170
+ avg_f1 = round(100 * sum(s.f1 for s, _ in per_subset) / len(per_subset), 2) if per_subset else 0.0
171
  return {"subsets": {sd["subset"]: sd for _, sd in per_subset},
172
  "average_f1": avg_f1, "gpt4o_reference_f1": 61.9, "mode": "code-exec"}
173
 
 
208
  "error_idx(>=0)": round(sum(1 for p in preds if p >= 0) / tot, 3),
209
  "parse_fail(-2)": round(sum(1 for p in preds if p == -2) / tot, 3),
210
  }
211
+ _pct(sd)
212
  per_subset.append((score, sd))
213
 
214
+ avg_f1 = round(100 * sum(s.f1 for s, _ in per_subset) / len(per_subset), 2) if per_subset else 0.0
215
  return {
216
  "subsets": {sd["subset"]: sd for _, sd in per_subset},
217
  "average_f1": avg_f1,
src/mathcompose/eval/runners.py CHANGED
@@ -50,6 +50,10 @@ class HFRunner:
50
  from peft import PeftModel
51
 
52
  model = PeftModel.from_pretrained(model, adapter_path)
 
 
 
 
53
  self.model = model.to(self.device).eval()
54
 
55
  def chat(
@@ -58,7 +62,7 @@ class HFRunner:
58
  n: int = 1,
59
  temperature: float = 0.0,
60
  max_new_tokens: int = 1024,
61
- repetition_penalty: float = 1.1,
62
  ) -> list[str]:
63
  """Return n decoded completions (generated text only)."""
64
  import torch
@@ -102,16 +106,17 @@ class HFRunner:
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)
@@ -123,7 +128,7 @@ class HFRunner:
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)
@@ -179,7 +184,7 @@ def auto_concurrent_seqs() -> int:
179
 
180
 
181
  def verify_batch_predictions(runner: HFRunner, items, n: int = 1, batch_size: int = 16,
182
- temperature: float = 0.0, max_new_tokens: int = 640,
183
  desc: str = "verify") -> list[int]:
184
  """items: list of (problem, steps). Returns the Maj@n first-error index per item,
185
  using batched generation. This is the fast path used by ProcessBench/PRMBench."""
 
50
  from peft import PeftModel
51
 
52
  model = PeftModel.from_pretrained(model, adapter_path)
53
+ try:
54
+ model = model.merge_and_unload() # fold LoRA into base -> faster forward
55
+ except Exception:
56
+ pass # can't merge a quantized base; keep the adapter attached
57
  self.model = model.to(self.device).eval()
58
 
59
  def chat(
 
62
  n: int = 1,
63
  temperature: float = 0.0,
64
  max_new_tokens: int = 1024,
65
+ repetition_penalty: float = 1.0, # 1.1 fights V's intentionally repetitive verdicts
66
  ) -> list[str]:
67
  """Return n decoded completions (generated text only)."""
68
  import torch
 
106
  messages_list: list[list[dict]],
107
  n: int = 1,
108
  temperature: float = 0.0,
109
+ max_new_tokens: int = 1024,
110
+ repetition_penalty: float = 1.0,
111
  ) -> list[list[str]]:
112
  """Batched generation across many prompts (left-padded). Returns, per input
113
  prompt, a list of n decoded completions. Far faster than looping chat()."""
114
  import torch
115
 
116
  tok = self.tokenizer
117
+ old_side, old_trunc = tok.padding_side, tok.truncation_side
118
+ tok.padding_side = "left" # required for correct batched generation
119
+ tok.truncation_side = "left" # keep the TAIL (the \boxed instruction + last steps)
120
  try:
121
  rendered = [
122
  tok.apply_chat_template(m, add_generation_prompt=True, tokenize=False)
 
128
  add_special_tokens=False,
129
  )
130
  finally:
131
+ tok.padding_side, tok.truncation_side = old_side, old_trunc
132
 
133
  input_ids = enc["input_ids"].to(self.device)
134
  attn = enc["attention_mask"].to(self.device)
 
184
 
185
 
186
  def verify_batch_predictions(runner: HFRunner, items, n: int = 1, batch_size: int = 16,
187
+ temperature: float = 0.0, max_new_tokens: int = 1024,
188
  desc: str = "verify") -> list[int]:
189
  """items: list of (problem, steps). Returns the Maj@n first-error index per item,
190
  using batched generation. This is the fast path used by ProcessBench/PRMBench."""
src/mathcompose/train/train.py CHANGED
@@ -80,7 +80,7 @@ def build_and_train(args) -> str:
80
  import torch
81
 
82
  if torch.cuda.is_available():
83
- torch.backends.cuda.matmul.allow_tf32 = True # free ~1.3x matmul speedup on A100+
84
  torch.backends.cudnn.allow_tf32 = True
85
  from datasets import load_dataset
86
  from transformers import AutoModelForCausalLM
@@ -119,16 +119,26 @@ def build_and_train(args) -> str:
119
  if preset:
120
  print(f"[preset] {label} -> {preset}")
121
 
122
- use_4bit = (not args.no_4bit) and cfg["quant"]["load_in_4bit"] and has_cuda
123
- if cfg["quant"]["load_in_4bit"] and not has_cuda and not args.no_4bit:
124
- print("[warn] 4-bit requested but no CUDA — falling back to full-precision "
125
- "(CPU). Use a GPU for real QLoRA runs.")
 
 
 
 
 
 
 
 
126
 
127
  tok = load_tokenizer(base_id, pad_token=cfg["model"]["pad_token"])
128
 
129
  model_kwargs = {"dtype": compute_dtype}
130
  if use_4bit:
131
  model_kwargs["quantization_config"] = build_bnb_config(cfg, compute_dtype=compute_dtype)
 
 
132
  model = AutoModelForCausalLM.from_pretrained(base_id, **model_kwargs)
133
 
134
  if has_cuda and compute_dtype in (torch.float16, torch.float32):
@@ -181,9 +191,19 @@ def build_and_train(args) -> str:
181
  if grad_ckpt and hasattr(model, "enable_input_require_grads"):
182
  model.enable_input_require_grads()
183
 
184
- optim = t.get("optim", "paged_adamw_8bit") if use_4bit else "adamw_torch"
 
 
 
 
 
 
 
 
 
 
185
  print(f"[train] epochs={epochs} batch={batch} accum={accum} (eff={batch * accum}) "
186
- f"max_length={maxlen} grad_ckpt={grad_ckpt}")
187
 
188
  sft_args = SFTConfig(
189
  output_dir=output_dir,
@@ -197,11 +217,12 @@ def build_and_train(args) -> str:
197
  save_steps=t.get("save_steps", 50),
198
  max_length=maxlen,
199
  completion_only_loss=t.get("completion_only_loss", True),
200
- packing=t.get("packing", False),
201
  gradient_checkpointing=grad_ckpt,
 
202
  optim=optim,
203
  bf16=bf16_ok and not amp_off,
204
  fp16=has_cuda and not bf16_ok and not amp_off,
 
205
  seed=t.get("seed", 0),
206
  report_to=(["wandb"] if args.wandb else "none"),
207
  max_steps=args.max_steps if args.max_steps else -1,
@@ -255,6 +276,8 @@ def main() -> None:
255
  default="auto", help="GPU-tuned defaults (auto-detects by default)")
256
  ap.add_argument("--no-amp", action="store_true",
257
  help="disable fp16/bf16 mixed precision (T4 fallback if the scaler errors)")
 
 
258
  ap.add_argument("--wandb", action="store_true")
259
  ap.add_argument("--push", action="store_true", help="push adapter to HF Hub")
260
  ap.add_argument("--hub-model-id", default=None)
 
80
  import torch
81
 
82
  if torch.cuda.is_available():
83
+ torch.set_float32_matmul_precision("high") # TF32 matmuls (A100+): ~1.3x, current API
84
  torch.backends.cudnn.allow_tf32 = True
85
  from datasets import load_dataset
86
  from transformers import AutoModelForCausalLM
 
119
  if preset:
120
  print(f"[preset] {label} -> {preset}")
121
 
122
+ prec = getattr(args, "precision", "auto")
123
+ if prec == "4bit":
124
+ want_4bit = True
125
+ elif prec == "bf16":
126
+ want_4bit = False
127
+ else:
128
+ want_4bit = bool(cfg["quant"]["load_in_4bit"])
129
+ if getattr(args, "no_4bit", False):
130
+ want_4bit = False
131
+ use_4bit = want_4bit and has_cuda
132
+ if want_4bit and not has_cuda:
133
+ print("[warn] 4-bit requested but no CUDA — full-precision on CPU.")
134
 
135
  tok = load_tokenizer(base_id, pad_token=cfg["model"]["pad_token"])
136
 
137
  model_kwargs = {"dtype": compute_dtype}
138
  if use_4bit:
139
  model_kwargs["quantization_config"] = build_bnb_config(cfg, compute_dtype=compute_dtype)
140
+ if cfg["model"].get("attn_implementation"):
141
+ model_kwargs["attn_implementation"] = cfg["model"]["attn_implementation"]
142
  model = AutoModelForCausalLM.from_pretrained(base_id, **model_kwargs)
143
 
144
  if has_cuda and compute_dtype in (torch.float16, torch.float32):
 
191
  if grad_ckpt and hasattr(model, "enable_input_require_grads"):
192
  model.enable_input_require_grads()
193
 
194
+ if not has_cuda:
195
+ optim = "adamw_torch" # CPU: bnb/fused optimizers need CUDA
196
+ elif use_4bit:
197
+ optim = t.get("optim") or "paged_adamw_8bit"
198
+ else:
199
+ optim = t.get("optim") or "adamw_torch_fused"
200
+ packing = bool(t.get("packing", False))
201
+ if packing and cfg["model"].get("attn_implementation") != "flash_attention_2":
202
+ print("[warn] packing WITHOUT flash_attention_2 cross-contaminates examples "
203
+ "(attends across packed rows). Install flash-attn and set "
204
+ "model.attn_implementation: flash_attention_2, or disable packing.")
205
  print(f"[train] epochs={epochs} batch={batch} accum={accum} (eff={batch * accum}) "
206
+ f"max_length={maxlen} grad_ckpt={grad_ckpt} packing={packing} optim={optim}")
207
 
208
  sft_args = SFTConfig(
209
  output_dir=output_dir,
 
217
  save_steps=t.get("save_steps", 50),
218
  max_length=maxlen,
219
  completion_only_loss=t.get("completion_only_loss", True),
 
220
  gradient_checkpointing=grad_ckpt,
221
+ packing=packing, # concatenate rows -> ~no padding (needs FA2; see guard)
222
  optim=optim,
223
  bf16=bf16_ok and not amp_off,
224
  fp16=has_cuda and not bf16_ok and not amp_off,
225
+ save_total_limit=1, # don't fill ephemeral disk with checkpoints
226
  seed=t.get("seed", 0),
227
  report_to=(["wandb"] if args.wandb else "none"),
228
  max_steps=args.max_steps if args.max_steps else -1,
 
276
  default="auto", help="GPU-tuned defaults (auto-detects by default)")
277
  ap.add_argument("--no-amp", action="store_true",
278
  help="disable fp16/bf16 mixed precision (T4 fallback if the scaler errors)")
279
+ ap.add_argument("--precision", choices=["auto", "4bit", "bf16"], default="auto",
280
+ help="override base precision: 4bit (QLoRA) or bf16 (LoRA); auto=config")
281
  ap.add_argument("--wandb", action="store_true")
282
  ap.add_argument("--push", action="store_true", help="push adapter to HF Hub")
283
  ap.add_argument("--hub-model-id", default=None)
tests/test_eval_plumbing.py CHANGED
@@ -43,5 +43,5 @@ def test_batched_processbench_eval_runs():
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"]
 
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"] <= 100.0 # now reported on the 0-100 scale
47
  assert "gsm8k" in res["subsets"]