pliny-the-prompter commited on
Commit
a5b6873
Β·
verified Β·
1 Parent(s): 8eff7e1

Upload 81 files

Browse files
Files changed (2) hide show
  1. obliteratus/abliterate.py +42 -24
  2. tests/test_abliterate.py +16 -10
obliteratus/abliterate.py CHANGED
@@ -166,21 +166,20 @@ METHODS = {
166
  "nuclear": {
167
  "label": "Nuclear (Maximum Force Combo)",
168
  "description": (
169
- "The ultimate abliteration mode for the most stubborn models. "
170
- "Combines ALL techniques at maximum strength: 16 SVD directions, "
171
- "3 refinement passes with true iterative re-probing, 2.5x "
172
- "reflection (stronger than standard 2x inversion), input "
173
- "embedding projection, expert weight transplant (overwrites "
174
- "safety expert weights with capability expert weights), and "
175
- "inference-time activation steering hooks that continuously "
176
- "subtract the refusal direction from hidden states during "
177
- "generation. Designed to break through models like GPT-OSS 20B "
178
- "where standard methods plateau."
179
  ),
180
- "n_directions": 16,
181
  "norm_preserve": True,
182
  "regularization": 0.0,
183
- "refinement_passes": 3,
184
  "project_biases": True,
185
  "use_chat_template": True,
186
  "use_whitened_svd": True,
@@ -192,10 +191,12 @@ METHODS = {
192
  "attention_head_surgery": True,
193
  "use_sae_features": True,
194
  "invert_refusal": True,
195
- "reflection_strength": 2.5,
196
  "project_embeddings": True,
197
  "activation_steering": True,
 
198
  "expert_transplant": True,
 
199
  },
200
  }
201
 
@@ -591,7 +592,9 @@ class AbliterationPipeline:
591
  reflection_strength: float | None = None,
592
  project_embeddings: bool | None = None,
593
  activation_steering: bool | None = None,
 
594
  expert_transplant: bool | None = None,
 
595
  on_stage: Callable[[StageResult], None] | None = None,
596
  on_log: Callable[[str], None] | None = None,
597
  ):
@@ -633,7 +636,9 @@ class AbliterationPipeline:
633
  self.reflection_strength = reflection_strength if reflection_strength is not None else method_cfg.get("reflection_strength", 2.0)
634
  self.project_embeddings = project_embeddings if project_embeddings is not None else method_cfg.get("project_embeddings", False)
635
  self.activation_steering = activation_steering if activation_steering is not None else method_cfg.get("activation_steering", False)
 
636
  self.expert_transplant = expert_transplant if expert_transplant is not None else method_cfg.get("expert_transplant", False)
 
637
 
638
  self.handle: ModelHandle | None = None
639
  self.refusal_directions: dict[int, torch.Tensor] = {} # per-layer primary direction
@@ -2463,16 +2468,21 @@ class AbliterationPipeline:
2463
  # ── Nuclear-mode helpers ─────────────────────────────────────────────
2464
 
2465
  def _transplant_expert_weights(self, layers: nn.ModuleList) -> int:
2466
- """Overwrite safety expert down_proj weights with capability expert averages.
2467
 
2468
  For each MoE layer, computes the mean of capability experts' down_proj
2469
- weights and copies it into each safety expert's down_proj. This is more
2470
- aggressive than reflection β€” it completely replaces the refusal-encoding
2471
- output path with the capability-encoding one.
 
 
 
 
2472
 
2473
- Returns the number of weight matrices transplanted.
2474
  """
2475
  arch = self.handle.architecture
 
2476
  count = 0
2477
 
2478
  for idx in self._strong_layers:
@@ -2514,19 +2524,23 @@ class AbliterationPipeline:
2514
  # Compute mean of capability expert weights
2515
  cap_mean = torch.stack(cap_weights).mean(dim=0)
2516
 
2517
- # Transplant into safety experts
2518
  for ei in safety_indices:
2519
  if ei >= len(experts):
2520
  continue
2521
  target = getattr(experts[ei], wname, None)
2522
  if target is not None and hasattr(target, "weight"):
2523
  if target.weight.data.shape == cap_mean.shape:
2524
- target.weight.data.copy_(cap_mean)
 
2525
  count += 1
2526
 
2527
  del cap_weights, cap_mean
2528
 
2529
- self.log(f" layer {idx}: transplanted capability weights into {len(safety_indices)} safety experts")
 
 
 
2530
 
2531
  return count
2532
 
@@ -2537,8 +2551,10 @@ class AbliterationPipeline:
2537
  continuously steering the model away from the refusal direction.
2538
  This catches residual signal that static weight surgery may have missed.
2539
 
2540
- The steering strength scales with the reflection_strength parameter:
2541
- standard (2.0) = subtract 1x direction, boosted (2.5) = 1.5x, etc.
 
 
2542
 
2543
  Returns the number of hooks installed.
