Cion-lab commited on
Commit
f45605d
·
verified ·
1 Parent(s): d0e35c4

review round 3 (probe/model-card/eval): rank-visible eval failure + cross-rank peak memory + complete run_summary; card derived-and-asserted from config; harness install --no-deps and task ids confirmed; torchrun redirects 3 + tee 3

Browse files
Files changed (1) hide show
  1. eval/run_benchmarks.py +59 -16
eval/run_benchmarks.py CHANGED
@@ -34,7 +34,8 @@ TASKS = ["arc_challenge", "arc_easy", "hellaswag", "mmlu", "piqa", "truthfulqa_m
34
  "truthfulqa_mc2", "winogrande", "gsm8k"]
35
  # Chance level of the *metric being reported*, from the number of answer options in the task, not from a
36
  # remembered leaderboard. Where a metric is not chance-normalised that is said rather than guessed at.
37
- CHANCE = {"arc_challenge": ("0.25", "4 options"), "arc_easy": ("0.25", "4 options"),
 
38
  "hellaswag": ("0.25", "4 continuations"), "mmlu": ("0.25", "4 options, macro over 57 subjects"),
39
  "piqa": ("0.50", "2 options"), "winogrande": ("0.50", "2 options"),
40
  "truthfulqa_mc1": ("~0.20", "mean over questions of 1/#choices"),
@@ -51,8 +52,11 @@ def sh(argv, label, timeout, env=None):
51
  e["PYTHONUNBUFFERED"] = "1"
52
  e.update(env or {})
53
  import threading
 
 
54
  p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
55
- env=e, bufsize=1, cwd=os.path.dirname(os.path.abspath(__file__)))
 
56
  killed = []
57
 
58
  def _kill():
@@ -78,6 +82,16 @@ def sh(argv, label, timeout, env=None):
78
  return rc, "\n".join(keep)
79
 
80
 
 
 
 
 
 
 
 
 
 
 
81
  def install_harness():
