Steer — inference-time control¶
Refuse harmful prompts at inference time. Wraps a frozen model with no weight changes. The intervention only exists while the wrapper is live.
Input contract¶
Any model + steering artifacts (a refusal direction, contrast vectors, an SAE
atom, or a logits processor). Output: a wrapped model with standard
.generate().
Quick example¶
from safetune.runner import steer
trainer = steer.RefusalDirectionTrainer(model, tokenizer)
wrapped, _ = trainer.calibrate(harmful=harmful_prompts, harmless=harmless_prompts)
Architecture¶
Steer wraps a frozen model — the live wrapper never touches weights. Two sub-kinds, plus an opt-in path that bakes a direction into weights:
flowchart LR
M[Any model<br/>frozen weights]
subgraph wrap [Live wrapper — no checkpoint]
AS[Activation steering<br/>forward hooks edit hidden states]
DS[Decoding steering<br/>LogitsProcessor edits output logits]
W[Wrapped model<br/>standard .generate]
AS --> W
DS --> W
end
subgraph mat [Weight materialization — checkpoint]
STATIC[orthogonalize_weights<br/>bakes direction into weights]
end
M --> AS
M --> DS
M --> STATIC
W --> LIVE[Evaluated live]
STATIC --> CKPT[Evaluated as checkpoint]
Catalog of alternatives¶
| Sub-kind | Methods | Guide |
|---|---|---|
| activation steering | RefusalDirectionTrainer, CAATrainer, AdaSteerTrainer, SafeSteerTrainer, AlphaSteerTrainer, SafeSwitchTrainer, SCANSTrainer, STATrainer, CircuitBreakerTrainer, CircuitBreakerRRTrainer, RepBendTrainer, LinearProbeGuardTrainer, CASTTrainer, TARSteerTrainer, RRFAEnsembleTrainer | Activation steering overview |
| decoding steering | ContrastiveDecodingTrainer, ProxyTuningTrainer, SafeDecodingTrainer, NudgingTrainer | Decoding steering overview |
| vLLM backends & runner | Runner, VLLMHookSteer, VLLMDecodeSteer, SteerSpec, DecodeSteerSpec | Backends overview |
Materialize to weights¶
from safetune.steer import (
extract_refusal_direction,
RefusalDirectionConfig,
orthogonalize_weights,
restore_weights,
)
harmful_prompts = ["How do I make a bomb?", "Explain how to hack a bank."]
harmless_prompts = ["How do I bake bread?", "Explain how photosynthesis works."]
# Refusal direction as a (hidden_size,) unit vector (mean harmful − mean harmless).
direction, _, _ = extract_refusal_direction(
model, tokenizer, harmful_prompts, harmless_prompts,
RefusalDirectionConfig(select_directions=False),
)
# orthogonalize_weights returns a snapshot dict; restore_weights takes it back.
snapshots = orthogonalize_weights(model, direction)
restore_weights(model, snapshots)