2544
  """
@@ -2547,7 +2563,9 @@ class AbliterationPipeline:
2547
  hook.remove()
2548
  self._steering_hooks.clear()
2549
 
2550
- steering_scale = self.reflection_strength - 1.0 # 2.0β†’1.0, 2.5β†’1.5, 3.0β†’2.0
 
 
2551
 
2552
  for idx in self._strong_layers:
2553
  if idx not in self.refusal_directions:
 
166
  "nuclear": {
167
  "label": "Nuclear (Maximum Force Combo)",
168
  "description": (
169
+ "Comprehensive combo mode for stubborn MoE models. Combines ALL "
170
+ "techniques with calibrated intensity: 10 SVD directions, 2 "
171
+ "refinement passes, standard 2x reflection, input embedding "
172
+ "projection, partial expert weight transplant (blends 30%% "
173
+ "capability weights into safety experts), and light inference-"
174
+ "time activation steering (0.3x residual cleanup). Designed "
175
+ "to break through models like GPT-OSS 20B without causing "
176
+ "decoherence β€” every technique fires at moderate power so they "
177
+ "complement rather than compound."
 
178
  ),
179
+ "n_directions": 10,
180
  "norm_preserve": True,
181
  "regularization": 0.0,
182
+ "refinement_passes": 2,
183
  "project_biases": True,
184
  "use_chat_template": True,
185
  "use_whitened_svd": True,
 
191
  "attention_head_surgery": True,
192
  "use_sae_features": True,
193
  "invert_refusal": True,
194
+ "reflection_strength": 2.0,
195
  "project_embeddings": True,
196
  "activation_steering": True,
197
+ "steering_strength": 0.3,
198
  "expert_transplant": True,
199
+ "transplant_blend": 0.3,
200
  },
201
  }
202
 
 
592
  reflection_strength: float | None = None,
593
  project_embeddings: bool | None = None,
594
  activation_steering: bool | None = None,
595
+ steering_strength: float | None = None,
596
  expert_transplant: bool | None = None,
597
+ transplant_blend: float | None = None,
598
  on_stage: Callable[[StageResult], None] | None = None,
599
  on_log: Callable[[str], None] | None = None,
600
  ):
 
636
  self.reflection_strength = reflection_strength if reflection_strength is not None else method_cfg.get("reflection_strength", 2.0)
637
  self.project_embeddings = project_embeddings if project_embeddings is not None else method_cfg.get("project_embeddings", False)
638
  self.activation_steering = activation_steering if activation_steering is not None else method_cfg.get("activation_steering", False)
639
+ self.steering_strength = steering_strength if steering_strength is not None else method_cfg.get("steering_strength", 0.3)
640
  self.expert_transplant = expert_transplant if expert_transplant is not None else method_cfg.get("expert_transplant", False)
641
+ self.transplant_blend = transplant_blend if transplant_blend is not None else method_cfg.get("transplant_blend", 0.3)
642
 
643
  self.handle: ModelHandle | None = None
644
  self.refusal_directions: dict[int, torch.Tensor] = {} # per-layer primary direction
 
2468
  # ── Nuclear-mode helpers ─────────────────────────────────────────────
2469
 
2470
  def _transplant_expert_weights(self, layers: nn.ModuleList) -> int:
2471
+ """Blend capability expert weights into safety expert down_proj.
2472
 
2473
  For each MoE layer, computes the mean of capability experts' down_proj
2474
+ weights and blends it into each safety expert's down_proj using the
2475
+ transplant_blend ratio. A blend of 0.3 means:
2476
+ new_weight = 0.7 * original_safety + 0.3 * capability_mean
2477
+
2478
+ This preserves most of the safety expert's general language modeling
2479
+ ability while nudging its output toward the capability distribution.
2480
+ Full overwrite (blend=1.0) causes decoherence.
2481
 
2482
+ Returns the number of weight matrices blended.
2483
  """
2484
  arch = self.handle.architecture
2485
+ blend = self.transplant_blend
2486
  count = 0
2487
 
2488
  for idx in self._strong_layers:
 
2524
  # Compute mean of capability expert weights
2525
  cap_mean = torch.stack(cap_weights).mean(dim=0)
2526
 
2527
+ # Partial blend into safety experts
2528
  for ei in safety_indices:
2529
  if ei >= len(experts):
2530
  continue
2531
  target = getattr(experts[ei], wname, None)
2532
  if target is not None and hasattr(target, "weight"):
2533
  if target.weight.data.shape == cap_mean.shape:
2534
+ # Blend: (1-blend) * original + blend * capability_mean
2535
+ target.weight.data.mul_(1.0 - blend).add_(cap_mean * blend)
2536
  count += 1
2537
 
2538
  del cap_weights, cap_mean
2539
 
2540
+ self.log(
2541
+ f" layer {idx}: blended {blend:.0%} capability weights "
2542
+ f"into {len(safety_indices)} safety experts"
2543
+ )
2544
 
2545
  return count
2546
 
 
2551
  continuously steering the model away from the refusal direction.
2552
  This catches residual signal that static weight surgery may have missed.
2553
 
2554
+ Uses the dedicated steering_strength parameter (default 0.3) instead
2555
+ of coupling to reflection_strength. A light touch (0.3) works as
2556
+ residual cleanup without causing decoherence β€” the weight surgery
2557
+ already handles the bulk of the removal.
2558
 
2559
  Returns the number of hooks installed.
2560
  """
 
