Skip to content

interlens.interp.steering

interlens.interp.steering

SteeringSpec dataclass

SteeringSpec(
    direction: Tensor,
    layers: tuple[int, ...],
    coef: float = 1.0,
    mode: Mode = "add",
)

A residual-stream intervention applied during generation via forward hooks on decoder layers.

mode='add' adds coef * direction to the residual at layers; mode='ablate' projects the direction component out of the residual (directional ablation). The same mechanism covers both because ablation is just the projection-removal variant of an additive hook.

A summary (mode, layers, coef, direction norm) is recorded into Message.metadata['steering'] by the participant so a steered/ablated turn is reproducible.

difference_of_means classmethod

difference_of_means(
    pos_acts,
    neg_acts,
    layers,
    coef: float = 1.0,
    mode: Mode = "add",
) -> "SteeringSpec"

Build a spec whose direction is the unit difference-of-means of two activation populations, normalize(mean(pos) − mean(neg)) — the classic contrastive/concept-direction recipe, pointing TOWARD the positive class. pos_acts/neg_acts are [n, d_model] (a stack of per-example residuals) or a pre-pooled [d_model] vector; anything torch.as_tensor accepts works. Steer toward the positive concept with coef>0 and away from it (suppress) with coef<0. layers is an int or an iterable of decoder-layer indices. This lives here (not re-hand-rolled per experiment) per the contribution convention.

Source code in src/interlens/interp/steering.py
@classmethod
def difference_of_means(cls, pos_acts, neg_acts, layers, coef: float = 1.0, mode: Mode = "add") -> "SteeringSpec":
	"""Build a spec whose ``direction`` is the **unit difference-of-means** of two activation populations,
	``normalize(mean(pos) − mean(neg))`` — the classic contrastive/concept-direction recipe, pointing TOWARD
	the positive class. ``pos_acts``/``neg_acts`` are ``[n, d_model]`` (a stack of per-example residuals) or a
	pre-pooled ``[d_model]`` vector; anything ``torch.as_tensor`` accepts works. Steer toward the positive
	concept with ``coef>0`` and **away** from it (suppress) with ``coef<0``. ``layers`` is an int or an
	iterable of decoder-layer indices. This lives here (not re-hand-rolled per experiment) per the
	contribution convention."""
	pos = torch.as_tensor(pos_acts, dtype=torch.float32)
	neg = torch.as_tensor(neg_acts, dtype=torch.float32)
	pm = pos if pos.ndim == 1 else pos.mean(0)
	nm = neg if neg.ndim == 1 else neg.mean(0)
	d = pm - nm
	d = d / (d.norm() + 1e-8)
	layers = (layers,) if isinstance(layers, int) else tuple(layers)
	return cls(direction=d, layers=layers, coef=coef, mode=mode)

register

register(model: 'PreTrainedModel') -> list

Register the steering hooks on model and return the handles (caller removes them after generate).

Source code in src/interlens/interp/steering.py
def register(self, model: "PreTrainedModel") -> list:
	"""Register the steering hooks on ``model`` and return the handles (caller removes them after generate)."""
	layers = decoder_layers(model)
	handles = []
	for li in self.layers:
		handles.append(layers[li].register_forward_hook(self._hook()))
	return handles