82
  """Pin the harness in a uv inline-script environment rather than into the Kaggle image's site-packages:
83
  transformers 5.0.0 must stay exactly as Gate 3 measured it, and `pip install` into the image would
@@ -92,13 +106,18 @@ def install_harness():
92
  return have
93
  print("lm_eval %r is not the pin %s -- installing it as a user package" % (have, PINNED_LM_EVAL),
94
  flush=True)
95
- rc, _ = sh([sys.executable, "-m", "pip", "install", "--user", "--quiet",
96
- "lm-eval[mathtasks]==%s" % PINNED_LM_EVAL], "pip_lm_eval", 1800)
 
 
 
 
97
  if rc != 0:
98
- rc, _ = sh([sys.executable, "-m", "pip", "install", "--user", "--quiet",
99
- "lm-eval==%s" % PINNED_LM_EVAL], "pip_lm_eval_plain", 1800)
100
- if rc != 0:
101
- raise SystemExit("could not install the pinned harness; refusing to eval on a different one")
 
102
  rc, out = sh([sys.executable, "-c", "import lm_eval, os; "
103
  "print(lm_eval.__version__, os.path.dirname(lm_eval.__file__))"],
104
  "verify_lm_eval", 300)
@@ -171,6 +190,16 @@ def main():
171
  shutil.rmtree(root, ignore_errors=True)
172
  os.makedirs(root, exist_ok=True)
173
 
 
 
 
 
 
 
 
 
 
 
174
  smoke = {}
175
  for t in TASKS:
176
  rc, cmd = run_task(a.model, t, None, os.path.join(root, "smoke"), 5, 0.5)
@@ -183,7 +212,7 @@ def main():
183
  json.dump(smoke, open(os.path.join(root, "smoke.json"), "w"), indent=1, default=str)
184
  raise SystemExit(3 if bad else 0)
185
 
186
- table = {}
187
  for t in TASKS:
188
  rec = {"chance": CHANCE.get(t, ("?", "?")), "lm_eval": ver}
189
  for shots, key in ((None, "primary"), (5, "secondary_5shot")):
@@ -196,13 +225,22 @@ def main():
196
  json.dumps(rec[key]["rows"], default=str)[:200]), flush=True)
197
  table[t] = rec
198
  json.dump(table, open(os.path.join(root, "results.json"), "w"), indent=1, default=str)
199
- push(a, root) # after every task: an interruption keeps what finished
200
- push(a, root)
201
- missing = [t for t in TASKS if not table.get(t, {}).get("primary", {}).get("rows")]
 
 
 
 
 
 
 
 
 
202
  # Nine invocations, eight benchmarks: TruthfulQA is one row of the published table and its mc1 and mc2
203
  # are separate task ids in the harness (docs/05-eval-plan.md §2).
204
- print("VERDICT PHASE6 runs %d/%d (8 benchmarks) lm_eval %s missing %s" % (
205
- len(TASKS) - len(missing), len(TASKS), ver, missing or "none"), flush=True)
206
  raise SystemExit(4 if missing else 0)
207
 
208
 
@@ -211,22 +249,27 @@ def push(a, root):
211
  repo = a.repo or a.model
212
  if not repo:
213
  print("(no --repo and no --model-derived target; results stay local)", flush=True)
214
- return
215
  import ounce100m_credentials
216
  ounce100m_credentials.install()
217
  tok = os.environ.get("HF_TOKEN")
218
  if tok is None:
219
  print("FATAL: no token, refusing to claim results were published", flush=True)
220
- return
221
  try:
222
  from huggingface_hub import HfApi
223
  rc = HfApi(token=tok).upload_folder(repo_id=repo, repo_type="model", folder_path=root,
224
  path_in_repo="eval", commit_message="Phase 6 results",
 
 
 
225
  commit_description="lm-eval 0.4.13, task-default shots plus a "
226
  "uniform 5-shot column, per docs/05-eval-plan.md")
227
  print("PUSHED eval/ ->", getattr(rc, "commit_url", str(rc)[:120]), flush=True)
 
228
  except Exception as e:
229
  print("PUSH_FAILED", type(e).__name__, str(e)[:200], flush=True)
 
230
 
231
 
232
  if __name__ == "__main__":
 
34
  "truthfulqa_mc2", "winogrande", "gsm8k"]
35
  # Chance level of the *metric being reported*, from the number of answer options in the task, not from a
36
  # remembered leaderboard. Where a metric is not chance-normalised that is said rather than guessed at.
37
+ CHANCE = {"arc_challenge": ("0.25-0.33", "items mix 3 and 4 options"),
38
+ "arc_easy": ("0.25-0.33", "items mix 3 and 4 options"),
39
  "hellaswag": ("0.25", "4 continuations"), "mmlu": ("0.25", "4 options, macro over 57 subjects"),
40
  "piqa": ("0.50", "2 options"), "winogrande": ("0.50", "2 options"),
41
  "truthfulqa_mc1": ("~0.20", "mean over questions of 1/#choices"),
 
52
  e["PYTHONUNBUFFERED"] = "1"
53
  e.update(env or {})
54
  import threading
55
+ # start_new_session, because _kill() signals getpgid(pid). Without its own group that group is the
56
+ # notebook's, so a timeout on the longest task would SIGTERM the very script watching for it (C1).
57
  p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
58
+ env=e, bufsize=1, cwd=os.path.dirname(os.path.abspath(__file__)),
59
+ start_new_session=True)
60
  killed = []
61
 
62
  def _kill():
 
82
  return rc, "\n".join(keep)
83
 
84
 
85
+ def tf_version():
86
+ """Which transformers the harness will actually import -- see the --no-deps note below."""
87
+ rc, out = sh([sys.executable, "-c", "import transformers; print(transformers.__version__)"],
88
+ "probe_transformers", 180)
89
+ for line in out.splitlines():
90
+ if line.strip()[:1].isdigit():
91
+ return line.strip()
92
+ return "?"
93
+
94
+
95
  def install_harness():
96
  """Pin the harness in a uv inline-script environment rather than into the Kaggle image's site-packages:
97
  transformers 5.0.0 must stay exactly as Gate 3 measured it, and `pip install` into the image would
 
106
  return have
107
  print("lm_eval %r is not the pin %s -- installing it as a user package" % (have, PINNED_LM_EVAL),
108
  flush=True)
109
+ before = tf_version()
110
+ # --no-deps deliberately. A dependency-resolving install can drop a different transformers into
111
+ # ~/.local, where it shadows the image's copy on sys.path -- the exact thing this file's header says it
112
+ # avoids, and it would move the harness pin silently (review finding C4).
113
+ rc, _ = sh([sys.executable, "-m", "pip", "install", "--user", "--quiet", "--no-deps",
114
+ "lm-eval==%s" % PINNED_LM_EVAL], "pip_lm_eval", 1800)
115
  if rc != 0:
116
+ raise SystemExit("could not install the pinned harness; refusing to eval on a different one")
117
+ after = tf_version()
118
+ if before != after:
119
+ raise SystemExit("installing the harness moved transformers %s -> %s: the eval would no longer run "
120
+ "on the stack Gate 3 measured" % (before, after))
121
  rc, out = sh([sys.executable, "-c", "import lm_eval, os; "
122
  "print(lm_eval.__version__, os.path.dirname(lm_eval.__file__))"],
123
  "verify_lm_eval", 300)
 
