Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
dc569ae
1
Parent(s): ba264d9
monitor progress
Browse files- scripts/run_hf_remote_job_uv.py +130 -6
scripts/run_hf_remote_job_uv.py
CHANGED
|
@@ -24,7 +24,10 @@ import shutil
|
|
| 24 |
import subprocess
|
| 25 |
import sys
|
| 26 |
import tempfile
|
|
|
|
|
|
|
| 27 |
import traceback
|
|
|
|
| 28 |
|
| 29 |
|
| 30 |
def _log(msg: str, logs: io.StringIO) -> None:
|
|
@@ -32,6 +35,117 @@ def _log(msg: str, logs: io.StringIO) -> None:
|
|
| 32 |
logs.write(msg + "\n")
|
| 33 |
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
def _ensure_git() -> None:
|
| 36 |
if shutil.which("git"):
|
| 37 |
return
|
|
@@ -128,13 +242,23 @@ def main() -> int:
|
|
| 128 |
condition_keys = None
|
| 129 |
if cond_raw:
|
| 130 |
condition_keys = json.loads(cond_raw)
|
| 131 |
-
|
| 132 |
-
model_id
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
custom_script=custom_script,
|
| 136 |
-
condition_keys=condition_keys,
|
| 137 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
artifact = build_artifact(
|
| 139 |
model_id=model_id,
|
| 140 |
family_id=family_id,
|
|
|
|
| 24 |
import subprocess
|
| 25 |
import sys
|
| 26 |
import tempfile
|
| 27 |
+
import threading
|
| 28 |
+
import time
|
| 29 |
import traceback
|
| 30 |
+
from typing import Callable
|
| 31 |
|
| 32 |
|
| 33 |
def _log(msg: str, logs: io.StringIO) -> None:
|
|
|
|
| 35 |
logs.write(msg + "\n")
|
| 36 |
|
| 37 |
|
| 38 |
+
def _progress_interval_s() -> float:
|
| 39 |
+
"""Heartbeat cadence (seconds) for the remote progress reporter."""
|
| 40 |
+
raw = os.environ.get("FFASR_REMOTE_PROGRESS_INTERVAL_S", "30").strip()
|
| 41 |
+
try:
|
| 42 |
+
v = float(raw) if raw else 30.0
|
| 43 |
+
except ValueError:
|
| 44 |
+
v = 30.0
|
| 45 |
+
return max(2.0, min(v, 600.0))
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _format_eta(seconds: float | None) -> str:
|
| 49 |
+
if seconds is None or seconds == float("inf"):
|
| 50 |
+
return "?"
|
| 51 |
+
if seconds < 60.0:
|
| 52 |
+
return f"{seconds:.0f}s"
|
| 53 |
+
if seconds < 3600.0:
|
| 54 |
+
return f"{seconds/60.0:.1f}m"
|
| 55 |
+
return f"{seconds/3600.0:.2f}h"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _make_progress_reporter() -> tuple[Callable[[int, int, str], None], Callable[[], None]]:
|
| 59 |
+
"""
|
| 60 |
+
Return ``(progress_cb, stop)``.
|
| 61 |
+
|
| 62 |
+
``progress_cb(done, total, condition)`` is the callback passed to ``run_evaluation``.
|
| 63 |
+
A daemon thread emits a heartbeat line periodically (``FFASR_REMOTE_PROGRESS_INTERVAL_S``,
|
| 64 |
+
default 30s) so the Hub Jobs log shows liveness even while the model is loading
|
| 65 |
+
(no per-sample callback fires during that phase).
|
| 66 |
+
|
| 67 |
+
Each emitted line is prefixed with ``[ffasr-progress]`` so it's easy to grep in the
|
| 68 |
+
streaming Hub log.
|
| 69 |
+
"""
|
| 70 |
+
started = time.monotonic()
|
| 71 |
+
state = {
|
| 72 |
+
"done": 0,
|
| 73 |
+
"total": 0,
|
| 74 |
+
"condition": "",
|
| 75 |
+
"last_emit": 0.0,
|
| 76 |
+
"saw_sample": False,
|
| 77 |
+
"finished": False,
|
| 78 |
+
}
|
| 79 |
+
stop_event = threading.Event()
|
| 80 |
+
lock = threading.Lock()
|
| 81 |
+
interval = _progress_interval_s()
|
| 82 |
+
|
| 83 |
+
def _format(now: float) -> str:
|
| 84 |
+
elapsed = now - started
|
| 85 |
+
done, total = state["done"], state["total"]
|
| 86 |
+
cond = state["condition"] or "-"
|
| 87 |
+
if state["saw_sample"] and total > 0:
|
| 88 |
+
pct = 100.0 * done / total
|
| 89 |
+
rate = done / elapsed if elapsed > 0 else 0.0
|
| 90 |
+
eta = (total - done) / rate if rate > 0 else None
|
| 91 |
+
return (
|
| 92 |
+
f"[ffasr-progress] {done}/{total} ({pct:.1f}%) "
|
| 93 |
+
f"condition={cond} elapsed={elapsed:.0f}s "
|
| 94 |
+
f"rate={rate:.2f} samples/s ETA={_format_eta(eta)}"
|
| 95 |
+
)
|
| 96 |
+
if total > 0:
|
| 97 |
+
return (
|
| 98 |
+
f"[ffasr-progress] loading model / first sample pending "
|
| 99 |
+
f"(total={total} samples) elapsed={elapsed:.0f}s"
|
| 100 |
+
)
|
| 101 |
+
return f"[ffasr-progress] starting up… elapsed={elapsed:.0f}s"
|
| 102 |
+
|
| 103 |
+
def _emit_locked(now: float) -> None:
|
| 104 |
+
state["last_emit"] = now
|
| 105 |
+
print(_format(now), flush=True)
|
| 106 |
+
|
| 107 |
+
def cb(done: int, total: int, condition: str) -> None:
|
| 108 |
+
now = time.monotonic()
|
| 109 |
+
with lock:
|
| 110 |
+
state["done"] = int(done)
|
| 111 |
+
state["total"] = int(total)
|
| 112 |
+
state["condition"] = str(condition or "")
|
| 113 |
+
if done > 0:
|
| 114 |
+
state["saw_sample"] = True
|
| 115 |
+
# Always emit on boundaries (start, condition change, finish); otherwise
|
| 116 |
+
# throttle to ~5s so the log isn't flooded for fast models.
|
| 117 |
+
since_last = now - state["last_emit"]
|
| 118 |
+
at_boundary = (
|
| 119 |
+
done == 0
|
| 120 |
+
or (total > 0 and done == total)
|
| 121 |
+
or state["last_emit"] == 0.0
|
| 122 |
+
)
|
| 123 |
+
if at_boundary or since_last >= 5.0:
|
| 124 |
+
_emit_locked(now)
|
| 125 |
+
|
| 126 |
+
def heartbeat() -> None:
|
| 127 |
+
# Emit at least one line per `interval` seconds even when no sample callback
|
| 128 |
+
# fires (long model loads, slow first transcription, hung backend).
|
| 129 |
+
while not stop_event.wait(interval):
|
| 130 |
+
now = time.monotonic()
|
| 131 |
+
with lock:
|
| 132 |
+
if state["finished"]:
|
| 133 |
+
return
|
| 134 |
+
# Only emit if we haven't already emitted recently from the callback.
|
| 135 |
+
if now - state["last_emit"] >= interval - 1.0:
|
| 136 |
+
_emit_locked(now)
|
| 137 |
+
|
| 138 |
+
t = threading.Thread(target=heartbeat, name="ffasr-progress-heartbeat", daemon=True)
|
| 139 |
+
t.start()
|
| 140 |
+
|
| 141 |
+
def stop() -> None:
|
| 142 |
+
with lock:
|
| 143 |
+
state["finished"] = True
|
| 144 |
+
stop_event.set()
|
| 145 |
+
|
| 146 |
+
return cb, stop
|
| 147 |
+
|
| 148 |
+
|
| 149 |
def _ensure_git() -> None:
|
| 150 |
if shutil.which("git"):
|
| 151 |
return
|
|
|
|
| 242 |
condition_keys = None
|
| 243 |
if cond_raw:
|
| 244 |
condition_keys = json.loads(cond_raw)
|
| 245 |
+
_log(
|
| 246 |
+
f"[ffasr-progress] beginning evaluation: model={model_id!r} "
|
| 247 |
+
f"family={family_id!r} conditions={condition_keys or 'default-set'}",
|
| 248 |
+
logs,
|
|
|
|
|
|
|
| 249 |
)
|
| 250 |
+
progress_cb, stop_progress = _make_progress_reporter()
|
| 251 |
+
try:
|
| 252 |
+
result = run_evaluation(
|
| 253 |
+
model_id,
|
| 254 |
+
family_id=family_id,
|
| 255 |
+
progress_cb=progress_cb,
|
| 256 |
+
custom_script=custom_script,
|
| 257 |
+
condition_keys=condition_keys,
|
| 258 |
+
)
|
| 259 |
+
finally:
|
| 260 |
+
stop_progress()
|
| 261 |
+
_log("[ffasr-progress] evaluation finished, uploading artifact…", logs)
|
| 262 |
artifact = build_artifact(
|
| 263 |
model_id=model_id,
|
| 264 |
family_id=family_id,
|