Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
.cache

# environments
.local/
.env
.venv/
.venv**/
Expand Down
6 changes: 6 additions & 0 deletions docs/.nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ nav:
- AngularSteering: "examples/notebooks/algorithms/angular_steering.ipynb"
- CAA: "examples/notebooks/algorithms/caa.ipynb"
- CAST: "examples/notebooks/algorithms/cast.ipynb"
- CorDA-derived PCA: "examples/notebooks/algorithms/corda_pca.ipynb"
- S-space: "examples/notebooks/algorithms/sspace.ipynb"
- Linear-AcT: "examples/notebooks/algorithms/linear_act.ipynb"
- DirectionalAblation: "examples/notebooks/algorithms/directional_ablation.ipynb"
- ITI: "examples/notebooks/algorithms/iti.ipynb"
- PASTA: "examples/notebooks/algorithms/pasta.ipynb"
Expand Down Expand Up @@ -109,6 +112,9 @@ nav:
- ActAdd: reference/algorithms/state_control/act_add.md
- ActivationAdapter: reference/algorithms/state_control/activation_adapter.md
- Angular Steering: reference/algorithms/state_control/angular_steering.md
- CorDA-derived PCA: reference/algorithms/state_control/corda_pca.md
- S-space: reference/algorithms/state_control/sspace.md
- Linear-AcT: reference/algorithms/state_control/linear_act.md
- CAA: reference/algorithms/state_control/caa.md
- CAST: reference/algorithms/state_control/cast.md
- Directional Ablation: reference/algorithms/state_control/directional_ablation.md
Expand Down
9 changes: 9 additions & 0 deletions docs/concepts/controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ patching. The toolkit implements:
- `AngularSteering` ([API reference](../reference/algorithms/state_control/angular_steering.md), [notebook](../examples/notebooks/algorithms/angular_steering.ipynb))
- *Description*: angular steering[@vu2025angular], rotating the hidden state within a per-layer 2D plane (feature axis + companion axis) to a target angle while leaving the orthogonal complement untouched. It is norm-preserving by construction, and vector addition and directional ablation are special cases.
- *Backends*: HF, vLLM (`intervention_point="layer_output"` only, since the default norm-input placement is HF-only).
- `CordaPCA` ([API reference](../reference/algorithms/state_control/corda_pca.md), [notebook](../examples/notebooks/algorithms/corda_pca.ipynb))
- *Description*: an activation-steering adaptation of CorDA's context-oriented decomposition, fitted from paired Linear inputs.
- *Backends*: HF.
- `SSpace` ([API reference](../reference/algorithms/state_control/sspace.md), [notebook](../examples/notebooks/algorithms/sspace.ipynb))
- *Description*: weight-SVD steering with contrast-ranked coordinates and per-token cosine gates.
- *Backends*: HF.
- `LinearAcT` ([API reference](../reference/algorithms/state_control/linear_act.md), [notebook](../examples/notebooks/algorithms/linear_act.ipynb))
- *Description*: coordinate-wise affine transport fitted by least squares on sorted activations.
- *Backends*: HF.
- `CAA` ([API reference](../reference/algorithms/state_control/caa.md), [notebook](../examples/notebooks/algorithms/caa.ipynb))
- *Description*: contrastive activation addition[@panickssery2023steering], adding a learned mean-difference direction to the residual stream at a single layer.
- *Backends*: HF, vLLM (norm-preserving configurations included).
Expand Down
44 changes: 44 additions & 0 deletions docs/reference/algorithms/state_control/corda_pca.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# CorDA-derived PCA

