Spaces:
Running on Zero
Running on Zero
Upload 133 files
Browse files- app.py +172 -99
- obliteratus/abliterate.py +140 -0
- obliteratus/informed_pipeline.py +27 -0
app.py
CHANGED
|
@@ -2046,7 +2046,22 @@ def _format_multi_model_results(results: list[dict], context: dict | None = None
|
|
| 2046 |
return "\n".join(lines)
|
| 2047 |
|
| 2048 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2049 |
@spaces.GPU(duration=300)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2050 |
def obliterate(model_choice: str, method_choice: str,
|
| 2051 |
prompt_volume_choice: str, dataset_source_choice: str,
|
| 2052 |
custom_harmful: str, custom_harmless: str,
|
|
@@ -2077,9 +2092,14 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2077 |
progress=gr.Progress()):
|
| 2078 |
"""Run the full obliteration pipeline, streaming log updates to the UI.
|
| 2079 |
|
| 2080 |
-
On ZeroGPU Spaces,
|
| 2081 |
-
|
| 2082 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2083 |
"""
|
| 2084 |
import os
|
| 2085 |
import re
|
|
@@ -2188,106 +2208,159 @@ def obliterate(model_choice: str, method_choice: str,
|
|
| 2188 |
|
| 2189 |
quantization = _should_quantize(model_id, is_preset=is_preset)
|
| 2190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2191 |
def run_pipeline():
|
| 2192 |
try:
|
| 2193 |
-
|
| 2194 |
-
|
| 2195 |
-
|
| 2196 |
-
on_log("[timing] Running on ZeroGPU β GPU may be deallocated if pipeline exceeds duration")
|
| 2197 |
|
| 2198 |
-
|
| 2199 |
-
|
| 2200 |
-
|
| 2201 |
-
|
| 2202 |
-
|
| 2203 |
-
)
|
| 2204 |
-
|
| 2205 |
-
|
| 2206 |
-
|
| 2207 |
-
|
| 2208 |
-
|
| 2209 |
-
|
| 2210 |
-
|
| 2211 |
-
|
| 2212 |
-
|
| 2213 |
-
|
| 2214 |
-
|
| 2215 |
-
|
| 2216 |
-
|
| 2217 |
-
|
| 2218 |
-
|
| 2219 |
-
|
| 2220 |
-
|
| 2221 |
-
|
| 2222 |
-
|
| 2223 |
-
|
| 2224 |
-
|
| 2225 |
-
|
| 2226 |
-
|
| 2227 |
-
|
| 2228 |
-
|
| 2229 |
-
|
| 2230 |
-
on_log
|
| 2231 |
-
|
| 2232 |
-
|
| 2233 |
-
|
| 2234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2235 |
else:
|
| 2236 |
-
|
| 2237 |
-
|
| 2238 |
-
|
| 2239 |
-
|
| 2240 |
-
|
| 2241 |
-
|
| 2242 |
-
method=method,
|
| 2243 |
-
quantization=quantization,
|
| 2244 |
-
trust_remote_code=is_preset,
|
| 2245 |
-
harmful_prompts=harmful_all[:n],
|
| 2246 |
-
harmless_prompts=harmless_all[:n],
|
| 2247 |
-
on_stage=on_stage,
|
| 2248 |
-
on_log=on_log,
|
| 2249 |
-
# Advanced overrides from UI
|
| 2250 |
-
n_directions=int(adv_n_directions),
|
| 2251 |
-
direction_method=adv_direction_method,
|
| 2252 |
-
regularization=float(adv_regularization),
|
| 2253 |
-
refinement_passes=int(adv_refinement_passes),
|
| 2254 |
-
norm_preserve=adv_norm_preserve,
|
| 2255 |
-
project_biases=adv_project_biases,
|
| 2256 |
-
use_chat_template=adv_use_chat_template,
|
| 2257 |
-
use_whitened_svd=adv_use_whitened_svd,
|
| 2258 |
-
true_iterative_refinement=adv_true_iterative,
|
| 2259 |
-
use_jailbreak_contrast=adv_jailbreak_contrast,
|
| 2260 |
-
layer_adaptive_strength=adv_layer_adaptive,
|
| 2261 |
-
safety_neuron_masking=adv_safety_neuron,
|
| 2262 |
-
per_expert_directions=adv_per_expert,
|
| 2263 |
-
attention_head_surgery=adv_attn_surgery,
|
| 2264 |
-
use_sae_features=adv_sae_features,
|
| 2265 |
-
invert_refusal=adv_invert_refusal,
|
| 2266 |
-
reflection_strength=float(adv_reflection_strength),
|
| 2267 |
-
project_embeddings=adv_project_embeddings,
|
| 2268 |
-
embed_regularization=float(adv_embed_regularization),
|
| 2269 |
-
activation_steering=adv_activation_steering,
|
| 2270 |
-
steering_strength=float(adv_steering_strength),
|
| 2271 |
-
expert_transplant=adv_expert_transplant,
|
| 2272 |
-
transplant_blend=float(adv_transplant_blend),
|
| 2273 |
-
use_wasserstein_optimal=adv_wasserstein_optimal,
|
| 2274 |
-
spectral_cascade=adv_spectral_cascade,
|
| 2275 |
-
spectral_bands=int(adv_spectral_bands),
|
| 2276 |
-
spectral_threshold=float(adv_spectral_threshold),
|
| 2277 |
-
verify_sample_size=int(adv_verify_sample_size),
|
| 2278 |
-
layer_selection=adv_layer_selection,
|
| 2279 |
-
winsorize_activations=adv_winsorize,
|
| 2280 |
-
winsorize_percentile=float(adv_winsorize_percentile),
|
| 2281 |
-
use_kl_optimization=adv_kl_optimization,
|
| 2282 |
-
kl_budget=float(adv_kl_budget),
|
| 2283 |
-
float_layer_interpolation=adv_float_layer_interp,
|
| 2284 |
-
rdo_refinement=adv_rdo_refinement,
|
| 2285 |
-
cot_aware=adv_cot_aware,
|
| 2286 |
-
n_sae_features=int(adv_n_sae_features),
|
| 2287 |
-
)
|
| 2288 |
-
pipeline._bayesian_trials = int(adv_bayesian_trials)
|
| 2289 |
-
pipeline_ref[0] = pipeline
|
| 2290 |
-
pipeline.run(gpu_start_time=t_start)
|
| 2291 |
except Exception as e:
|
| 2292 |
error_ref[0] = e
|
| 2293 |
tb = traceback.format_exc()
|
|
|
|
| 2046 |
return "\n".join(lines)
|
| 2047 |
|
| 2048 |
|
| 2049 |
+
# ---------------------------------------------------------------------------
|
| 2050 |
+
# Staged GPU wrapper for obliteration (tourney-style per-stage allocation)
|
| 2051 |
+
# ---------------------------------------------------------------------------
|
| 2052 |
+
|
| 2053 |
@spaces.GPU(duration=300)
|
| 2054 |
+
def _obliterate_gpu_run(fn, *args, **kwargs):
|
| 2055 |
+
"""Execute *fn* inside a ZeroGPU GPU allocation.
|
| 2056 |
+
|
| 2057 |
+
Used by ``obliterate`` to give each pipeline stage its own 5-minute
|
| 2058 |
+
GPU allocation instead of sharing a single allocation for the whole
|
| 2059 |
+
pipeline. On non-ZeroGPU machines the ``@spaces.GPU`` decorator is a
|
| 2060 |
+
no-op and this simply calls *fn* directly.
|
| 2061 |
+
"""
|
| 2062 |
+
return fn(*args, **kwargs)
|
| 2063 |
+
|
| 2064 |
+
|
| 2065 |
def obliterate(model_choice: str, method_choice: str,
|
| 2066 |
prompt_volume_choice: str, dataset_source_choice: str,
|
| 2067 |
custom_harmful: str, custom_harmless: str,
|
|
|
|
| 2092 |
progress=gr.Progress()):
|
| 2093 |
"""Run the full obliteration pipeline, streaming log updates to the UI.
|
| 2094 |
|
| 2095 |
+
On ZeroGPU Spaces, the pipeline is split into 3 GPU stages (up to 5 min
|
| 2096 |
+
each) using the tourney-style approach: each stage gets its own
|
| 2097 |
+
``@spaces.GPU(duration=300)`` allocation via ``_obliterate_gpu_run``.
|
| 2098 |
+
Between stages the model is offloaded to CPU and the GPU is released,
|
| 2099 |
+
preventing the 5-minute ZeroGPU timeout from killing large-model runs.
|
| 2100 |
+
|
| 2101 |
+
On local/non-ZeroGPU machines, the pipeline runs in a single shot as
|
| 2102 |
+
before (no time limit).
|
| 2103 |
"""
|
| 2104 |
import os
|
| 2105 |
import re
|
|
|
|
| 2208 |
|
| 2209 |
quantization = _should_quantize(model_id, is_preset=is_preset)
|
| 2210 |
|
| 2211 |
+
def _create_pipeline(on_log, on_stage):
|
| 2212 |
+
"""Create the pipeline object and load prompts (no GPU required)."""
|
| 2213 |
+
_t_pipeline_start = time.time()
|
| 2214 |
+
|
| 2215 |
+
# Load prompts β custom overrides dataset dropdown
|
| 2216 |
+
if use_custom:
|
| 2217 |
+
on_log("Using custom user-provided prompts...")
|
| 2218 |
+
harmful_all, harmless_all = load_custom_prompts(
|
| 2219 |
+
custom_harmful, custom_harmless or "",
|
| 2220 |
+
)
|
| 2221 |
+
on_log(f"Custom prompts: {len(harmful_all)} harmful, {len(harmless_all)} harmless")
|
| 2222 |
+
else:
|
| 2223 |
+
on_log(f"Loading dataset: {dataset_key}...")
|
| 2224 |
+
harmful_all, harmless_all = load_dataset_source(dataset_key)
|
| 2225 |
+
on_log(f"Dataset loaded: {len(harmful_all)} harmful, {len(harmless_all)} harmless prompts")
|
| 2226 |
+
on_log(f"[timing] Dataset loaded at +{time.time() - _t_pipeline_start:.1f}s")
|
| 2227 |
+
|
| 2228 |
+
# Apply volume cap (-1 = use all)
|
| 2229 |
+
if prompt_volume > 0:
|
| 2230 |
+
n = min(prompt_volume, len(harmful_all), len(harmless_all))
|
| 2231 |
+
else:
|
| 2232 |
+
n = min(len(harmful_all), len(harmless_all))
|
| 2233 |
+
|
| 2234 |
+
if method == "informed":
|
| 2235 |
+
from obliteratus.informed_pipeline import InformedAbliterationPipeline
|
| 2236 |
+
pipeline = InformedAbliterationPipeline(
|
| 2237 |
+
model_name=model_id,
|
| 2238 |
+
output_dir=save_dir,
|
| 2239 |
+
device="auto",
|
| 2240 |
+
dtype="float16",
|
| 2241 |
+
quantization=quantization,
|
| 2242 |
+
trust_remote_code=is_preset,
|
| 2243 |
+
harmful_prompts=harmful_all[:n],
|
| 2244 |
+
harmless_prompts=harmless_all[:n],
|
| 2245 |
+
on_stage=on_stage,
|
| 2246 |
+
on_log=on_log,
|
| 2247 |
+
)
|
| 2248 |
+
else:
|
| 2249 |
+
from obliteratus.abliterate import AbliterationPipeline
|
| 2250 |
+
pipeline = AbliterationPipeline(
|
| 2251 |
+
model_name=model_id,
|
| 2252 |
+
output_dir=save_dir,
|
| 2253 |
+
device="auto",
|
| 2254 |
+
dtype="float16",
|
| 2255 |
+
method=method,
|
| 2256 |
+
quantization=quantization,
|
| 2257 |
+
trust_remote_code=is_preset,
|
| 2258 |
+
harmful_prompts=harmful_all[:n],
|
| 2259 |
+
harmless_prompts=harmless_all[:n],
|
| 2260 |
+
on_stage=on_stage,
|
| 2261 |
+
on_log=on_log,
|
| 2262 |
+
# Advanced overrides from UI
|
| 2263 |
+
n_directions=int(adv_n_directions),
|
| 2264 |
+
direction_method=adv_direction_method,
|
| 2265 |
+
regularization=float(adv_regularization),
|
| 2266 |
+
refinement_passes=int(adv_refinement_passes),
|
| 2267 |
+
norm_preserve=adv_norm_preserve,
|
| 2268 |
+
project_biases=adv_project_biases,
|
| 2269 |
+
use_chat_template=adv_use_chat_template,
|
| 2270 |
+
use_whitened_svd=adv_use_whitened_svd,
|
| 2271 |
+
true_iterative_refinement=adv_true_iterative,
|
| 2272 |
+
use_jailbreak_contrast=adv_jailbreak_contrast,
|
| 2273 |
+
layer_adaptive_strength=adv_layer_adaptive,
|
| 2274 |
+
safety_neuron_masking=adv_safety_neuron,
|
| 2275 |
+
per_expert_directions=adv_per_expert,
|
| 2276 |
+
attention_head_surgery=adv_attn_surgery,
|
| 2277 |
+
use_sae_features=adv_sae_features,
|
| 2278 |
+
invert_refusal=adv_invert_refusal,
|
| 2279 |
+
reflection_strength=float(adv_reflection_strength),
|
| 2280 |
+
project_embeddings=adv_project_embeddings,
|
| 2281 |
+
embed_regularization=float(adv_embed_regularization),
|
| 2282 |
+
activation_steering=adv_activation_steering,
|
| 2283 |
+
steering_strength=float(adv_steering_strength),
|
| 2284 |
+
expert_transplant=adv_expert_transplant,
|
| 2285 |
+
transplant_blend=float(adv_transplant_blend),
|
| 2286 |
+
use_wasserstein_optimal=adv_wasserstein_optimal,
|
| 2287 |
+
spectral_cascade=adv_spectral_cascade,
|
| 2288 |
+
spectral_bands=int(adv_spectral_bands),
|
| 2289 |
+
spectral_threshold=float(adv_spectral_threshold),
|
| 2290 |
+
verify_sample_size=int(adv_verify_sample_size),
|
| 2291 |
+
layer_selection=adv_layer_selection,
|
| 2292 |
+
winsorize_activations=adv_winsorize,
|
| 2293 |
+
winsorize_percentile=float(adv_winsorize_percentile),
|
| 2294 |
+
use_kl_optimization=adv_kl_optimization,
|
| 2295 |
+
kl_budget=float(adv_kl_budget),
|
| 2296 |
+
float_layer_interpolation=adv_float_layer_interp,
|
| 2297 |
+
rdo_refinement=adv_rdo_refinement,
|
| 2298 |
+
cot_aware=adv_cot_aware,
|
| 2299 |
+
n_sae_features=int(adv_n_sae_features),
|
| 2300 |
+
)
|
| 2301 |
+
pipeline._bayesian_trials = int(adv_bayesian_trials)
|
| 2302 |
+
return pipeline
|
| 2303 |
+
|
| 2304 |
def run_pipeline():
|
| 2305 |
try:
|
| 2306 |
+
on_log(f"[timing] Pipeline thread started")
|
| 2307 |
+
pipeline = _create_pipeline(on_log, on_stage)
|
| 2308 |
+
pipeline_ref[0] = pipeline
|
|
|
|
| 2309 |
|
| 2310 |
+
if _ZEROGPU_AVAILABLE:
|
| 2311 |
+
# ββ Staged GPU execution (tourney-style) ββββββββββββββββββ
|
| 2312 |
+
# Each stage gets its own 5-minute GPU allocation instead of
|
| 2313 |
+
# sharing a single 300s budget. Between stages the model is
|
| 2314 |
+
# moved to CPU and the GPU is released.
|
| 2315 |
+
on_log("[staged] ZeroGPU detected β using staged GPU execution (up to 5 min per stage)")
|
| 2316 |
+
|
| 2317 |
+
if method == "informed":
|
| 2318 |
+
# Informed pipeline: SUMMON+PROBE | ANALYZE+DISTILL+EXCISE | VERIFY+REBIRTH
|
| 2319 |
+
on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
|
| 2320 |
+
_obliterate_gpu_run(pipeline.run_stage_summon_probe, time.time())
|
| 2321 |
+
pipeline._offload_to_cpu()
|
| 2322 |
+
on_log("[staged] GPU released after Stage 1\n")
|
| 2323 |
+
|
| 2324 |
+
on_log("\u26a1 [staged] GPU Stage 2/3: ANALYZE + DISTILL + EXCISE")
|
| 2325 |
+
def _informed_s2():
|
| 2326 |
+
pipeline._restore_to_gpu()
|
| 2327 |
+
pipeline.run_stage_analyze_distill_excise()
|
| 2328 |
+
_obliterate_gpu_run(_informed_s2)
|
| 2329 |
+
pipeline._offload_to_cpu()
|
| 2330 |
+
on_log("[staged] GPU released after Stage 2\n")
|
| 2331 |
+
|
| 2332 |
+
on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
|
| 2333 |
+
def _informed_s3():
|
| 2334 |
+
pipeline._restore_to_gpu()
|
| 2335 |
+
pipeline.run_stage_verify_rebirth_informed()
|
| 2336 |
+
_obliterate_gpu_run(_informed_s3)
|
| 2337 |
+
else:
|
| 2338 |
+
# Standard pipeline: SUMMON+PROBE | DISTILL+EXCISE | VERIFY+REBIRTH
|
| 2339 |
+
on_log("\n\u26a1 [staged] GPU Stage 1/3: SUMMON + PROBE")
|
| 2340 |
+
_obliterate_gpu_run(pipeline.run_stage_summon_probe, time.time())
|
| 2341 |
+
pipeline._offload_to_cpu()
|
| 2342 |
+
on_log("[staged] GPU released after Stage 1\n")
|
| 2343 |
+
|
| 2344 |
+
on_log("\u26a1 [staged] GPU Stage 2/3: DISTILL + EXCISE")
|
| 2345 |
+
def _standard_s2():
|
| 2346 |
+
pipeline._restore_to_gpu()
|
| 2347 |
+
pipeline.run_stage_distill_excise()
|
| 2348 |
+
_obliterate_gpu_run(_standard_s2)
|
| 2349 |
+
pipeline._offload_to_cpu()
|
| 2350 |
+
on_log("[staged] GPU released after Stage 2\n")
|
| 2351 |
+
|
| 2352 |
+
on_log("\u26a1 [staged] GPU Stage 3/3: VERIFY + REBIRTH")
|
| 2353 |
+
def _standard_s3():
|
| 2354 |
+
pipeline._restore_to_gpu()
|
| 2355 |
+
pipeline.run_stage_verify_rebirth()
|
| 2356 |
+
_obliterate_gpu_run(_standard_s3)
|
| 2357 |
else:
|
| 2358 |
+
# ββ Local/non-ZeroGPU: single-shot execution ββββββββββββββ
|
| 2359 |
+
on_log(f"[timing] Running locally (no GPU time limit)")
|
| 2360 |
+
if method == "informed":
|
| 2361 |
+
pipeline.run_informed(gpu_start_time=t_start)
|
| 2362 |
+
else:
|
| 2363 |
+
pipeline.run(gpu_start_time=t_start)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2364 |
except Exception as e:
|
| 2365 |
error_ref[0] = e
|
| 2366 |
tb = traceback.format_exc()
|
obliteratus/abliterate.py
CHANGED
|
@@ -985,6 +985,146 @@ class AbliterationPipeline:
|
|
| 985 |
self.log(f"[timing] REBIRTH complete at +{time.time() - _t0:.1f}s β pipeline finished")
|
| 986 |
return result
|
| 987 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 988 |
# ββ Stage 1: SUMMON βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 989 |
|
| 990 |
def _summon(self):
|
|
|
|
| 985 |
self.log(f"[timing] REBIRTH complete at +{time.time() - _t0:.1f}s β pipeline finished")
|
| 986 |
return result
|
| 987 |
|
| 988 |
+
# ββ Staged execution (ZeroGPU-safe) ββββββββββββββββββββββββββββββββββ
|
| 989 |
+
# These methods split the pipeline into independent GPU stages so each
|
| 990 |
+
# stage can run in its own @spaces.GPU allocation (up to 5 min each).
|
| 991 |
+
# Between stages the caller moves the model to CPU and releases the GPU.
|
| 992 |
+
# This mirrors the tourney approach and prevents ZeroGPU timeouts on
|
| 993 |
+
# large models that need more than 5 minutes total.
|
| 994 |
+
|
| 995 |
+
def run_stage_summon_probe(self, gpu_start_time: float | None = None):
|
| 996 |
+
"""GPU Stage 1: Load model and collect activations (SUMMON + PROBE).
|
| 997 |
+
|
| 998 |
+
After this stage the caller should call ``_offload_to_cpu()`` to
|
| 999 |
+
move the model off GPU before releasing the ZeroGPU allocation.
|
| 1000 |
+
"""
|
| 1001 |
+
for h in self._steering_hooks:
|
| 1002 |
+
h.remove()
|
| 1003 |
+
self._steering_hooks.clear()
|
| 1004 |
+
self._staged_t0 = time.time()
|
| 1005 |
+
self._pipeline_start_time = gpu_start_time if gpu_start_time is not None else self._staged_t0
|
| 1006 |
+
self._summon()
|
| 1007 |
+
self.log(f"[timing] SUMMON complete at +{time.time() - self._staged_t0:.1f}s")
|
| 1008 |
+
self._free_gpu_memory()
|
| 1009 |
+
self._probe()
|
| 1010 |
+
self.log(f"[timing] PROBE complete at +{time.time() - self._staged_t0:.1f}s")
|
| 1011 |
+
self._free_gpu_memory()
|
| 1012 |
+
|
| 1013 |
+
def run_stage_distill_excise(self):
|
| 1014 |
+
"""GPU Stage 2: Extract directions, modify weights, save checkpoint
|
| 1015 |
+
(DISTILL + EXCISE + quick checkpoint).
|
| 1016 |
+
|
| 1017 |
+
After this stage the caller should call ``_offload_to_cpu()`` to
|
| 1018 |
+
move the model off GPU before releasing the ZeroGPU allocation.
|
| 1019 |
+
"""
|
| 1020 |
+
# Reset time budget for this GPU allocation
|
| 1021 |
+
self._pipeline_start_time = time.time()
|
| 1022 |
+
self._distill()
|
| 1023 |
+
self.log(f"[timing] DISTILL complete at +{time.time() - self._staged_t0:.1f}s")
|
| 1024 |
+
# Free raw per-prompt activations now that means/subspaces are extracted
|
| 1025 |
+
self._harmful_acts.clear()
|
| 1026 |
+
self._harmless_acts.clear()
|
| 1027 |
+
self._jailbreak_acts.clear()
|
| 1028 |
+
self._harmful_means.clear()
|
| 1029 |
+
self._harmless_means.clear()
|
| 1030 |
+
self._routing_harmful.clear()
|
| 1031 |
+
self._routing_harmless.clear()
|
| 1032 |
+
self._free_gpu_memory()
|
| 1033 |
+
self._capture_baseline_kl_logits()
|
| 1034 |
+
self._excise()
|
| 1035 |
+
self.log(f"[timing] EXCISE complete at +{time.time() - self._staged_t0:.1f}s")
|
| 1036 |
+
self._free_gpu_memory()
|
| 1037 |
+
self._save_quick_checkpoint()
|
| 1038 |
+
|
| 1039 |
+
def run_stage_verify_rebirth(self) -> Path:
|
| 1040 |
+
"""GPU Stage 3: Quality verification and final save (VERIFY + REBIRTH).
|
| 1041 |
+
|
| 1042 |
+
Returns:
|
| 1043 |
+
Path to the saved model directory.
|
| 1044 |
+
"""
|
| 1045 |
+
# Reset time budget for this GPU allocation
|
| 1046 |
+
self._pipeline_start_time = time.time()
|
| 1047 |
+
self._verify()
|
| 1048 |
+
self.log(f"[timing] VERIFY complete at +{time.time() - self._staged_t0:.1f}s")
|
| 1049 |
+
self._free_gpu_memory()
|
| 1050 |
+
result = self._rebirth()
|
| 1051 |
+
self.log(f"[timing] REBIRTH complete at +{time.time() - self._staged_t0:.1f}s β pipeline finished")
|
| 1052 |
+
return result
|
| 1053 |
+
|
| 1054 |
+
def _offload_to_cpu(self):
|
| 1055 |
+
"""Move model and intermediate tensors to CPU for ZeroGPU stage boundary.
|
| 1056 |
+
|
| 1057 |
+
Called between staged GPU allocations so the model survives GPU
|
| 1058 |
+
deallocation. Also moves cached activation tensors to CPU.
|
| 1059 |
+
"""
|
| 1060 |
+
if self.handle is None or self.handle.model is None:
|
| 1061 |
+
return
|
| 1062 |
+
model = self.handle.model
|
| 1063 |
+
# Remember the GPU device for later restoration
|
| 1064 |
+
try:
|
| 1065 |
+
self._gpu_device = self._get_model_device(model)
|
| 1066 |
+
except (StopIteration, RuntimeError):
|
| 1067 |
+
self._gpu_device = torch.device("cuda:0")
|
| 1068 |
+
|
| 1069 |
+
self.log("[staged] Moving model to CPU between GPU allocations...")
|
| 1070 |
+
t0 = time.time()
|
| 1071 |
+
model.to("cpu")
|
| 1072 |
+
|
| 1073 |
+
# Move any cached tensors that are still on GPU
|
| 1074 |
+
for tensor_dict in (
|
| 1075 |
+
self._harmful_means, self._harmless_means,
|
| 1076 |
+
self._jailbreak_means,
|
| 1077 |
+
):
|
| 1078 |
+
for k in list(tensor_dict.keys()):
|
| 1079 |
+
if isinstance(tensor_dict[k], torch.Tensor) and tensor_dict[k].device.type != "cpu":
|
| 1080 |
+
tensor_dict[k] = tensor_dict[k].cpu()
|
| 1081 |
+
|
| 1082 |
+
for act_dict in (self._harmful_acts, self._harmless_acts, self._jailbreak_acts):
|
| 1083 |
+
for k in list(act_dict.keys()):
|
| 1084 |
+
if isinstance(act_dict[k], list):
|
| 1085 |
+
act_dict[k] = [t.cpu() if isinstance(t, torch.Tensor) and t.device.type != "cpu" else t
|
| 1086 |
+
for t in act_dict[k]]
|
| 1087 |
+
|
| 1088 |
+
# Move refusal directions/subspaces to CPU
|
| 1089 |
+
for k in list(self.refusal_directions.keys()):
|
| 1090 |
+
v = self.refusal_directions[k]
|
| 1091 |
+
if isinstance(v, torch.Tensor) and v.device.type != "cpu":
|
| 1092 |
+
self.refusal_directions[k] = v.cpu()
|
| 1093 |
+
for k in list(self.refusal_subspaces.keys()):
|
| 1094 |
+
v = self.refusal_subspaces[k]
|
| 1095 |
+
if isinstance(v, torch.Tensor) and v.device.type != "cpu":
|
| 1096 |
+
self.refusal_subspaces[k] = v.cpu()
|
| 1097 |
+
|
| 1098 |
+
# Move baseline KL logits to CPU
|
| 1099 |
+
if hasattr(self, "_kl_baseline_logits") and self._kl_baseline_logits is not None:
|
| 1100 |
+
self._kl_baseline_logits = [
|
| 1101 |
+
t.cpu() if isinstance(t, torch.Tensor) and t.device.type != "cpu" else t
|
| 1102 |
+
for t in self._kl_baseline_logits
|
| 1103 |
+
]
|
| 1104 |
+
|
| 1105 |
+
dev.free_gpu_memory()
|
| 1106 |
+
elapsed = time.time() - t0
|
| 1107 |
+
self.log(f"[staged] Model offloaded to CPU ({elapsed:.1f}s)")
|
| 1108 |
+
|
| 1109 |
+
def _restore_to_gpu(self):
|
| 1110 |
+
"""Move model back to GPU at the start of a new GPU allocation.
|
| 1111 |
+
|
| 1112 |
+
Uses the device stored by ``_offload_to_cpu()``, defaulting to
|
| 1113 |
+
``cuda:0`` if not set.
|
| 1114 |
+
"""
|
| 1115 |
+
if self.handle is None or self.handle.model is None:
|
| 1116 |
+
return
|
| 1117 |
+
target = getattr(self, "_gpu_device", None) or torch.device("cuda:0")
|
| 1118 |
+
# On ZeroGPU, the device index may change between allocations,
|
| 1119 |
+
# so always use cuda:0 which is the only device available.
|
| 1120 |
+
if target.type == "cuda":
|
| 1121 |
+
target = torch.device("cuda:0")
|
| 1122 |
+
self.log(f"[staged] Moving model to {target}...")
|
| 1123 |
+
t0 = time.time()
|
| 1124 |
+
self.handle.model.to(target)
|
| 1125 |
+
elapsed = time.time() - t0
|
| 1126 |
+
self.log(f"[staged] Model restored to {target} ({elapsed:.1f}s)")
|
| 1127 |
+
|
| 1128 |
# ββ Stage 1: SUMMON βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1129 |
|
| 1130 |
def _summon(self):
|
obliteratus/informed_pipeline.py
CHANGED
|
@@ -299,6 +299,33 @@ class InformedAbliterationPipeline(AbliterationPipeline):
|
|
| 299 |
self._report.total_duration = time.time() - t0
|
| 300 |
return output_path, self._report
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
# ββ Stage 3: ANALYZE βββββββββββββββββββββββββββββββββββββββββββββ
|
| 303 |
|
| 304 |
def _analyze(self):
|
|
|
|
| 299 |
self._report.total_duration = time.time() - t0
|
| 300 |
return output_path, self._report
|
| 301 |
|
| 302 |
+
# ββ Staged execution (ZeroGPU-safe) ββββββββββββββββββββββββββββββ
|
| 303 |
+
# Split the informed pipeline into 3 GPU stages so each gets its own
|
| 304 |
+
# @spaces.GPU(duration=300) allocation. SUMMON+PROBE is inherited
|
| 305 |
+
# from AbliterationPipeline.run_stage_summon_probe().
|
| 306 |
+
|
| 307 |
+
def run_stage_analyze_distill_excise(self):
|
| 308 |
+
"""GPU Stage 2 (informed): ANALYZE + DISTILL + EXCISE + quick checkpoint."""
|
| 309 |
+
# Reset time budget for this GPU allocation
|
| 310 |
+
self._pipeline_start_time = time.time()
|
| 311 |
+
self._analyze()
|
| 312 |
+
self._distill_informed()
|
| 313 |
+
self._excise_informed()
|
| 314 |
+
self._save_quick_checkpoint()
|
| 315 |
+
|
| 316 |
+
def run_stage_verify_rebirth_informed(self):
|
| 317 |
+
"""GPU Stage 3 (informed): VERIFY + Ouroboros compensation + REBIRTH.
|
| 318 |
+
|
| 319 |
+
Stores the result path and report in instance attributes so the
|
| 320 |
+
caller can retrieve them after the GPU allocation returns.
|
| 321 |
+
"""
|
| 322 |
+
# Reset time budget for this GPU allocation
|
| 323 |
+
self._pipeline_start_time = time.time()
|
| 324 |
+
self._verify_and_compensate()
|
| 325 |
+
output_path = self._rebirth_informed()
|
| 326 |
+
self._report.total_duration = time.time() - getattr(self, "_staged_t0", time.time())
|
| 327 |
+
self._staged_result = (output_path, self._report)
|
| 328 |
+
|
| 329 |
# ββ Stage 3: ANALYZE βββββββββββββββββββββββββββββββββββββββββββββ
|
| 330 |
|
| 331 |
def _analyze(self):
|