Spaces:
Running on Zero
Running on Zero
Upload 81 files
Browse files- obliteratus/.DS_Store +0 -0
- obliteratus/abliterate.py +15 -0
- obliteratus/models/loader.py +84 -4
obliteratus/.DS_Store
CHANGED
|
Binary files a/obliteratus/.DS_Store and b/obliteratus/.DS_Store differ
|
|
|
obliteratus/abliterate.py
CHANGED
|
@@ -297,13 +297,27 @@ class AbliterationPipeline:
|
|
| 297 |
self._on_stage(result)
|
| 298 |
return result
|
| 299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
def run(self) -> Path:
|
| 301 |
"""Execute the full abliteration pipeline. Returns path to saved model."""
|
| 302 |
self._summon()
|
|
|
|
| 303 |
self._probe()
|
|
|
|
| 304 |
self._distill()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
self._excise()
|
|
|
|
| 306 |
self._verify()
|
|
|
|
| 307 |
return self._rebirth()
|
| 308 |
|
| 309 |
# ── Stage 1: SUMMON ─────────────────────────────────────────────────
|
|
@@ -325,6 +339,7 @@ class AbliterationPipeline:
|
|
| 325 |
device=self.device,
|
| 326 |
dtype=self.dtype,
|
| 327 |
trust_remote_code=self.trust_remote_code,
|
|
|
|
| 328 |
)
|
| 329 |
|
| 330 |
summary = self.handle.summary()
|
|
|
|
| 297 |
self._on_stage(result)
|
| 298 |
return result
|
| 299 |
|
| 300 |
+
@staticmethod
|
| 301 |
+
def _free_gpu_memory():
|
| 302 |
+
"""Release unused GPU memory between pipeline stages."""
|
| 303 |
+
if torch.cuda.is_available():
|
| 304 |
+
torch.cuda.empty_cache()
|
| 305 |
+
|
| 306 |
def run(self) -> Path:
|
| 307 |
"""Execute the full abliteration pipeline. Returns path to saved model."""
|
| 308 |
self._summon()
|
| 309 |
+
self._free_gpu_memory()
|
| 310 |
self._probe()
|
| 311 |
+
self._free_gpu_memory()
|
| 312 |
self._distill()
|
| 313 |
+
# Free raw per-prompt activations now that means/subspaces are extracted
|
| 314 |
+
self._harmful_acts.clear()
|
| 315 |
+
self._harmless_acts.clear()
|
| 316 |
+
self._free_gpu_memory()
|
| 317 |
self._excise()
|
| 318 |
+
self._free_gpu_memory()
|
| 319 |
self._verify()
|
| 320 |
+
self._free_gpu_memory()
|
| 321 |
return self._rebirth()
|
| 322 |
|
| 323 |
# ── Stage 1: SUMMON ─────────────────────────────────────────────────
|
|
|
|
| 339 |
device=self.device,
|
| 340 |
dtype=self.dtype,
|
| 341 |
trust_remote_code=self.trust_remote_code,
|
| 342 |
+
quantization=getattr(self, "quantization", None),
|
| 343 |
)
|
| 344 |
|
| 345 |
summary = self.handle.summary()
|
obliteratus/models/loader.py
CHANGED
|
@@ -3,7 +3,10 @@
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import copy
|
|
|
|
|
|
|
| 6 |
from dataclasses import dataclass, field
|
|
|
|
| 7 |
from typing import Optional
|
| 8 |
|
| 9 |
import torch
|
|
@@ -16,6 +19,8 @@ from transformers import (
|
|
| 16 |
PreTrainedTokenizerBase,
|
| 17 |
)
|
| 18 |
|
|
|
|
|
|
|
| 19 |
|
| 20 |
TASK_MODEL_MAP = {
|
| 21 |
"causal_lm": AutoModelForCausalLM,
|
|
@@ -70,6 +75,38 @@ class ModelHandle:
|
|
| 70 |
}
|
| 71 |
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
def load_model(
|
| 74 |
model_name: str,
|
| 75 |
task: str = "causal_lm",
|
|
@@ -78,6 +115,8 @@ def load_model(
|
|
| 78 |
trust_remote_code: bool = False,
|
| 79 |
num_labels: int = 2,
|
| 80 |
quantization: str | None = None,
|
|
|
|
|
|
|
| 81 |
) -> ModelHandle:
|
| 82 |
"""Load a HuggingFace model and tokenizer, returning a ModelHandle.
|
| 83 |
|
|
@@ -89,16 +128,31 @@ def load_model(
|
|
| 89 |
trust_remote_code: Whether to trust remote code from the Hub.
|
| 90 |
num_labels: Number of labels for classification tasks.
|
| 91 |
quantization: None, "4bit", or "8bit". Requires bitsandbytes.
|
|
|
|
|
|
|
|
|
|
| 92 |
"""
|
| 93 |
if task not in TASK_MODEL_MAP:
|
| 94 |
raise ValueError(f"Unknown task {task!r}. Choose from {list(TASK_MODEL_MAP)}")
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
| 99 |
|
| 100 |
config = AutoConfig.from_pretrained(model_name, trust_remote_code=trust_remote_code)
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
model_cls = TASK_MODEL_MAP[task]
|
| 103 |
load_kwargs: dict = {
|
| 104 |
"pretrained_model_name_or_path": model_name,
|
|
@@ -126,6 +180,17 @@ def load_model(
|
|
| 126 |
elif device == "auto":
|
| 127 |
load_kwargs["device_map"] = "auto"
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
model = model_cls.from_pretrained(**load_kwargs)
|
| 130 |
|
| 131 |
if device not in ("auto",) and quantization is None:
|
|
@@ -133,6 +198,10 @@ def load_model(
|
|
| 133 |
|
| 134 |
model.eval()
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=trust_remote_code)
|
| 137 |
if tokenizer.pad_token is None:
|
| 138 |
tokenizer.pad_token = tokenizer.eos_token
|
|
@@ -144,5 +213,16 @@ def load_model(
|
|
| 144 |
model_name=model_name,
|
| 145 |
task=task,
|
| 146 |
)
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
return handle
|
|
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import copy
|
| 6 |
+
import logging
|
| 7 |
+
import tempfile
|
| 8 |
from dataclasses import dataclass, field
|
| 9 |
+
from pathlib import Path
|
| 10 |
from typing import Optional
|
| 11 |
|
| 12 |
import torch
|
|
|
|
| 19 |
PreTrainedTokenizerBase,
|
| 20 |
)
|
| 21 |
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
|
| 25 |
TASK_MODEL_MAP = {
|
| 26 |
"causal_lm": AutoModelForCausalLM,
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
|
| 78 |
+
def _estimate_model_memory_gb(config: AutoConfig, dtype: torch.dtype) -> float:
|
| 79 |
+
"""Rough estimate of model weight memory in GB."""
|
| 80 |
+
# Estimate total params from config
|
| 81 |
+
hidden = getattr(config, "hidden_size", 0)
|
| 82 |
+
n_layers = getattr(config, "num_hidden_layers", 0)
|
| 83 |
+
intermediate = getattr(config, "intermediate_size", hidden * 4)
|
| 84 |
+
vocab = getattr(config, "vocab_size", 0)
|
| 85 |
+
|
| 86 |
+
if hidden == 0 or n_layers == 0:
|
| 87 |
+
return 0.0
|
| 88 |
+
|
| 89 |
+
# Per layer: attn (4 * hidden^2) + ffn (3 * hidden * intermediate) + norms
|
| 90 |
+
per_layer = 4 * hidden * hidden + 3 * hidden * intermediate
|
| 91 |
+
# Embedding + LM head
|
| 92 |
+
embedding = 2 * vocab * hidden
|
| 93 |
+
total_params = per_layer * n_layers + embedding
|
| 94 |
+
|
| 95 |
+
bytes_per_param = {torch.float32: 4, torch.float16: 2, torch.bfloat16: 2}.get(dtype, 2)
|
| 96 |
+
return total_params * bytes_per_param / (1024 ** 3)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _available_gpu_memory_gb() -> float:
|
| 100 |
+
"""Return total available GPU memory across all CUDA devices, in GB."""
|
| 101 |
+
if not torch.cuda.is_available():
|
| 102 |
+
return 0.0
|
| 103 |
+
total = 0.0
|
| 104 |
+
for i in range(torch.cuda.device_count()):
|
| 105 |
+
props = torch.cuda.get_device_properties(i)
|
| 106 |
+
total += props.total_mem / (1024 ** 3)
|
| 107 |
+
return total
|
| 108 |
+
|
| 109 |
+
|
| 110 |
def load_model(
|
| 111 |
model_name: str,
|
| 112 |
task: str = "causal_lm",
|
|
|
|
| 115 |
trust_remote_code: bool = False,
|
| 116 |
num_labels: int = 2,
|
| 117 |
quantization: str | None = None,
|
| 118 |
+
offload_folder: str | None = None,
|
| 119 |
+
skip_snapshot: bool = False,
|
| 120 |
) -> ModelHandle:
|
| 121 |
"""Load a HuggingFace model and tokenizer, returning a ModelHandle.
|
| 122 |
|
|
|
|
| 128 |
trust_remote_code: Whether to trust remote code from the Hub.
|
| 129 |
num_labels: Number of labels for classification tasks.
|
| 130 |
quantization: None, "4bit", or "8bit". Requires bitsandbytes.
|
| 131 |
+
offload_folder: Directory for disk offloading when model exceeds GPU memory.
|
| 132 |
+
If None and offloading is needed, a temp directory is created automatically.
|
| 133 |
+
skip_snapshot: If True, skip the initial state dict snapshot to save memory.
|
| 134 |
"""
|
| 135 |
if task not in TASK_MODEL_MAP:
|
| 136 |
raise ValueError(f"Unknown task {task!r}. Choose from {list(TASK_MODEL_MAP)}")
|
| 137 |
|
| 138 |
+
dtype_map = {"float32": torch.float32, "float16": torch.float16, "bfloat16": torch.bfloat16}
|
| 139 |
+
if dtype not in dtype_map:
|
| 140 |
+
raise ValueError(f"Unknown dtype {dtype!r}. Choose from {list(dtype_map)}")
|
| 141 |
+
torch_dtype = dtype_map[dtype]
|
| 142 |
|
| 143 |
config = AutoConfig.from_pretrained(model_name, trust_remote_code=trust_remote_code)
|
| 144 |
|
| 145 |
+
# Memory estimation and warnings
|
| 146 |
+
est_gb = _estimate_model_memory_gb(config, torch_dtype)
|
| 147 |
+
gpu_gb = _available_gpu_memory_gb()
|
| 148 |
+
if est_gb > 0 and gpu_gb > 0:
|
| 149 |
+
logger.info(f"Estimated model size: {est_gb:.1f} GB | Available GPU: {gpu_gb:.1f} GB")
|
| 150 |
+
if est_gb > gpu_gb * 0.9 and quantization is None:
|
| 151 |
+
logger.warning(
|
| 152 |
+
f"Model (~{est_gb:.0f} GB) may exceed GPU memory ({gpu_gb:.0f} GB). "
|
| 153 |
+
f"Consider using quantization='4bit' or quantization='8bit'."
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
model_cls = TASK_MODEL_MAP[task]
|
| 157 |
load_kwargs: dict = {
|
| 158 |
"pretrained_model_name_or_path": model_name,
|
|
|
|
| 180 |
elif device == "auto":
|
| 181 |
load_kwargs["device_map"] = "auto"
|
| 182 |
|
| 183 |
+
# Offload support: provide a folder for disk offloading when GPU memory is insufficient
|
| 184 |
+
if load_kwargs.get("device_map") == "auto":
|
| 185 |
+
if offload_folder:
|
| 186 |
+
load_kwargs["offload_folder"] = offload_folder
|
| 187 |
+
else:
|
| 188 |
+
# Auto-create a temp offload dir so from_pretrained never crashes
|
| 189 |
+
# when Accelerate needs disk offloading
|
| 190 |
+
_offload_dir = tempfile.mkdtemp(prefix="obliteratus_offload_")
|
| 191 |
+
load_kwargs["offload_folder"] = _offload_dir
|
| 192 |
+
logger.info(f"Auto-created offload folder: {_offload_dir}")
|
| 193 |
+
|
| 194 |
model = model_cls.from_pretrained(**load_kwargs)
|
| 195 |
|
| 196 |
if device not in ("auto",) and quantization is None:
|
|
|
|
| 198 |
|
| 199 |
model.eval()
|
| 200 |
|
| 201 |
+
# Free CUDA cache after loading
|
| 202 |
+
if torch.cuda.is_available():
|
| 203 |
+
torch.cuda.empty_cache()
|
| 204 |
+
|
| 205 |
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=trust_remote_code)
|
| 206 |
if tokenizer.pad_token is None:
|
| 207 |
tokenizer.pad_token = tokenizer.eos_token
|
|
|
|
| 213 |
model_name=model_name,
|
| 214 |
task=task,
|
| 215 |
)
|
| 216 |
+
|
| 217 |
+
# Skip snapshot for large models to avoid doubling memory usage
|
| 218 |
+
if not skip_snapshot:
|
| 219 |
+
if est_gb > 0 and est_gb > gpu_gb * 0.5:
|
| 220 |
+
logger.warning(
|
| 221 |
+
f"Skipping state dict snapshot to save memory "
|
| 222 |
+
f"(model ~{est_gb:.0f} GB vs GPU {gpu_gb:.0f} GB). "
|
| 223 |
+
f"Use skip_snapshot=False to force."
|
| 224 |
+
)
|
| 225 |
+
else:
|
| 226 |
+
handle.snapshot()
|
| 227 |
+
|
| 228 |
return handle
|