`CordaPCA` is an activation-steering adaptation of the context-oriented weight
decomposition in [Yang et al., 2024](https://arxiv.org/abs/2406.05223). Its steering
variant follows [steering-lite at 0a064ba](https://github.com/wassname/steering-lite/blob/0a064ba0c23a4998637ff41c5ab0fb5ca50a4271/src/steering_lite/variants/corda_pca.py).

The CorDA paper uses weights and a calibration dataset to initialize trainable
adapters. Here, the decomposition is used to construct a steering vector instead.
For each target Linear module, the fit pools positive and negative input activations
to form a damped, uncentered second-moment matrix. This matrix and the module's
weights define the CorDA basis. Paired positive-minus-negative differences are
expressed in that basis; their first centered principal component is oriented toward
the mean difference and mapped back to an output-space steering vector.

Inference adds `strength * direction` to each token's module output. Model weights
stay fixed, and only the resulting vector is needed at inference; the decomposition
is not retained. Supplying `directions` reuses fitted output vectors without fitting
again. The PCA vector has unit norm before reconstruction; the output vector need
not have unit norm.

<!-- Authored by PI/Astra. -->

Numerically zero centered differences raise instead of selecting an arbitrary PCA
direction. This includes constant pair differences. The separate `pca_pairwise`
estimator elsewhere in steerability is not a `CordaPCA` option.

::: steerability.algorithms.state_control.corda_pca
handler: python
options:
show_if_no_docstring: true
show_source: true
show_root_heading: true
docstring_style: google
show_root_full_path: true
show_object_full_path: false
separate_signature: false
inherited_members: true
show_submodules: true
show_symbol_type_heading: true
show_symbol_type_toc: true
filters:
- "!.*Args$"
- "!^registry"
- "!^STEERING_METHOD"
35 changes: 35 additions & 0 deletions docs/reference/algorithms/state_control/linear_act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Linear-AcT

`LinearAcT` adapts coordinate-wise affine activation transport from
[Rodriguez et al., ICLR 2025](https://openreview.net/forum?id=l2zFn6TIQi), following
[steering-lite at 0a064ba](https://github.com/wassname/steering-lite/blob/0a064ba0c23a4998637ff41c5ab0fb5ca50a4271/src/steering_lite/variants/linear_act.py).
The fit treats negative activations as the source and positive activations as the
target. It sorts samples independently within each coordinate and fits a scalar
slope and bias by least squares. Equal sample counts are required, but pairing is
not used after sorting. This is not a standard-deviation-ratio map.

Inference applies `h + strength * (slope * h + bias - h)` at each selected decoder
layer's output, for every token. Only the slope and bias vectors are needed; model
weights stay fixed. `affine` accepts an already fitted map. This port implements the
coordinate-wise map, without support masking or sequential layerwise fitting.

<!-- Authored by PI/Astra. -->

::: steerability.algorithms.state_control.linear_act
handler: python
options:
show_if_no_docstring: true
show_source: true
show_root_heading: true
docstring_style: google
show_root_full_path: true
show_object_full_path: false
separate_signature: false
inherited_members: true
show_submodules: true
show_symbol_type_heading: true
show_symbol_type_toc: true
filters:
- "!.*Args$"
- "!^registry"
- "!^STEERING_METHOD"
44 changes: 44 additions & 0 deletions docs/reference/algorithms/state_control/sspace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# S-space

`SSpace` implements a weight-SVD activation-steering variant. The method draws
on [S-Space Steering for Eval-Awareness Control in Reasoning Models](https://apartresearch.com/project/sspace-steering-for-evalawareness-control-in-reasoning-models-7j1i)
by Michael J Clark; this control follows [steering-lite at 0a064ba](https://github.com/wassname/steering-lite/blob/0a064ba0c23a4998637ff41c5ab0fb5ca50a4271/src/steering_lite/variants/sspace.py).

For each target Linear module, the fit decomposes its weight matrix as
`W = U diag(s) Vᵀ`. It expresses positive and negative module outputs in coordinates
`z = (output - bias) U / sqrt(s)` and takes their mean difference. `rank` retains the
coordinates with the largest absolute contrast, not necessarily the largest singular
values. These are weight-scaled coordinates; their activation covariance is not
necessarily identity.

At inference, the retained basis is used to read each token's coordinates and map
the edit back to the module output. `cosine` scales the edit by the absolute cosine
with the fitted direction; `off` applies a constant edit. `signed` keeps the cosine's
sign: with positive strength it reinforces either pole of the axis, rather than
always pushing toward the positive examples. Reversing a direction leaves `signed`
unchanged, but reverses the `cosine` and `off` edits.

Unlike CorDA-PCA's fixed output vector, the gated variants need the retained basis
at inference. Model weights stay fixed. For fp16/bf16 outputs, application uses
float32 arithmetic with autocast disabled, then restores the output dtype.

<!-- Authored by PI/Astra. -->

::: steerability.algorithms.state_control.sspace
handler: python
options:
show_if_no_docstring: true
show_source: true
show_root_heading: true
docstring_style: google
show_root_full_path: true
show_object_full_path: false
separate_signature: false
inherited_members: true
show_submodules: true
show_symbol_type_heading: true
show_symbol_type_toc: true
filters:
- "!.*Args$"
- "!^registry"
- "!^STEERING_METHOD"
6 changes: 6 additions & 0 deletions examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ Algorithm notebooks demonstrate how each method (i.e., control) operates. The me

:octicons-arrow-right-24: [CAST](./notebooks/algorithms/cast.ipynb)

:octicons-arrow-right-24: [CorDA-derived PCA](./notebooks/algorithms/corda_pca.ipynb)

:octicons-arrow-right-24: [S-space](./notebooks/algorithms/sspace.ipynb)

:octicons-arrow-right-24: [Linear-AcT](./notebooks/algorithms/linear_act.ipynb)
Comment thread
Copilot marked this conversation as resolved.

:octicons-arrow-right-24: [DirectionalAblation](./notebooks/algorithms/directional_ablation.ipynb)

:octicons-arrow-right-24: [ITI](./notebooks/algorithms/iti.ipynb)
Expand Down
179 changes: 179 additions & 0 deletions examples/notebooks/algorithms/corda_pca.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "51e1f617",
"metadata": {},
"source": [
"# CordaPCA\n",
"\n",
"This is steering-lite's CorDA-derived PCA variant, rather than CorDA fine-tuning. The covariance and paired PCA use Linear inputs.\n",
"\n",
"We use a random CPU Llama and token ids to demonstrate calibration and execution without downloads. The output checks establish a numerical effect, not behavior control. Run with the repository environment (`uv sync --extra all`).\n",
"\n",
"Implementation source: [steering-lite 0a064ba](https://github.com/wassname/steering-lite/tree/0a064ba0c23a4998637ff41c5ab0fb5ca50a4271/src/steering_lite/variants).\n",
"\n",
"Authored by PI/OpenAI."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "bc3c0f8e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-16T06:48:51.771707Z",
"iopub.status.busy": "2026-09-16T06:48:51.771595Z",
"iopub.status.idle": "2026-09-16T06:48:56.741308Z",
"shell.execute_reply": "2026-09-16T06:48:56.740639Z"
}
},
"outputs": [],
"source": [
"from steerability.algorithms.state_control.corda_pca.control import CordaPCA\n",
"import torch\n",
"from tokenizers import Tokenizer, models, pre_tokenizers\n",
"from transformers import LlamaConfig, LlamaForCausalLM, PreTrainedTokenizerFast\n",
"from steerability.algorithms.core.steering_pipeline import SteeringPipeline\n",
"\n",
"torch.manual_seed(12)\n",
"torch.set_num_threads(1)\n",
"model = LlamaForCausalLM(LlamaConfig(\n",
" vocab_size=16, hidden_size=16, intermediate_size=32,\n",
" num_hidden_layers=2, num_attention_heads=2, num_key_value_heads=2,\n",
")).eval()\n",
"raw = Tokenizer(models.WordLevel({\"<pad>\": 0, \"<s>\": 1, \"</s>\": 2, \"yes\": 3, \"no\": 4}, unk_token=\"<pad>\"))\n",
"raw.pre_tokenizer = pre_tokenizers.Whitespace()\n",
"tokenizer = PreTrainedTokenizerFast(tokenizer_object=raw, pad_token=\"<pad>\", bos_token=\"<s>\", eos_token=\"</s>\")\n",
"positive_ids = torch.tensor([[1, 3, 5], [1, 6, 7], [1, 8, 9], [1, 10, 11]])\n",
"negative_ids = torch.tensor([[1, 4, 6], [1, 7, 8], [1, 9, 10], [1, 11, 12]])\n"
]
},
{
"cell_type": "markdown",
"id": "a5724798",
"metadata": {},
"source": [
"## Calibration\n",
"\n",
"Each row is one prompt. These equal-length examples have no padding, so position `-1` is the last prompt token. For padded data, gather the last position whose attention mask is one. Capture from the same frozen model that will be steered. CorDA requires paired rows; Linear-AcT requires equal counts. The fit runs during `steer()`, and the inference edit applies at every token, including the prompt."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "903e54fa",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-16T06:48:56.743315Z",
"iopub.status.busy": "2026-09-16T06:48:56.743016Z",
"iopub.status.idle": "2026-09-16T06:48:56.807541Z",
"shell.execute_reply": "2026-09-16T06:48:56.806841Z"
}
},
"outputs": [],
"source": [
"target = \"model.layers.0.mlp.down_proj\"\n",
"module = model.get_submodule(target)\n",
"\n",
"def capture_last(ids):\n",
" captured = []\n",
" def capture(module, inputs, output):\n",
" captured.append(inputs[0][:, -1, :].detach().cpu())\n",
" handle = module.register_forward_hook(capture)\n",
" try:\n",
" with torch.no_grad():\n",
" model(input_ids=ids, attention_mask=torch.ones_like(ids))\n",
" finally:\n",
" handle.remove()\n",
" return captured[0]\n",
"\n",
"positive, negative = capture_last(positive_ids), capture_last(negative_ids)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "18d90609",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-16T06:48:56.809267Z",
"iopub.status.busy": "2026-09-16T06:48:56.809117Z",
"iopub.status.idle": "2026-09-16T06:48:56.811949Z",
"shell.execute_reply": "2026-09-16T06:48:56.811188Z"
}
},
"outputs": [],
"source": [
"control = CordaPCA(positive_inputs={target: positive}, negative_inputs={target: negative}, rank=4, strength=0.5)"
]
},
{
"cell_type": "markdown",
"id": "bdf615c6",
"metadata": {},
"source": [
"## Apply\n",
"\n",
"Scoring the same continuation shows a nonzero change. Scoring again without the control checks that hooks do not remain on the model. For a pretrained model, use contrastive text and evaluate held-out behavior separately."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "d7cd5d91",
"metadata": {
"execution": {
"iopub.execute_input": "2026-09-16T06:48:56.813058Z",
"iopub.status.busy": "2026-09-16T06:48:56.812896Z",
"iopub.status.idle": "2026-09-16T06:48:57.352304Z",
"shell.execute_reply": "2026-09-16T06:48:57.351797Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Maximum log-probability change: 0.0005376338958740234\n",
"Generated token ids: [[10, 10, 10]]\n"
]
}
],
"source": [
"baseline = SteeringPipeline(model=model, tokenizer=tokenizer, controls=[])\n",
"pipeline = SteeringPipeline(model=model, tokenizer=tokenizer, controls=[control])\n",
"baseline.steer()\n",
"pipeline.steer()\n",
"query, reference = torch.tensor([[1, 3, 4]]), torch.tensor([[5, 6]])\n",
"base_scores = baseline.compute_logprobs(query, ref_output_ids=reference)\n",
"steered_scores = pipeline.compute_logprobs(query, ref_output_ids=reference)\n",
"assert not torch.allclose(base_scores, steered_scores)\n",
"torch.testing.assert_close(baseline.compute_logprobs(query, ref_output_ids=reference), base_scores)\n",
"print(\"Maximum log-probability change:\", (steered_scores - base_scores).abs().max().item())\n",
"print(\"Generated token ids:\", pipeline.generate(input_ids=query, max_new_tokens=3, do_sample=False).tolist())"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading