whojavumusic commited on
Commit
a65ea12
·
1 Parent(s): 57e0d5a

custom eval update

Browse files
app.py CHANGED
@@ -178,6 +178,15 @@ def submit_model(
178
  if not on_hub:
179
  return styled_error(f"Model '{model_id}' {err_msg}")
180
 
 
 
 
 
 
 
 
 
 
181
  try:
182
  job_id, position, err, awaiting_mod = job_queue.enqueue(
183
  model_id,
@@ -226,18 +235,24 @@ def submit_model(
226
  return styled_warning(err)
227
 
228
  if awaiting_mod:
229
- return styled_message(
230
- f"Request <code>{job_id}</code> recorded for <strong>{model_id}</strong>. "
231
- f"<strong>A moderator must approve</strong> it before evaluation starts "
232
- f"(see the <strong>Moderate</strong> tab). "
233
- f"Approx. backlog awaiting approval: <strong>{position}</strong>."
 
 
 
234
  )
235
 
236
- return styled_message(
237
- f"Queued job <code>{job_id}</code> for <strong>{model_id}</strong>. "
238
- f"Approx. position in queue: <strong>{position}</strong>. "
239
- f"Evaluation runs in the background; refresh the <strong>Leaderboard</strong> tab "
240
- f"after a few minutes to see WER when the job finishes."
 
 
 
241
  )
242
 
243
 
@@ -424,13 +439,22 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
424
  max_length=8000,
425
  )
426
  script_input = gr.Code(
427
- label="Optional custom uv-run script (only run if a moderator approves it)",
428
  language="python",
429
  lines=12,
430
  )
431
  gr.Markdown(
432
- "Custom scripts are **not** executed on this Space. They are stored with your submission "
433
- "and run on a Hub Job **only** after a moderator explicitly approves using that script."
 
 
 
 
 
 
 
 
 
434
  )
435
  with gr.Row():
436
  is_gated_input = gr.Checkbox(label="This is a gated repo", value=False)
@@ -508,7 +532,7 @@ with gr.Blocks(title=APP_TITLE, theme=_theme, css=LEADERBOARD_CSS) as demo:
508
  value=None,
509
  )
510
  mod_run_custom_script = gr.Checkbox(
511
- label="Run submitter's custom script on approval (if provided)",
512
  value=False,
513
  )
514
  with gr.Row():
 
178
  if not on_hub:
179
  return styled_error(f"Model '{model_id}' {err_msg}")
180
 
181
+ script_hint = ""
182
+ if (custom_script or "").strip():
183
+ if not job_queue.custom_script_defines_evaluate(custom_script):
184
+ script_hint = (
185
+ "<p style='color:orange'><strong>Note:</strong> Your custom script should define "
186
+ "<code>evaluate(file: pathlib.Path) -&gt; str</code> at the top level. "
187
+ "It will be called once per sample during evaluation.</p>"
188
+ )
189
+
190
  try:
191
  job_id, position, err, awaiting_mod = job_queue.enqueue(
192
  model_id,
 
235
  return styled_warning(err)
236
 
237
  if awaiting_mod:
238
+ return (
239
+ script_hint
240
+ + styled_message(
241
+ f"Request <code>{job_id}</code> recorded for <strong>{model_id}</strong>. "
242
+ f"<strong>A moderator must approve</strong> it before evaluation starts "
243
+ f"(see the <strong>Moderate</strong> tab). "
244
+ f"Approx. backlog awaiting approval: <strong>{position}</strong>."
245
+ )
246
  )
247
 
248
+ return (
249
+ script_hint
250
+ + styled_message(
251
+ f"Queued job <code>{job_id}</code> for <strong>{model_id}</strong>. "
252
+ f"Approx. position in queue: <strong>{position}</strong>. "
253
+ f"Evaluation runs in the background; refresh the <strong>Leaderboard</strong> tab "
254
+ f"after a few minutes to see WER when the job finishes."
255
+ )
256
  )
257
 
258
 
 
439
  max_length=8000,
440
  )
441
  script_input = gr.Code(
442
+ label="Optional custom evaluator (Python)",
443
  language="python",
444
  lines=12,
445
  )
446
  gr.Markdown(
447
+ "Define a function that transcribes one WAV file:\n\n"
448
+ "```python\n"
449
+ "from pathlib import Path\n\n"
450
+ "def evaluate(file: Path) -> str:\n"
451
+ " # return the transcription for this audio file\n"
452
+ " ...\n"
453
+ "```\n\n"
454
+ "It is called **once per audio sample** inside the existing eval loop. "
455
+ "Put any extra Python dependencies in the requirements box above. "
456
+ "Custom evaluators are **not** run on this Space; they run on a Hub Job "
457
+ "**only** after a moderator approves them."
458
  )
459
  with gr.Row():
460
  is_gated_input = gr.Checkbox(label="This is a gated repo", value=False)
 
532
  value=None,
533
  )
