Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c7e6f1f
Add the multi-backend execution seam with in-process and vLLM backends
emiehling Aug 1, 2026
6b5dd24
Add offline capture-backed fitting and declarative constrained decoding
emiehling Aug 1, 2026
c53c0b7
Introduce a declarative intervention representation for state controls
emiehling Aug 2, 2026
7bbf091
Unify the evaluation generation path and make benchmarking backend-aware
emiehling Aug 2, 2026
4b2bdfd
Route judge metrics and Perplexity through the backend seam
emiehling Aug 3, 2026
4597d09
Refresh docs, README, and notebooks for the backend layer
emiehling Aug 4, 2026
eabfea3
Adopt one backend per pipeline with a deterministic release lifecycle
emiehling Aug 9, 2026
65e7e5b
Align the vLLM backend with the in-process capture and constraint con…
emiehling Aug 11, 2026
60742a1
Add reasoning-model handling and consolidate the CAA notebooks
emiehling Aug 13, 2026
c6bc886
Add shared isort configuration and normalize imports repo-wide
emiehling Aug 14, 2026
da3303b
Extract pipeline internals into modules and make construction cheap
emiehling Aug 14, 2026
2f2daa9
Restructure gating into structured gates and relocate routing
emiehling Aug 16, 2026
8fe2970
Reorganize backend and common packages, fix ActAdd, and polish docs
emiehling Aug 19, 2026
a5a56ae
Redesign RAD/SASA output controls on the core probes substrate; add v…
emiehling Aug 20, 2026
d691e30
Restore TRL example notebook with vLLM serving sections
emiehling Aug 20, 2026
92a0961
Address backend-layer PR review follow-ups
emiehling Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ examples/**/profiles_*/
.claude/
**/CLAUDE.md
**/_run_notebooks.sh
**/.nbrun/
**/.nbrun/
256 changes: 203 additions & 53 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ We use [MkDocs](https://www.mkdocs.org/) to write documentation.
To run the documentation server, run:

```bash
uv run mkdocs serve
uv run mkdocs serve
```

The server will be available at [http://localhost:8000](http://localhost:8000).
Expand Down
35 changes: 11 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,27 @@

---

Welcome to AI Steerability 360 (AISteer360), a toolkit for steering large language models.
The AI Steerability 360 toolkit is an open source Python package for steering large language models.

AISteer360 provides an expressive library of reusable components (termed generics) across four model control surfaces
(input, structural, state, and output). This allows for the modular construction of novel steering methods, composition
of steering methods into steering pipelines, and benchmarking of pipelines on custom use cases and metrics (including
measurement of steering side effects).
The toolkit enables the development and evaluation of a wide range of steering methods through an expressive library of
reusable components across four model control surfaces (input, structure, state, and output). Features include modular abstractions for the
construction of steering methods, functionality for composition of steering methods into [steering pipelines](docs/concepts/steering_pipelines.md),
and benchmarking of pipelines on custom use cases and metrics (including measurement of steering side effects).

To get started, please see the documentation at <https://ibm.github.io/AISteer360/> and the [example notebooks](examples/index.md).

## Installation

The toolkit uses [uv](https://docs.astral.sh/uv/) as the package manager (Python 3.11+). After installing `uv`, install
the toolkit by running:
The toolkit uses [uv](https://docs.astral.sh/uv/) as the package manager (Python 3.11+). After installing `uv` and cloning the repo,
install the toolkit by running:

```commandline
uv venv --python 3.11 && uv pip install .
```
Activate by running `source .venv/bin/activate`. Note that on Windows, you may need to split the above script into two
separate commands (instead of chained via `&&`).

Optional features are available via extra. Install everything with `uv pip install ".[all]"`.

Inference is facilitated by Hugging Face. Before steering, create a `.env` file in the root directory for your Hugging
Face API key in the following format:
```
HUGGINGFACE_TOKEN=hf_***
```

Some Hugging Face models (e.g. `meta-llama/Meta-Llama-3.1-8B-Instruct`) are behind an access gate. Check that you have
access via the model's Hub page with the same account whose token you pass to the toolkit.

> [!NOTE]
> AISteer360 runs the model inside your process. For efficient inference, please run the toolkit from a machine that
> has enough GPU memory for both the base checkpoint and the extra overhead your steering method/pipeline adds.
By default, pipelines load and run the model *in process* (via Hugging Face `transformers`). The toolkit additionally provides
support for inference through vLLM (either offline engine or server) via [vLLM-Hook](https://github.com/IBM/vLLM-Hook). To enable this,
install the extra with `uv pip install ".[vllm]"`.

## Contributing

Expand All @@ -67,4 +54,4 @@ If you find the toolkit useful in your work, please cite the following:

## IBM ❤️ Open Source AI

The AI Steerability 360 toolkit has been brought to you by IBM.
The AI Steerability 360 toolkit has been brought to you by IBM.
4 changes: 4 additions & 0 deletions aisteer360/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
steering pipelines). Benchmarks enable comparison of steering pipelines on common use cases.
"""

import logging as _logging

try:
from .version import version as __version__
except ImportError:
pass

_logging.getLogger(__name__).addHandler(_logging.NullHandler())
66 changes: 66 additions & 0 deletions aisteer360/algorithms/core/base_control.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Shared base class for steering controls across all four categories."""
import copy
from abc import ABC
from dataclasses import fields

from aisteer360.algorithms.core.base_args import BaseArgs
from aisteer360.algorithms.core.execution.access import ModelAccess
from aisteer360.algorithms.core.execution.contracts import Capability, Requirements, needs


class BaseControl(ABC):
Expand Down Expand Up @@ -53,6 +56,69 @@ def _configure(self) -> None:
"""
pass

def requirements(self) -> Requirements:
"""Backend requirements computed from this instance's configuration, per phase.

The default requires `Capability.IN_PROCESS_TORCH` at generate and nothing at score,
which only the Hugging Face backend satisfies. A control with portable mechanisms
overrides this to state weaker or alternative requirements. Configuration determines the
result, so two configurations of one class may differ. Only enabled controls are
consulted during support evaluation.

Returns:
The control's phase-keyed requirements.
"""
return Requirements(generate=needs(Capability.IN_PROCESS_TORCH))

def steer_access(self) -> ModelAccess:
"""The model access this instance's steer step requires, on the `ModelAccess` ladder.

The default is `ModelAccess.FACTS` (layout and tokenizer only). A control whose steer
step generates or scores through the session declares `ROLLOUTS`, one that captures
hidden states declares `CAPTURE`, and one that touches the model as a live
`torch.nn.Module` declares `MODULE`. Configuration determines the result. The pipeline
hands `steer()` a session scoped to the declared rung, and the live model only at
`MODULE`. A control may retain the pipeline model beyond `steer()` only if its
generate phase requires `Capability.IN_PROCESS_TORCH`.

Returns:
The declared access rung.
"""
return ModelAccess.FACTS

def steer_fits(self) -> tuple[tuple[str, str], ...]:
"""The fit artifacts this instance's steer step will produce, for the steer plan.

Each entry is `(artifact, artifact_class)`, where `artifact` is the fit source or
recipe class name and `artifact_class` is `"direction"` or `"calibrated"`. The default
is an empty tuple (no fits).

Returns:
The declared fit artifacts, in declaration order.
"""
return ()

def clone_for_call(self, seed: int | None = None):
"""A configuration-preserving shallow clone for one generation call.

The clone shares steer-time artifacts (memories, steering vectors, attached tokenizers)
with the original but has its own attribute namespace, so per-call attribute mutation on
the clone never races another call using the original. When `seed` is given and the
control defines `reseed(seed)`, the clone's client-side RNG is re-seeded.

Args:
seed: Optional seed forwarded to the clone's `reseed()`.

Returns:
The clone.
"""
clone = copy.copy(self)
if seed is not None:
reseed = getattr(clone, "reseed", None)
if callable(reseed):
reseed(seed)
return clone

def cleanup(self) -> None:
"""Release resources allocated during `steer()`.

Expand Down
123 changes: 123 additions & 0 deletions aisteer360/algorithms/core/execution/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Execution seam types for multi-backend steering.

A `Backend` owns identity, capability advertisement, and session creation; a `SteeringSession`
is the scope within which steering is in force and the unit of concurrency. The pipeline
interacts with backends through these two interfaces. Backend implementations live in
`aisteer360.backends`; this package holds every seam type and imports nothing from
`aisteer360.backends` at module level.
"""
from aisteer360.algorithms.core.execution.access import ModelAccess, PlannedFit, PlannedStep, SteerPlan
from aisteer360.algorithms.core.execution.backend import (
Backend,
SteeringSession,
capabilities_for_spec,
resolve_backend_class,
)
from aisteer360.algorithms.core.execution.contracts import (
Alternative,
BackendCapabilities,
Capability,
CaptureKinds,
ConstraintKinds,
InterventionKinds,
ProcessorKinds,
Requirements,
SpecConstraint,
SupportFailure,
SupportReport,
UnsupportedOperationError,
UnsupportedPipelineError,
any_of,
evaluate_support,
needs,
)
from aisteer360.algorithms.core.execution.fanout import (
PartialBatchError,
TransportError,
derive_item_seed,
run_bounded,
with_transport_retries,
)
from aisteer360.algorithms.core.execution.params import GenerationParams, merge_lowered_params
from aisteer360.algorithms.core.execution.payloads import (
Artifact,
ArtifactProvenance,
CaptureResult,
CheckpointArtifact,
ConstraintEntry,
ConstraintSource,
GenerationItem,
HookEntry,
InterventionEntry,
InterventionSpec,
ItemResult,
LoRAArtifact,
ModelArtifact,
ModelFacts,
OutputControlEntry,
PreparedPrompt,
ProcessorSpec,
ProcessorSpecEntry,
ScoringItem,
StackEntry,
StateControlEntry,
as_constraint_source,
)
from aisteer360.algorithms.core.execution.spec import BackendSpec

__all__ = [
"Artifact",
"ArtifactProvenance",
"Backend",
"BackendCapabilities",
"BackendSpec",
"Capability",
"CaptureKinds",
"ConstraintEntry",
"ConstraintKinds",
"ConstraintSource",
"as_constraint_source",
"CaptureResult",
"CheckpointArtifact",
"GenerationItem",
"GenerationParams",
"HookEntry",
"InterventionEntry",
"InterventionKinds",
"InterventionSpec",
"ItemResult",
"LoRAArtifact",
"ModelAccess",
"ModelArtifact",
"ModelFacts",
"OutputControlEntry",
"PlannedFit",
"PlannedStep",
"PreparedPrompt",
"ProcessorKinds",
"ProcessorSpec",
"ProcessorSpecEntry",
"Requirements",
"Alternative",
"ScoringItem",
"SpecConstraint",
"StackEntry",
"StateControlEntry",
"SteerPlan",
"SteeringSession",
"SupportFailure",
"SupportReport",
"UnsupportedOperationError",
"UnsupportedPipelineError",
"PartialBatchError",
"TransportError",
"any_of",
"capabilities_for_spec",
"derive_item_seed",
"evaluate_support",
"merge_lowered_params",
"needs",
"resolve_backend_class",
"run_bounded",
"with_transport_retries",
]
90 changes: 90 additions & 0 deletions aisteer360/algorithms/core/execution/access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Model access declarations and the steer plan.

`ModelAccess` names what a control's steer step requires of the pipeline model. The pipeline
satisfies every declaration deterministically: the bottom two rungs through the backend's
session, `CAPTURE` through session capture where advertised and a staged in-process model where
not, and `MODULE` through a staged in-process model always. `SteerPlan` records, per enabled
control and per fit artifact, where the pipeline will run each steer step for one backend
configuration.
"""
import enum
from dataclasses import dataclass
from typing import Literal


class ModelAccess(enum.IntEnum):
"""What a control's steer step requires of the pipeline model, as a cumulative ladder.

Attributes:
FACTS: Structural facts (`session.layout`) and a tokenizer.
ROLLOUTS: FACTS plus generation and scoring through the session.
CAPTURE: ROLLOUTS plus hidden-state capture through the session.
MODULE: The model as a live `torch.nn.Module` in the client process.
"""

FACTS = 0
ROLLOUTS = 1
CAPTURE = 2
MODULE = 3


Venue = Literal["live", "session", "stage"]
ArtifactClass = Literal["direction", "calibrated"]


@dataclass(frozen=True, slots=True)
class PlannedStep:
"""One enabled control's steer step in the plan.

Attributes:
control: Class name of the control.
access: The control's declared steer access.
venue: Where the step runs. `"live"` is the persistent in-process model on the Hugging
Face backend, `"session"` the engine session, and `"stage"` the temporary
in-process model on engine backends.
"""

control: str
access: ModelAccess
venue: Venue


@dataclass(frozen=True, slots=True)
class PlannedFit:
"""One fit artifact's venue in the plan.

Fits execute inside their owning control's steer step, so a fit's venue is the step's.

Attributes:
control: Class name of the control whose steer runs the fit.
artifact: Class name of the fit source or recipe, e.g. `"ContrastiveFit"`.
artifact_class: `"direction"` for translation-robust fits (mean-difference or PCA
directions) or `"calibrated"` for artifacts compared against absolute activation
statistics (probe biases, gate thresholds).
venue: Where the fit runs.
"""

control: str
artifact: str
artifact_class: ArtifactClass
venue: Venue


@dataclass(frozen=True, slots=True)
class SteerPlan:
"""The deterministic steer plan for one backend configuration.

A pure function of the enabled controls' declarations and the backend spec, so the same
configuration always yields the same plan.

Attributes:
steps: Every enabled control, in pipeline order.
fits: Every fit artifact the steer phase will run, in pipeline order.
stages: True when a staged in-process model will be constructed.
notices: Deterministic warnings the steer phase will emit (calibration crossings).
"""

steps: tuple[PlannedStep, ...] = ()
fits: tuple[PlannedFit, ...] = ()
stages: bool = False
notices: tuple[str, ...] = ()
Loading