FeatureLens / tests /test_interventions.py
ArchitSharma's picture
Release FeatureLens v0.3.0
b3d11b8
Raw
History Blame
2.27 kB
from __future__ import annotations
import pytest
import torch
from featurelens.interventions import (
InterventionSpec,
joint_residual_delta,
normalized_random_control,
residual_delta,
)
def test_ablation_delta() -> None:
spec = InterventionSpec('ablate', 0.0)
assert spec.delta_activation(3.5) == -3.5
def test_scale_delta() -> None:
spec = InterventionSpec('scale', 2.0)
assert spec.delta_activation(3.5) == 3.5
assert InterventionSpec('scale', 0.0).delta_activation(3.5) == -3.5
def test_injection_delta() -> None:
spec = InterventionSpec('inject', -4.0)
assert spec.delta_activation(123.0) == -4.0
def test_residual_delta_is_decoder_direction_times_coefficient_delta() -> None:
direction = torch.tensor([1.0, 2.0, -1.0])
delta = residual_delta(direction, 3.0, InterventionSpec('ablate', 0.0))
assert torch.allclose(delta, torch.tensor([-3.0, -6.0, 3.0]))
def test_joint_ablation_sums_feature_deltas() -> None:
directions = torch.tensor(
[
[1.0, 0.0, 2.0],
[0.0, 1.0, -1.0],
]
)
delta, coefficient_deltas = joint_residual_delta(
directions,
[2.0, 3.0],
InterventionSpec('ablate', 0.0),
)
assert coefficient_deltas == [-2.0, -3.0]
assert torch.allclose(delta, torch.tensor([-2.0, -3.0, -1.0]))
def test_joint_scale_uses_same_multiplier_per_feature() -> None:
directions = torch.eye(2)
delta, coefficient_deltas = joint_residual_delta(
directions,
[2.0, 4.0],
InterventionSpec('scale', 1.5),
)
assert coefficient_deltas == [1.0, 2.0]
assert torch.allclose(delta, torch.tensor([1.0, 2.0]))
def test_joint_injection_is_rejected() -> None:
with pytest.raises(ValueError, match='only'):
joint_residual_delta(
torch.eye(2),
[1.0, 1.0],
InterventionSpec('inject', 2.0),
)
def test_random_control_matches_norm_and_is_deterministic() -> None:
delta = torch.tensor([3.0, 4.0, 0.0])
a = normalized_random_control(delta, seed=7)
b = normalized_random_control(delta, seed=7)
assert torch.allclose(a, b)
assert torch.allclose(torch.linalg.vector_norm(a), torch.linalg.vector_norm(delta), atol=1e-5)