whojavumusic commited on
Commit
dc569ae
·
1 Parent(s): ba264d9

monitor progress

Browse files
Files changed (1) hide show
  1. 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
- result = run_evaluation(
132
- model_id,
133
- family_id=family_id,
134
- progress_cb=None,
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,