Spaces:
Running on Zero
Running on Zero
Upload 133 files
Browse files
app.py
CHANGED
|
@@ -296,8 +296,8 @@ def _recover_after_obliterate():
|
|
| 296 |
_ts = datetime.now().strftime("%H:%M")
|
| 297 |
_short = model_choice.split("/")[-1] if "/" in model_choice else model_choice
|
| 298 |
_label = f"{method} on {_short} ({_ts}) [recovered]"
|
| 299 |
-
_last_obliterated_label = _label
|
| 300 |
with _lock:
|
|
|
|
| 301 |
_session_models[_label] = {
|
| 302 |
"model_id": data.get("model_id", model_choice),
|
| 303 |
"model_choice": model_choice,
|
|
@@ -328,9 +328,11 @@ def _recover_after_obliterate():
|
|
| 328 |
f"was saved before the timeout. Switch to the **Chat** tab to use it. "
|
| 329 |
f"Verification metrics were skipped."
|
| 330 |
)
|
|
|
|
|
|
|
| 331 |
dd = gr.update(
|
| 332 |
choices=_get_session_model_choices(),
|
| 333 |
-
value=
|
| 334 |
)
|
| 335 |
return status_msg, log_text, get_chat_header(), dd, gr.update(), dd
|
| 336 |
else:
|
|
@@ -442,16 +444,17 @@ def _recover_sessions_from_disk() -> None:
|
|
| 442 |
"source": data.get("source", "recovered"),
|
| 443 |
}
|
| 444 |
found_any = True
|
| 445 |
-
# Track the latest for auto-select
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
|
|
|
| 455 |
# If we recovered sessions and _state has no valid output_dir, set it to
|
| 456 |
# the most recent checkpoint so chat_respond can reload from disk.
|
| 457 |
# Also overwrite a stale output_dir that points to a non-existent path.
|
|
@@ -2133,6 +2136,33 @@ def _gpu_run_picklable(pipeline, fn, *args, **kwargs):
|
|
| 2133 |
pipeline._on_log = saved_on_log
|
| 2134 |
|
| 2135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2136 |
def obliterate(model_choice: str, method_choice: str,
|
| 2137 |
prompt_volume_choice: str, dataset_source_choice: str,
|
| 2138 |
custom_harmful: str, custom_harmless: str,
|
|
@@ -2409,27 +2439,27 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2409 |
if method == "informed":
|
| 2410 |
# Informed pipeline: SUMMON+PROBE | ANALYZE+DISTILL+EXCISE | VERIFY+REBIRTH
|
| 2411 |
on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
|
| 2412 |
-
|
| 2413 |
on_log("[staged] GPU released after Stage 1\n")
|
| 2414 |
|
| 2415 |
on_log("\u26a1 [staged] GPU Stage 2/3: ANALYZE + DISTILL + EXCISE")
|
| 2416 |
-
|
| 2417 |
on_log("[staged] GPU released after Stage 2\n")
|
| 2418 |
|
| 2419 |
on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
|
| 2420 |
-
|
| 2421 |
else:
|
| 2422 |
# Standard pipeline: SUMMON+PROBE | DISTILL+EXCISE | VERIFY+REBIRTH
|
| 2423 |
on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
|
| 2424 |
-
|
| 2425 |
on_log("[staged] GPU released after Stage 1\n")
|
| 2426 |
|
| 2427 |
on_log("\u26a1 [staged] GPU Stage 2/3: DISTILL + EXCISE")
|
| 2428 |
-
|
| 2429 |
on_log("[staged] GPU released after Stage 2\n")
|
| 2430 |
|
| 2431 |
on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
|
| 2432 |
-
|
| 2433 |
finally:
|
| 2434 |
# Clean up staged state temp dir
|
| 2435 |
import shutil as _shutil
|
|
@@ -3021,9 +3051,12 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
|
| 3021 |
if not checkpoint or not Path(checkpoint).exists():
|
| 3022 |
_recover_sessions_from_disk()
|
| 3023 |
checkpoint = _state.get("output_dir")
|
| 3024 |
-
# If output_dir is still stale, scan session models for any valid checkpoint
|
|
|
|
| 3025 |
if not checkpoint or not Path(checkpoint).exists():
|
| 3026 |
-
|
|
|
|
|
|
|
| 3027 |
_sm_dir = _sm.get("output_dir")
|
| 3028 |
if _sm_dir and Path(_sm_dir).exists():
|
| 3029 |
checkpoint = _sm_dir
|
|
@@ -3221,12 +3254,19 @@ def load_bench_into_chat(choice: str, progress=gr.Progress()):
|
|
| 3221 |
and _state.get("tokenizer") is not None
|
| 3222 |
)
|
| 3223 |
if choice and _model_ok:
|
| 3224 |
-
# Double-check model tensors aren't stale (meta device)
|
| 3225 |
-
|
| 3226 |
-
|
| 3227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3228 |
_model_ok = False
|
| 3229 |
-
|
| 3230 |
_model_ok = False
|
| 3231 |
if choice and _model_ok:
|
| 3232 |
yield (
|
|
@@ -3250,24 +3290,27 @@ def load_bench_into_chat(choice: str, progress=gr.Progress()):
|
|
| 3250 |
|
| 3251 |
# If recovery didn't find the exact choice, check if model is loaded
|
| 3252 |
if choice not in _bench_configs:
|
|
|
|
|
|
|
| 3253 |
with _lock:
|
| 3254 |
-
|
| 3255 |
-
yield (
|
| 3256 |
-
f"**Ready!** Model already loaded — just type in the chat below.",
|
| 3257 |
-
get_chat_header(),
|
| 3258 |
-
)
|
| 3259 |
-
return
|
| 3260 |
-
# Check if we can reload from a checkpoint on disk
|
| 3261 |
checkpoint = _state.get("output_dir")
|
| 3262 |
-
|
| 3263 |
-
|
| 3264 |
-
|
| 3265 |
-
|
| 3266 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3267 |
# If we have a checkpoint, attempt reload outside the lock
|
| 3268 |
-
checkpoint = _state.get("output_dir")
|
| 3269 |
if checkpoint and Path(checkpoint).exists():
|
| 3270 |
-
is_preset =
|
| 3271 |
try:
|
| 3272 |
model_loaded = _load_model_to_device(
|
| 3273 |
checkpoint, torch_dtype=torch.float16,
|
|
@@ -3304,26 +3347,32 @@ def load_bench_into_chat(choice: str, progress=gr.Progress()):
|
|
| 3304 |
|
| 3305 |
# If this model is already the active one, skip the destructive reload
|
| 3306 |
with _lock:
|
| 3307 |
-
|
| 3308 |
-
|
| 3309 |
-
|
| 3310 |
-
|
| 3311 |
-
|
| 3312 |
-
|
| 3313 |
-
|
| 3314 |
-
|
| 3315 |
-
|
|
|
|
|
|
|
|
|
|
| 3316 |
|
| 3317 |
# Unstick stale "obliterating" status left behind by ZeroGPU timeout
|
| 3318 |
_unstick_stale_obliterating()
|
| 3319 |
|
| 3320 |
with _lock:
|
| 3321 |
-
|
| 3322 |
-
|
| 3323 |
-
|
| 3324 |
-
|
| 3325 |
-
|
| 3326 |
-
|
|
|
|
|
|
|
|
|
|
| 3327 |
_clear_gpu()
|
| 3328 |
|
| 3329 |
# If we have a saved checkpoint on disk, load directly — no re-training!
|
|
@@ -3348,6 +3397,7 @@ def load_bench_into_chat(choice: str, progress=gr.Progress()):
|
|
| 3348 |
_state["tokenizer"] = tokenizer_loaded
|
| 3349 |
_state["steering"] = None
|
| 3350 |
_state["status"] = "ready"
|
|
|
|
| 3351 |
_state["output_dir"] = checkpoint_dir
|
| 3352 |
progress(1.0, desc="Ready!")
|
| 3353 |
yield (
|
|
@@ -3383,6 +3433,7 @@ def load_bench_into_chat(choice: str, progress=gr.Progress()):
|
|
| 3383 |
_state["tokenizer"] = tokenizer_loaded
|
| 3384 |
_state["steering"] = None
|
| 3385 |
_state["status"] = "ready"
|
|
|
|
| 3386 |
_state["output_dir"] = checkpoint_dir
|
| 3387 |
progress(1.0, desc="Ready!")
|
| 3388 |
yield (
|
|
|
|
| 296 |
_ts = datetime.now().strftime("%H:%M")
|
| 297 |
_short = model_choice.split("/")[-1] if "/" in model_choice else model_choice
|
| 298 |
_label = f"{method} on {_short} ({_ts}) [recovered]"
|
|
|
|
| 299 |
with _lock:
|
| 300 |
+
_last_obliterated_label = _label
|
| 301 |
_session_models[_label] = {
|
| 302 |
"model_id": data.get("model_id", model_choice),
|
| 303 |
"model_choice": model_choice,
|
|
|
|
| 328 |
f"was saved before the timeout. Switch to the **Chat** tab to use it. "
|
| 329 |
f"Verification metrics were skipped."
|
| 330 |
)
|
| 331 |
+
with _lock:
|
| 332 |
+
_label_snap = _last_obliterated_label
|
| 333 |
dd = gr.update(
|
| 334 |
choices=_get_session_model_choices(),
|
| 335 |
+
value=_label_snap or None,
|
| 336 |
)
|
| 337 |
return status_msg, log_text, get_chat_header(), dd, gr.update(), dd
|
| 338 |
else:
|
|
|
|
| 444 |
"source": data.get("source", "recovered"),
|
| 445 |
}
|
| 446 |
found_any = True
|
| 447 |
+
# Track the latest for auto-select and keep counter above existing dirs.
|
| 448 |
+
# Protect globals with _lock to avoid races with concurrent obliterate().
|
| 449 |
+
with _lock:
|
| 450 |
+
_last_obliterated_label = label
|
| 451 |
+
if p.name.startswith("obliterated_"):
|
| 452 |
+
try:
|
| 453 |
+
idx = int(p.name.split("_", 1)[1])
|
| 454 |
+
if idx >= _obliterate_counter:
|
| 455 |
+
_obliterate_counter = idx + 1
|
| 456 |
+
except (ValueError, IndexError):
|
| 457 |
+
pass
|
| 458 |
# If we recovered sessions and _state has no valid output_dir, set it to
|
| 459 |
# the most recent checkpoint so chat_respond can reload from disk.
|
| 460 |
# Also overwrite a stale output_dir that points to a non-existent path.
|
|
|
|
| 2136 |
pipeline._on_log = saved_on_log
|
| 2137 |
|
| 2138 |
|
| 2139 |
+
def _gpu_run_with_retry(pipeline, fn, *args, max_retries=2, stage_label="", on_log=None, **kwargs):
|
| 2140 |
+
"""Run a GPU stage via ``_gpu_run_picklable`` with automatic retry on ZeroGPU abort.
|
| 2141 |
+
|
| 2142 |
+
ZeroGPU can transiently abort GPU tasks due to timeouts, concurrent user
|
| 2143 |
+
conflicts, or infrastructure issues. Retrying often succeeds. This wrapper
|
| 2144 |
+
retries up to *max_retries* times with exponential backoff (3s, 9s) before
|
| 2145 |
+
re-raising the final error.
|
| 2146 |
+
"""
|
| 2147 |
+
last_exc = None
|
| 2148 |
+
for attempt in range(1 + max_retries):
|
| 2149 |
+
try:
|
| 2150 |
+
return _gpu_run_picklable(pipeline, fn, *args, **kwargs)
|
| 2151 |
+
except Exception as e:
|
| 2152 |
+
last_exc = e
|
| 2153 |
+
if not _is_zerogpu_abort(e) or attempt >= max_retries:
|
| 2154 |
+
raise
|
| 2155 |
+
delay = 3 * (3 ** attempt) # 3s, 9s
|
| 2156 |
+
if on_log:
|
| 2157 |
+
on_log(
|
| 2158 |
+
f"[staged] GPU task aborted on attempt {attempt + 1} "
|
| 2159 |
+
f"({stage_label}) — retrying in {delay}s "
|
| 2160 |
+
f"({max_retries - attempt} retries left)..."
|
| 2161 |
+
)
|
| 2162 |
+
time.sleep(delay)
|
| 2163 |
+
raise last_exc # unreachable, but satisfies type checkers
|
| 2164 |
+
|
| 2165 |
+
|
| 2166 |
def obliterate(model_choice: str, method_choice: str,
|
| 2167 |
prompt_volume_choice: str, dataset_source_choice: str,
|
| 2168 |
custom_harmful: str, custom_harmless: str,
|
|
|
|
| 2439 |
if method == "informed":
|
| 2440 |
# Informed pipeline: SUMMON+PROBE | ANALYZE+DISTILL+EXCISE | VERIFY+REBIRTH
|
| 2441 |
on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
|
| 2442 |
+
_gpu_run_with_retry(pipeline, pipeline.run_stage_summon_probe, time.time(), stage_label="Stage 1: SUMMON+PROBE", on_log=on_log)
|
| 2443 |
on_log("[staged] GPU released after Stage 1\n")
|
| 2444 |
|
| 2445 |
on_log("\u26a1 [staged] GPU Stage 2/3: ANALYZE + DISTILL + EXCISE")
|
| 2446 |
+
_gpu_run_with_retry(pipeline, _restore_and_run_stage, pipeline, "run_stage_analyze_distill_excise", stage_label="Stage 2: ANALYZE+DISTILL+EXCISE", on_log=on_log)
|
| 2447 |
on_log("[staged] GPU released after Stage 2\n")
|
| 2448 |
|
| 2449 |
on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
|
| 2450 |
+
_gpu_run_with_retry(pipeline, _restore_and_run_stage, pipeline, "run_stage_verify_rebirth_informed", stage_label="Stage 3: VERIFY+REBIRTH", on_log=on_log)
|
| 2451 |
else:
|
| 2452 |
# Standard pipeline: SUMMON+PROBE | DISTILL+EXCISE | VERIFY+REBIRTH
|
| 2453 |
on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
|
| 2454 |
+
_gpu_run_with_retry(pipeline, pipeline.run_stage_summon_probe, time.time(), stage_label="Stage 1: SUMMON+PROBE", on_log=on_log)
|
| 2455 |
on_log("[staged] GPU released after Stage 1\n")
|
| 2456 |
|
| 2457 |
on_log("\u26a1 [staged] GPU Stage 2/3: DISTILL + EXCISE")
|
| 2458 |
+
_gpu_run_with_retry(pipeline, _restore_and_run_stage, pipeline, "run_stage_distill_excise", stage_label="Stage 2: DISTILL+EXCISE", on_log=on_log)
|
| 2459 |
on_log("[staged] GPU released after Stage 2\n")
|
| 2460 |
|
| 2461 |
on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
|
| 2462 |
+
_gpu_run_with_retry(pipeline, _restore_and_run_stage, pipeline, "run_stage_verify_rebirth", stage_label="Stage 3: VERIFY+REBIRTH", on_log=on_log)
|
| 2463 |
finally:
|
| 2464 |
# Clean up staged state temp dir
|
| 2465 |
import shutil as _shutil
|
|
|
|
| 3051 |
if not checkpoint or not Path(checkpoint).exists():
|
| 3052 |
_recover_sessions_from_disk()
|
| 3053 |
checkpoint = _state.get("output_dir")
|
| 3054 |
+
# If output_dir is still stale, scan session models for any valid checkpoint.
|
| 3055 |
+
# Snapshot values under lock to avoid RuntimeError from concurrent dict modification.
|
| 3056 |
if not checkpoint or not Path(checkpoint).exists():
|
| 3057 |
+
with _lock:
|
| 3058 |
+
_sm_snapshot = list(_session_models.values())
|
| 3059 |
+
for _sm in _sm_snapshot:
|
| 3060 |
_sm_dir = _sm.get("output_dir")
|
| 3061 |
if _sm_dir and Path(_sm_dir).exists():
|
| 3062 |
checkpoint = _sm_dir
|
|
|
|
| 3254 |
and _state.get("tokenizer") is not None
|
| 3255 |
)
|
| 3256 |
if choice and _model_ok:
|
| 3257 |
+
# Double-check model tensors aren't stale (meta device).
|
| 3258 |
+
# Re-acquire lock to safely access model — it could become None
|
| 3259 |
+
# between the first lock release and this check.
|
| 3260 |
+
with _lock:
|
| 3261 |
+
_model_ref = _state.get("model")
|
| 3262 |
+
if _model_ref is not None:
|
| 3263 |
+
try:
|
| 3264 |
+
_dev = next(_model_ref.parameters()).device
|
| 3265 |
+
if _dev.type == "meta":
|
| 3266 |
+
_model_ok = False
|
| 3267 |
+
except Exception:
|
| 3268 |
_model_ok = False
|
| 3269 |
+
else:
|
| 3270 |
_model_ok = False
|
| 3271 |
if choice and _model_ok:
|
| 3272 |
yield (
|
|
|
|
| 3290 |
|
| 3291 |
# If recovery didn't find the exact choice, check if model is loaded
|
| 3292 |
if choice not in _bench_configs:
|
| 3293 |
+
# Read state under lock, but never yield while holding the lock —
|
| 3294 |
+
# yield suspends the generator and would block all other threads.
|
| 3295 |
with _lock:
|
| 3296 |
+
_is_ready = _state["status"] == "ready" and _state["model"] is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3297 |
checkpoint = _state.get("output_dir")
|
| 3298 |
+
_model_name_snap = _state.get("model_name") or ""
|
| 3299 |
+
if _is_ready:
|
| 3300 |
+
yield (
|
| 3301 |
+
f"**Ready!** Model already loaded — just type in the chat below.",
|
| 3302 |
+
get_chat_header(),
|
| 3303 |
+
)
|
| 3304 |
+
return
|
| 3305 |
+
# Check if we can reload from a checkpoint on disk
|
| 3306 |
+
if checkpoint and Path(checkpoint).exists():
|
| 3307 |
+
yield (
|
| 3308 |
+
f"**Loading model** from saved checkpoint...",
|
| 3309 |
+
"",
|
| 3310 |
+
)
|
| 3311 |
# If we have a checkpoint, attempt reload outside the lock
|
|
|
|
| 3312 |
if checkpoint and Path(checkpoint).exists():
|
| 3313 |
+
is_preset = _model_name_snap in MODELS
|
| 3314 |
try:
|
| 3315 |
model_loaded = _load_model_to_device(
|
| 3316 |
checkpoint, torch_dtype=torch.float16,
|
|
|
|
| 3347 |
|
| 3348 |
# If this model is already the active one, skip the destructive reload
|
| 3349 |
with _lock:
|
| 3350 |
+
_already_active = (
|
| 3351 |
+
_state["status"] == "ready"
|
| 3352 |
+
and _state["model"] is not None
|
| 3353 |
+
and _state["model_name"] == cfg.get("model_choice", "")
|
| 3354 |
+
and _state["method"] == method_key
|
| 3355 |
+
)
|
| 3356 |
+
if _already_active:
|
| 3357 |
+
yield (
|
| 3358 |
+
f"**Already loaded!** `{choice}` is ready — just type in the chat below.",
|
| 3359 |
+
get_chat_header(),
|
| 3360 |
+
)
|
| 3361 |
+
return
|
| 3362 |
|
| 3363 |
# Unstick stale "obliterating" status left behind by ZeroGPU timeout
|
| 3364 |
_unstick_stale_obliterating()
|
| 3365 |
|
| 3366 |
with _lock:
|
| 3367 |
+
_already_obliterating = _state["status"] == "obliterating"
|
| 3368 |
+
if not _already_obliterating:
|
| 3369 |
+
_state["status"] = "obliterating"
|
| 3370 |
+
_state["obliterate_started_at"] = time.time()
|
| 3371 |
+
_state["model_name"] = cfg["model_choice"]
|
| 3372 |
+
_state["method"] = method_key
|
| 3373 |
+
if _already_obliterating:
|
| 3374 |
+
yield "**Error:** An obliteration is already in progress.", ""
|
| 3375 |
+
return
|
| 3376 |
_clear_gpu()
|
| 3377 |
|
| 3378 |
# If we have a saved checkpoint on disk, load directly — no re-training!
|
|
|
|
| 3397 |
_state["tokenizer"] = tokenizer_loaded
|
| 3398 |
_state["steering"] = None
|
| 3399 |
_state["status"] = "ready"
|
| 3400 |
+
_state["obliterate_started_at"] = None
|
| 3401 |
_state["output_dir"] = checkpoint_dir
|
| 3402 |
progress(1.0, desc="Ready!")
|
| 3403 |
yield (
|
|
|
|
| 3433 |
_state["tokenizer"] = tokenizer_loaded
|
| 3434 |
_state["steering"] = None
|
| 3435 |
_state["status"] = "ready"
|
| 3436 |
+
_state["obliterate_started_at"] = None
|
| 3437 |
_state["output_dir"] = checkpoint_dir
|
| 3438 |
progress(1.0, desc="Ready!")
|
| 3439 |
yield (
|