Spaces:
Running on Zero
Running on Zero
Upload 133 files
Browse files- app.py +37 -13
- obliteratus/tourney.py +33 -4
app.py
CHANGED
|
@@ -3143,6 +3143,33 @@ def _tourney_gpu_run(fn, *args, **kwargs):
|
|
| 3143 |
return fn(*args, **kwargs)
|
| 3144 |
|
| 3145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3146 |
def run_tourney(model_choice, dataset, quantization):
|
| 3147 |
"""Run an elimination tournament across all abliteration methods.
|
| 3148 |
|
|
@@ -3169,10 +3196,7 @@ def run_tourney(model_choice, dataset, quantization):
|
|
| 3169 |
|
| 3170 |
quant = quantization if quantization != "none" else None
|
| 3171 |
|
| 3172 |
-
|
| 3173 |
-
|
| 3174 |
-
def on_log(msg):
|
| 3175 |
-
log_lines.append(msg)
|
| 3176 |
|
| 3177 |
dataset_key = get_source_key_from_label(dataset) if dataset else "builtin"
|
| 3178 |
|
|
@@ -3191,7 +3215,7 @@ def run_tourney(model_choice, dataset, quantization):
|
|
| 3191 |
hub_repo=None,
|
| 3192 |
dataset_key=dataset_key,
|
| 3193 |
quantization=quant,
|
| 3194 |
-
on_log=
|
| 3195 |
resume=resume,
|
| 3196 |
)
|
| 3197 |
except Exception as e:
|
|
@@ -3218,16 +3242,16 @@ def run_tourney(model_choice, dataset, quantization):
|
|
| 3218 |
|
| 3219 |
result = None
|
| 3220 |
try:
|
| 3221 |
-
for status_msg, partial_result in runner.run_iter(gpu_wrapper=
|
| 3222 |
result = partial_result
|
| 3223 |
yield (
|
| 3224 |
status_msg,
|
| 3225 |
"",
|
| 3226 |
-
|
| 3227 |
)
|
| 3228 |
except Exception as e:
|
| 3229 |
tb = traceback.format_exc()
|
| 3230 |
-
|
| 3231 |
if _is_quota_error(e):
|
| 3232 |
# Provide a helpful message for ZeroGPU quota exhaustion
|
| 3233 |
bracket_md = ""
|
|
@@ -3242,18 +3266,18 @@ def run_tourney(model_choice, dataset, quantization):
|
|
| 3242 |
"HuggingFace Pro subscribers get 7x more daily quota.\n\n"
|
| 3243 |
"**Tip:** use quantization to reduce per-method GPU time.",
|
| 3244 |
bracket_md,
|
| 3245 |
-
|
| 3246 |
)
|
| 3247 |
else:
|
| 3248 |
yield (
|
| 3249 |
f"**Error:** {type(e).__name__}: {e}",
|
| 3250 |
"",
|
| 3251 |
-
|
| 3252 |
)
|
| 3253 |
return
|
| 3254 |
|
| 3255 |
if not result:
|
| 3256 |
-
yield ("**Error:** Tournament produced no result.", "",
|
| 3257 |
return
|
| 3258 |
|
| 3259 |
winner = result.winner
|
|
@@ -3306,7 +3330,7 @@ def run_tourney(model_choice, dataset, quantization):
|
|
| 3306 |
f"(score: {winner.score:.4f})\n"
|
| 3307 |
f"Push it to HuggingFace Hub from the **Push to Hub** tab.",
|
| 3308 |
bracket_md,
|
| 3309 |
-
|
| 3310 |
)
|
| 3311 |
else:
|
| 3312 |
n_errors = sum(
|
|
@@ -3320,7 +3344,7 @@ def run_tourney(model_choice, dataset, quantization):
|
|
| 3320 |
yield (
|
| 3321 |
msg,
|
| 3322 |
bracket_md,
|
| 3323 |
-
|
| 3324 |
)
|
| 3325 |
|
| 3326 |
|
|
|
|
| 3143 |
return fn(*args, **kwargs)
|
| 3144 |
|
| 3145 |
|
| 3146 |
+
class _TourneyLogger:
|
| 3147 |
+
"""Picklable log collector for tournament progress.
|
| 3148 |
+
|
| 3149 |
+
Gradio's queue system pickles generator frames, so closures like
|
| 3150 |
+
``lambda msg: log_lines.append(msg)`` cause PicklingError. This
|
| 3151 |
+
simple class is picklable and serves the same purpose.
|
| 3152 |
+
"""
|
| 3153 |
+
|
| 3154 |
+
def __init__(self):
|
| 3155 |
+
self.lines: list[str] = []
|
| 3156 |
+
|
| 3157 |
+
def __call__(self, msg: str):
|
| 3158 |
+
self.lines.append(msg)
|
| 3159 |
+
|
| 3160 |
+
def tail(self, n: int = 100) -> str:
|
| 3161 |
+
"""Return the last *n* log lines joined by newlines. ``n=0`` returns all."""
|
| 3162 |
+
if n <= 0:
|
| 3163 |
+
return "\n".join(self.lines)
|
| 3164 |
+
return "\n".join(self.lines[-n:])
|
| 3165 |
+
|
| 3166 |
+
|
| 3167 |
+
def _tourney_gpu_wrapper(fn, *args, **kwargs):
|
| 3168 |
+
"""Indirection so the @spaces.GPU-wrapped function is resolved at call
|
| 3169 |
+
time rather than captured in the generator frame (which Gradio pickles)."""
|
| 3170 |
+
return _tourney_gpu_run(fn, *args, **kwargs)
|
| 3171 |
+
|
| 3172 |
+
|
| 3173 |
def run_tourney(model_choice, dataset, quantization):
|
| 3174 |
"""Run an elimination tournament across all abliteration methods.
|
| 3175 |
|
|
|
|
| 3196 |
|
| 3197 |
quant = quantization if quantization != "none" else None
|
| 3198 |
|
| 3199 |
+
logger = _TourneyLogger()
|
|
|
|
|
|
|
|
|
|
| 3200 |
|
| 3201 |
dataset_key = get_source_key_from_label(dataset) if dataset else "builtin"
|
| 3202 |
|
|
|
|
| 3215 |
hub_repo=None,
|
| 3216 |
dataset_key=dataset_key,
|
| 3217 |
quantization=quant,
|
| 3218 |
+
on_log=logger,
|
| 3219 |
resume=resume,
|
| 3220 |
)
|
| 3221 |
except Exception as e:
|
|
|
|
| 3242 |
|
| 3243 |
result = None
|
| 3244 |
try:
|
| 3245 |
+
for status_msg, partial_result in runner.run_iter(gpu_wrapper=_tourney_gpu_wrapper):
|
| 3246 |
result = partial_result
|
| 3247 |
yield (
|
| 3248 |
status_msg,
|
| 3249 |
"",
|
| 3250 |
+
logger.tail(),
|
| 3251 |
)
|
| 3252 |
except Exception as e:
|
| 3253 |
tb = traceback.format_exc()
|
| 3254 |
+
logger(f"\nTRACEBACK:\n{tb}")
|
| 3255 |
if _is_quota_error(e):
|
| 3256 |
# Provide a helpful message for ZeroGPU quota exhaustion
|
| 3257 |
bracket_md = ""
|
|
|
|
| 3266 |
"HuggingFace Pro subscribers get 7x more daily quota.\n\n"
|
| 3267 |
"**Tip:** use quantization to reduce per-method GPU time.",
|
| 3268 |
bracket_md,
|
| 3269 |
+
logger.tail(0),
|
| 3270 |
)
|
| 3271 |
else:
|
| 3272 |
yield (
|
| 3273 |
f"**Error:** {type(e).__name__}: {e}",
|
| 3274 |
"",
|
| 3275 |
+
logger.tail(0),
|
| 3276 |
)
|
| 3277 |
return
|
| 3278 |
|
| 3279 |
if not result:
|
| 3280 |
+
yield ("**Error:** Tournament produced no result.", "", logger.tail(0))
|
| 3281 |
return
|
| 3282 |
|
| 3283 |
winner = result.winner
|
|
|
|
| 3330 |
f"(score: {winner.score:.4f})\n"
|
| 3331 |
f"Push it to HuggingFace Hub from the **Push to Hub** tab.",
|
| 3332 |
bracket_md,
|
| 3333 |
+
logger.tail(0),
|
| 3334 |
)
|
| 3335 |
else:
|
| 3336 |
n_errors = sum(
|
|
|
|
| 3344 |
yield (
|
| 3345 |
msg,
|
| 3346 |
bracket_md,
|
| 3347 |
+
logger.tail(0),
|
| 3348 |
)
|
| 3349 |
|
| 3350 |
|
obliteratus/tourney.py
CHANGED
|
@@ -727,6 +727,33 @@ tokenizer = AutoTokenizer.from_pretrained("{result.hub_repo or 'this-repo'}")
|
|
| 727 |
"""
|
| 728 |
|
| 729 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
# ---------------------------------------------------------------------------
|
| 731 |
# Core runner
|
| 732 |
# ---------------------------------------------------------------------------
|
|
@@ -773,8 +800,8 @@ class TourneyRunner:
|
|
| 773 |
if self.output_dir.exists():
|
| 774 |
shutil.rmtree(self.output_dir, ignore_errors=True)
|
| 775 |
self.output_dir.mkdir(parents=True, exist_ok=True)
|
| 776 |
-
self._on_log = on_log or
|
| 777 |
-
self._on_round = on_round or
|
| 778 |
|
| 779 |
def log(self, msg: str):
|
| 780 |
self._on_log(msg)
|
|
@@ -801,6 +828,8 @@ class TourneyRunner:
|
|
| 801 |
|
| 802 |
try:
|
| 803 |
# Use informed pipeline for 'informed' method
|
|
|
|
|
|
|
| 804 |
if method == "informed":
|
| 805 |
from obliteratus.informed_pipeline import InformedAbliterationPipeline
|
| 806 |
pipeline = InformedAbliterationPipeline(
|
|
@@ -812,7 +841,7 @@ class TourneyRunner:
|
|
| 812 |
trust_remote_code=True,
|
| 813 |
harmful_prompts=harmful,
|
| 814 |
harmless_prompts=harmless,
|
| 815 |
-
on_log=
|
| 816 |
)
|
| 817 |
pipeline.run_informed()
|
| 818 |
else:
|
|
@@ -828,7 +857,7 @@ class TourneyRunner:
|
|
| 828 |
harmful_prompts=harmful,
|
| 829 |
harmless_prompts=harmless,
|
| 830 |
verify_sample_size=verify_sample_size,
|
| 831 |
-
on_log=
|
| 832 |
)
|
| 833 |
pipeline.run()
|
| 834 |
|
|
|
|
| 727 |
"""
|
| 728 |
|
| 729 |
|
| 730 |
+
def _noop_log(msg: str) -> None:
|
| 731 |
+
"""Picklable no-op log callback (lambdas can't be pickled by ZeroGPU)."""
|
| 732 |
+
pass
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
def _noop_round(r: TourneyRound) -> None:
|
| 736 |
+
"""Picklable no-op round callback."""
|
| 737 |
+
pass
|
| 738 |
+
|
| 739 |
+
|
| 740 |
+
class _MethodLogger:
|
| 741 |
+
"""Picklable per-method log adapter that prefixes messages.
|
| 742 |
+
|
| 743 |
+
ZeroGPU pickles bound methods (and their ``self``) when shipping work to
|
| 744 |
+
the GPU worker process. Plain lambdas like
|
| 745 |
+
``lambda msg: self.log(f" [{method}] {msg}")`` can't survive that, so
|
| 746 |
+
this small class replaces them.
|
| 747 |
+
"""
|
| 748 |
+
|
| 749 |
+
def __init__(self, parent_log: Callable[[str], None], method: str):
|
| 750 |
+
self._parent = parent_log
|
| 751 |
+
self._method = method
|
| 752 |
+
|
| 753 |
+
def __call__(self, msg: str):
|
| 754 |
+
self._parent(f" [{self._method}] {msg}")
|
| 755 |
+
|
| 756 |
+
|
| 757 |
# ---------------------------------------------------------------------------
|
| 758 |
# Core runner
|
| 759 |
# ---------------------------------------------------------------------------
|
|
|
|
| 800 |
if self.output_dir.exists():
|
| 801 |
shutil.rmtree(self.output_dir, ignore_errors=True)
|
| 802 |
self.output_dir.mkdir(parents=True, exist_ok=True)
|
| 803 |
+
self._on_log = on_log or _noop_log
|
| 804 |
+
self._on_round = on_round or _noop_round
|
| 805 |
|
| 806 |
def log(self, msg: str):
|
| 807 |
self._on_log(msg)
|
|
|
|
| 828 |
|
| 829 |
try:
|
| 830 |
# Use informed pipeline for 'informed' method
|
| 831 |
+
method_log = _MethodLogger(self._on_log, method)
|
| 832 |
+
|
| 833 |
if method == "informed":
|
| 834 |
from obliteratus.informed_pipeline import InformedAbliterationPipeline
|
| 835 |
pipeline = InformedAbliterationPipeline(
|
|
|
|
| 841 |
trust_remote_code=True,
|
| 842 |
harmful_prompts=harmful,
|
| 843 |
harmless_prompts=harmless,
|
| 844 |
+
on_log=method_log,
|
| 845 |
)
|
| 846 |
pipeline.run_informed()
|
| 847 |
else:
|
|
|
|
| 857 |
harmful_prompts=harmful,
|
| 858 |
harmless_prompts=harmless,
|
| 859 |
verify_sample_size=verify_sample_size,
|
| 860 |
+
on_log=method_log,
|
| 861 |
)
|
| 862 |
pipeline.run()
|
| 863 |
|