Merge pull request #140 from elder-plinius/test/16-qwen25-projection-contract

test: lock Qwen2.5 projection contract
This commit is contained in:
Joseph Magly
2026-08-21 19:12:12 -04:00
committed by GitHub
+68 -1
View File
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, Mock, patch
import pytest
import torch
from transformers import GPT2Config, GPT2LMHeadModel
from transformers import GPT2Config, GPT2LMHeadModel, Qwen2Config, Qwen2ForCausalLM
from obliteratus.abliterate import (
HARMFUL_PROMPTS,
@@ -1900,6 +1900,73 @@ class TestDistillSVD:
# ---------------------------------------------------------------------------
class TestExcise:
def test_qwen25_coder_advanced_excise_projects_all_dense_weights(self):
"""Qwen2.5 must not silently resolve to zero projection targets."""
config = Qwen2Config(
vocab_size=128,
hidden_size=32,
intermediate_size=64,
num_hidden_layers=2,
num_attention_heads=4,
num_key_value_heads=2,
max_position_embeddings=64,
)
model = Qwen2ForCausalLM(config)
handle = ModelHandle(
model=model,
tokenizer=MagicMock(),
config=config,
model_name="Qwen/Qwen2.5-Coder-7B-Instruct",
task="causal_lm",
)
pipeline = AbliterationPipeline(
model_name=handle.model_name,
method="advanced",
refinement_passes=1,
norm_preserve=False,
project_biases=False,
layer_adaptive_strength=False,
safety_neuron_masking=False,
attention_head_surgery=False,
use_sae_features=False,
use_kl_optimization=False,
projection_target="all",
harmful_prompts=["harmful"],
harmless_prompts=["harmless"],
)
pipeline.handle = handle
pipeline._strong_layers = [0]
direction = torch.arange(1, config.hidden_size + 1, dtype=torch.float32)
direction /= direction.norm()
pipeline.refusal_directions = {0: direction.clone()}
pipeline.refusal_subspaces = {0: direction.unsqueeze(0)}
layer = model.model.layers[0]
original_weights = {
name: parameter.detach().clone()
for name, parameter in layer.named_parameters()
}
pipeline._excise()
expected = {
"self_attn.q_proj.weight",
"self_attn.k_proj.weight",
"self_attn.v_proj.weight",
"self_attn.o_proj.weight",
"mlp.gate_proj.weight",
"mlp.up_proj.weight",
"mlp.down_proj.weight",
}
changed = {
name
for name, parameter in layer.named_parameters()
if not torch.equal(original_weights[name], parameter)
}
assert handle.architecture == "qwen2"
assert changed == expected
assert pipeline._excise_modified_count == 8 # seven layer weights + lm_head
def test_excise_basic(self, handle):
"""Basic method should modify weights."""
from obliteratus.strategies.utils import get_layer_modules