Spaces:
Running
Running
Upload from GitHub Actions: eval: check runtime budget per-batch so a slow model can't blow the 6h cap
Browse filesThe 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 +7 -5
- evals/main.py +26 -10
.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
|
| 93 |
-
#
|
| 94 |
-
#
|
| 95 |
-
#
|
| 96 |
-
|
|
|
|
|
|
|
| 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).
|