whojavumusic commited on
Commit
aba3a90
·
1 Parent(s): cfe0185

retry done jobs too

Browse files
Files changed (2) hide show
  1. app.py +3 -1
  2. 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, submitted_at_iso: str, submission_notes: str
 
 
 
 
485
  ) -> None:
486
- from init import leaderboard_row_from_eval_result, load_raw_results, save_raw_results
 
 
 
 
 
 
487
 
 
488
  rows = load_raw_results()
489
- rows.append(
490
- leaderboard_row_from_eval_result(
491
- result, submitted_at_iso, submission_notes=submission_notes
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
- if any(r["model_id"] == mid for r in rows):
 
 
 
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
- _merge_eval_result_to_leaderboard(result, _now_iso(), notes)
 
 
 
 
 
 
 
 
 
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 failed job for another evaluation run (moderator only)."""
 
 
 
 
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 == JobStatus.running:
1094
- return False, "Job is currently running."
1095
- if job.status != JobStatus.failed:
1096
- return False, "Only failed jobs can be retried."
 
 
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
- _work_queue.put(job_id)
 
1111
  _persist_jobs()
1112
  _ensure_worker()
1113
- return True, f"Re-queued job {job_id}; it will run after jobs ahead of it."
 
 
 
 
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 == JobStatus.failed,
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
  }