534
  mod_run_custom_script = gr.Checkbox(
535
+ label="Use submitter's custom evaluate() function (if provided)",
536
  value=False,
537
  )
538
  with gr.Row():
backends/custom_eval.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Load a user-submitted custom evaluator script and wrap it as a transcriber callable.
3
+
4
+ The script must define::
5
+
6
+ from pathlib import Path
7
+ def evaluate(file: Path) -> str:
8
+ ...
9
+
10
+ Each packed benchmark sample is written to a temporary WAV file and passed to
11
+ ``evaluate`` once per sample inside the existing eval loop.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import importlib.util
17
+ import os
18
+ import shutil
19
+ import tempfile
20
+ from collections.abc import Callable
21
+ from pathlib import Path
22
+
23
+
24
+ def build_transcriber_from_custom_script(
25
+ script_text: str,
26
+ ) -> tuple[Callable[..., str], Callable[[], None]]:
27
+ """
28
+ Load ``script_text`` as a module and return ``(transcribe, cleanup)``.
29
+
30
+ ``transcribe(audio_np, sampling_rate)`` writes a temp WAV and calls
31
+ ``evaluate(path)`` on the loaded module.
32
+ """
33
+ import soundfile as sf
34
+
35
+ script_text = (script_text or "").strip()
36
+ if not script_text:
37
+ raise RuntimeError("Custom script is empty.")
38
+
39
+ fd, script_path = tempfile.mkstemp(prefix="ffasr_custom_eval_", suffix=".py")
40
+ try:
41
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
42
+ f.write(script_text)
43
+ except Exception:
44
+ try:
45
+ os.unlink(script_path)
46
+ except FileNotFoundError:
47
+ pass
48
+ raise
49
+
50
+ spec = importlib.util.spec_from_file_location("ffasr_user_eval", script_path)
51
+ if spec is None or spec.loader is None:
52
+ try:
53
+ os.unlink(script_path)
54
+ except FileNotFoundError:
55
+ pass
56
+ raise RuntimeError("Could not load custom evaluator script.")
57
+
58
+ mod = importlib.util.module_from_spec(spec)
59
+ spec.loader.exec_module(mod)
60
+
61
+ evaluate_fn = getattr(mod, "evaluate", None)
62
+ if not callable(evaluate_fn):
63
+ try:
64
+ os.unlink(script_path)
65
+ except FileNotFoundError:
66
+ pass
67
+ raise RuntimeError(
68
+ "Custom script must define `evaluate(file: pathlib.Path) -> str`."
69
+ )
70
+
71
+ wav_dir = tempfile.mkdtemp(prefix="ffasr_custom_wav_")
72
+ counter = {"i": 0}
73
+
74
+ def transcribe(audio_np, sampling_rate: int) -> str:
75
+ counter["i"] += 1
76
+ wav_path = Path(wav_dir) / f"sample_{counter['i']:08d}.wav"
77
+ sf.write(str(wav_path), audio_np, int(sampling_rate), subtype="PCM_16")
78
+ try:
79
+ text = evaluate_fn(wav_path)
80
+ finally:
81
+ try:
82
+ wav_path.unlink()
83
+ except FileNotFoundError:
84
+ pass
85
+ return str(text or "")
86
+
87
+ transcribe._num_params = 0 # type: ignore[attr-defined]
88
+
89
+ def cleanup() -> None:
90
+ shutil.rmtree(wav_dir, ignore_errors=True)
91
+ try:
92
+ os.unlink(script_path)
93
+ except FileNotFoundError:
94
+ pass
95
+
96
+ return transcribe, cleanup
backends/universal.py CHANGED
@@ -230,6 +230,15 @@ def _normalize_input_features_layout(model, batch: dict) -> dict:
230
  return batch
231
 
232
 
 
 
 
 
 
 
 
 
 
233
  def _generate(model, batch: dict, max_new_tokens: int, language: str):
234
  """Try language-aware generate first (Whisper path); fall back to plain generate."""
235
  batch = _normalize_input_features_layout(model, batch)
@@ -240,7 +249,7 @@ def _generate(model, batch: dict, max_new_tokens: int, language: str):
240
  # Cohere's remote generate() path can leave decoder_attention_mask as None,
241
  # then HF generation tries `decoder_attention_mask.new_ones(...)` and crashes.
242
  # Passing an explicit one-token decoder mask keeps generation state valid.
243
- src = batch.get("input_features") or batch.get("input_values")
244
  if src is not None and hasattr(src, "shape"):
245
  try:
246
  batch = dict(batch)
 
230
  return batch
231
 
232
 
233
+ def _encoder_tensor_from_batch(batch: dict):
234
+ """Return encoder inputs without using ``or`` on tensors (ambiguous bool)."""
235
+ if batch.get("input_features") is not None:
236
+ return batch["input_features"]
237
+ if batch.get("input_values") is not None:
238
+ return batch["input_values"]
239
+ return None
240
+
241
+
242
  def _generate(model, batch: dict, max_new_tokens: int, language: str):
243
  """Try language-aware generate first (Whisper path); fall back to plain generate."""
244
  batch = _normalize_input_features_layout(model, batch)
 
249
  # Cohere's remote generate() path can leave decoder_attention_mask as None,
250
  # then HF generation tries `decoder_attention_mask.new_ones(...)` and crashes.
251
  # Passing an explicit one-token decoder mask keeps generation state valid.
252
+ src = _encoder_tensor_from_batch(batch)
253
  if src is not None and hasattr(src, "shape"):
254
  try:
255
  batch = dict(batch)
evaluation/orchestrator.py CHANGED
@@ -57,6 +57,21 @@ def _samples_per_gpu_segment() -> int:
57
  return max(0, v)
58
 
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  def _zerogpu_run_segment(payload: dict[str, Any]) -> dict[str, Any]:
61
  """Single ZeroGPU segment: build model, transcribe ``[start:end)`` for one condition."""
62
  model_id = str(payload["model_id"])
@@ -64,9 +79,12 @@ def _zerogpu_run_segment(payload: dict[str, Any]) -> dict[str, Any]:
64
  condition_key = str(payload["condition_key"])
65
  start = int(payload["start"])
66
  end = int(payload["end"])
 
67
 
68
  device_str, device_int = resolve_eval_devices()
69
- transcribe, cleanup = build_transcriber(family_id, model_id, device_str, device_int)
 
 
70
  try:
71
  num_params = int(getattr(transcribe, "_num_params", 0) or 0)