190
  shutil.rmtree(root, ignore_errors=True)
191
  os.makedirs(root, exist_ok=True)
192
 
193
+ # Confirm the task ids resolve before any GPU time goes into them: one typo among nine would otherwise
194
+ # fail the all-or-nothing smoke gate and yield no table at all (C5; §2 says to confirm with --tasks list).
195
+ rc, out = sh([sys.executable, "-m", "lm_eval", "--tasks", "list"], "list_tasks", 900)
196
+ listed = set(re.findall(r"^[ \t]+([a-z][a-z0-9_]+)[ \t]*$", out, re.M))
197
+ unknown = [t for t in TASKS if listed and t not in listed]
198
+ if unknown:
199
+ raise SystemExit("these task ids do not resolve in lm-eval %s: %s" % (ver, unknown))
200
+ print("task ids checked against the harness:", len(TASKS), "requested,",
201
+ (str(len(listed)) + " ids listed") if listed else "list output unparsed", flush=True)
202
+
203
  smoke = {}
204
  for t in TASKS:
205
  rc, cmd = run_task(a.model, t, None, os.path.join(root, "smoke"), 5, 0.5)
 
212
  json.dump(smoke, open(os.path.join(root, "smoke.json"), "w"), indent=1, default=str)
213
  raise SystemExit(3 if bad else 0)
214
 
215
+ table, push_fail = {}, []
216
  for t in TASKS:
217
  rec = {"chance": CHANCE.get(t, ("?", "?")), "lm_eval": ver}
218
  for shots, key in ((None, "primary"), (5, "secondary_5shot")):
 
225
  json.dumps(rec[key]["rows"], default=str)[:200]), flush=True)
226
  table[t] = rec
227
  json.dump(table, open(os.path.join(root, "results.json"), "w"), indent=1, default=str)
228
+ if not push(a, root): # after every task: an interruption keeps what finished
229
+ push_fail.append("after " + t)
230
+ if not push(a, root):
231
+ push_fail.append("final")
232
+ def ok(t, key):
233
+ r = table.get(t, {}).get(key, {})
234
+ return bool(r.get("rows")) and r.get("rc") == 0
235
+
236
+ # Both columns and both exit codes. `primary` rows alone would report a full house with an entirely
237
+ # empty 5-shot column, or with a leg that exited non-zero after writing something partial (C2).
238
+ missing = [f"{t}/{k}" for t in TASKS for k in ("primary", "secondary_5shot") if not ok(t, k)]
239
+ missing += push_fail
240
  # Nine invocations, eight benchmarks: TruthfulQA is one row of the published table and its mc1 and mc2
241
  # are separate task ids in the harness (docs/05-eval-plan.md §2).
242
+ print("VERDICT PHASE6 cells %d/%d (8 benchmarks x 2 columns) lm_eval %s missing %s" % (
243
+ 2 * len(TASKS) - len(missing), 2 * len(TASKS), ver, missing or "none"), flush=True)
244
  raise SystemExit(4 if missing else 0)
245
 
246
 
 
249
  repo = a.repo or a.model
250
  if not repo:
251
  print("(no --repo and no --model-derived target; results stay local)", flush=True)
252
+ return False
253
  import ounce100m_credentials
254
  ounce100m_credentials.install()
255
  tok = os.environ.get("HF_TOKEN")
256
  if tok is None:
257
  print("FATAL: no token, refusing to claim results were published", flush=True)
258
+ return False
259
  try:
260
  from huggingface_hub import HfApi
261
  rc = HfApi(token=tok).upload_folder(repo_id=repo, repo_type="model", folder_path=root,
262
  path_in_repo="eval", commit_message="Phase 6 results",
263
+ # The --limit 5 smoke rows must not sit in the public repo beside
264
+ # the real table looking like results (C3).
265
+ ignore_patterns=["smoke*", "smoke/*", "smoke.json"],
266
  commit_description="lm-eval 0.4.13, task-default shots plus a "
267
  "uniform 5-shot column, per docs/05-eval-plan.md")
268
  print("PUSHED eval/ ->", getattr(rc, "commit_url", str(rc)[:120]), flush=True)
269
+ return True
270
  except Exception as e:
271
  print("PUSH_FAILED", type(e).__name__, str(e)[:200], flush=True)
272
+ return False
273
 
274
 
275
  if __name__ == "__main__":