Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
395e3c6
1
Parent(s): aba3a90
selected dataset eval
Browse files- app.py +35 -15
- benchmark/__init__.py +10 -2
- benchmark/dataset.py +25 -0
- evaluation/orchestrator.py +25 -7
- init.py +57 -0
- job_queue.py +115 -9
- remote_jobs.py +5 -0
- scripts/run_hf_remote_job_uv.py +5 -0
app.py
CHANGED
|
@@ -521,7 +521,16 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 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 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
)
|
| 526 |
gr.Markdown("### Current job progress")
|
| 527 |
mod_progress = gr.HTML(
|
|
@@ -667,30 +676,35 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 667 |
*data,
|
| 668 |
)
|
| 669 |
|
| 670 |
-
def _mod_approve(secret, jid, unlocked, run_custom):
|
| 671 |
if not unlocked:
|
| 672 |
return _mod_bundle("<p style='color:red'>Unlock moderator tools first.</p>")
|
| 673 |
if not jid or not str(jid).strip():
|
| 674 |
return _mod_bundle("<p style='color:red'>Select a job.</p>")
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
|
|
|
| 680 |
color = "green" if ok else "red"
|
| 681 |
return _mod_bundle(f"<p style='color:{color}'>{msg}</p>")
|
| 682 |
|
| 683 |
def _mod_reject(secret, jid, unlocked):
|
| 684 |
return _mod_run(job_queue.reject_job, secret, jid, "Select a job.", unlocked)
|
| 685 |
|
| 686 |
-
def _mod_retry(secret, jid, unlocked):
|
| 687 |
-
|
| 688 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
secret,
|
| 690 |
-
|
| 691 |
-
"Select a job in Retry / remove.",
|
| 692 |
-
unlocked,
|
| 693 |
)
|
|
|
|
|
|
|
| 694 |
|
| 695 |
def _mod_remove(secret, jid, unlocked):
|
| 696 |
return _mod_run(
|
|
@@ -745,7 +759,13 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 745 |
)
|
| 746 |
approve_btn.click(
|
| 747 |
fn=_mod_approve,
|
| 748 |
-
inputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
outputs=_MOD_OUTS,
|
| 750 |
)
|
| 751 |
reject_btn.click(
|
|
@@ -755,7 +775,7 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
|
|
| 755 |
)
|
| 756 |
retry_job_btn.click(
|
| 757 |
fn=_mod_retry,
|
| 758 |
-
inputs=[mod_secret, mod_action_job, mod_unlocked],
|
| 759 |
outputs=_MOD_OUTS,
|
| 760 |
)
|
| 761 |
remove_job_btn.click(
|
|
|
|
| 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 |
+
"Use **Datasets to evaluate** to run only selected packed splits; "
|
| 525 |
+
"other leaderboard WER columns are left unchanged on success. "
|
| 526 |
+
"A full re-run (all datasets selected) **replaces** the row when the model is already listed."
|
| 527 |
+
)
|
| 528 |
+
from benchmark.dataset import CONDITION_UI_CHOICES
|
| 529 |
+
|
| 530 |
+
mod_eval_conditions = gr.CheckboxGroup(
|
| 531 |
+
choices=[(lbl, key) for lbl, key in CONDITION_UI_CHOICES],
|
| 532 |
+
value=[key for _, key in CONDITION_UI_CHOICES],
|
| 533 |
+
label="Datasets to evaluate (uncheck to skip; all selected = full benchmark)",
|
| 534 |
)
|
| 535 |
gr.Markdown("### Current job progress")
|
| 536 |
mod_progress = gr.HTML(
|
|
|
|
| 676 |
*data,
|
| 677 |
)
|
| 678 |
|
| 679 |
+
def _mod_approve(secret, jid, unlocked, run_custom, eval_conds):
|
| 680 |
if not unlocked:
|
| 681 |
return _mod_bundle("<p style='color:red'>Unlock moderator tools first.</p>")
|
| 682 |
if not jid or not str(jid).strip():
|
| 683 |
return _mod_bundle("<p style='color:red'>Select a job.</p>")
|
| 684 |
+
ok, msg = job_queue.approve_job(
|
| 685 |
+
str(jid).strip(),
|
| 686 |
+
secret,
|
| 687 |
+
run_custom_script=bool(run_custom),
|
| 688 |
+
eval_conditions=list(eval_conds or []),
|
| 689 |
+
)
|
| 690 |
color = "green" if ok else "red"
|
| 691 |
return _mod_bundle(f"<p style='color:{color}'>{msg}</p>")
|
| 692 |
|
| 693 |
def _mod_reject(secret, jid, unlocked):
|
| 694 |
return _mod_run(job_queue.reject_job, secret, jid, "Select a job.", unlocked)
|
| 695 |
|
| 696 |
+
def _mod_retry(secret, jid, unlocked, eval_conds):
|
| 697 |
+
if not unlocked:
|
| 698 |
+
return _mod_bundle("<p style='color:red'>Unlock moderator tools first.</p>")
|
| 699 |
+
if not jid or not str(jid).strip():
|
| 700 |
+
return _mod_bundle("<p style='color:red'>Select a job in Retry / remove.</p>")
|
| 701 |
+
ok, msg = job_queue.retry_failed_job(
|
| 702 |
+
str(jid).strip(),
|
| 703 |
secret,
|
| 704 |
+
eval_conditions=list(eval_conds or []),
|
|
|
|
|
|
|
| 705 |
)
|
| 706 |
+
color = "green" if ok else "red"
|
| 707 |
+
return _mod_bundle(f"<p style='color:{color}'>{msg}</p>")
|
| 708 |
|
| 709 |
def _mod_remove(secret, jid, unlocked):
|
| 710 |
return _mod_run(
|
|
|
|
| 759 |
)
|
| 760 |
approve_btn.click(
|
| 761 |
fn=_mod_approve,
|
| 762 |
+
inputs=[
|
| 763 |
+
mod_secret,
|
| 764 |
+
job_pick,
|
| 765 |
+
mod_unlocked,
|
| 766 |
+
mod_run_custom_script,
|
| 767 |
+
mod_eval_conditions,
|
| 768 |
+
],
|
| 769 |
outputs=_MOD_OUTS,
|
| 770 |
)
|
| 771 |
reject_btn.click(
|
|
|
|
| 775 |
)
|
| 776 |
retry_job_btn.click(
|
| 777 |
fn=_mod_retry,
|
| 778 |
+
inputs=[mod_secret, mod_action_job, mod_unlocked, mod_eval_conditions],
|
| 779 |
outputs=_MOD_OUTS,
|
| 780 |
)
|
| 781 |
remove_job_btn.click(
|
benchmark/__init__.py
CHANGED
|
@@ -1,17 +1,25 @@
|
|
| 1 |
"""Shared evaluation data loading and metrics (WER over packed tensors)."""
|
| 2 |
|
| 3 |
from .dataset import (
|
| 4 |
-
|
| 5 |
CONDITION_WER_SUFFIX,
|
|
|
|
|
|
|
| 6 |
evaluate_condition_wer,
|
|
|
|
| 7 |
load_packed_condition,
|
|
|
|
| 8 |
wer_result_key,
|
| 9 |
)
|
| 10 |
|
| 11 |
__all__ = [
|
| 12 |
-
"
|
| 13 |
"CONDITION_WER_SUFFIX",
|
|
|
|
|
|
|
| 14 |
"evaluate_condition_wer",
|
|
|
|
| 15 |
"load_packed_condition",
|
|
|
|
| 16 |
"wer_result_key",
|
| 17 |
]
|
|
|
|
| 1 |
"""Shared evaluation data loading and metrics (WER over packed tensors)."""
|
| 2 |
|
| 3 |
from .dataset import (
|
| 4 |
+
CONDITION_UI_CHOICES,
|
| 5 |
CONDITION_WER_SUFFIX,
|
| 6 |
+
DEFAULT_CONDITION_KEYS,
|
| 7 |
+
PACKED_FILES,
|
| 8 |
evaluate_condition_wer,
|
| 9 |
+
is_full_condition_set,
|
| 10 |
load_packed_condition,
|
| 11 |
+
resolve_condition_keys,
|
| 12 |
wer_result_key,
|
| 13 |
)
|
| 14 |
|
| 15 |
__all__ = [
|
| 16 |
+
"CONDITION_UI_CHOICES",
|
| 17 |
"CONDITION_WER_SUFFIX",
|
| 18 |
+
"DEFAULT_CONDITION_KEYS",
|
| 19 |
+
"PACKED_FILES",
|
| 20 |
"evaluate_condition_wer",
|
| 21 |
+
"is_full_condition_set",
|
| 22 |
"load_packed_condition",
|
| 23 |
+
"resolve_condition_keys",
|
| 24 |
"wer_result_key",
|
| 25 |
]
|
benchmark/dataset.py
CHANGED
|
@@ -37,6 +37,31 @@ def wer_result_key(condition_key: str) -> str:
|
|
| 37 |
return f"wer_{suffix}"
|
| 38 |
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
_dataset_cache: dict[str, dict] = {}
|
| 41 |
|
| 42 |
|
|
|
|
| 37 |
return f"wer_{suffix}"
|
| 38 |
|
| 39 |
|
| 40 |
+
# Gradio checkbox labels (display, condition_key).
|
| 41 |
+
CONDITION_UI_CHOICES: tuple[tuple[str, str], ...] = (
|
| 42 |
+
("Dry Speech", "clean"),
|
| 43 |
+
("Lab Measured", "measured"),
|
| 44 |
+
("Lab Simulated", "sim"),
|
| 45 |
+
("High SNR", "high"),
|
| 46 |
+
("Mid SNR", "mid"),
|
| 47 |
+
("Low SNR", "low"),
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
DEFAULT_CONDITION_KEYS: tuple[str, ...] = tuple(PACKED_FILES.keys())
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def resolve_condition_keys(keys: list[str] | None) -> tuple[str, ...]:
|
| 54 |
+
"""Return validated condition keys; ``None`` or empty means all packed splits."""
|
| 55 |
+
if not keys:
|
| 56 |
+
return DEFAULT_CONDITION_KEYS
|
| 57 |
+
valid = [k for k in keys if k in PACKED_FILES]
|
| 58 |
+
return tuple(valid) if valid else DEFAULT_CONDITION_KEYS
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def is_full_condition_set(keys: list[str] | None) -> bool:
|
| 62 |
+
return set(resolve_condition_keys(keys)) == set(PACKED_FILES.keys())
|
| 63 |
+
|
| 64 |
+
|
| 65 |
_dataset_cache: dict[str, dict] = {}
|
| 66 |
|
| 67 |
|
evaluation/orchestrator.py
CHANGED
|
@@ -10,10 +10,11 @@ from typing import Any
|
|
| 10 |
import torch
|
| 11 |
|
| 12 |
from benchmark.dataset import (
|
| 13 |
-
PACKED_FILES,
|
| 14 |
accumulate_predictions_for_slice,
|
| 15 |
evaluate_condition_wer_timed,
|
|
|
|
| 16 |
load_packed_condition,
|
|
|
|
| 17 |
wer_result_key,
|
| 18 |
)
|
| 19 |
from backends.registry import build_transcriber, resolve_label
|
|
@@ -110,13 +111,15 @@ def _run_evaluation_core(
|
|
| 110 |
family_id: str = "auto",
|
| 111 |
progress_cb: Callable[[int, int, str], None] | None = None,
|
| 112 |
custom_script: str = "",
|
|
|
|
| 113 |
) -> dict:
|
| 114 |
device_str, device_int = resolve_eval_devices()
|
|
|
|
| 115 |
|
| 116 |
# Precompute totals so the progress bar has a known denominator from the start.
|
| 117 |
condition_totals: dict[str, int] = {}
|
| 118 |
grand_total = 0
|
| 119 |
-
for ck in
|
| 120 |
try:
|
| 121 |
condition_totals[ck] = len(load_packed_condition(ck))
|
| 122 |
except Exception:
|
|
@@ -146,7 +149,7 @@ def _run_evaluation_core(
|
|
| 146 |
total_infer_s = 0.0
|
| 147 |
done_so_far = 0
|
| 148 |
|
| 149 |
-
for condition_key in
|
| 150 |
condition_base = done_so_far
|
| 151 |
|
| 152 |
def _sample_cb(ck: str, i: int, _n: int, _base: int = condition_base) -> None:
|
|
@@ -166,6 +169,8 @@ def _run_evaluation_core(
|
|
| 166 |
total_infer_s += infer_s
|
| 167 |
done_so_far += count
|
| 168 |
|
|
|
|
|
|
|
| 169 |
results["num_samples"] = total_samples
|
| 170 |
results["eval_audio_seconds"] = round(total_audio_s, 3)
|
| 171 |
results["eval_wall_time_s"] = round(total_infer_s, 3)
|
|
@@ -194,6 +199,7 @@ def _run_evaluation_core_segmented_local(
|
|
| 194 |
*,
|
| 195 |
segment_samples: int,
|
| 196 |
custom_script: str = "",
|
|
|
|
| 197 |
) -> dict:
|
| 198 |
"""
|
| 199 |
Same semantics as ``_run_evaluation_core_segmented`` but without ``spaces.GPU``:
|
|
@@ -201,9 +207,10 @@ def _run_evaluation_core_segmented_local(
|
|
| 201 |
"""
|
| 202 |
from jiwer import wer as compute_wer
|
| 203 |
|
|
|
|
| 204 |
condition_totals: dict[str, int] = {}
|
| 205 |
grand_total = 0
|
| 206 |
-
for ck in
|
| 207 |
try:
|
| 208 |
condition_totals[ck] = len(load_packed_condition(ck))
|
| 209 |
except Exception:
|
|
@@ -227,7 +234,7 @@ def _run_evaluation_core_segmented_local(
|
|
| 227 |
done_so_far = 0
|
| 228 |
first_num_params: int | None = None
|
| 229 |
|
| 230 |
-
for condition_key in
|
| 231 |
data = load_packed_condition(condition_key)
|
| 232 |
n_items = len(data)
|
| 233 |
all_p: list[str] = []
|
|
@@ -270,6 +277,8 @@ def _run_evaluation_core_segmented_local(
|
|
| 270 |
total_samples = max(total_samples, len(all_p))
|
| 271 |
|
| 272 |
results["num_params"] = first_num_params or 0
|
|
|
|
|
|
|
| 273 |
results["num_samples"] = total_samples
|
| 274 |
results["eval_audio_seconds"] = round(total_audio_s, 3)
|
| 275 |
results["eval_wall_time_s"] = round(total_infer_s, 3)
|
|
@@ -295,6 +304,7 @@ def _run_evaluation_core_segmented(
|
|
| 295 |
gpu_duration_s: int,
|
| 296 |
gpu_size: str,
|
| 297 |
custom_script: str = "",
|
|
|
|
| 298 |
) -> dict:
|
| 299 |
"""
|
| 300 |
Long evaluations: each slice of ``segment_samples`` runs under its own ``spaces.GPU``
|
|
@@ -304,9 +314,10 @@ def _run_evaluation_core_segmented(
|
|
| 304 |
|
| 305 |
import spaces
|
| 306 |
|
|
|
|
| 307 |
condition_totals: dict[str, int] = {}
|
| 308 |
grand_total = 0
|
| 309 |
-
for ck in
|
| 310 |
try:
|
| 311 |
condition_totals[ck] = len(load_packed_condition(ck))
|
| 312 |
except Exception:
|
|
@@ -332,7 +343,7 @@ def _run_evaluation_core_segmented(
|
|
| 332 |
done_so_far = 0
|
| 333 |
first_num_params: int | None = None
|
| 334 |
|
| 335 |
-
for condition_key in
|
| 336 |
data = load_packed_condition(condition_key)
|
| 337 |
n_items = len(data)
|
| 338 |
all_p: list[str] = []
|
|
@@ -375,6 +386,8 @@ def _run_evaluation_core_segmented(
|
|
| 375 |
total_samples = max(total_samples, len(all_p))
|
| 376 |
|
| 377 |
results["num_params"] = first_num_params or 0
|
|
|
|
|
|
|
| 378 |
results["num_samples"] = total_samples
|
| 379 |
results["eval_audio_seconds"] = round(total_audio_s, 3)
|
| 380 |
results["eval_wall_time_s"] = round(total_infer_s, 3)
|
|
@@ -410,6 +423,7 @@ def run_evaluation(
|
|
| 410 |
family_id: str = "auto",
|
| 411 |
progress_cb: Callable[[int, int, str], None] | None = None,
|
| 412 |
custom_script: str = "",
|
|
|
|
| 413 |
) -> dict:
|
| 414 |
"""
|
| 415 |
Evaluate `model_id` on all packed conditions using the selected family backend.
|
|
@@ -455,12 +469,14 @@ def run_evaluation(
|
|
| 455 |
progress_cb=progress_cb,
|
| 456 |
segment_samples=seg,
|
| 457 |
custom_script=custom_script,
|
|
|
|
| 458 |
)
|
| 459 |
return _run_evaluation_core(
|
| 460 |
model_id,
|
| 461 |
family_id=family_id,
|
| 462 |
progress_cb=progress_cb,
|
| 463 |
custom_script=custom_script,
|
|
|
|
| 464 |
)
|
| 465 |
|
| 466 |
eff = _effective_gpu_duration_s()
|
|
@@ -475,6 +491,7 @@ def run_evaluation(
|
|
| 475 |
family_id=family_id,
|
| 476 |
progress_cb=progress_cb,
|
| 477 |
custom_script=custom_script,
|
|
|
|
| 478 |
)
|
| 479 |
|
| 480 |
return _run_evaluation_core_segmented(
|
|
@@ -485,4 +502,5 @@ def run_evaluation(
|
|
| 485 |
gpu_duration_s=eff,
|
| 486 |
gpu_size=size,
|
| 487 |
custom_script=custom_script,
|
|
|
|
| 488 |
)
|
|
|
|
| 10 |
import torch
|
| 11 |
|
| 12 |
from benchmark.dataset import (
|
|
|
|
| 13 |
accumulate_predictions_for_slice,
|
| 14 |
evaluate_condition_wer_timed,
|
| 15 |
+
is_full_condition_set,
|
| 16 |
load_packed_condition,
|
| 17 |
+
resolve_condition_keys,
|
| 18 |
wer_result_key,
|
| 19 |
)
|
| 20 |
from backends.registry import build_transcriber, resolve_label
|
|
|
|
| 111 |
family_id: str = "auto",
|
| 112 |
progress_cb: Callable[[int, int, str], None] | None = None,
|
| 113 |
custom_script: str = "",
|
| 114 |
+
condition_keys: list[str] | None = None,
|
| 115 |
) -> dict:
|
| 116 |
device_str, device_int = resolve_eval_devices()
|
| 117 |
+
keys = resolve_condition_keys(condition_keys)
|
| 118 |
|
| 119 |
# Precompute totals so the progress bar has a known denominator from the start.
|
| 120 |
condition_totals: dict[str, int] = {}
|
| 121 |
grand_total = 0
|
| 122 |
+
for ck in keys:
|
| 123 |
try:
|
| 124 |
condition_totals[ck] = len(load_packed_condition(ck))
|
| 125 |
except Exception:
|
|
|
|
| 149 |
total_infer_s = 0.0
|
| 150 |
done_so_far = 0
|
| 151 |
|
| 152 |
+
for condition_key in keys:
|
| 153 |
condition_base = done_so_far
|
| 154 |
|
| 155 |
def _sample_cb(ck: str, i: int, _n: int, _base: int = condition_base) -> None:
|
|
|
|
| 169 |
total_infer_s += infer_s
|
| 170 |
done_so_far += count
|
| 171 |
|
| 172 |
+
results["eval_conditions"] = list(keys)
|
| 173 |
+
results["eval_full_run"] = is_full_condition_set(condition_keys)
|
| 174 |
results["num_samples"] = total_samples
|
| 175 |
results["eval_audio_seconds"] = round(total_audio_s, 3)
|
| 176 |
results["eval_wall_time_s"] = round(total_infer_s, 3)
|
|
|
|
| 199 |
*,
|
| 200 |
segment_samples: int,
|
| 201 |
custom_script: str = "",
|
| 202 |
+
condition_keys: list[str] | None = None,
|
| 203 |
) -> dict:
|
| 204 |
"""
|
| 205 |
Same semantics as ``_run_evaluation_core_segmented`` but without ``spaces.GPU``:
|
|
|
|
| 207 |
"""
|
| 208 |
from jiwer import wer as compute_wer
|
| 209 |
|
| 210 |
+
keys = resolve_condition_keys(condition_keys)
|
| 211 |
condition_totals: dict[str, int] = {}
|
| 212 |
grand_total = 0
|
| 213 |
+
for ck in keys:
|
| 214 |
try:
|
| 215 |
condition_totals[ck] = len(load_packed_condition(ck))
|
| 216 |
except Exception:
|
|
|
|
| 234 |
done_so_far = 0
|
| 235 |
first_num_params: int | None = None
|
| 236 |
|
| 237 |
+
for condition_key in keys:
|
| 238 |
data = load_packed_condition(condition_key)
|
| 239 |
n_items = len(data)
|
| 240 |
all_p: list[str] = []
|
|
|
|
| 277 |
total_samples = max(total_samples, len(all_p))
|
| 278 |
|
| 279 |
results["num_params"] = first_num_params or 0
|
| 280 |
+
results["eval_conditions"] = list(keys)
|
| 281 |
+
results["eval_full_run"] = is_full_condition_set(condition_keys)
|
| 282 |
results["num_samples"] = total_samples
|
| 283 |
results["eval_audio_seconds"] = round(total_audio_s, 3)
|
| 284 |
results["eval_wall_time_s"] = round(total_infer_s, 3)
|
|
|
|
| 304 |
gpu_duration_s: int,
|
| 305 |
gpu_size: str,
|
| 306 |
custom_script: str = "",
|
| 307 |
+
condition_keys: list[str] | None = None,
|
| 308 |
) -> dict:
|
| 309 |
"""
|
| 310 |
Long evaluations: each slice of ``segment_samples`` runs under its own ``spaces.GPU``
|
|
|
|
| 314 |
|
| 315 |
import spaces
|
| 316 |
|
| 317 |
+
keys = resolve_condition_keys(condition_keys)
|
| 318 |
condition_totals: dict[str, int] = {}
|
| 319 |
grand_total = 0
|
| 320 |
+
for ck in keys:
|
| 321 |
try:
|
| 322 |
condition_totals[ck] = len(load_packed_condition(ck))
|
| 323 |
except Exception:
|
|
|
|
| 343 |
done_so_far = 0
|
| 344 |
first_num_params: int | None = None
|
| 345 |
|
| 346 |
+
for condition_key in keys:
|
| 347 |
data = load_packed_condition(condition_key)
|
| 348 |
n_items = len(data)
|
| 349 |
all_p: list[str] = []
|
|
|
|
| 386 |
total_samples = max(total_samples, len(all_p))
|
| 387 |
|
| 388 |
results["num_params"] = first_num_params or 0
|
| 389 |
+
results["eval_conditions"] = list(keys)
|
| 390 |
+
results["eval_full_run"] = is_full_condition_set(condition_keys)
|
| 391 |
results["num_samples"] = total_samples
|
| 392 |
results["eval_audio_seconds"] = round(total_audio_s, 3)
|
| 393 |
results["eval_wall_time_s"] = round(total_infer_s, 3)
|
|
|
|
| 423 |
family_id: str = "auto",
|
| 424 |
progress_cb: Callable[[int, int, str], None] | None = None,
|
| 425 |
custom_script: str = "",
|
| 426 |
+
condition_keys: list[str] | None = None,
|
| 427 |
) -> dict:
|
| 428 |
"""
|
| 429 |
Evaluate `model_id` on all packed conditions using the selected family backend.
|
|
|
|
| 469 |
progress_cb=progress_cb,
|
| 470 |
segment_samples=seg,
|
| 471 |
custom_script=custom_script,
|
| 472 |
+
condition_keys=condition_keys,
|
| 473 |
)
|
| 474 |
return _run_evaluation_core(
|
| 475 |
model_id,
|
| 476 |
family_id=family_id,
|
| 477 |
progress_cb=progress_cb,
|
| 478 |
custom_script=custom_script,
|
| 479 |
+
condition_keys=condition_keys,
|
| 480 |
)
|
| 481 |
|
| 482 |
eff = _effective_gpu_duration_s()
|
|
|
|
| 491 |
family_id=family_id,
|
| 492 |
progress_cb=progress_cb,
|
| 493 |
custom_script=custom_script,
|
| 494 |
+
condition_keys=condition_keys,
|
| 495 |
)
|
| 496 |
|
| 497 |
return _run_evaluation_core_segmented(
|
|
|
|
| 502 |
gpu_duration_s=eff,
|
| 503 |
gpu_size=size,
|
| 504 |
custom_script=custom_script,
|
| 505 |
+
condition_keys=condition_keys,
|
| 506 |
)
|
init.py
CHANGED
|
@@ -139,6 +139,63 @@ def leaderboard_row_from_eval_result(
|
|
| 139 |
return row
|
| 140 |
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
# ---------------------------------------------------------------------------
|
| 143 |
# Storage helpers
|
| 144 |
# ---------------------------------------------------------------------------
|
|
|
|
| 139 |
return row
|
| 140 |
|
| 141 |
|
| 142 |
+
def merge_leaderboard_row_from_eval_result(
|
| 143 |
+
result: dict,
|
| 144 |
+
submitted_at_iso: str,
|
| 145 |
+
submission_notes: str = "",
|
| 146 |
+
) -> None:
|
| 147 |
+
"""
|
| 148 |
+
Merge evaluation output into an existing leaderboard row (partial re-eval).
|
| 149 |
+
|
| 150 |
+
Only scenario WER columns and ``eval_family`` / ``num_params`` present in
|
| 151 |
+
``result`` are overwritten; other metrics and timing fields are kept.
|
| 152 |
+
Creates a new row if the model is not on the leaderboard yet.
|
| 153 |
+
"""
|
| 154 |
+
from metrics_config import SCENARIO_KEYS
|
| 155 |
+
|
| 156 |
+
model_id = str(result.get("model_id", "")).strip()
|
| 157 |
+
if not model_id:
|
| 158 |
+
raise ValueError("merge_leaderboard_row_from_eval_result requires model_id")
|
| 159 |
+
|
| 160 |
+
rows = load_raw_results()
|
| 161 |
+
match_idx: int | None = None
|
| 162 |
+
for i, r in enumerate(rows):
|
| 163 |
+
if (r.get("model_id") or "").strip() == model_id:
|
| 164 |
+
match_idx = i
|
| 165 |
+
break
|
| 166 |
+
|
| 167 |
+
patch = leaderboard_row_from_eval_result(
|
| 168 |
+
result, submitted_at_iso, submission_notes=submission_notes
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
if match_idx is None:
|
| 172 |
+
normalize_legacy_csv_row(patch)
|
| 173 |
+
rows.append(patch)
|
| 174 |
+
else:
|
| 175 |
+
row = dict(rows[match_idx])
|
| 176 |
+
for k in SCENARIO_KEYS:
|
| 177 |
+
v = patch.get(k)
|
| 178 |
+
if v is not None and str(v).strip() != "":
|
| 179 |
+
row[k] = v
|
| 180 |
+
if patch.get("eval_family"):
|
| 181 |
+
row["eval_family"] = patch["eval_family"]
|
| 182 |
+
if patch.get("num_params"):
|
| 183 |
+
row["num_params"] = patch["num_params"]
|
| 184 |
+
if (submission_notes or "").strip():
|
| 185 |
+
row["submission_notes"] = patch["submission_notes"]
|
| 186 |
+
normalize_legacy_csv_row(row)
|
| 187 |
+
rows[match_idx] = row
|
| 188 |
+
|
| 189 |
+
from analytics import compute_far_field_score_map
|
| 190 |
+
|
| 191 |
+
scores = compute_far_field_score_map(rows)
|
| 192 |
+
rows.sort(
|
| 193 |
+
key=lambda r: (-scores.get((r.get("model_id") or "").strip(), 0.0), r.get("model_id") or "")
|
| 194 |
+
)
|
| 195 |
+
save_raw_results(rows)
|
| 196 |
+
invalidate_results_cache()
|
| 197 |
+
|
| 198 |
+
|
| 199 |
# ---------------------------------------------------------------------------
|
| 200 |
# Storage helpers
|
| 201 |
# ---------------------------------------------------------------------------
|
job_queue.py
CHANGED
|
@@ -74,6 +74,8 @@ class Job:
|
|
| 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] = {}
|
|
@@ -110,6 +112,7 @@ _JOBS_CSV_FIELDS = [
|
|
| 110 |
"run_custom_script",
|
| 111 |
"hf_remote_job_id",
|
| 112 |
"remote_artifact_path",
|
|
|
|
| 113 |
]
|
| 114 |
|
| 115 |
|
|
@@ -258,6 +261,9 @@ def _persist_jobs() -> None:
|
|
| 258 |
"run_custom_script": _bool_to_csv(j.run_custom_script),
|
| 259 |
"hf_remote_job_id": (j.hf_remote_job_id or "").strip(),
|
| 260 |
"remote_artifact_path": (j.remote_artifact_path or "").strip(),
|
|
|
|
|
|
|
|
|
|
| 261 |
}
|
| 262 |
)
|
| 263 |
rows.sort(key=lambda r: r["created_at"])
|
|
@@ -329,6 +335,7 @@ def _load_persisted_jobs_once() -> None:
|
|
| 329 |
fid = (row.get("family_id") or "").strip()
|
| 330 |
hf_rid = (row.get("hf_remote_job_id") or "").strip() or None
|
| 331 |
art_path = (row.get("remote_artifact_path") or "").strip() or None
|
|
|
|
| 332 |
if not mid:
|
| 333 |
continue
|
| 334 |
|
|
@@ -357,6 +364,7 @@ def _load_persisted_jobs_once() -> None:
|
|
| 357 |
run_custom_script=run_custom,
|
| 358 |
hf_remote_job_id=hf_rid,
|
| 359 |
remote_artifact_path=art_path,
|
|
|
|
| 360 |
)
|
| 361 |
_jobs[jid] = job
|
| 362 |
|
|
@@ -488,15 +496,23 @@ def _merge_eval_result_to_leaderboard(
|
|
| 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:
|
|
@@ -514,6 +530,55 @@ def _merge_eval_result_to_leaderboard(
|
|
| 514 |
invalidate_results_cache()
|
| 515 |
|
| 516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
def _load_artifact_json_from_bucket(artifact_path: str) -> dict:
|
| 518 |
"""Download and parse a remote eval JSON artifact from the Hub bucket."""
|
| 519 |
from storage import STORAGE_BACKEND, download_bucket_file
|
|
@@ -727,6 +792,7 @@ def _remote_dispatch_job(job_id: str, mid: str, fid: str, jobs_token: str) -> st
|
|
| 727 |
extra_req = (j_dispatch.extra_requirements or "") if j_dispatch else ""
|
| 728 |
custom_script = (j_dispatch.custom_script or "") if j_dispatch else ""
|
| 729 |
run_custom = bool(j_dispatch and j_dispatch.run_custom_script)
|
|
|
|
| 730 |
|
| 731 |
info = remote_jobs.submit_eval_job(
|
| 732 |
model_id=mid,
|
|
@@ -737,6 +803,7 @@ def _remote_dispatch_job(job_id: str, mid: str, fid: str, jobs_token: str) -> st
|
|
| 737 |
extra_requirements=extra_req,
|
| 738 |
custom_script=custom_script,
|
| 739 |
run_custom_script=run_custom,
|
|
|
|
| 740 |
)
|
| 741 |
hf_id = info.id
|
| 742 |
with _jobs_lock:
|
|
@@ -776,7 +843,8 @@ def _remote_start_queued_job(job_id: str, jobs_token: str) -> None:
|
|
| 776 |
with _jobs_lock:
|
| 777 |
j_chk = _jobs.get(job_id)
|
| 778 |
replace_lb = bool(j_chk and j_chk.replace_leaderboard)
|
| 779 |
-
|
|
|
|
| 780 |
_fail_job(job_id, "Model already on leaderboard (skipped duplicate race).")
|
| 781 |
return
|
| 782 |
|
|
@@ -836,8 +904,13 @@ def _remote_tick_job(job_id: str, jobs_token: str) -> None:
|
|
| 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,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 841 |
)
|
| 842 |
with _jobs_lock:
|
| 843 |
j_clr = _jobs.get(job_id)
|
|
@@ -1026,7 +1099,11 @@ def enqueue(
|
|
| 1026 |
|
| 1027 |
|
| 1028 |
def approve_job(
|
| 1029 |
-
job_id: str,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1030 |
) -> tuple[bool, str]:
|
| 1031 |
"""Move a pending job into the execution queue (moderator only)."""
|
| 1032 |
if not moderation_active():
|
|
@@ -1037,6 +1114,11 @@ def approve_job(
|
|
| 1037 |
if not job_id:
|
| 1038 |
return False, "Enter a job ID."
|
| 1039 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1040 |
with _jobs_lock:
|
| 1041 |
job = _jobs.get(job_id)
|
| 1042 |
if not job or job.status != JobStatus.pending_moderation:
|
|
@@ -1049,6 +1131,8 @@ def approve_job(
|
|
| 1049 |
job.run_custom_script = bool(run_custom_script)
|
| 1050 |
else:
|
| 1051 |
job.run_custom_script = False
|
|
|
|
|
|
|
| 1052 |
_touch(job)
|
| 1053 |
|
| 1054 |
_work_queue.put(job_id)
|
|
@@ -1056,13 +1140,16 @@ def approve_job(
|
|
| 1056 |
_ensure_worker()
|
| 1057 |
max_n = remote_max_concurrent_jobs()
|
| 1058 |
custom_note = ""
|
|
|
|
| 1059 |
with _jobs_lock:
|
| 1060 |
j = _jobs.get(job_id)
|
| 1061 |
if j and j.run_custom_script and (j.custom_script or "").strip():
|
| 1062 |
custom_note = " Custom script will run on the Hub Job."
|
|
|
|
|
|
|
| 1063 |
return True, (
|
| 1064 |
f"Approved job {job_id}. Up to {max_n} Hub Jobs may run in parallel; "
|
| 1065 |
-
f"this job starts when a slot is free.{custom_note}"
|
| 1066 |
)
|
| 1067 |
|
| 1068 |
|
|
@@ -1121,7 +1208,12 @@ def _job_can_retry(status: JobStatus) -> bool:
|
|
| 1121 |
return status in (JobStatus.failed, JobStatus.done, JobStatus.queued)
|
| 1122 |
|
| 1123 |
|
| 1124 |
-
def retry_failed_job(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
@@ -1134,6 +1226,11 @@ def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
|
| 1134 |
if not job_id:
|
| 1135 |
return False, "Select a job."
|
| 1136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1137 |
with _jobs_lock:
|
| 1138 |
job = _jobs.get(job_id)
|
| 1139 |
if not job:
|
|
@@ -1146,7 +1243,6 @@ def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
|
| 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
|
|
@@ -1156,17 +1252,27 @@ def retry_failed_job(job_id: str, secret: str) -> tuple[bool, str]:
|
|
| 1156 |
job.progress_done = 0
|
| 1157 |
job.progress_total = 0
|
| 1158 |
job.progress_condition = ""
|
| 1159 |
-
job.
|
|
|
|
| 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 |
|
|
|
|
| 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 |
+
# Subset of PACKED_FILES keys; None means evaluate all conditions.
|
| 78 |
+
eval_conditions: tuple[str, ...] | None = None
|
| 79 |
|
| 80 |
|
| 81 |
_jobs: dict[str, Job] = {}
|
|
|
|
| 112 |
"run_custom_script",
|
| 113 |
"hf_remote_job_id",
|
| 114 |
"remote_artifact_path",
|
| 115 |
+
"eval_conditions",
|
| 116 |
]
|
| 117 |
|
| 118 |
|
|
|
|
| 261 |
"run_custom_script": _bool_to_csv(j.run_custom_script),
|
| 262 |
"hf_remote_job_id": (j.hf_remote_job_id or "").strip(),
|
| 263 |
"remote_artifact_path": (j.remote_artifact_path or "").strip(),
|
| 264 |
+
"eval_conditions": ",".join(j.eval_conditions)
|
| 265 |
+
if j.eval_conditions
|
| 266 |
+
else "",
|
| 267 |
}
|
| 268 |
)
|
| 269 |
rows.sort(key=lambda r: r["created_at"])
|
|
|
|
| 335 |
fid = (row.get("family_id") or "").strip()
|
| 336 |
hf_rid = (row.get("hf_remote_job_id") or "").strip() or None
|
| 337 |
art_path = (row.get("remote_artifact_path") or "").strip() or None
|
| 338 |
+
eval_conds = _parse_eval_conditions_csv(row.get("eval_conditions") or "")
|
| 339 |
if not mid:
|
| 340 |
continue
|
| 341 |
|
|
|
|
| 364 |
run_custom_script=run_custom,
|
| 365 |
hf_remote_job_id=hf_rid,
|
| 366 |
remote_artifact_path=art_path,
|
| 367 |
+
eval_conditions=eval_conds,
|
| 368 |
)
|
| 369 |
_jobs[jid] = job
|
| 370 |
|
|
|
|
| 496 |
submission_notes: str,
|
| 497 |
*,
|
| 498 |
replace_existing: bool = False,
|
| 499 |
+
merge_partial: bool = False,
|
| 500 |
) -> None:
|
| 501 |
from init import (
|
| 502 |
invalidate_results_cache,
|
| 503 |
leaderboard_row_from_eval_result,
|
| 504 |
load_raw_results,
|
| 505 |
+
merge_leaderboard_row_from_eval_result,
|
| 506 |
normalize_legacy_csv_row,
|
| 507 |
save_raw_results,
|
| 508 |
)
|
| 509 |
|
| 510 |
+
if merge_partial:
|
| 511 |
+
merge_leaderboard_row_from_eval_result(
|
| 512 |
+
result, submitted_at_iso, submission_notes=submission_notes
|
| 513 |
+
)
|
| 514 |
+
return
|
| 515 |
+
|
| 516 |
model_id = str(result.get("model_id", "")).strip()
|
| 517 |
rows = load_raw_results()
|
| 518 |
if replace_existing and model_id:
|
|
|
|
| 530 |
invalidate_results_cache()
|
| 531 |
|
| 532 |
|
| 533 |
+
def _parse_eval_conditions_csv(raw: str) -> tuple[str, ...] | None:
|
| 534 |
+
from benchmark.dataset import resolve_condition_keys
|
| 535 |
+
|
| 536 |
+
parts = [p.strip() for p in (raw or "").split(",") if p.strip()]
|
| 537 |
+
if not parts:
|
| 538 |
+
return None
|
| 539 |
+
keys = resolve_condition_keys(parts)
|
| 540 |
+
from benchmark.dataset import PACKED_FILES
|
| 541 |
+
|
| 542 |
+
if set(keys) == set(PACKED_FILES.keys()):
|
| 543 |
+
return None
|
| 544 |
+
return keys
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def _encode_eval_conditions_csv(keys: tuple[str, ...] | None) -> str:
|
| 548 |
+
if not keys:
|
| 549 |
+
return ""
|
| 550 |
+
return ",".join(keys)
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
def normalize_moderator_eval_conditions(
|
| 554 |
+
selected: list[str] | None,
|
| 555 |
+
) -> tuple[str, ...] | None:
|
| 556 |
+
"""``None`` = all packed conditions; otherwise a validated non-empty subset."""
|
| 557 |
+
from benchmark.dataset import PACKED_FILES, resolve_condition_keys
|
| 558 |
+
|
| 559 |
+
if not selected:
|
| 560 |
+
return None
|
| 561 |
+
keys = resolve_condition_keys(selected)
|
| 562 |
+
if not keys:
|
| 563 |
+
raise ValueError("Select at least one dataset to evaluate.")
|
| 564 |
+
if set(keys) == set(PACKED_FILES.keys()):
|
| 565 |
+
return None
|
| 566 |
+
return keys
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
def eval_condition_checkbox_defaults() -> tuple[list[str], list[str]]:
|
| 570 |
+
"""(labels, values) for Gradio CheckboxGroup defaults (all selected)."""
|
| 571 |
+
from benchmark.dataset import CONDITION_UI_CHOICES
|
| 572 |
+
|
| 573 |
+
labels = [lbl for lbl, _ in CONDITION_UI_CHOICES]
|
| 574 |
+
values = [key for _, key in CONDITION_UI_CHOICES]
|
| 575 |
+
return labels, values
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
def _job_is_partial_eval(job: Job | None) -> bool:
|
| 579 |
+
return bool(job and job.eval_conditions)
|
| 580 |
+
|
| 581 |
+
|
| 582 |
def _load_artifact_json_from_bucket(artifact_path: str) -> dict:
|
| 583 |
"""Download and parse a remote eval JSON artifact from the Hub bucket."""
|
| 584 |
from storage import STORAGE_BACKEND, download_bucket_file
|
|
|
|
| 792 |
extra_req = (j_dispatch.extra_requirements or "") if j_dispatch else ""
|
| 793 |
custom_script = (j_dispatch.custom_script or "") if j_dispatch else ""
|
| 794 |
run_custom = bool(j_dispatch and j_dispatch.run_custom_script)
|
| 795 |
+
eval_conds = j_dispatch.eval_conditions if j_dispatch else None
|
| 796 |
|
| 797 |
info = remote_jobs.submit_eval_job(
|
| 798 |
model_id=mid,
|
|
|
|
| 803 |
extra_requirements=extra_req,
|
| 804 |
custom_script=custom_script,
|
| 805 |
run_custom_script=run_custom,
|
| 806 |
+
eval_conditions=eval_conds,
|
| 807 |
)
|
| 808 |
hf_id = info.id
|
| 809 |
with _jobs_lock:
|
|
|
|
| 843 |
with _jobs_lock:
|
| 844 |
j_chk = _jobs.get(job_id)
|
| 845 |
replace_lb = bool(j_chk and j_chk.replace_leaderboard)
|
| 846 |
+
partial = _job_is_partial_eval(j_chk)
|
| 847 |
+
if not replace_lb and not partial and any(r["model_id"] == mid for r in rows):
|
| 848 |
_fail_job(job_id, "Model already on leaderboard (skipped duplicate race).")
|
| 849 |
return
|
| 850 |
|
|
|
|
| 904 |
with _jobs_lock:
|
| 905 |
j_done = _jobs.get(job_id)
|
| 906 |
replace_lb = bool(j_done and j_done.replace_leaderboard)
|
| 907 |
+
partial = _job_is_partial_eval(j_done)
|
| 908 |
_merge_eval_result_to_leaderboard(
|
| 909 |
+
result,
|
| 910 |
+
_now_iso(),
|
| 911 |
+
notes,
|
| 912 |
+
replace_existing=replace_lb and not partial,
|
| 913 |
+
merge_partial=partial,
|
| 914 |
)
|
| 915 |
with _jobs_lock:
|
| 916 |
j_clr = _jobs.get(job_id)
|
|
|
|
| 1099 |
|
| 1100 |
|
| 1101 |
def approve_job(
|
| 1102 |
+
job_id: str,
|
| 1103 |
+
secret: str,
|
| 1104 |
+
*,
|
| 1105 |
+
run_custom_script: bool = False,
|
| 1106 |
+
eval_conditions: list[str] | None = None,
|
| 1107 |
) -> tuple[bool, str]:
|
| 1108 |
"""Move a pending job into the execution queue (moderator only)."""
|
| 1109 |
if not moderation_active():
|
|
|
|
| 1114 |
if not job_id:
|
| 1115 |
return False, "Enter a job ID."
|
| 1116 |
|
| 1117 |
+
try:
|
| 1118 |
+
conds = normalize_moderator_eval_conditions(eval_conditions)
|
| 1119 |
+
except ValueError as e:
|
| 1120 |
+
return False, str(e)
|
| 1121 |
+
|
| 1122 |
with _jobs_lock:
|
| 1123 |
job = _jobs.get(job_id)
|
| 1124 |
if not job or job.status != JobStatus.pending_moderation:
|
|
|
|
| 1131 |
job.run_custom_script = bool(run_custom_script)
|
| 1132 |
else:
|
| 1133 |
job.run_custom_script = False
|
| 1134 |
+
job.eval_conditions = conds
|
| 1135 |
+
job.replace_leaderboard = False
|
| 1136 |
_touch(job)
|
| 1137 |
|
| 1138 |
_work_queue.put(job_id)
|
|
|
|
| 1140 |
_ensure_worker()
|
| 1141 |
max_n = remote_max_concurrent_jobs()
|
| 1142 |
custom_note = ""
|
| 1143 |
+
cond_note = ""
|
| 1144 |
with _jobs_lock:
|
| 1145 |
j = _jobs.get(job_id)
|
| 1146 |
if j and j.run_custom_script and (j.custom_script or "").strip():
|
| 1147 |
custom_note = " Custom script will run on the Hub Job."
|
| 1148 |
+
if j and j.eval_conditions:
|
| 1149 |
+
cond_note = f" Evaluating: {', '.join(j.eval_conditions)} (others unchanged on success)."
|
| 1150 |
return True, (
|
| 1151 |
f"Approved job {job_id}. Up to {max_n} Hub Jobs may run in parallel; "
|
| 1152 |
+
f"this job starts when a slot is free.{custom_note}{cond_note}"
|
| 1153 |
)
|
| 1154 |
|
| 1155 |
|
|
|
|
| 1208 |
return status in (JobStatus.failed, JobStatus.done, JobStatus.queued)
|
| 1209 |
|
| 1210 |
|
| 1211 |
+
def retry_failed_job(
|
| 1212 |
+
job_id: str,
|
| 1213 |
+
secret: str,
|
| 1214 |
+
*,
|
| 1215 |
+
eval_conditions: list[str] | None = None,
|
| 1216 |
+
) -> tuple[bool, str]:
|
| 1217 |
"""Re-queue a job for another evaluation run (moderator only).
|
| 1218 |
|
| 1219 |
Allowed for failed, done, and queued jobs. Successful re-runs replace the
|
|
|
|
| 1226 |
if not job_id:
|
| 1227 |
return False, "Select a job."
|
| 1228 |
|
| 1229 |
+
try:
|
| 1230 |
+
conds = normalize_moderator_eval_conditions(eval_conditions)
|
| 1231 |
+
except ValueError as e:
|
| 1232 |
+
return False, str(e)
|
| 1233 |
+
|
| 1234 |
with _jobs_lock:
|
| 1235 |
job = _jobs.get(job_id)
|
| 1236 |
if not job:
|
|
|
|
| 1243 |
)
|
| 1244 |
if _work_queue.qsize() >= _MAX_QUEUE_BACKLOG:
|
| 1245 |
return False, "Execution queue is full; try again later."
|
|
|
|
| 1246 |
was_queued = job.status == JobStatus.queued
|
| 1247 |
job.status = JobStatus.queued
|
| 1248 |
job.error = None
|
|
|
|
| 1252 |
job.progress_done = 0
|
| 1253 |
job.progress_total = 0
|
| 1254 |
job.progress_condition = ""
|
| 1255 |
+
job.eval_conditions = conds
|
| 1256 |
+
job.replace_leaderboard = conds is None
|
| 1257 |
_touch(job)
|
| 1258 |
|
| 1259 |
if not was_queued:
|
| 1260 |
_work_queue.put(job_id)
|
| 1261 |
_persist_jobs()
|
| 1262 |
_ensure_worker()
|
| 1263 |
+
cond_note = ""
|
| 1264 |
+
with _jobs_lock:
|
| 1265 |
+
j = _jobs.get(job_id)
|
| 1266 |
+
if j and j.eval_conditions:
|
| 1267 |
+
cond_note = (
|
| 1268 |
+
f" Only {', '.join(j.eval_conditions)} will run; "
|
| 1269 |
+
"existing leaderboard WER columns for other datasets are kept."
|
| 1270 |
+
)
|
| 1271 |
+
elif j and j.replace_leaderboard:
|
| 1272 |
+
cond_note = " Full re-eval; the leaderboard row will be replaced on success."
|
| 1273 |
return (
|
| 1274 |
True,
|
| 1275 |
+
f"Re-queued job {job_id}; it will run after jobs ahead of it.{cond_note}"
|
|
|
|
| 1276 |
)
|
| 1277 |
|
| 1278 |
|
remote_jobs.py
CHANGED
|
@@ -153,6 +153,7 @@ def submit_eval_job(
|
|
| 153 |
extra_requirements: str = "",
|
| 154 |
custom_script: str = "",
|
| 155 |
run_custom_script: bool = False,
|
|
|
|
| 156 |
) -> JobInfo:
|
| 157 |
"""Start a Hub UV Job; returns initial ``JobInfo`` (may already be RUNNING)."""
|
| 158 |
namespace = os.environ.get("FFASR_REMOTE_JOB_NAMESPACE", "").strip() or None
|
|
@@ -225,6 +226,10 @@ def submit_eval_job(
|
|
| 225 |
env["FFASR_CUSTOM_SCRIPT_B64"] = base64.b64encode(
|
| 226 |
custom_script.strip().encode("utf-8")
|
| 227 |
).decode("ascii")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
secrets = {"HF_TOKEN": bucket_tok}
|
| 229 |
|
| 230 |
api = HfApi(token=jobs_tok)
|
|
|
|
| 153 |
extra_requirements: str = "",
|
| 154 |
custom_script: str = "",
|
| 155 |
run_custom_script: bool = False,
|
| 156 |
+
eval_conditions: tuple[str, ...] | None = None,
|
| 157 |
) -> JobInfo:
|
| 158 |
"""Start a Hub UV Job; returns initial ``JobInfo`` (may already be RUNNING)."""
|
| 159 |
namespace = os.environ.get("FFASR_REMOTE_JOB_NAMESPACE", "").strip() or None
|
|
|
|
| 226 |
env["FFASR_CUSTOM_SCRIPT_B64"] = base64.b64encode(
|
| 227 |
custom_script.strip().encode("utf-8")
|
| 228 |
).decode("ascii")
|
| 229 |
+
if eval_conditions:
|
| 230 |
+
import json
|
| 231 |
+
|
| 232 |
+
env["FFASR_EVAL_CONDITIONS"] = json.dumps(list(eval_conditions))
|
| 233 |
secrets = {"HF_TOKEN": bucket_tok}
|
| 234 |
|
| 235 |
api = HfApi(token=jobs_tok)
|
scripts/run_hf_remote_job_uv.py
CHANGED
|
@@ -119,11 +119,16 @@ def main() -> int:
|
|
| 119 |
custom_script = (
|
| 120 |
base64.b64decode(custom_b64).decode("utf-8") if custom_b64 else ""
|
| 121 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
result = run_evaluation(
|
| 123 |
model_id,
|
| 124 |
family_id=family_id,
|
| 125 |
progress_cb=None,
|
| 126 |
custom_script=custom_script,
|
|
|
|
| 127 |
)
|
| 128 |
artifact = build_artifact(
|
| 129 |
model_id=model_id,
|
|
|
|
| 119 |
custom_script = (
|
| 120 |
base64.b64decode(custom_b64).decode("utf-8") if custom_b64 else ""
|
| 121 |
)
|
| 122 |
+
cond_raw = os.environ.get("FFASR_EVAL_CONDITIONS", "").strip()
|
| 123 |
+
condition_keys = None
|
| 124 |
+
if cond_raw:
|
| 125 |
+
condition_keys = json.loads(cond_raw)
|
| 126 |
result = run_evaluation(
|
| 127 |
model_id,
|
| 128 |
family_id=family_id,
|
| 129 |
progress_cb=None,
|
| 130 |
custom_script=custom_script,
|
| 131 |
+
condition_keys=condition_keys,
|
| 132 |
)
|
| 133 |
artifact = build_artifact(
|
| 134 |
model_id=model_id,
|