-
Notifications
You must be signed in to change notification settings - Fork 36
Add experimental CorDA-PCA, S-space, and Linear-AcT controls #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
wassname
wants to merge
4
commits into
generative-computing:main
Choose a base branch
from
wassname:feat/steering-lite-methods
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
31e70b3
Add experimental CorDA-PCA, S-space, and Linear-AcT controls
claudypoo cdac427
Validate frozen tensor model provenance before steering
claudypoo 048c119
Cache token-independent CorDA and S-space arithmetic
claudypoo 2c0a198
Allow model-free freezing of precomputed Linear-AcT
claudypoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| .cache | ||
|
|
||
| # environments | ||
| .local/ | ||
| .env | ||
| .venv/ | ||
| .venv**/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.