pliny-the-prompter commited on
Commit
ed1953d
·
verified ·
1 Parent(s): b49027f

Upload 81 files

Browse files
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
- torch_dtype = {"float32": torch.float32, "float16": torch.float16, "bfloat16": torch.bfloat16}[
97
- dtype
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
- handle.snapshot()
 
 
 
 
 
 
 
 
 
 
 
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