davidpomerenke commited on
Commit
594d28a
Β·
verified Β·
1 Parent(s): 15a7ddf

Upload from GitHub Actions: eval: check runtime budget per-batch so a slow model can't blow the 6h cap

Browse files

The graceful budget was only checked between models, so a single rate-limited
model (kimi-k2.6 ran >50min) blew past 5.5h and the run got hard-cancelled at
6h. Now check the budget (a) before starting each model and (b) after every
batch β€” a slow model is interrupted at a batch boundary, its partial work saved
to results-detailed (skipped next run) but NOT published (not coverage-complete).
Lower the workflow budget to 5h for headroom. Checkpointing already worked: the
cancelled run published 42 clean fully-covered models before the cap.

.github/workflows/nightly-evals.yml CHANGED
@@ -89,11 +89,13 @@ jobs:
89
  # Bumped 2026-05-19 from 40 to 150 to cover the auto-discovered cohort
90
  # (typically ~100 models after dedupe + cost cap).
91
  N_MODELS: 150
92
- # GitHub-hosted runners hard-cap a job at 6h. Stop gracefully after
93
- # the current model at 5.5h so the run exits cleanly, restarts the
94
- # Space, and resumes the rest next run (per-model checkpointing makes
95
- # this safe β€” completed models are skipped on the next run).
96
- MAX_RUNTIME_SECONDS: 19800
 
 
97
  run: |
98
  # `huggingface-cli login` only warms a credential cache; all dataset
99
  # load/save calls in the code pass token=HUGGINGFACE_ACCESS_TOKEN
 
89
  # Bumped 2026-05-19 from 40 to 150 to cover the auto-discovered cohort
90
  # (typically ~100 models after dedupe + cost cap).
91
  N_MODELS: 150
92
+ # GitHub-hosted runners hard-cap a job at 6h. Stop gracefully at 5h
93
+ # (checked per-batch, so even a rate-limited model that takes >1h is
94
+ # interrupted at a batch boundary) β€” leaving ~1h for a slow trailing
95
+ # batch + the final checkpoint + Space restart, well under the 6h cap.
96
+ # Per-model checkpointing makes this safe: completed models are
97
+ # published and skipped next run; a partial model resumes.
98
+ MAX_RUNTIME_SECONDS: 18000
99
  run: |
100
  # `huggingface-cli login` only warms a credential cache; all dataset
101
  # load/save calls in the code pass token=HUGGINGFACE_ACCESS_TOKEN
evals/main.py CHANGED
@@ -172,8 +172,17 @@ async def evaluate():
172
  covered = current_models - set(pending_models)
173
  print(f"{len(covered)} models already fully covered; "
174
  f"{len(pending_models)} pending this run")
 
 
 
175
  results_agg = None
 
176
  for mi, model_id in enumerate(pending_models, 1):
 
 
 
 
 
177
  model_combis = combis[combis["model"] == model_id]
178
  print(f"[{mi}/{len(pending_models)}] {model_id}: {len(model_combis)} new samples")
179
  model_out = []
@@ -200,7 +209,24 @@ async def evaluate():
200
  print(f" ! {t}/{m}/{b}#{s} skipped: {type(res).__name__}: {str(res)[:120]}")
201
  else:
202
  model_out.extend(res)
 
 
 
 
 
203
  model_df = pd.DataFrame(model_out) if model_out else pd.DataFrame(columns=all_results.columns)
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  if not model_df.empty and "status" in model_df.columns:
206
  err = (model_df["status"] != "ok").mean()
@@ -209,20 +235,10 @@ async def evaluate():
209
  # it out of the aggregate this run.
210
  print(f" ⚠ {model_id}: {err:.0%} of new rows errored β€” logged, not published")
211
 
212
- all_results = pd.concat([all_results, model_df]).drop_duplicates(
213
- subset=dedup_keys, keep="last"
214
- )
215
  # This model's full matrix is now attempted β†’ coverage-complete.
216
  covered.add(model_id)
217
  results_agg = checkpoint(all_results, covered, current_languages, model_id)
218
 
219
- if max_runtime_seconds and (time.time() - start_time) > max_runtime_seconds:
220
- deferred = len(pending_models) - mi
221
- print(f"[main] runtime budget ({max_runtime_seconds}s) reached after "
222
- f"{model_id}; exiting cleanly with {deferred} model(s) deferred "
223
- f"to the next run (their progress is already checkpointed).")
224
- break
225
-
226
  if results_agg is None:
227
  # Everything was already cached β€” still refresh the published tables
228
  # from the existing log (e.g. cohort/cost metadata may have changed).
 
172
  covered = current_models - set(pending_models)
173
  print(f"{len(covered)} models already fully covered; "
174
  f"{len(pending_models)} pending this run")
175
+ def over_budget():
176
+ return max_runtime_seconds and (time.time() - start_time) > max_runtime_seconds
177
+
178
  results_agg = None
179
+ budget_hit = False
180
  for mi, model_id in enumerate(pending_models, 1):
181
+ # Don't START a new model once over budget β€” stop cleanly between models.
182
+ if over_budget():
183
+ print(f"[budget] {max_runtime_seconds}s reached before {model_id}; "
184
+ f"{len(pending_models) - mi + 1} model(s) deferred to next run.")
185
+ break
186
  model_combis = combis[combis["model"] == model_id]
187
  print(f"[{mi}/{len(pending_models)}] {model_id}: {len(model_combis)} new samples")
188
  model_out = []
 
209
  print(f" ! {t}/{m}/{b}#{s} skipped: {type(res).__name__}: {str(res)[:120]}")
210
  else:
211
  model_out.extend(res)
212
+ # Budget can be hit MID-model (a rate-limited model can take >>1h).
213
+ # Stop at this batch boundary so we never blow past the 6h hard cap.
214
+ if over_budget():
215
+ budget_hit = True
216
+ break
217
  model_df = pd.DataFrame(model_out) if model_out else pd.DataFrame(columns=all_results.columns)
218
+ # Persist whatever completed (full model, or partial batches if the
219
+ # budget was hit). Partial work is saved to results-detailed so its
220
+ # done combos are skipped next run β€” but the model is NOT added to
221
+ # `covered`, so a partial model never enters the published aggregate.
222
+ all_results = pd.concat([all_results, model_df]).drop_duplicates(
223
+ subset=dedup_keys, keep="last"
224
+ )
225
+ if budget_hit:
226
+ print(f"[budget] {max_runtime_seconds}s reached mid-{model_id}; saved partial "
227
+ f"progress, {len(pending_models) - mi} model(s) deferred to next run.")
228
+ results_agg = checkpoint(all_results, covered, current_languages, f"budget stop @ {model_id}")
229
+ break
230
 
231
  if not model_df.empty and "status" in model_df.columns:
232
  err = (model_df["status"] != "ok").mean()
 
235
  # it out of the aggregate this run.
236
  print(f" ⚠ {model_id}: {err:.0%} of new rows errored β€” logged, not published")
237
 
 
 
 
238
  # This model's full matrix is now attempted β†’ coverage-complete.
239
  covered.add(model_id)
240
  results_agg = checkpoint(all_results, covered, current_languages, model_id)
241
 
 
 
 
 
 
 
 
242
  if results_agg is None:
243
  # Everything was already cached β€” still refresh the published tables
244
  # from the existing log (e.g. cohort/cost metadata may have changed).