72
  preds, refs, audio_s, infer_s, n_done = accumulate_predictions_for_slice(
@@ -90,6 +108,7 @@ def _run_evaluation_core(
90
  model_id: str,
91
  family_id: str = "auto",
92
  progress_cb: Callable[[int, int, str], None] | None = None,
 
93
  ) -> dict:
94
  device_str, device_int = resolve_eval_devices()
95
 
@@ -109,7 +128,9 @@ def _run_evaluation_core(
109
  except Exception:
110
  pass
111
 
112
- transcribe, cleanup = build_transcriber(family_id, model_id, device_str, device_int)
 
 
113
  try:
114
  # Backends attach `transcribe._num_params` via `_model_utils.attach_params`; defaults to 0
115
  # if the backend couldn't introspect a module.
@@ -171,6 +192,7 @@ def _run_evaluation_core_segmented_local(
171
  progress_cb: Callable[[int, int, str], None] | None = None,
172
  *,
173
  segment_samples: int,
 
174
  ) -> dict:
175
  """
176
  Same semantics as ``_run_evaluation_core_segmented`` but without ``spaces.GPU``:
@@ -224,6 +246,7 @@ def _run_evaluation_core_segmented_local(
224
  "condition_key": condition_key,
225
  "start": start,
226
  "end": end,
 
227
  }
228
  out = _zerogpu_run_segment(payload)
229
  if first_num_params is None:
@@ -270,6 +293,7 @@ def _run_evaluation_core_segmented(
270
  segment_samples: int,
271
  gpu_duration_s: int,
272
  gpu_size: str,
 
273
  ) -> dict:
274
  """
275
  Long evaluations: each slice of ``segment_samples`` runs under its own ``spaces.GPU``
@@ -327,6 +351,7 @@ def _run_evaluation_core_segmented(
327
  "condition_key": condition_key,
328
  "start": start,
329
  "end": end,
 
330
  }
331
  out = wrapped_seg(payload)
332
  if first_num_params is None:
@@ -383,6 +408,7 @@ def run_evaluation(
383
  model_id: str,
384
  family_id: str = "auto",
385
  progress_cb: Callable[[int, int, str], None] | None = None,
 
386
  ) -> dict:
387
  """
388
  Evaluate `model_id` on all packed conditions using the selected family backend.
@@ -410,6 +436,9 @@ def run_evaluation(
410
  ``progress_cb(samples_done_across_all_conditions, samples_total_across_all_conditions, current_condition_key)``
411
  periodically during the run.
412
 
 
 
 
413
  Returns wer_clean, wer_noisy, wer_reverberant, wer_real, wer_difficult, num_samples, model_id, eval_family, plus timing.
414
  """
415
  apply_cpu_thread_settings_once()
@@ -423,8 +452,14 @@ def run_evaluation(
423
  family_id=family_id,
424
  progress_cb=progress_cb,
425
  segment_samples=seg,
 
426
  )
427
- return _run_evaluation_core(model_id, family_id=family_id, progress_cb=progress_cb)
 
 
 
 
 
428
 
429
  eff = _effective_gpu_duration_s()
430
  size = os.environ.get("FFASR_ZEROGPU_GPU_SIZE", "large").strip().lower()
@@ -433,7 +468,12 @@ def run_evaluation(
433
 
434
  if seg <= 0:
435
  wrapped = _spaces_gpu_wrapped(eff, size)
436
- return wrapped(model_id, family_id=family_id, progress_cb=progress_cb)
 
 
 
 
 
437
 
438
  return _run_evaluation_core_segmented(
439
  model_id,
@@ -442,4 +482,5 @@ def run_evaluation(
442
  segment_samples=seg,
443
  gpu_duration_s=eff,
444
  gpu_size=size,
 
445
  )
 
57
  return max(0, v)
58
 
59
 
60
+ def _build_transcriber(
61
+ family_id: str,
62
+ model_id: str,
63
+ device_str: str,
64
+ device_int: int,
65
+ custom_script: str = "",
66
+ ) -> tuple[Callable[..., str], Callable[[], None]]:
67
+ """Family backend transcriber, or user ``evaluate(Path)`` script when provided."""
68
+ if (custom_script or "").strip():
69
+ from backends.custom_eval import build_transcriber_from_custom_script
70
+
71
+ return build_transcriber_from_custom_script(custom_script)
72
+ return build_transcriber(family_id, model_id, device_str, device_int)
73
+
74
+
75
  def _zerogpu_run_segment(payload: dict[str, Any]) -> dict[str, Any]:
76
  """Single ZeroGPU segment: build model, transcribe ``[start:end)`` for one condition."""
77
  model_id = str(payload["model_id"])
 
79
  condition_key = str(payload["condition_key"])
80
  start = int(payload["start"])
81
  end = int(payload["end"])
82
+ custom_script = str(payload.get("custom_script") or "")
83
 
84
  device_str, device_int = resolve_eval_devices()
85
+ transcribe, cleanup = _build_transcriber(
86
+ family_id, model_id, device_str, device_int, custom_script=custom_script
87
+ )
88
  try:
89
  num_params = int(getattr(transcribe, "_num_params", 0) or 0)
90
  preds, refs, audio_s, infer_s, n_done = accumulate_predictions_for_slice(
 
108
  model_id: str,
109
  family_id: str = "auto",
110
  progress_cb: Callable[[int, int, str], None] | None = None,
111
+ custom_script: str = "",
112
  ) -> dict:
113
  device_str, device_int = resolve_eval_devices()
114
 
 
128
  except Exception:
129
  pass
130
 
131
+ transcribe, cleanup = _build_transcriber(
132
+ family_id, model_id, device_str, device_int, custom_script=custom_script
133
+ )
134
  try:
135
  # Backends attach `transcribe._num_params` via `_model_utils.attach_params`; defaults to 0
136
  # if the backend couldn't introspect a module.
 
192
  progress_cb: Callable[[int, int, str], None] | None = None,
193
  *,
194
  segment_samples: int,
195
+ custom_script: str = "",
196
  ) -> dict:
197
  """
198
  Same semantics as ``_run_evaluation_core_segmented`` but without ``spaces.GPU``:
 
246
  "condition_key": condition_key,
247
  "start": start,
248
  "end": end,
249
+ "custom_script": custom_script,
250
  }
251
  out = _zerogpu_run_segment(payload)
252
  if first_num_params is None:
 
293
  segment_samples: int,
294
  gpu_duration_s: int,
295
  gpu_size: str,
296
+ custom_script: str = "",
297
  ) -> dict:
298
  """
299
  Long evaluations: each slice of ``segment_samples`` runs under its own ``spaces.GPU``
 
351
  "condition_key": condition_key,
352
  "start": start,
353
  "end": end,
354
+ "custom_script": custom_script,
355
  }
356
  out = wrapped_seg(payload)
357
  if first_num_params is None:
 
408
  model_id: str,
409
  family_id: str = "auto",
410
  progress_cb: Callable[[int, int, str], None] | None = None,
411
+ custom_script: str = "",
412
  ) -> dict:
413
  """
414
  Evaluate `model_id` on all packed conditions using the selected family backend.
 
436
  ``progress_cb(samples_done_across_all_conditions, samples_total_across_all_conditions, current_condition_key)``
437
  periodically during the run.
438
 
439
+ When ``custom_script`` is non-empty, it must define ``evaluate(file: pathlib.Path) -> str``;
440
+ that function is called once per packed sample (via a temp WAV) instead of the family backend.
441
+
442
  Returns wer_clean, wer_noisy, wer_reverberant, wer_real, wer_difficult, num_samples, model_id, eval_family, plus timing.
443
  """
444
  apply_cpu_thread_settings_once()
 
452
  family_id=family_id,
453
  progress_cb=progress_cb,
454
  segment_samples=seg,
455
+ custom_script=custom_script,
456
  )
