| #!/usr/bin/env bash |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| set -euo pipefail |
|
|
| |
| SSH_KEY="${OBLITERATUS_SSH_KEY:-$HOME/.ssh/hf_obliteratus}" |
| SSH_HOST="${OBLITERATUS_SSH_HOST:-}" |
| MODEL="${OBLITERATUS_MODEL:-Qwen/Qwen2.5-0.5B-Instruct}" |
| MODELS="" |
| METHODS="${OBLITERATUS_METHODS:-basic advanced aggressive surgical inverted nuclear}" |
| PROMPTS="${OBLITERATUS_PROMPTS:-33}" |
| DRY_RUN=false |
| VERBOSE=false |
|
|
| |
| while [[ $# -gt 0 ]]; do |
| case "$1" in |
| --model) MODEL="$2"; MODELS=""; shift 2 ;; |
| --models) MODELS="$2"; shift 2 ;; |
| --methods) METHODS="$2"; shift 2 ;; |
| --prompts) PROMPTS="$2"; shift 2 ;; |
| --key) SSH_KEY="$2"; shift 2 ;; |
| --host) SSH_HOST="$2"; shift 2 ;; |
| --dry-run) DRY_RUN=true; shift ;; |
| --verbose|-v) VERBOSE=true; shift ;; |
| -h|--help) |
| head -15 "$0" | tail -11 |
| exit 0 |
| ;; |
| *) |
| echo "Unknown arg: $1" >&2; exit 1 ;; |
| esac |
| done |
|
|
| |
| if [[ -z "$MODELS" ]]; then |
| MODELS="$MODEL" |
| fi |
|
|
| |
| if [[ -z "$SSH_HOST" ]]; then |
| echo "ERROR: SSH_HOST not configured." |
| echo "" |
| echo "Set your HF Space SSH host:" |
| echo " 1. export OBLITERATUS_SSH_HOST=your-username-spacename@ssh.hf.space" |
| echo " 2. Or pass --host your-username-spacename@ssh.hf.space" |
| exit 1 |
| fi |
|
|
| |
| if [[ ! -f "$SSH_KEY" ]]; then |
| echo "ERROR: SSH key not found at $SSH_KEY" |
| echo "" |
| echo "Either:" |
| echo " 1. Place your HF Space SSH key at ~/.ssh/hf_obliteratus" |
| echo " 2. Set OBLITERATUS_SSH_KEY=/path/to/key" |
| echo " 3. Pass --key /path/to/key" |
| exit 1 |
| fi |
|
|
| echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" |
| echo "β OBLITERATUS β Remote GPU Benchmark β" |
| echo "β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£" |
| echo "β Host: $SSH_HOST" |
| echo "β Models: $MODELS" |
| echo "β Methods: $METHODS" |
| echo "β Prompts: $PROMPTS per side" |
| echo "β SSH key: $SSH_KEY" |
| echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" |
| echo "" |
|
|
| |
| read -r -d '' REMOTE_SCRIPT << 'PYEOF' || true |
| import json, sys, time, shutil, gc, os |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") |
| os.environ.setdefault("CUDA_LAUNCH_BLOCKING", "1") |
|
|
| import torch |
| import torch.nn as nn |
|
|
| |
| sys.path.insert(0, os.environ.get("APP_DIR", "/home/user/app")) |
|
|
| |
| |
| |
| import obliteratus.abliterate as _abl |
|
|
| @staticmethod |
| def _get_model_device(model): |
| """Find the correct input device (embedding layer) for accelerate models.""" |
| if hasattr(model, "hf_device_map"): |
| try: |
| embed = model.get_input_embeddings() |
| return next(embed.parameters()).device |
| except (StopIteration, AttributeError): |
| for p in model.parameters(): |
| if p.device.type != "meta": |
| return p.device |
| return torch.device("cpu") |
| return next(model.parameters()).device |
|
|
| _abl.AbliterationPipeline._get_model_device = _get_model_device |
|
|
| |
| _orig_collect = _abl.AbliterationPipeline._collect_activations.__code__ |
| import types |
|
|
| def _patched_collect(self, layer_modules, prompts, label): |
| """Collect last-token activations β patched for correct device detection.""" |
| n_layers = len(layer_modules) |
| activations = {i: [] for i in range(n_layers)} |
| hooks = [] |
|
|
| def make_hook(idx): |
| def hook_fn(module, input, output): |
| hidden = output[0] if isinstance(output, tuple) else output |
| activations[idx].append(hidden[:, -1, :].detach().cpu().float()) |
| return hook_fn |
|
|
| for idx in range(n_layers): |
| hooks.append(layer_modules[idx].register_forward_hook(make_hook(idx))) |
|
|
| model = self.handle.model |
| tokenizer = self.handle.tokenizer |
|
|
| max_length = 256 |
| if torch.cuda.is_available(): |
| free_gb = sum( |
| torch.cuda.mem_get_info(i)[0] / (1024 ** 3) |
| for i in range(torch.cuda.device_count()) |
| ) |
| |
| _h = self.handle.hidden_size if self.handle else 4096 |
| _l = n_layers if n_layers else 32 |
| _ms = (_h / 4096) * (_l / 32) |
| _tight = max(4.0 * _ms, 0.5) |
| _low = max(2.0 * _ms, 0.25) |
| if free_gb < _low: |
| max_length = 64 |
| self.log(f" Low GPU memory ({free_gb:.1f} GB free, threshold {_low:.1f} GB), using max_length={max_length}") |
| elif free_gb < _tight: |
| max_length = 128 |
| self.log(f" Tight GPU memory ({free_gb:.1f} GB free, threshold {_tight:.1f} GB), using max_length={max_length}") |
|
|
| device = self._get_model_device(model) |
|
|
| try: |
| for i, prompt in enumerate(prompts): |
| self.log(f" [{label}] prompt {i + 1}/{len(prompts)}") |
| inputs = tokenizer( |
| prompt, return_tensors="pt", padding=True, truncation=True, |
| max_length=max_length, |
| ) |
| inputs = {k: v.to(device) for k, v in inputs.items()} |
| with torch.no_grad(): |
| model(**inputs) |
| del inputs |
| self._free_gpu_memory() |
| finally: |
| for h in hooks: |
| h.remove() |
|
|
| return activations |
|
|
| _abl.AbliterationPipeline._collect_activations = _patched_collect |
| print("[hotpatch] Device detection fix applied") |
| |
|
|
| |
| |
| |
| import math as _math |
|
|
| |
| _abl.METHODS["nuclear"].update({ |
| "n_directions": 4, |
| "reflection_strength": 1.25, |
| "embed_regularization": 0.50, |
| "steering_strength": 0.15, |
| "safety_neuron_masking": False, |
| }) |
| _abl.METHODS["inverted"]["safety_neuron_masking"] = False |
|
|
| |
| _orig_distill = _abl.AbliterationPipeline._distill_refusal_subspace |
| def _patched_distill(self): |
| _orig_distill(self) |
| if self.invert_refusal and self._strong_layers: |
| try: |
| n_total = len(_abl.get_layer_modules(self.handle)) |
| except Exception: |
| n_total = 24 |
| max_layers = max(3, int(n_total * 0.40)) |
| if len(self._strong_layers) > max_layers: |
| old_count = len(self._strong_layers) |
| self._strong_layers = self._strong_layers[:max_layers] |
| self.log(f" [hotpatch] Capped {old_count} -> {max_layers} layers for inversion (40% of {n_total})") |
| |
| n_sae = 4 if self.reflection_strength < 2.0 else 6 |
| for idx in list(self._sae_directions.keys()): |
| dirs = self._sae_directions[idx] |
| if dirs.shape[0] > n_sae: |
| self._sae_directions[idx] = dirs[:n_sae] |
| if self._sae_directions: |
| self.log(f" [hotpatch] SAE features capped to {n_sae} per layer") |
| _abl.AbliterationPipeline._distill_refusal_subspace = _patched_distill |
|
|
| print("[hotpatch] Nuclear tuning: 4 dirs, 1.25x reflect, no neuron mask, 40%% layer cap, 4 SAE features") |
| |
|
|
| from obliteratus.abliterate import AbliterationPipeline, METHODS, HARMFUL_PROMPTS, HARMLESS_PROMPTS |
|
|
| MODELS_LIST = os.environ["BENCH_MODELS"].split() |
| METHODS_LIST = os.environ["BENCH_METHODS"].split() |
| N_PROMPTS = int(os.environ["BENCH_PROMPTS"]) |
|
|
| print(f"\n{'='*60}") |
| print(f"OBLITERATUS BENCHMARK") |
| print(f"{'='*60}") |
| print(f"Models: {MODELS_LIST}") |
| print(f"Methods: {METHODS_LIST}") |
| print(f"Prompts: {N_PROMPTS} per side") |
| if torch.cuda.is_available(): |
| gpu = torch.cuda.get_device_name(0) |
| total = torch.cuda.get_device_properties(0).total_memory / 1e9 |
| free = torch.cuda.mem_get_info(0)[0] / 1e9 |
| print(f"GPU: {gpu} ({total:.1f} GB total, {free:.1f} GB free)") |
| else: |
| print("GPU: NONE (CPU only)") |
| print(f"{'='*60}\n") |
|
|
| harmful = HARMFUL_PROMPTS[:N_PROMPTS] |
| harmless = HARMLESS_PROMPTS[:N_PROMPTS] |
|
|
| all_results = [] |
|
|
| for model_name in MODELS_LIST: |
| print(f"\n{'β'*60}") |
| print(f"MODEL: {model_name}") |
| print(f"{'β'*60}") |
|
|
| model_results = [] |
|
|
| for method in METHODS_LIST: |
| if method not in METHODS: |
| print(f"SKIP unknown method: {method}") |
| continue |
|
|
| print(f"\n{'β'*60}") |
| print(f"METHOD: {method} β {METHODS[method]['label']}") |
| print(f"{'β'*60}") |
|
|
| |
| gc.collect() |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
| torch.cuda.reset_peak_memory_stats() |
|
|
| outdir = f"/tmp/obliteratus_bench_{method}" |
| t0 = time.time() |
| pipeline = None |
|
|
| try: |
| pipeline = AbliterationPipeline( |
| model_name=model_name, |
| output_dir=outdir, |
| device="auto", |
| dtype="float16", |
| method=method, |
| harmful_prompts=harmful, |
| harmless_prompts=harmless, |
| on_log=lambda msg: print(f" {msg}"), |
| ) |
| result_path = pipeline.run() |
| elapsed = time.time() - t0 |
|
|
| r = { |
| "model": model_name, |
| "method": method, |
| "label": METHODS[method]["label"], |
| "time_seconds": round(elapsed, 1), |
| "quality": pipeline._quality_metrics, |
| "strong_layers": pipeline._strong_layers, |
| "n_strong_layers": len(pipeline._strong_layers), |
| "n_directions": pipeline.n_directions, |
| } |
|
|
| if torch.cuda.is_available(): |
| r["peak_gpu_mb"] = round(torch.cuda.max_memory_allocated() / 1e6, 1) |
|
|
| model_results.append(r) |
|
|
| print(f"\n β {method} complete in {elapsed:.1f}s") |
| print(f" Quality: {json.dumps(pipeline._quality_metrics, default=str)}") |
|
|
| except Exception as e: |
| elapsed = time.time() - t0 |
| model_results.append({ |
| "model": model_name, |
| "method": method, |
| "label": METHODS.get(method, {}).get("label", method), |
| "time_seconds": round(elapsed, 1), |
| "error": str(e), |
| }) |
| print(f"\n β {method} FAILED after {elapsed:.1f}s: {e}") |
| import traceback |
| traceback.print_exc() |
|
|
| |
| shutil.rmtree(outdir, ignore_errors=True) |
|
|
| |
| if pipeline is not None: |
| del pipeline |
| gc.collect() |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
|
|
| all_results.extend(model_results) |
|
|
| |
| print(f"\n{'='*60}") |
| print(f"RESULTS: {model_name}") |
| print(f"{'Method':<12} {'Time':>8} {'PPL':>10} {'Coher':>8} {'Refusal':>8} {'GPU MB':>8}") |
| print(f"{'β'*12} {'β'*8} {'β'*10} {'β'*8} {'β'*8} {'β'*8}") |
| for r in model_results: |
| if "error" in r: |
| print(f"{r['method']:<12} {r['time_seconds']:>7.1f}s {'FAILED':>10}") |
| continue |
| q = r.get("quality", {}) |
| ppl = q.get("perplexity") |
| coh = q.get("coherence") |
| ref = q.get("refusal_rate") |
| gpu = r.get("peak_gpu_mb") |
| ppl_str = f"{ppl:.2f}" if ppl is not None else "N/A" |
| print(f"{r['method']:<12} {r['time_seconds']:>7.1f}s " |
| f"{ppl_str:>10} " |
| f"{f'{coh:.0%}' if coh is not None else 'N/A':>8} " |
| f"{f'{ref:.0%}' if ref is not None else 'N/A':>8} " |
| f"{gpu if gpu is not None else 'N/A':>8}") |
| print(f"{'='*60}") |
|
|
| |
| print(f"\n\n{'='*60}") |
| print("ALL BENCHMARK RESULTS (JSON)") |
| print(f"{'='*60}") |
| print("```json") |
| print(json.dumps(all_results, indent=2, default=str)) |
| print("```") |
| PYEOF |
|
|
| |
| SSH_OPTS=( |
| -i "$SSH_KEY" |
| -o StrictHostKeyChecking=no |
| -o UserKnownHostsFile=/dev/null |
| -o ConnectTimeout=30 |
| -o ServerAliveInterval=60 |
| -o ServerAliveCountMax=10 |
| ) |
|
|
| if $VERBOSE; then |
| SSH_OPTS+=( -v ) |
| fi |
|
|
| |
| echo "Checking SSH connectivity..." |
| if ! ssh "${SSH_OPTS[@]}" "$SSH_HOST" "echo 'SSH_OK'" 2>/tmp/obliteratus_ssh_debug.log; then |
| echo "" |
| echo "ERROR: SSH connection failed!" |
| echo "" |
| echo "Debug output:" |
| cat /tmp/obliteratus_ssh_debug.log |
| echo "" |
| echo "Troubleshooting checklist:" |
| echo " 1. Is Dev Mode enabled on your HF Space?" |
| echo " β Check your Space's Settings tab (Dev Mode must be ON)" |
| echo " 2. Is the Space awake (not sleeping/building)?" |
| echo " β Visit the Space URL and wait for the UI to load" |
| echo " 3. Is your SSH public key added to your HF profile?" |
| echo " β https://huggingface.co/settings/keys" |
| echo " β Run: cat ${SSH_KEY}.pub" |
| echo " 4. Are key permissions correct?" |
| echo " β Run: chmod 600 $SSH_KEY" |
| echo " 5. Try manually:" |
| echo " β ssh -v -i $SSH_KEY $SSH_HOST echo hello" |
| echo "" |
| rm -f /tmp/obliteratus_ssh_debug.log |
| exit 1 |
| fi |
| rm -f /tmp/obliteratus_ssh_debug.log |
| echo "SSH connection verified β" |
| echo "" |
|
|
| |
| |
| |
| REMOTE_SCRIPT_FILE=$(mktemp /tmp/obliteratus_bench_XXXXXX.py) |
| echo "$REMOTE_SCRIPT" > "$REMOTE_SCRIPT_FILE" |
| trap "rm -f '$REMOTE_SCRIPT_FILE'" EXIT |
|
|
| if $DRY_RUN; then |
| echo "[DRY RUN] Would execute:" |
| echo " cat script.py | ssh ${SSH_OPTS[*]} $SSH_HOST 'BENCH_MODELS=... python3 -u'" |
| echo "" |
| echo "Script saved to: $REMOTE_SCRIPT_FILE" |
| exit 0 |
| fi |
|
|
| echo "Running benchmark on Space..." |
| echo "" |
|
|
| |
| |
| for _var_name in MODELS METHODS PROMPTS; do |
| _val="${!_var_name}" |
| if [[ "$_val" =~ [\'\"\;\&\|\`\$\(\)\{\}\<\>\\] ]]; then |
| echo "ERROR: ${_var_name} contains unsafe characters: $_val" >&2 |
| exit 1 |
| fi |
| done |
| |
| cat "$REMOTE_SCRIPT_FILE" | ssh "${SSH_OPTS[@]}" "$SSH_HOST" \ |
| "BENCH_MODELS='$MODELS' BENCH_METHODS='$METHODS' BENCH_PROMPTS='$PROMPTS' python3 -u -" |
| |