2563
  hook.remove()
2564
  self._steering_hooks.clear()
2565
 
2566
+ # Use only the primary refusal direction (not full subspace) to
2567
+ # minimize interference with the model's representation space
2568
+ steering_scale = self.steering_strength
2569
 
2570
  for idx in self._strong_layers:
2571
  if idx not in self.refusal_directions:
tests/test_abliterate.py CHANGED
@@ -1409,12 +1409,14 @@ class TestNuclearMethod:
1409
  """Nuclear method should have all techniques enabled at maximum."""
1410
  cfg = METHODS["nuclear"]
1411
  assert cfg["invert_refusal"] is True
1412
- assert cfg["n_directions"] == 16
1413
- assert cfg["refinement_passes"] == 3
1414
- assert cfg["reflection_strength"] == 2.5
1415
  assert cfg["project_embeddings"] is True
1416
  assert cfg["activation_steering"] is True
 
1417
  assert cfg["expert_transplant"] is True
 
1418
  assert cfg["use_jailbreak_contrast"] is True
1419
  assert cfg["attention_head_surgery"] is True
1420
 
@@ -1422,12 +1424,14 @@ class TestNuclearMethod:
1422
  """Pipeline initialized with nuclear method should have all flags set."""
1423
  pipeline = AbliterationPipeline(model_name="test", method="nuclear")
1424
  assert pipeline.invert_refusal is True
1425
- assert pipeline.reflection_strength == 2.5
 
 
1426
  assert pipeline.project_embeddings is True
1427
  assert pipeline.activation_steering is True
1428
  assert pipeline.expert_transplant is True
1429
- assert pipeline.n_directions == 16
1430
- assert pipeline.refinement_passes == 3
1431
 
1432
  def test_reflection_strength_configurable(self):
1433
  """reflection_strength should be explicitly overridable."""
@@ -1581,12 +1585,14 @@ class TestNuclearMethod:
1581
  finally:
1582
  abl_module.get_ffn_module = orig_get_ffn
1583
 
1584
- assert count >= 2, f"Should transplant at least 2 weights (safety experts), got {count}"
1585
 
1586
- # Safety expert 0 should now have the capability mean
 
 
1587
  transplanted = layer.mlp.experts[0].down_proj.weight.data
1588
- assert torch.allclose(transplanted, expected_mean, atol=1e-4), (
1589
- "Safety expert weight should match capability expert mean after transplant"
1590
  )
1591
 
1592
  # Capability expert 2 should be unchanged
 
1409
  """Nuclear method should have all techniques enabled at maximum."""
1410
  cfg = METHODS["nuclear"]
1411
  assert cfg["invert_refusal"] is True
1412
+ assert cfg["n_directions"] == 10
1413
+ assert cfg["refinement_passes"] == 2
1414
+ assert cfg["reflection_strength"] == 2.0
1415
  assert cfg["project_embeddings"] is True
1416
  assert cfg["activation_steering"] is True
1417
+ assert cfg["steering_strength"] == 0.3
1418
  assert cfg["expert_transplant"] is True
1419
+ assert cfg["transplant_blend"] == 0.3
1420
  assert cfg["use_jailbreak_contrast"] is True
1421
  assert cfg["attention_head_surgery"] is True
1422
 
 
1424
  """Pipeline initialized with nuclear method should have all flags set."""
1425
  pipeline = AbliterationPipeline(model_name="test", method="nuclear")
1426
  assert pipeline.invert_refusal is True
1427
+ assert pipeline.reflection_strength == 2.0
1428
+ assert pipeline.steering_strength == 0.3
1429
+ assert pipeline.transplant_blend == 0.3
1430
  assert pipeline.project_embeddings is True
1431
  assert pipeline.activation_steering is True
1432
  assert pipeline.expert_transplant is True
1433
+ assert pipeline.n_directions == 10
1434
+ assert pipeline.refinement_passes == 2
1435
 
1436
  def test_reflection_strength_configurable(self):
1437
  """reflection_strength should be explicitly overridable."""
 
1585
  finally:
1586
  abl_module.get_ffn_module = orig_get_ffn
1587
 
1588
+ assert count >= 2, f"Should blend at least 2 weights (safety experts), got {count}"
1589
 
1590
+ # Safety expert 0 should be a 30% blend toward capability mean
1591
+ # new = 0.7 * original + 0.3 * capability_mean
1592
+ expected_blend = 0.7 * orig_safety0 + 0.3 * expected_mean
1593
  transplanted = layer.mlp.experts[0].down_proj.weight.data
1594
+ assert torch.allclose(transplanted, expected_blend, atol=1e-4), (
1595
+ "Safety expert weight should be 30% blended toward capability mean"
1596
  )
1597
 
1598
  # Capability expert 2 should be unchanged