457
+ return _run_evaluation_core(
458
+ model_id,
459
+ family_id=family_id,
460
+ progress_cb=progress_cb,
461
+ custom_script=custom_script,
462
+ )
463
 
464
  eff = _effective_gpu_duration_s()
465
  size = os.environ.get("FFASR_ZEROGPU_GPU_SIZE", "large").strip().lower()
 
468
 
469
  if seg <= 0:
470
  wrapped = _spaces_gpu_wrapped(eff, size)
471
+ return wrapped(
472
+ model_id,
473
+ family_id=family_id,
474
+ progress_cb=progress_cb,
475
+ custom_script=custom_script,
476
+ )
477
 
478
  return _run_evaluation_core_segmented(
479
  model_id,
 
482
  segment_samples=seg,
483
  gpu_duration_s=eff,
484
  gpu_size=size,
485
+ custom_script=custom_script,
486
  )
job_queue.py CHANGED
@@ -138,6 +138,23 @@ def sanitize_custom_script(text: str) -> str:
138
  return s
139
 
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  def _bool_to_csv(flag: bool) -> str:
142
  return "1" if flag else "0"
143
 
@@ -1262,7 +1279,7 @@ def next_up_html(limit: int = 5) -> str:
1262
  return (
1263
  "<div class='next-up-panel' style='font-size:0.9em'>"
1264
  f"<p><strong>Next models to evaluate</strong> ({len(queued)} shown):</p>"
1265
- f"<ol style='margin:0.25rem 0 0 1.1rem'>{items}</ol></motionmotionmotionmotiondiv>"
1266
  )
1267
 
1268
 
 
138
  return s
139
 
140
 
141
+ def custom_script_defines_evaluate(text: str) -> bool:
142
+ """Best-effort check that the script defines a top-level ``evaluate`` function."""
143
+ s = (text or "").strip()
144
+ if not s:
145
+ return True
146
+ try:
147
+ import ast
148
+
149
+ tree = ast.parse(s)
150
+ except SyntaxError:
151
+ return False
152
+ return any(
153
+ isinstance(node, ast.FunctionDef) and node.name == "evaluate"
154
+ for node in tree.body
155
+ )
156
+
157
+
158
  def _bool_to_csv(flag: bool) -> str:
159
  return "1" if flag else "0"
