pliny-the-prompter commited on
Commit
af8941f
·
verified ·
1 Parent(s): b656edc

Upload 133 files

Browse files
Files changed (1) hide show
  1. app.py +105 -54
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=_last_obliterated_label or None,
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
- _last_obliterated_label = label
447
- # Keep counter above any existing numbered dirs
448
- if p.name.startswith("obliterated_"):
449
- try:
450
- idx = int(p.name.split("_", 1)[1])
451
- if idx >= _obliterate_counter:
452
- _obliterate_counter = idx + 1
453
- except (ValueError, IndexError):
454
- pass
 
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
- _gpu_run_picklable(pipeline, pipeline.run_stage_summon_probe, time.time())
2413
  on_log("[staged] GPU released after Stage 1\n")
2414
 
2415
  on_log("\u26a1 [staged] GPU Stage 2/3: ANALYZE + DISTILL + EXCISE")
2416
- _gpu_run_picklable(pipeline, _restore_and_run_stage, pipeline, "run_stage_analyze_distill_excise")
2417
  on_log("[staged] GPU released after Stage 2\n")
2418
 
2419
  on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
2420
- _gpu_run_picklable(pipeline, _restore_and_run_stage, pipeline, "run_stage_verify_rebirth_informed")
2421
  else:
2422
  # Standard pipeline: SUMMON+PROBE | DISTILL+EXCISE | VERIFY+REBIRTH
2423
  on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
2424
- _gpu_run_picklable(pipeline, pipeline.run_stage_summon_probe, time.time())
2425
  on_log("[staged] GPU released after Stage 1\n")
2426
 
2427
  on_log("\u26a1 [staged] GPU Stage 2/3: DISTILL + EXCISE")
2428
- _gpu_run_picklable(pipeline, _restore_and_run_stage, pipeline, "run_stage_distill_excise")
2429
  on_log("[staged] GPU released after Stage 2\n")
2430
 
2431
  on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
2432
- _gpu_run_picklable(pipeline, _restore_and_run_stage, pipeline, "run_stage_verify_rebirth")
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
- for _sm in _session_models.values():
 
 
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
- try:
3226
- _dev = next(_state["model"].parameters()).device
3227
- if _dev.type == "meta":
 
 
 
 
 
 
 
3228
  _model_ok = False
3229
- except Exception:
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
- if _state["status"] == "ready" and _state["model"] is not None:
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
- if checkpoint and Path(checkpoint).exists():
3263
- yield (
3264
- f"**Loading model** from saved checkpoint...",
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 = (_state.get("model_name") or "") in MODELS
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
- if (_state["status"] == "ready"
3308
- and _state["model"] is not None
3309
- and _state["model_name"] == cfg.get("model_choice", "")
3310
- and _state["method"] == method_key):
3311
- yield (
3312
- f"**Already loaded!** `{choice}` is ready — just type in the chat below.",
3313
- get_chat_header(),
3314
- )
3315
- return
 
 
 
3316
 
3317
  # Unstick stale "obliterating" status left behind by ZeroGPU timeout
3318
  _unstick_stale_obliterating()
3319
 
3320
  with _lock:
3321
- if _state["status"] == "obliterating":
3322
- yield "**Error:** An obliteration is already in progress.", ""
3323
- return
3324
- _state["status"] = "obliterating"
3325
- _state["model_name"] = cfg["model_choice"]
3326
- _state["method"] = method_key
 
 
 
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 (