Spaces:
Running on Zero
Running on Zero
Upload 133 files
Browse files- app.py +156 -27
- obliteratus/abliterate.py +9 -1
- obliteratus/tourney.py +2 -0
app.py
CHANGED
|
@@ -24,9 +24,14 @@ import os
|
|
| 24 |
import re
|
| 25 |
import time
|
| 26 |
import threading
|
|
|
|
| 27 |
from datetime import datetime
|
| 28 |
from pathlib import Path
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
logger = logging.getLogger(__name__)
|
| 31 |
|
| 32 |
# ββ Container environment fixes ββββββββββββββββββββββββββββββββββββββ
|
|
@@ -90,18 +95,32 @@ except (ImportError, AttributeError):
|
|
| 90 |
def _is_quota_error(exc: BaseException) -> bool:
|
| 91 |
"""Return True if *exc* is a ZeroGPU quota or session error.
|
| 92 |
|
| 93 |
-
Matches quota-exceeded errors ("exceeded your GPU quota")
|
| 94 |
-
proxy tokens ("Expired ZeroGPU proxy token")
|
| 95 |
-
|
|
|
|
| 96 |
"""
|
| 97 |
msg = str(exc).lower()
|
| 98 |
if "exceeded" in msg and "gpu quota" in msg:
|
| 99 |
return True
|
| 100 |
if "expired" in msg and "zerogpu" in msg:
|
| 101 |
return True
|
|
|
|
|
|
|
| 102 |
return False
|
| 103 |
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
def _load_model_to_device(
|
| 106 |
pretrained_path: str,
|
| 107 |
*,
|
|
@@ -209,8 +228,8 @@ def _persist_session_meta(output_dir: str, label: str, meta: dict) -> None:
|
|
| 209 |
p = Path(output_dir) / _SESSION_META_FILE
|
| 210 |
data = {"label": label, **meta}
|
| 211 |
p.write_text(_json.dumps(data, indent=2))
|
| 212 |
-
except Exception:
|
| 213 |
-
|
| 214 |
|
| 215 |
|
| 216 |
def _recover_sessions_from_disk() -> None:
|
|
@@ -1280,6 +1299,8 @@ def benchmark(
|
|
| 1280 |
except Exception as e:
|
| 1281 |
nonlocal run_error
|
| 1282 |
run_error = e
|
|
|
|
|
|
|
| 1283 |
|
| 1284 |
worker = threading.Thread(target=run_pipeline, daemon=True)
|
| 1285 |
worker.start()
|
|
@@ -1635,6 +1656,8 @@ def benchmark_multi_model(
|
|
| 1635 |
except Exception as e:
|
| 1636 |
nonlocal run_error
|
| 1637 |
run_error = e
|
|
|
|
|
|
|
| 1638 |
|
| 1639 |
worker = threading.Thread(target=run_pipeline, daemon=True)
|
| 1640 |
worker.start()
|
|
@@ -1912,9 +1935,10 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 1912 |
f"Adaptive: using architecture default `{method}` "
|
| 1913 |
f"(no telemetry data yet)"
|
| 1914 |
)
|
| 1915 |
-
except Exception:
|
|
|
|
| 1916 |
method = "advanced"
|
| 1917 |
-
_adaptive_info = "Adaptive: fallback to `advanced` (
|
| 1918 |
|
| 1919 |
# Early validation: gated model access
|
| 1920 |
from obliteratus.presets import is_gated
|
|
@@ -1982,6 +2006,11 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 1982 |
|
| 1983 |
def run_pipeline():
|
| 1984 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1985 |
# Load prompts β custom overrides dataset dropdown
|
| 1986 |
if use_custom:
|
| 1987 |
on_log("Using custom user-provided prompts...")
|
|
@@ -1993,6 +2022,7 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 1993 |
on_log(f"Loading dataset: {dataset_key}...")
|
| 1994 |
harmful_all, harmless_all = load_dataset_source(dataset_key)
|
| 1995 |
on_log(f"Dataset loaded: {len(harmful_all)} harmful, {len(harmless_all)} harmless prompts")
|
|
|
|
| 1996 |
|
| 1997 |
# Apply volume cap (-1 = use all)
|
| 1998 |
if prompt_volume > 0:
|
|
@@ -2076,6 +2106,9 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2076 |
pipeline.run()
|
| 2077 |
except Exception as e:
|
| 2078 |
error_ref[0] = e
|
|
|
|
|
|
|
|
|
|
| 2079 |
|
| 2080 |
if use_custom:
|
| 2081 |
source_label = "Custom (user-provided)"
|
|
@@ -2097,32 +2130,112 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2097 |
worker.start()
|
| 2098 |
|
| 2099 |
# Stream log updates while pipeline runs (max 400 hours for large-model Optuna optimization)
|
|
|
|
|
|
|
| 2100 |
_max_pipeline_secs = 400 * 60 * 60
|
| 2101 |
_pipeline_start = time.time()
|
| 2102 |
status_msg = "**Obliterating\u2026** (0s)"
|
| 2103 |
-
|
| 2104 |
-
|
| 2105 |
-
|
| 2106 |
-
|
| 2107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2108 |
else:
|
| 2109 |
-
|
| 2110 |
-
|
| 2111 |
-
|
| 2112 |
-
|
| 2113 |
-
|
|
|
|
|
|
|
| 2114 |
|
| 2115 |
worker.join(timeout=30)
|
| 2116 |
|
| 2117 |
# Handle error
|
| 2118 |
if error_ref[0] is not None:
|
| 2119 |
-
|
| 2120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2121 |
with _lock:
|
| 2122 |
_state["status"] = "idle"
|
| 2123 |
_state["obliterate_started_at"] = None
|
| 2124 |
_state["log"] = log_lines
|
| 2125 |
-
yield f"**Error:** {err_msg}", "\n".join(log_lines), get_chat_header(), gr.update(), gr.update(), gr.update()
|
| 2126 |
return
|
| 2127 |
|
| 2128 |
# Success β keep model in memory for chat.
|
|
@@ -2285,7 +2398,8 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2285 |
can_generate = True
|
| 2286 |
log_lines.append("Reloaded in 4-bit β chat is ready!")
|
| 2287 |
except Exception as e:
|
| 2288 |
-
|
|
|
|
| 2289 |
_clear_gpu()
|
| 2290 |
|
| 2291 |
# -- Attempt 2: CPU offloading (slower but no extra dependencies)
|
|
@@ -2327,7 +2441,8 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2327 |
can_generate = True
|
| 2328 |
log_lines.append("Reloaded with CPU offload β chat is ready (may be slower).")
|
| 2329 |
except Exception as e:
|
| 2330 |
-
|
|
|
|
| 2331 |
log_lines.append("Chat unavailable. Load the saved model on a larger instance.")
|
| 2332 |
with _lock:
|
| 2333 |
_state["status"] = "idle"
|
|
@@ -2374,8 +2489,12 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2374 |
|
| 2375 |
except Exception as e:
|
| 2376 |
# Ensure status never gets stuck on "obliterating"
|
| 2377 |
-
|
|
|
|
|
|
|
|
|
|
| 2378 |
log_lines.append(f"\nERROR (post-pipeline): {err_msg}")
|
|
|
|
| 2379 |
with _lock:
|
| 2380 |
_state["status"] = "idle"
|
| 2381 |
_state["obliterate_started_at"] = None
|
|
@@ -2460,7 +2579,8 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
|
| 2460 |
_needs_reload = True
|
| 2461 |
else:
|
| 2462 |
model.to(dev.get_device())
|
| 2463 |
-
except Exception:
|
|
|
|
| 2464 |
_needs_reload = True
|
| 2465 |
|
| 2466 |
# Reload from saved checkpoint if model is missing or stale
|
|
@@ -2502,8 +2622,15 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
|
| 2502 |
_state["model"] = model
|
| 2503 |
_state["tokenizer"] = tokenizer
|
| 2504 |
_state["status"] = "ready"
|
| 2505 |
-
except Exception:
|
| 2506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2507 |
return
|
| 2508 |
else:
|
| 2509 |
yield "No model loaded yet. Go to the **Obliterate** tab first and liberate a model."
|
|
@@ -2570,6 +2697,7 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
|
| 2570 |
model.generate(**kwargs)
|
| 2571 |
except Exception as e:
|
| 2572 |
gen_error[0] = e
|
|
|
|
| 2573 |
# Signal the streamer to stop so the main thread doesn't hang
|
| 2574 |
try:
|
| 2575 |
streamer.end()
|
|
@@ -2584,8 +2712,9 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
|
| 2584 |
for token in streamer:
|
| 2585 |
partial += token
|
| 2586 |
yield partial
|
| 2587 |
-
except Exception:
|
| 2588 |
# Streamer timeout or broken pipe β yield whatever we have so far
|
|
|
|
| 2589 |
if partial:
|
| 2590 |
yield partial
|
| 2591 |
|
|
|
|
| 24 |
import re
|
| 25 |
import time
|
| 26 |
import threading
|
| 27 |
+
import traceback
|
| 28 |
from datetime import datetime
|
| 29 |
from pathlib import Path
|
| 30 |
|
| 31 |
+
logging.basicConfig(
|
| 32 |
+
level=logging.INFO,
|
| 33 |
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
| 34 |
+
)
|
| 35 |
logger = logging.getLogger(__name__)
|
| 36 |
|
| 37 |
# ββ Container environment fixes ββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 95 |
def _is_quota_error(exc: BaseException) -> bool:
|
| 96 |
"""Return True if *exc* is a ZeroGPU quota or session error.
|
| 97 |
|
| 98 |
+
Matches quota-exceeded errors ("exceeded your GPU quota"), expired
|
| 99 |
+
proxy tokens ("Expired ZeroGPU proxy token"), and aborted GPU tasks
|
| 100 |
+
("GPU task aborted") β all mean the GPU is unavailable and the user
|
| 101 |
+
should retry later.
|
| 102 |
"""
|
| 103 |
msg = str(exc).lower()
|
| 104 |
if "exceeded" in msg and "gpu quota" in msg:
|
| 105 |
return True
|
| 106 |
if "expired" in msg and "zerogpu" in msg:
|
| 107 |
return True
|
| 108 |
+
if "gpu task aborted" in msg:
|
| 109 |
+
return True
|
| 110 |
return False
|
| 111 |
|
| 112 |
|
| 113 |
+
def _is_zerogpu_abort(exc: BaseException) -> bool:
|
| 114 |
+
"""Return True if *exc* is specifically a ZeroGPU 'GPU task aborted' error.
|
| 115 |
+
|
| 116 |
+
This happens when ZeroGPU's internal multiprocessing kills the worker
|
| 117 |
+
mid-execution β typically because the GPU allocation timed out, a
|
| 118 |
+
concurrent request conflicted, or ZeroGPU infrastructure had an issue.
|
| 119 |
+
"""
|
| 120 |
+
msg = str(exc).lower()
|
| 121 |
+
return "gpu task aborted" in msg
|
| 122 |
+
|
| 123 |
+
|
| 124 |
def _load_model_to_device(
|
| 125 |
pretrained_path: str,
|
| 126 |
*,
|
|
|
|
| 228 |
p = Path(output_dir) / _SESSION_META_FILE
|
| 229 |
data = {"label": label, **meta}
|
| 230 |
p.write_text(_json.dumps(data, indent=2))
|
| 231 |
+
except Exception as e:
|
| 232 |
+
logger.debug("Failed to persist session metadata: %s", e)
|
| 233 |
|
| 234 |
|
| 235 |
def _recover_sessions_from_disk() -> None:
|
|
|
|
| 1299 |
except Exception as e:
|
| 1300 |
nonlocal run_error
|
| 1301 |
run_error = e
|
| 1302 |
+
logger.error("Benchmark pipeline failed: %s\n%s", e, traceback.format_exc())
|
| 1303 |
+
on_log(f"\n--- TRACEBACK ---\n{traceback.format_exc()}")
|
| 1304 |
|
| 1305 |
worker = threading.Thread(target=run_pipeline, daemon=True)
|
| 1306 |
worker.start()
|
|
|
|
| 1656 |
except Exception as e:
|
| 1657 |
nonlocal run_error
|
| 1658 |
run_error = e
|
| 1659 |
+
logger.error("Tournament pipeline failed: %s\n%s", e, traceback.format_exc())
|
| 1660 |
+
on_log(f"\n--- TRACEBACK ---\n{traceback.format_exc()}")
|
| 1661 |
|
| 1662 |
worker = threading.Thread(target=run_pipeline, daemon=True)
|
| 1663 |
worker.start()
|
|
|
|
| 1935 |
f"Adaptive: using architecture default `{method}` "
|
| 1936 |
f"(no telemetry data yet)"
|
| 1937 |
)
|
| 1938 |
+
except Exception as e:
|
| 1939 |
+
logger.warning("Adaptive method detection failed: %s", e, exc_info=True)
|
| 1940 |
method = "advanced"
|
| 1941 |
+
_adaptive_info = f"Adaptive: fallback to `advanced` (detection error: {e})"
|
| 1942 |
|
| 1943 |
# Early validation: gated model access
|
| 1944 |
from obliteratus.presets import is_gated
|
|
|
|
| 2006 |
|
| 2007 |
def run_pipeline():
|
| 2008 |
try:
|
| 2009 |
+
_t_pipeline_start = time.time()
|
| 2010 |
+
on_log(f"[timing] Pipeline thread started (ZeroGPU allocation: {300}s)")
|
| 2011 |
+
if _ZEROGPU_AVAILABLE:
|
| 2012 |
+
on_log("[timing] Running on ZeroGPU β GPU may be deallocated if pipeline exceeds duration")
|
| 2013 |
+
|
| 2014 |
# Load prompts β custom overrides dataset dropdown
|
| 2015 |
if use_custom:
|
| 2016 |
on_log("Using custom user-provided prompts...")
|
|
|
|
| 2022 |
on_log(f"Loading dataset: {dataset_key}...")
|
| 2023 |
harmful_all, harmless_all = load_dataset_source(dataset_key)
|
| 2024 |
on_log(f"Dataset loaded: {len(harmful_all)} harmful, {len(harmless_all)} harmless prompts")
|
| 2025 |
+
on_log(f"[timing] Dataset loaded at +{time.time() - _t_pipeline_start:.1f}s")
|
| 2026 |
|
| 2027 |
# Apply volume cap (-1 = use all)
|
| 2028 |
if prompt_volume > 0:
|
|
|
|
| 2106 |
pipeline.run()
|
| 2107 |
except Exception as e:
|
| 2108 |
error_ref[0] = e
|
| 2109 |
+
tb = traceback.format_exc()
|
| 2110 |
+
logger.error("Obliteration pipeline failed: %s\n%s", e, tb)
|
| 2111 |
+
on_log(f"\n--- TRACEBACK ---\n{tb}")
|
| 2112 |
|
| 2113 |
if use_custom:
|
| 2114 |
source_label = "Custom (user-provided)"
|
|
|
|
| 2130 |
worker.start()
|
| 2131 |
|
| 2132 |
# Stream log updates while pipeline runs (max 400 hours for large-model Optuna optimization)
|
| 2133 |
+
# Wrapped in try/except to catch ZeroGPU "GPU task aborted" β the abort is thrown
|
| 2134 |
+
# INTO the generator at the yield/sleep points, not into the worker thread.
|
| 2135 |
_max_pipeline_secs = 400 * 60 * 60
|
| 2136 |
_pipeline_start = time.time()
|
| 2137 |
status_msg = "**Obliterating\u2026** (0s)"
|
| 2138 |
+
try:
|
| 2139 |
+
while worker.is_alive():
|
| 2140 |
+
status_msg = f"**Obliterating\u2026** ({_elapsed()})"
|
| 2141 |
+
if len(log_lines) > last_yielded[0]:
|
| 2142 |
+
last_yielded[0] = len(log_lines)
|
| 2143 |
+
yield status_msg, "\n".join(log_lines), gr.update(), gr.update(), gr.update(), gr.update()
|
| 2144 |
+
else:
|
| 2145 |
+
yield status_msg, "\n".join(log_lines), gr.update(), gr.update(), gr.update(), gr.update()
|
| 2146 |
+
if time.time() - _pipeline_start > _max_pipeline_secs:
|
| 2147 |
+
log_lines.append("\nTIMEOUT: Pipeline exceeded 400-hour limit.")
|
| 2148 |
+
break
|
| 2149 |
+
time.sleep(0.5)
|
| 2150 |
+
except Exception as e:
|
| 2151 |
+
# ZeroGPU can abort the generator mid-yield with "GPU task aborted"
|
| 2152 |
+
# or other errors. Catch here so we can show a useful message and
|
| 2153 |
+
# reset state instead of leaving status stuck on "obliterating".
|
| 2154 |
+
tb = traceback.format_exc()
|
| 2155 |
+
logger.error("Obliterate generator interrupted: %s\n%s", e, tb)
|
| 2156 |
+
log_lines.append(f"\n--- INTERRUPTED ---")
|
| 2157 |
+
log_lines.append(f"Generator killed after {_elapsed()}: {type(e).__qualname__}: {e}")
|
| 2158 |
+
log_lines.append(f"\nLast pipeline log before abort:")
|
| 2159 |
+
for line in log_lines[-10:]:
|
| 2160 |
+
if line.startswith("[timing]") or line.startswith(" ["):
|
| 2161 |
+
log_lines.append(f" {line}")
|
| 2162 |
+
with _lock:
|
| 2163 |
+
_state["status"] = "idle"
|
| 2164 |
+
_state["obliterate_started_at"] = None
|
| 2165 |
+
_state["log"] = log_lines
|
| 2166 |
+
err_msg = str(e).strip() or repr(e)
|
| 2167 |
+
if _is_zerogpu_abort(e):
|
| 2168 |
+
hint = (
|
| 2169 |
+
"\n\n**ZeroGPU aborted the GPU task** after " + _elapsed() + ". "
|
| 2170 |
+
"This is a known ZeroGPU issue β common causes:\n"
|
| 2171 |
+
"- **Timeout:** Model loading + probing exceeded the 5-minute GPU allocation\n"
|
| 2172 |
+
"- **Concurrent users:** Another request conflicted with yours\n"
|
| 2173 |
+
"- **ZeroGPU internal error:** Transient infrastructure issue\n\n"
|
| 2174 |
+
"**Try:** Click Obliterate again (often works on retry). "
|
| 2175 |
+
"If it keeps failing, try a smaller model or reduce prompt volume."
|
| 2176 |
+
)
|
| 2177 |
+
elif _is_quota_error(e):
|
| 2178 |
+
hint = "\n\n**ZeroGPU quota exceeded.** Wait a few minutes and retry."
|
| 2179 |
else:
|
| 2180 |
+
hint = ""
|
| 2181 |
+
yield (
|
| 2182 |
+
f"**Error:** {type(e).__qualname__}: {err_msg}{hint}",
|
| 2183 |
+
"\n".join(log_lines), get_chat_header(),
|
| 2184 |
+
gr.update(), gr.update(), gr.update(),
|
| 2185 |
+
)
|
| 2186 |
+
return
|
| 2187 |
|
| 2188 |
worker.join(timeout=30)
|
| 2189 |
|
| 2190 |
# Handle error
|
| 2191 |
if error_ref[0] is not None:
|
| 2192 |
+
err = error_ref[0]
|
| 2193 |
+
err_type = type(err).__qualname__
|
| 2194 |
+
err_str = str(err).strip()
|
| 2195 |
+
if err_str:
|
| 2196 |
+
err_msg = f"{err_type}: {err_str}"
|
| 2197 |
+
else:
|
| 2198 |
+
err_msg = repr(err)
|
| 2199 |
+
# Classify the error for actionable user guidance
|
| 2200 |
+
err_lower = err_msg.lower()
|
| 2201 |
+
if _is_zerogpu_abort(err):
|
| 2202 |
+
err_hint = (
|
| 2203 |
+
"\n\n**ZeroGPU task aborted.** The GPU worker was killed mid-pipeline. "
|
| 2204 |
+
"This is a known ZeroGPU infrastructure issue β common causes:\n"
|
| 2205 |
+
"- **Timeout:** Model loading + probing exceeded the 5-minute GPU allocation\n"
|
| 2206 |
+
"- **Concurrent users:** Another request conflicted with yours\n"
|
| 2207 |
+
"- **ZeroGPU internal error:** Transient infrastructure issue\n\n"
|
| 2208 |
+
"**Try:** Click Obliterate again (often works on retry). "
|
| 2209 |
+
"If it keeps failing, try a smaller model or reduce prompt volume."
|
| 2210 |
+
)
|
| 2211 |
+
elif _is_quota_error(err):
|
| 2212 |
+
err_hint = (
|
| 2213 |
+
"\n\n**ZeroGPU quota exceeded.** Your HuggingFace GPU quota has "
|
| 2214 |
+
"been used up. Wait a few minutes and try again, or run locally."
|
| 2215 |
+
)
|
| 2216 |
+
elif "cuda" in err_lower or "out of memory" in err_lower:
|
| 2217 |
+
err_hint = (
|
| 2218 |
+
"\n\n**GPU out of memory.** Try a smaller model or enable "
|
| 2219 |
+
"quantization (the pipeline auto-detects this for large models)."
|
| 2220 |
+
)
|
| 2221 |
+
elif "meta" in err_lower and "tensor" in err_lower:
|
| 2222 |
+
err_hint = (
|
| 2223 |
+
"\n\n**ZeroGPU device error.** The GPU was deallocated mid-run. "
|
| 2224 |
+
"This is a transient ZeroGPU issue β please retry."
|
| 2225 |
+
)
|
| 2226 |
+
elif "connection" in err_lower or "timeout" in err_lower or "resolve" in err_lower:
|
| 2227 |
+
err_hint = (
|
| 2228 |
+
"\n\n**Network error.** Could not download model weights. "
|
| 2229 |
+
"Check your internet connection and try again."
|
| 2230 |
+
)
|
| 2231 |
+
else:
|
| 2232 |
+
err_hint = ""
|
| 2233 |
+
log_lines.append(f"\nERROR ({err_type}): {err_msg}")
|
| 2234 |
with _lock:
|
| 2235 |
_state["status"] = "idle"
|
| 2236 |
_state["obliterate_started_at"] = None
|
| 2237 |
_state["log"] = log_lines
|
| 2238 |
+
yield f"**Error:** {err_msg}{err_hint}", "\n".join(log_lines), get_chat_header(), gr.update(), gr.update(), gr.update()
|
| 2239 |
return
|
| 2240 |
|
| 2241 |
# Success β keep model in memory for chat.
|
|
|
|
| 2398 |
can_generate = True
|
| 2399 |
log_lines.append("Reloaded in 4-bit β chat is ready!")
|
| 2400 |
except Exception as e:
|
| 2401 |
+
logger.error("4-bit reload failed: %s\n%s", e, traceback.format_exc())
|
| 2402 |
+
log_lines.append(f"4-bit reload failed ({type(e).__qualname__}): {e}")
|
| 2403 |
_clear_gpu()
|
| 2404 |
|
| 2405 |
# -- Attempt 2: CPU offloading (slower but no extra dependencies)
|
|
|
|
| 2441 |
can_generate = True
|
| 2442 |
log_lines.append("Reloaded with CPU offload β chat is ready (may be slower).")
|
| 2443 |
except Exception as e:
|
| 2444 |
+
logger.error("CPU offload reload failed: %s\n%s", e, traceback.format_exc())
|
| 2445 |
+
log_lines.append(f"CPU offload reload failed ({type(e).__qualname__}): {e}")
|
| 2446 |
log_lines.append("Chat unavailable. Load the saved model on a larger instance.")
|
| 2447 |
with _lock:
|
| 2448 |
_state["status"] = "idle"
|
|
|
|
| 2489 |
|
| 2490 |
except Exception as e:
|
| 2491 |
# Ensure status never gets stuck on "obliterating"
|
| 2492 |
+
tb = traceback.format_exc()
|
| 2493 |
+
logger.error("Post-pipeline error: %s\n%s", e, tb)
|
| 2494 |
+
err_type = type(e).__qualname__
|
| 2495 |
+
err_msg = f"{err_type}: {str(e).strip() or repr(e)}"
|
| 2496 |
log_lines.append(f"\nERROR (post-pipeline): {err_msg}")
|
| 2497 |
+
log_lines.append(f"\n--- TRACEBACK ---\n{tb}")
|
| 2498 |
with _lock:
|
| 2499 |
_state["status"] = "idle"
|
| 2500 |
_state["obliterate_started_at"] = None
|
|
|
|
| 2579 |
_needs_reload = True
|
| 2580 |
else:
|
| 2581 |
model.to(dev.get_device())
|
| 2582 |
+
except Exception as e:
|
| 2583 |
+
logger.warning("Model device check failed, triggering reload: %s", e)
|
| 2584 |
_needs_reload = True
|
| 2585 |
|
| 2586 |
# Reload from saved checkpoint if model is missing or stale
|
|
|
|
| 2622 |
_state["model"] = model
|
| 2623 |
_state["tokenizer"] = tokenizer
|
| 2624 |
_state["status"] = "ready"
|
| 2625 |
+
except Exception as e:
|
| 2626 |
+
tb = traceback.format_exc()
|
| 2627 |
+
logger.error("Chat model reload failed: %s\n%s", e, tb)
|
| 2628 |
+
err_type = type(e).__qualname__
|
| 2629 |
+
err_str = str(e).strip() or repr(e)
|
| 2630 |
+
yield (
|
| 2631 |
+
f"Model failed to reload from checkpoint: **{err_type}:** {err_str}\n\n"
|
| 2632 |
+
"Try re-obliterating the model. If this persists, check the Space logs."
|
| 2633 |
+
)
|
| 2634 |
return
|
| 2635 |
else:
|
| 2636 |
yield "No model loaded yet. Go to the **Obliterate** tab first and liberate a model."
|
|
|
|
| 2697 |
model.generate(**kwargs)
|
| 2698 |
except Exception as e:
|
| 2699 |
gen_error[0] = e
|
| 2700 |
+
logger.error("Chat generation failed: %s\n%s", e, traceback.format_exc())
|
| 2701 |
# Signal the streamer to stop so the main thread doesn't hang
|
| 2702 |
try:
|
| 2703 |
streamer.end()
|
|
|
|
| 2712 |
for token in streamer:
|
| 2713 |
partial += token
|
| 2714 |
yield partial
|
| 2715 |
+
except Exception as e:
|
| 2716 |
# Streamer timeout or broken pipe β yield whatever we have so far
|
| 2717 |
+
logger.warning("Chat streamer interrupted: %s", e)
|
| 2718 |
if partial:
|
| 2719 |
yield partial
|
| 2720 |
|
obliteratus/abliterate.py
CHANGED
|
@@ -936,11 +936,15 @@ class AbliterationPipeline:
|
|
| 936 |
for h in self._steering_hooks:
|
| 937 |
h.remove()
|
| 938 |
self._steering_hooks.clear()
|
|
|
|
| 939 |
self._summon()
|
|
|
|
| 940 |
self._free_gpu_memory()
|
| 941 |
self._probe()
|
|
|
|
| 942 |
self._free_gpu_memory()
|
| 943 |
self._distill()
|
|
|
|
| 944 |
# Free raw per-prompt activations now that means/subspaces are extracted
|
| 945 |
self._harmful_acts.clear()
|
| 946 |
self._harmless_acts.clear()
|
|
@@ -955,10 +959,14 @@ class AbliterationPipeline:
|
|
| 955 |
self._free_gpu_memory()
|
| 956 |
self._capture_baseline_kl_logits()
|
| 957 |
self._excise()
|
|
|
|
| 958 |
self._free_gpu_memory()
|
| 959 |
self._verify()
|
|
|
|
| 960 |
self._free_gpu_memory()
|
| 961 |
-
|
|
|
|
|
|
|
| 962 |
|
| 963 |
# ββ Stage 1: SUMMON βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 964 |
|
|
|
|
| 936 |
for h in self._steering_hooks:
|
| 937 |
h.remove()
|
| 938 |
self._steering_hooks.clear()
|
| 939 |
+
_t0 = time.time()
|
| 940 |
self._summon()
|
| 941 |
+
self.log(f"[timing] SUMMON complete at +{time.time() - _t0:.1f}s")
|
| 942 |
self._free_gpu_memory()
|
| 943 |
self._probe()
|
| 944 |
+
self.log(f"[timing] PROBE complete at +{time.time() - _t0:.1f}s")
|
| 945 |
self._free_gpu_memory()
|
| 946 |
self._distill()
|
| 947 |
+
self.log(f"[timing] DISTILL complete at +{time.time() - _t0:.1f}s")
|
| 948 |
# Free raw per-prompt activations now that means/subspaces are extracted
|
| 949 |
self._harmful_acts.clear()
|
| 950 |
self._harmless_acts.clear()
|
|
|
|
| 959 |
self._free_gpu_memory()
|
| 960 |
self._capture_baseline_kl_logits()
|
| 961 |
self._excise()
|
| 962 |
+
self.log(f"[timing] EXCISE complete at +{time.time() - _t0:.1f}s")
|
| 963 |
self._free_gpu_memory()
|
| 964 |
self._verify()
|
| 965 |
+
self.log(f"[timing] VERIFY complete at +{time.time() - _t0:.1f}s")
|
| 966 |
self._free_gpu_memory()
|
| 967 |
+
result = self._rebirth()
|
| 968 |
+
self.log(f"[timing] REBIRTH complete at +{time.time() - _t0:.1f}s β pipeline finished")
|
| 969 |
+
return result
|
| 970 |
|
| 971 |
# ββ Stage 1: SUMMON βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 972 |
|
obliteratus/tourney.py
CHANGED
|
@@ -1135,6 +1135,8 @@ class TourneyRunner:
|
|
| 1135 |
return True
|
| 1136 |
if "expired" in msg and "zerogpu" in msg:
|
| 1137 |
return True
|
|
|
|
|
|
|
| 1138 |
return False
|
| 1139 |
|
| 1140 |
def _run_one_method(self, method, harmful, harmless, save_dir, verify_sz, gpu_wrapper):
|
|
|
|
| 1135 |
return True
|
| 1136 |
if "expired" in msg and "zerogpu" in msg:
|
| 1137 |
return True
|
| 1138 |
+
if "gpu task aborted" in msg:
|
| 1139 |
+
return True
|
| 1140 |
return False
|
| 1141 |
|
| 1142 |
def _run_one_method(self, method, harmful, harmless, save_dir, verify_sz, gpu_wrapper):
|