160
 
 
1279
  return (
1280
  "<div class='next-up-panel' style='font-size:0.9em'>"
1281
  f"<p><strong>Next models to evaluate</strong> ({len(queued)} shown):</p>"
1282
+ f"<ol style='margin:0.25rem 0 0 1.1rem'>{items}</ol></div>"
1283
  )
1284
 
1285
 
remote_jobs.py CHANGED
@@ -187,21 +187,7 @@ def submit_eval_job(
187
  )
188
 
189
  script = _worker_script_path()
190
- custom_script_path: str | None = None
191
- if run_custom_script and (custom_script or "").strip():
192
- import tempfile
193
-
194
- fd, custom_script_path = tempfile.mkstemp(
195
- prefix=f"ffasr_custom_{space_job_id}_", suffix=".py"
196
- )
197
- try:
198
- with os.fdopen(fd, "w", encoding="utf-8") as f:
199
- f.write(custom_script.strip())
200
- except Exception:
201
- os.unlink(custom_script_path)
202
- raise
203
- script = custom_script_path
204
- elif not os.path.isfile(script):
205
  raise RuntimeError(f"Remote worker script not found: {script}")
206
 
207
  deps = _select_deps(model_id, family_id)
@@ -233,6 +219,12 @@ def submit_eval_job(
233
  "FFASR_REMOTE_EVAL_GIT_BRANCH": eval_branch,
234
  "HF_HUB_DISABLE_PROGRESS_BARS": "1",
235
  }
 
 
 
 
 
 
236
  secrets = {"HF_TOKEN": bucket_tok}
237
 
238
  api = HfApi(token=jobs_tok)
@@ -257,12 +249,6 @@ def submit_eval_job(
257
  if last_err is not None:
258
  raise last_err
259
  raise RuntimeError("submit_eval_job: unreachable")
260
- finally:
261
- if custom_script_path and os.path.isfile(custom_script_path):
262
- try:
263
- os.unlink(custom_script_path)
264
- except Exception:
265
- pass
266
 
267
 
268
  def inspect_job_once(hf_job_id: str, *, token: str | None = None) -> JobInfo:
 
187
  )
188
 
189
  script = _worker_script_path()
190
+ if not os.path.isfile(script):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  raise RuntimeError(f"Remote worker script not found: {script}")
192
 
193
  deps = _select_deps(model_id, family_id)
 
219
  "FFASR_REMOTE_EVAL_GIT_BRANCH": eval_branch,
220
  "HF_HUB_DISABLE_PROGRESS_BARS": "1",
221
  }
222
+ if run_custom_script and (custom_script or "").strip():
223
+ import base64
224
+
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)
 
249
  if last_err is not None:
250
  raise last_err
251
  raise RuntimeError("submit_eval_job: unreachable")
 
 
 
 
 
 
252
 
253
 
254
  def inspect_job_once(hf_job_id: str, *, token: str | None = None) -> JobInfo:
scripts/run_hf_remote_job_uv.py CHANGED
@@ -16,6 +16,7 @@ Environment (set by ``remote_jobs.submit_eval_job``):
16
 
17
  from __future__ import annotations
18
 
 
19
  import io
20
  import json
21
  import os
@@ -113,7 +114,16 @@ def main() -> int:
113
  from storage import HF_BUCKET_ID, upload_to_bucket
114
 
115
  apply_cpu_thread_settings_once()
116
- result = run_evaluation(model_id, family_id=family_id, progress_cb=None)
 
 
 
 
 
 
 
 
 
117
  artifact = build_artifact(
118
  model_id=model_id,
119
  family_id=family_id,
 
16
 
17
  from __future__ import annotations
18
 
19
+ import base64
20
  import io
21
  import json
22
  import os
 
114
  from storage import HF_BUCKET_ID, upload_to_bucket
115
 
116
  apply_cpu_thread_settings_once()
117
+ custom_b64 = os.environ.get("FFASR_CUSTOM_SCRIPT_B64", "").strip()
118
+ custom_script = (
119
+ base64.b64decode(custom_b64).decode("utf-8") if custom_b64 else ""
120
+ )
121
+ result = run_evaluation(
122
+ model_id,
123
+ family_id=family_id,
124
+ progress_cb=None,
125
+ custom_script=custom_script,
126
+ )
127
  artifact = build_artifact(
128
  model_id=model_id,
129
  family_id=family_id,