Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
aba3a90
1
Parent(s): cfe0185
retry done jobs too
Browse files- app.py +3 -1
- job_queue.py +73 -16
app.py
CHANGED
|
@@ -519,7 +519,9 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 519 |
"When <code>FFASR_MODERATION=1</code> is set, new submissions **wait here** until you approve them. "
|
| 520 |
"Privileged actions require the same secret you used to unlock this panel.\n\n"
|
| 521 |
"When <code>FFASR_REMOTE_JOBS=1</code>, use **Open Hub Job logs** while a job runs. "
|
| 522 |
-
"The bucket <code>results/remote_artifacts/</code> folder only gets a JSON file **after** a successful run."
|
|
|
|
|
|
|
| 523 |
)
|
| 524 |
gr.Markdown("### Current job progress")
|
| 525 |
mod_progress = gr.HTML(
|
|
|
|
| 519 |
"When <code>FFASR_MODERATION=1</code> is set, new submissions **wait here** until you approve them. "
|
| 520 |
"Privileged actions require the same secret you used to unlock this panel.\n\n"
|
| 521 |
"When <code>FFASR_REMOTE_JOBS=1</code>, use **Open Hub Job logs** while a job runs. "
|
| 522 |
+
"The bucket <code>results/remote_artifacts/</code> folder only gets a JSON file **after** a successful run.\n\n"
|
| 523 |
+
"**Retry** re-queues failed, done, or queued jobs (not while running). "
|
| 524 |
+
"A successful re-run **replaces** the leaderboard row for that model if it already exists."
|
| 525 |
)
|
| 526 |
gr.Markdown("### Current job progress")
|
| 527 |
mod_progress = gr.HTML(
|
job_queue.py
CHANGED
|
@@ -72,6 +72,8 @@ class Job:
|
|
| 72 |
progress_condition: str = ""
|
| 73 |
hf_remote_job_id: str | None = None
|
| 74 |
remote_artifact_path: str | None = None
|
|
|
|
|
|
|
| 75 |
|
| 76 |
|
| 77 |
_jobs: dict[str, Job] = {}
|
|
@@ -481,18 +483,35 @@ def _succeed_job(job_id: str, result: dict) -> None:
|
|
| 481 |
|
| 482 |
|
| 483 |
def _merge_eval_result_to_leaderboard(
|
| 484 |
-
result: dict,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
) -> None:
|
| 486 |
-
from init import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
|
|
|
|
| 488 |
rows = load_raw_results()
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
|
|
|
|
|
|
|
|
|
| 493 |
)
|
|
|
|
|
|
|
| 494 |
_leaderboard_sort_rows_inplace(rows)
|
| 495 |
save_raw_results(rows)
|
|
|
|
| 496 |
|
| 497 |
|
| 498 |
def _load_artifact_json_from_bucket(artifact_path: str) -> dict:
|
|
@@ -754,7 +773,10 @@ def _remote_start_queued_job(job_id: str, jobs_token: str) -> None:
|
|
| 754 |
_persist_jobs()
|
| 755 |
|
| 756 |
rows = load_raw_results()
|
| 757 |
-
|
|
|
|
|
|
|
|
|
|
| 758 |
_fail_job(job_id, "Model already on leaderboard (skipped duplicate race).")
|
| 759 |
return
|
| 760 |
|
|
@@ -811,7 +833,16 @@ def _remote_tick_job(job_id: str, jobs_token: str) -> None:
|
|
| 811 |
|
| 812 |
try:
|
| 813 |
result = _remote_collect_result(job_id, hf_id, jobs_token)
|
| 814 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
_succeed_job(job_id, result)
|
| 816 |
except Exception as e:
|
| 817 |
_fail_job(job_id, str(e))
|
|
@@ -1077,8 +1108,25 @@ def moderation_locked_placeholder_html() -> str:
|
|
| 1077 |
return "<p><em>Moderator tools are locked. Enter the secret above and click Unlock.</em></p>"
|
| 1078 |
|
| 1079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1080 |
def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
| 1081 |
-
"""Re-queue a
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
ok, msg = _moderator_secret_ok(secret)
|
| 1083 |
if not ok:
|
| 1084 |
return False, msg
|
|
@@ -1090,13 +1138,16 @@ def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
|
| 1090 |
job = _jobs.get(job_id)
|
| 1091 |
if not job:
|
| 1092 |
return False, "Job not found."
|
| 1093 |
-
if job.status
|
| 1094 |
-
return
|
| 1095 |
-
|
| 1096 |
-
|
|
|
|
|
|
|
| 1097 |
if _work_queue.qsize() >= _MAX_QUEUE_BACKLOG:
|
| 1098 |
return False, "Execution queue is full; try again later."
|
| 1099 |
|
|
|
|
| 1100 |
job.status = JobStatus.queued
|
| 1101 |
job.error = None
|
| 1102 |
job.result = None
|
|
@@ -1105,12 +1156,18 @@ def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
|
| 1105 |
job.progress_done = 0
|
| 1106 |
job.progress_total = 0
|
| 1107 |
job.progress_condition = ""
|
|
|
|
| 1108 |
_touch(job)
|
| 1109 |
|
| 1110 |
-
|
|
|
|
| 1111 |
_persist_jobs()
|
| 1112 |
_ensure_worker()
|
| 1113 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1114 |
|
| 1115 |
|
| 1116 |
def remove_job_entry(job_id: str, secret: str) -> tuple[bool, str]:
|
|
@@ -1317,7 +1374,7 @@ def recent_jobs_for_render(limit: int = 30) -> list[dict[str, Any]]:
|
|
| 1317 |
"error": err[:200] + ("…" if len(err) > 200 else ""),
|
| 1318 |
"hub_link_html": hub,
|
| 1319 |
"updated_at": (j.updated_at or j.created_at)[:19],
|
| 1320 |
-
"can_retry": j.status
|
| 1321 |
"can_remove": j.status != JobStatus.running,
|
| 1322 |
"has_custom_script": bool((j.custom_script or "").strip()),
|
| 1323 |
}
|
|
|
|
| 72 |
progress_condition: str = ""
|
| 73 |
hf_remote_job_id: str | None = None
|
| 74 |
remote_artifact_path: str | None = None
|
| 75 |
+
# Set by moderator retry: skip duplicate guard and replace CSV row on success.
|
| 76 |
+
replace_leaderboard: bool = False
|
| 77 |
|
| 78 |
|
| 79 |
_jobs: dict[str, Job] = {}
|
|
|
|
| 483 |
|
| 484 |
|
| 485 |
def _merge_eval_result_to_leaderboard(
|
| 486 |
+
result: dict,
|
| 487 |
+
submitted_at_iso: str,
|
| 488 |
+
submission_notes: str,
|
| 489 |
+
*,
|
| 490 |
+
replace_existing: bool = False,
|
| 491 |
) -> None:
|
| 492 |
+
from init import (
|
| 493 |
+
invalidate_results_cache,
|
| 494 |
+
leaderboard_row_from_eval_result,
|
| 495 |
+
load_raw_results,
|
| 496 |
+
normalize_legacy_csv_row,
|
| 497 |
+
save_raw_results,
|
| 498 |
+
)
|
| 499 |
|
| 500 |
+
model_id = str(result.get("model_id", "")).strip()
|
| 501 |
rows = load_raw_results()
|
| 502 |
+
if replace_existing and model_id:
|
| 503 |
+
existing = [i for i, r in enumerate(rows) if (r.get("model_id") or "").strip() == model_id]
|
| 504 |
+
for i in sorted(existing, reverse=True):
|
| 505 |
+
rows.pop(i)
|
| 506 |
+
|
| 507 |
+
new_row = leaderboard_row_from_eval_result(
|
| 508 |
+
result, submitted_at_iso, submission_notes=submission_notes
|
| 509 |
)
|
| 510 |
+
normalize_legacy_csv_row(new_row)
|
| 511 |
+
rows.append(new_row)
|
| 512 |
_leaderboard_sort_rows_inplace(rows)
|
| 513 |
save_raw_results(rows)
|
| 514 |
+
invalidate_results_cache()
|
| 515 |
|
| 516 |
|
| 517 |
def _load_artifact_json_from_bucket(artifact_path: str) -> dict:
|
|
|
|
| 773 |
_persist_jobs()
|
| 774 |
|
| 775 |
rows = load_raw_results()
|
| 776 |
+
with _jobs_lock:
|
| 777 |
+
j_chk = _jobs.get(job_id)
|
| 778 |
+
replace_lb = bool(j_chk and j_chk.replace_leaderboard)
|
| 779 |
+
if not replace_lb and any(r["model_id"] == mid for r in rows):
|
| 780 |
_fail_job(job_id, "Model already on leaderboard (skipped duplicate race).")
|
| 781 |
return
|
| 782 |
|
|
|
|
| 833 |
|
| 834 |
try:
|
| 835 |
result = _remote_collect_result(job_id, hf_id, jobs_token)
|
| 836 |
+
with _jobs_lock:
|
| 837 |
+
j_done = _jobs.get(job_id)
|
| 838 |
+
replace_lb = bool(j_done and j_done.replace_leaderboard)
|
| 839 |
+
_merge_eval_result_to_leaderboard(
|
| 840 |
+
result, _now_iso(), notes, replace_existing=replace_lb
|
| 841 |
+
)
|
| 842 |
+
with _jobs_lock:
|
| 843 |
+
j_clr = _jobs.get(job_id)
|
| 844 |
+
if j_clr:
|
| 845 |
+
j_clr.replace_leaderboard = False
|
| 846 |
_succeed_job(job_id, result)
|
| 847 |
except Exception as e:
|
| 848 |
_fail_job(job_id, str(e))
|
|
|
|
| 1108 |
return "<p><em>Moderator tools are locked. Enter the secret above and click Unlock.</em></p>"
|
| 1109 |
|
| 1110 |
|
| 1111 |
+
def _job_can_retry(status: JobStatus) -> bool:
|
| 1112 |
+
"""Moderator may re-run jobs that are not actively executing on Hub."""
|
| 1113 |
+
if status in (
|
| 1114 |
+
JobStatus.running,
|
| 1115 |
+
JobStatus.dispatching,
|
| 1116 |
+
JobStatus.remote_running,
|
| 1117 |
+
JobStatus.collecting,
|
| 1118 |
+
JobStatus.pending_moderation,
|
| 1119 |
+
):
|
| 1120 |
+
return False
|
| 1121 |
+
return status in (JobStatus.failed, JobStatus.done, JobStatus.queued)
|
| 1122 |
+
|
| 1123 |
+
|
| 1124 |
def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
| 1125 |
+
"""Re-queue a job for another evaluation run (moderator only).
|
| 1126 |
+
|
| 1127 |
+
Allowed for failed, done, and queued jobs. Successful re-runs replace the
|
| 1128 |
+
existing leaderboard row for that model when one is present.
|
| 1129 |
+
"""
|
| 1130 |
ok, msg = _moderator_secret_ok(secret)
|
| 1131 |
if not ok:
|
| 1132 |
return False, msg
|
|
|
|
| 1138 |
job = _jobs.get(job_id)
|
| 1139 |
if not job:
|
| 1140 |
return False, "Job not found."
|
| 1141 |
+
if not _job_can_retry(job.status):
|
| 1142 |
+
return (
|
| 1143 |
+
False,
|
| 1144 |
+
"Cannot retry while the job is running or awaiting moderation. "
|
| 1145 |
+
"Wait for it to finish, or approve/reject pending jobs first.",
|
| 1146 |
+
)
|
| 1147 |
if _work_queue.qsize() >= _MAX_QUEUE_BACKLOG:
|
| 1148 |
return False, "Execution queue is full; try again later."
|
| 1149 |
|
| 1150 |
+
was_queued = job.status == JobStatus.queued
|
| 1151 |
job.status = JobStatus.queued
|
| 1152 |
job.error = None
|
| 1153 |
job.result = None
|
|
|
|
| 1156 |
job.progress_done = 0
|
| 1157 |
job.progress_total = 0
|
| 1158 |
job.progress_condition = ""
|
| 1159 |
+
job.replace_leaderboard = True
|
| 1160 |
_touch(job)
|
| 1161 |
|
| 1162 |
+
if not was_queued:
|
| 1163 |
+
_work_queue.put(job_id)
|
| 1164 |
_persist_jobs()
|
| 1165 |
_ensure_worker()
|
| 1166 |
+
return (
|
| 1167 |
+
True,
|
| 1168 |
+
f"Re-queued job {job_id}; it will run after jobs ahead of it. "
|
| 1169 |
+
"If the model is already on the leaderboard, the row will be replaced on success.",
|
| 1170 |
+
)
|
| 1171 |
|
| 1172 |
|
| 1173 |
def remove_job_entry(job_id: str, secret: str) -> tuple[bool, str]:
|
|
|
|
| 1374 |
"error": err[:200] + ("…" if len(err) > 200 else ""),
|
| 1375 |
"hub_link_html": hub,
|
| 1376 |
"updated_at": (j.updated_at or j.created_at)[:19],
|
| 1377 |
+
"can_retry": _job_can_retry(j.status),
|
| 1378 |
"can_remove": j.status != JobStatus.running,
|
| 1379 |
"has_custom_script": bool((j.custom_script or "").strip()),
|
| 1380 |
}
|