ActivationAdapter
steerability.algorithms.state_control.activation_adapter
__all__ = ['ActivationAdapter', 'ActivationAdapterArgs', 'TransformContext', 'STEERING_METHOD']
module-attribute
ActivationAdapter
Bases: InterventionControl
Composable activation-steering control (single-behavior atom).
ActivationAdapter wires together the state_control/common component families (a transform
that carries its own steering artifact, a selector, a gate, and a token scope) so an
activation-steering recipe can be assembled directly without writing a new control class. It
edits the residual stream at one or more layers during generation, applying the transform at
masked positions whenever its gate is open.
The transform is the sole artifact carrier. It holds a concrete SteeringVector / directions
mapping (bound at construction), or an ArtifactSource such as ContrastiveFit(data=...) that
is resolved once at steer() time. The adapter has no artifact slots and never sees a
SteeringVector directly.
Gating is likewise self-describing. A Gate carries its evidence (condition layers, pooling,
and a readout mapping pooled hidden states to per-row values) and a rule deciding over the
values; gate_from_probe and Probe.as_gate assemble one from a fitted probe, and a
GateSource (e.g. ConditionPointSearch) resolves one at steer() time. The gate freezes
its decision once every evidence layer has reported on the prompt and holds it for the
generation.
The control is declarative: _configure maps the validated args onto one Intervention
(transform, layers, scope, and gate), and the base class binds it at steer(), verifying the
transform covers every behavior layer.
Steering multiple behaviors is done by placing multiple adapters in a pipeline's controls
list (each adapter owns exactly one transform chain / gate / token scope). Joint conditioning
is achieved by sharing one Gate instance across adapters. One driver carries the gate and
feeds it through its condition hooks; N followers pass the same gate instance with
gate_driven_externally=True and read its decision. Gate reads are side-effect-free and gate
reset is idempotent, so the shared instance is reset harmlessly once per adapter when hooks
are built.
Within a forward pass, a follower's behavior hook at layer L reads is_open() when L forwards,
so it observes driver evidence only from condition layers < L. Evidence from layers >= L
takes effect on the next pass. When a driver and follower hook the same layer index, place the
driver before the follower in the pipeline's controls list (registration order = execution
order).
Batching is native (supports_batching = True); gates are row-vectorized, so a gated adapter
scores and gates each prompt of a batch independently. The gate rejects scalar values for
multi-row batches, so a mis-specified readout fails loudly rather than silently applying one
decision batch-wide.
Source code in steerability/algorithms/state_control/activation_adapter/control.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
RUNTIME_KWARGS_SCHEMA = []
class-attribute
instance-attribute
_condition_layer_ids
property
The gate's evidence layers (empty when ungated).
_gate
property
The first intervention's gate (None before steer() or when unconditional).
_layer_ids
property
The resolved behavior layers (empty before steer()).
_template = ()
class-attribute
instance-attribute
_transform
property
writable
The first intervention's transform (None before steer()).
Assignment replaces the transform on the first intervention, so a wrapped or instrumented transform takes effect in subsequently built hooks.
args = self.Args.validate(*args, **kwargs)
instance-attribute
enabled = True
class-attribute
instance-attribute
hook_only_hint
property
interventions = ()
class-attribute
instance-attribute
supports_batching = True
class-attribute
instance-attribute
tokenizer = None
class-attribute
instance-attribute
__init__(*args, **kwargs)
Source code in steerability/algorithms/core/base_control.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
_configure()
Post-construction hook, called after Args fields are mirrored onto the instance.
Driver presets override this to map their mirrored args onto the fields their generic base
reads, so subclasses never bypass a parent __init__. Default no-op.
Source code in steerability/algorithms/state_control/activation_adapter/control.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | |
_fit_sources()
Yield the template's fit sources: unbound sources carrying an artifact_class
whose resolution does model-side work (access above ModelAccess.FACTS).
Source code in steerability/algorithms/state_control/base.py
377 378 379 380 381 382 383 384 385 | |
_resolve_module_layout(model=None)
The module-path layout, resolved from the module tree on first use.
Source code in steerability/algorithms/state_control/base.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | |
_unbound_sources()
Yield each unbound template element's source (or undeclared factory slot).
Yields the transform sources of unbound transform elements, factory transform slots
themselves (which declare their own access or default to the live model), and
unresolved gate sources, in template order.
Source code in steerability/algorithms/state_control/base.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | |
cleanup()
Drop references to the bound interventions.
Source code in steerability/algorithms/state_control/activation_adapter/control.py
113 114 115 | |
clone_for_call(seed=None)
A per-call clone whose interventions carry independent gate state.
Gates are deep-copied with one shared memo across the control's interventions, so a gate instance shared by several interventions stays shared inside the clone while being isolated from the original and from sibling clones. Transforms, scorers, and steer-time artifacts stay shared.
Source code in steerability/algorithms/state_control/base.py
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |
export_intervention_spec(runtime_kwargs=None)
The control's InterventionSpec, lowered from the bound interventions, or None.
Must be called after steer(). Returns None when the configuration has no wire form.
Source code in steerability/algorithms/state_control/base.py
320 321 322 323 324 325 326 327 328 329 330 331 332 | |
export_state()
Bound steering artifacts, keyed by intervention position.
For intervention i, the core transform's artifact exports as
"intervention_{i}/transform" (a SteeringVector view), each wrapper's own artifact
as "intervention_{i}/modifier_{j}", and the resolved gate as
"intervention_{i}/gate" when the template's gate slot held a source. Must be called
after steer().
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Mapping from logical name to artifact value. |
Source code in steerability/algorithms/state_control/base.py
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | |
fit_identity()
The template's fit sources in template order, each in its encodable form, or None when the template declares no fits.
Source code in steerability/algorithms/state_control/base.py
394 395 396 397 398 399 400 | |
frozen_form(state)
One activation_adapter entry per bound intervention, in intervention order.
Each entry's args mirror the intervention: the bound transform, the resolved behavior
layers, the hook point, the resolved gate, and the token scope. Must be called after
steer().
Returns:
| Type | Description |
|---|---|
list[tuple[str, dict[str, Any]]]
|
List of |
Raises:
| Type | Description |
|---|---|
NotFreezableError
|
If the control is unsteered, an intervention hooks the
|
Source code in steerability/algorithms/state_control/base.py
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | |
get_hooks(input_ids, runtime_kwargs=None, attention_mask=None, **kwargs)
Compile the bound interventions to hooks for the current generation.
Delegates to build_hooks: a fresh hook runtime is created, gates reset to the
logical batch, and one behavior hook is emitted per (intervention, layer).
Generation-time context arrives through **kwargs; model is consulted to resolve
hook module names when steering ran without a live model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor | dict
|
Prompt token ids of shape |
required |
runtime_kwargs
|
dict | None
|
Unused. |
None
|
attention_mask
|
Tensor | None
|
The prompt attention mask matching |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, list[HookSpec]]
|
Hook specifications with |
Source code in steerability/algorithms/state_control/base.py
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | |
requirements()
Backend requirements derived from the declared interventions, per phase.
Generate offers the intervention-spec alternative whenever every component of every intervention has a wire form; hook-only configurations require the in-process backend. Score is in-process: remote prompt-logprob scoring anchors token scopes at the request's prompt end (the end of the prompt-plus-reference concatenation), which would silently unanchor prompt-relative interventions.
Source code in steerability/algorithms/state_control/base.py
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
steer(model=None, tokenizer=None, session=None, **kwargs)
Bind the intervention template against the model (or the session's layout).
Structural facts come from the steering session's layout when a session is given, so a
fully concrete template (precomputed vectors, manual thresholds) binds with
model=None. Templates carrying sources (fits, searches) resolve them here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
PreTrainedModel | None
|
The base language model, or None for concrete templates bound against a session layout. |
None
|
tokenizer
|
PreTrainedTokenizerBase | None
|
Tokenizer used when fitting sources. |
None
|
session
|
SteeringSession | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
PreTrainedModel | None
|
The input model, unchanged. |
Source code in steerability/algorithms/state_control/base.py
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
steer_access()
The model access the template's steer step requires, folded over its sources.
A fully bound template requires ModelAccess.FACTS, since pure layer selectors
resolve from structural facts available on any session. Otherwise the strongest
declared source access wins; a source or factory slot without an access declaration
builds against the live model, so it counts as ModelAccess.MODULE.
Source code in steerability/algorithms/state_control/base.py
364 365 366 367 368 369 370 371 372 373 374 375 | |
steer_fits()
The template's fit artifacts, i.e. every unbound source carrying an
artifact_class, as (artifact, artifact_class) pairs in template order.
Source code in steerability/algorithms/state_control/base.py
387 388 389 390 391 392 | |
wire_kinds()
The combined wire kinds of the bound interventions (or the template before
steer()), or None when any intervention is hook-only.
Source code in steerability/algorithms/state_control/base.py
334 335 336 337 338 339 340 | |
TransformContext
dataclass
Fully-resolved layer/sizing context passed to a transform's bind or a factory.
A transform slot may be a BaseTransform instance (bound, or source-carrying and bound via
bind(ctx)) or a factory Callable[[TransformContext], BaseTransform]. Either way it receives
this context after the behavior layers are resolved, so it can turn a source into a concrete
artifact (ctx.resolve(...)) and size any per-layer or per-head buffers to the model.
Transforms never touch the model directly: fit orchestration lives behind resolve, a closure
the adapter builds over (model, tokenizer, device, dtype) that coerces the input, resolves the
source (memoized master, cloned), and moves the clone onto the model's device/dtype.
Attributes:
| Name | Type | Description |
|---|---|---|
layer_ids |
list[int]
|
The resolved behavior layer(s). |
num_layers |
int
|
Total number of transformer layers in the model. |
hidden_size |
int
|
Residual-stream width |
num_heads |
int | None
|
Number of attention heads per layer, or None if unavailable from the config. |
head_dim |
int | None
|
Dimension of each attention head, or None if unavailable from the config. |
dtype |
dtype
|
The model's parameter dtype. |
device |
device
|
The model's device. |
resolve |
Callable[[ArtifactSource | SteeringVector | Mapping[int, Tensor]], SteeringVector]
|
Turns a |
Source code in steerability/algorithms/state_control/common/transforms/context.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
device
instance-attribute
dtype
instance-attribute
head_dim
instance-attribute
hidden_size
instance-attribute
layer_ids
instance-attribute
num_heads
instance-attribute
num_layers
instance-attribute
resolve
instance-attribute
__init__(layer_ids, num_layers, hidden_size, num_heads, head_dim, dtype, device, resolve)
args
ActivationAdapter argument validation.
_ARTIFACT_KWARG_HINTS = {'steering_vector': 'pass it to the transform, e.g. AdditiveTransform(sv, strength=...) or ProjectionTransform(sv, alpha=...)', 'data': 'wrap it in a source on the transform: AdditiveTransform(ContrastiveFit(data=...), ...)', 'train_spec': 'its fields are ContrastiveFit kwargs (method / accumulate / batch_size / prompt_format / location)', 'estimator': 'ContrastiveFit(estimator=...)', 'estimator_kwargs': 'ContrastiveFit(estimator_kwargs=...)', 'strength': 'AdditiveTransform(..., strength=...)', 'normalize_vector': 'ContrastiveFit(normalize=True) or SteeringVector.normalized()'}
module-attribute
control
ActivationAdapter: assemble an activation-steering recipe from common components.
logger = logging.getLogger(__name__)
module-attribute
ActivationAdapter
Bases: InterventionControl
Composable activation-steering control (single-behavior atom).
ActivationAdapter wires together the state_control/common component families (a transform
that carries its own steering artifact, a selector, a gate, and a token scope) so an
activation-steering recipe can be assembled directly without writing a new control class. It
edits the residual stream at one or more layers during generation, applying the transform at
masked positions whenever its gate is open.
The transform is the sole artifact carrier. It holds a concrete SteeringVector / directions
mapping (bound at construction), or an ArtifactSource such as ContrastiveFit(data=...) that
is resolved once at steer() time. The adapter has no artifact slots and never sees a
SteeringVector directly.
Gating is likewise self-describing. A Gate carries its evidence (condition layers, pooling,
and a readout mapping pooled hidden states to per-row values) and a rule deciding over the
values; gate_from_probe and Probe.as_gate assemble one from a fitted probe, and a
GateSource (e.g. ConditionPointSearch) resolves one at steer() time. The gate freezes
its decision once every evidence layer has reported on the prompt and holds it for the
generation.
The control is declarative: _configure maps the validated args onto one Intervention
(transform, layers, scope, and gate), and the base class binds it at steer(), verifying the
transform covers every behavior layer.
Steering multiple behaviors is done by placing multiple adapters in a pipeline's controls
list (each adapter owns exactly one transform chain / gate / token scope). Joint conditioning
is achieved by sharing one Gate instance across adapters. One driver carries the gate and
feeds it through its condition hooks; N followers pass the same gate instance with
gate_driven_externally=True and read its decision. Gate reads are side-effect-free and gate
reset is idempotent, so the shared instance is reset harmlessly once per adapter when hooks
are built.
Within a forward pass, a follower's behavior hook at layer L reads is_open() when L forwards,
so it observes driver evidence only from condition layers < L. Evidence from layers >= L
takes effect on the next pass. When a driver and follower hook the same layer index, place the
driver before the follower in the pipeline's controls list (registration order = execution
order).
Batching is native (supports_batching = True); gates are row-vectorized, so a gated adapter
scores and gates each prompt of a batch independently. The gate rejects scalar values for
multi-row batches, so a mis-specified readout fails loudly rather than silently applying one
decision batch-wide.
Source code in steerability/algorithms/state_control/activation_adapter/control.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
RUNTIME_KWARGS_SCHEMA = []
class-attribute
instance-attribute
_condition_layer_ids
property
The gate's evidence layers (empty when ungated).
_gate
property
The first intervention's gate (None before steer() or when unconditional).
_layer_ids
property
The resolved behavior layers (empty before steer()).
_template = ()
class-attribute
instance-attribute
_transform
property
writable
The first intervention's transform (None before steer()).
Assignment replaces the transform on the first intervention, so a wrapped or instrumented transform takes effect in subsequently built hooks.
args = self.Args.validate(*args, **kwargs)
instance-attribute
enabled = True
class-attribute
instance-attribute
hook_only_hint
property
interventions = ()
class-attribute
instance-attribute
supports_batching = True
class-attribute
instance-attribute
tokenizer = None
class-attribute
instance-attribute
__init__(*args, **kwargs)
Source code in steerability/algorithms/core/base_control.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
_configure()
Post-construction hook, called after Args fields are mirrored onto the instance.
Driver presets override this to map their mirrored args onto the fields their generic base
reads, so subclasses never bypass a parent __init__. Default no-op.
Source code in steerability/algorithms/state_control/activation_adapter/control.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | |
_fit_sources()
Yield the template's fit sources: unbound sources carrying an artifact_class
whose resolution does model-side work (access above ModelAccess.FACTS).
Source code in steerability/algorithms/state_control/base.py
377 378 379 380 381 382 383 384 385 | |
_resolve_module_layout(model=None)
The module-path layout, resolved from the module tree on first use.
Source code in steerability/algorithms/state_control/base.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | |
_unbound_sources()
Yield each unbound template element's source (or undeclared factory slot).
Yields the transform sources of unbound transform elements, factory transform slots
themselves (which declare their own access or default to the live model), and
unresolved gate sources, in template order.
Source code in steerability/algorithms/state_control/base.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | |
cleanup()
Drop references to the bound interventions.
Source code in steerability/algorithms/state_control/activation_adapter/control.py
113 114 115 | |
clone_for_call(seed=None)
A per-call clone whose interventions carry independent gate state.
Gates are deep-copied with one shared memo across the control's interventions, so a gate instance shared by several interventions stays shared inside the clone while being isolated from the original and from sibling clones. Transforms, scorers, and steer-time artifacts stay shared.
Source code in steerability/algorithms/state_control/base.py
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |
export_intervention_spec(runtime_kwargs=None)
The control's InterventionSpec, lowered from the bound interventions, or None.
Must be called after steer(). Returns None when the configuration has no wire form.
Source code in steerability/algorithms/state_control/base.py
320 321 322 323 324 325 326 327 328 329 330 331 332 | |
export_state()
Bound steering artifacts, keyed by intervention position.
For intervention i, the core transform's artifact exports as
"intervention_{i}/transform" (a SteeringVector view), each wrapper's own artifact
as "intervention_{i}/modifier_{j}", and the resolved gate as
"intervention_{i}/gate" when the template's gate slot held a source. Must be called
after steer().
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Mapping from logical name to artifact value. |
Source code in steerability/algorithms/state_control/base.py
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | |
fit_identity()
The template's fit sources in template order, each in its encodable form, or None when the template declares no fits.
Source code in steerability/algorithms/state_control/base.py
394 395 396 397 398 399 400 | |
frozen_form(state)
One activation_adapter entry per bound intervention, in intervention order.
Each entry's args mirror the intervention: the bound transform, the resolved behavior
layers, the hook point, the resolved gate, and the token scope. Must be called after
steer().
Returns:
| Type | Description |
|---|---|
list[tuple[str, dict[str, Any]]]
|
List of |
Raises:
| Type | Description |
|---|---|
NotFreezableError
|
If the control is unsteered, an intervention hooks the
|
Source code in steerability/algorithms/state_control/base.py
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | |
get_hooks(input_ids, runtime_kwargs=None, attention_mask=None, **kwargs)
Compile the bound interventions to hooks for the current generation.
Delegates to build_hooks: a fresh hook runtime is created, gates reset to the
logical batch, and one behavior hook is emitted per (intervention, layer).
Generation-time context arrives through **kwargs; model is consulted to resolve
hook module names when steering ran without a live model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor | dict
|
Prompt token ids of shape |
required |
runtime_kwargs
|
dict | None
|
Unused. |
None
|
attention_mask
|
Tensor | None
|
The prompt attention mask matching |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, list[HookSpec]]
|
Hook specifications with |
Source code in steerability/algorithms/state_control/base.py
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | |
requirements()
Backend requirements derived from the declared interventions, per phase.
Generate offers the intervention-spec alternative whenever every component of every intervention has a wire form; hook-only configurations require the in-process backend. Score is in-process: remote prompt-logprob scoring anchors token scopes at the request's prompt end (the end of the prompt-plus-reference concatenation), which would silently unanchor prompt-relative interventions.
Source code in steerability/algorithms/state_control/base.py
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
steer(model=None, tokenizer=None, session=None, **kwargs)
Bind the intervention template against the model (or the session's layout).
Structural facts come from the steering session's layout when a session is given, so a
fully concrete template (precomputed vectors, manual thresholds) binds with
model=None. Templates carrying sources (fits, searches) resolve them here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
PreTrainedModel | None
|
The base language model, or None for concrete templates bound against a session layout. |
None
|
tokenizer
|
PreTrainedTokenizerBase | None
|
Tokenizer used when fitting sources. |
None
|
session
|
SteeringSession | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
PreTrainedModel | None
|
The input model, unchanged. |
Source code in steerability/algorithms/state_control/base.py
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
steer_access()
The model access the template's steer step requires, folded over its sources.
A fully bound template requires ModelAccess.FACTS, since pure layer selectors
resolve from structural facts available on any session. Otherwise the strongest
declared source access wins; a source or factory slot without an access declaration
builds against the live model, so it counts as ModelAccess.MODULE.
Source code in steerability/algorithms/state_control/base.py
364 365 366 367 368 369 370 371 372 373 374 375 | |
steer_fits()
The template's fit artifacts, i.e. every unbound source carrying an
artifact_class, as (artifact, artifact_class) pairs in template order.
Source code in steerability/algorithms/state_control/base.py
387 388 389 390 391 392 | |
wire_kinds()
The combined wire kinds of the bound interventions (or the template before
steer()), or None when any intervention is hook-only.
Source code in steerability/algorithms/state_control/base.py
334 335 336 337 338 339 340 | |