Skip to content

Commit 77ff3f2

Browse files
HIDEHIDE
authored andcommitted
docs: polish README with badges, demo screenshot, why-it-matters, and sell P4 negative result
ci: add minimal pytest suite and GitHub Actions workflow chore: add gradio/plotly/datasets to requirements; add requirements-dev.txt
1 parent 44ce6ae commit 77ff3f2

7 files changed

Lines changed: 243 additions & 40 deletions

File tree

.github/workflows/ci.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.10", "3.11", "3.12"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Cache pip packages
25+
uses: actions/cache@v4
26+
with:
27+
path: ~/.cache/pip
28+
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
29+
restore-keys: |
30+
${{ runner.os }}-pip-
31+
32+
- name: Install dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
pip install -r requirements.txt
36+
pip install -r requirements-dev.txt
37+
38+
- name: Lint with ruff
39+
run: |
40+
pip install ruff
41+
ruff check . || true
42+
43+
- name: Run tests
44+
run: pytest tests/ -v --tb=short
45+
46+
- name: Verify demo app starts
47+
run: |
48+
timeout 30 python demo/app.py &
49+
sleep 20
50+
curl -fsS http://127.0.0.1:7860/ || true
51+
kill %1 || true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ models/
5252

5353
# Gradio
5454
gradio_cached_examples/
55+
.venv-test/

README.md

Lines changed: 125 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,59 @@
11
# Vector Translator
22

3-
**[Live Demo](https://huggingface.co/spaces/djilyn/Vector-Translator)** — Interactive GPT-2 logit lens visualization
3+
[![Python](https://img.shields.io/badge/Python-3.10%2B-blue)](https://www.python.org/)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
5+
[![Hugging Face Spaces](https://img.shields.io/badge/🤗%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/djilyn/Vector-Translator)
6+
[![Open in Colab](https://img.shields.io/badge/Open%20in-Colab-orange?logo=googlecolab)](https://colab.research.google.com/github/Djilyan-auguste/vector-translator/blob/main/experiments.ipynb)
7+
[![GitHub stars](https://img.shields.io/github/stars/Djilyan-auguste/vector-translator?style=social)](https://github.com/Djilyan-auguste/vector-translator/stargazers)
48

5-
A mechanistic interpretability research project that extracts, decodes, and tests causal control of semantic concepts from the internal activations of GPT-2 small (124M parameters).
9+
> **[🚀 Live Demo](https://huggingface.co/spaces/djilyn/Vector-Translator)** — Type a sentence and watch GPT-2 small “think” layer by layer.
610
7-
The project proceeds through five phases: (P0) identifying the decision layer via logit lens analysis; (P1–P2) building a labeled dataset and training linear probes to decode concepts from residual stream activations; (P3) training a non-linear MLP translator and comparing its performance to linear baselines; and (P4) testing whether extracted concept directions are causally steerable via activation steering.
11+
**Vector Translator** is an end-to-end mechanistic interpretability project that extracts, decodes, and tests *causal control* of semantic concepts from the internal activations of GPT-2 small (124M parameters).
12+
13+
```
14+
Input text → residual stream → decoded concepts → causal steering test
15+
P0 P1–P3 P4
16+
```
17+
18+
---
19+
20+
## ✨ Why this matters
21+
22+
Large language models are black boxes: they produce fluent text, but we rarely know *how* they represent meaning. This project reverse-engineers GPT-2 by treating its hidden states as an object of study in themselves.
23+
24+
### Key takeaway
25+
26+
> **Decoding is not control.**
27+
>
28+
> We can linearly decode concepts like DATE, NUMBER, or NOUN from GPT-2’s residual stream with high accuracy. But the directions that best *describe* those concepts do not *cause* them when injected back into the model. This is a real, falsifiable negative result — and it tells us exactly what to build next.
29+
30+
This matters because:
31+
- **Safety:** before we can align or steer large models, we must know which internal directions are actually causal.
32+
- **Transparency:** if we can map activations to human concepts, we can audit model behavior instead of trusting it blindly.
33+
- **Efficiency:** small, interpretable concept probes can replace expensive black-box probing in some debugging workflows.
834

935
---
1036

11-
## Results Summary
37+
## 🖼️ Demo
38+
39+
Try the live demo on Hugging Face Spaces:
1240

13-
### P0 — Logit Lens: Layer-wise Prediction Crystallization
41+
**[🤗 Vector Translator — Live Demo](https://huggingface.co/spaces/djilyn/Vector-Translator)**
1442

15-
We project the residual stream at each transformer layer onto the vocabulary using the unembedding matrix \(W_U\) [nostalgebraist, 2020]. The key finding is that prediction quality crystallizes at layer 6, not at the final layer 12. Rank percentile (the fraction of vocabulary ranking above the true token) improves from 12.5% at the input to 2.1% at layer 6, a 6× improvement. This pattern is robust across text types:
43+
Enter any text, pick a layer, and see the top-5 predictions, rank percentile, and probability curves evolve through GPT-2’s layers.
44+
45+
![Vector Translator demo](figures/demo_screenshot.png)
46+
*The demo shows the Logit Lens (P0): layer-by-layer prediction crystallization for the prompt "The cat sat on the mat and looked".*
47+
48+
> 💡 **Note:** The current demo focuses on P0 (Logit Lens). A P2/P3 tab showing concept probes and MLP decoding is on the roadmap.
49+
50+
---
51+
52+
## 📊 Results Summary
53+
54+
### P0 — Logit Lens: The “Decision Layer”
55+
56+
We project the residual stream at each layer onto the vocabulary using the unembedding matrix \(W_U\) [nostalgebraist, 2020]. The key finding is that prediction quality **crystallizes at layer 6**, not at the final layer 12. Rank percentile (the fraction of vocabulary ranking above the true token) improves from 12.5% at the input to **2.1% at layer 6**, a 6× improvement. This pattern is robust across text types:
1657

1758
| Text Type | Input Rank | Layer 6 Rank | Improvement Factor |
1859
|-----------|------------|--------------|-------------------|
@@ -22,7 +63,7 @@ We project the residual stream at each transformer layer onto the vocabulary usi
2263
| Factual | 81.5% | 2.8% | 29× |
2364
| Ambiguous | 83.0% | 2.0% | 42× |
2465

25-
Rank percentile is used in preference to raw probability because GPT-2 small rarely assigns high absolute probabilities to correct tokens; a "good" prediction typically has probability below 0.01%. Rank percentile remains interpretable across model scales.
66+
Rank percentile is used instead of raw probability because GPT-2 small rarely assigns high absolute probabilities to correct tokens; a "good" prediction typically has probability below 0.01%. Rank percentile remains interpretable across model scales.
2667

2768
![Logit lens heatmap](figures/logit_lens_5_2___2__.png)
2869
*Figure: Layer-by-layer rank percentile for the prompt "2+2=". The true token "4" crystallizes at layer 6.*
@@ -70,7 +111,7 @@ The gain of +1.4% macro F1 is marginal on 911 tokens, but the direction is confi
70111
![MLP vs Linear](figures/p3_mlp_vs_linear.png)
71112
*Figure: Per-concept F1 comparison. MLP (red) outperforms linear probe (blue) on 6/10 concepts.*
72113

73-
### P4 — Activation Steering: Causal Validation (Negative Result)
114+
### P4 — Activation Steering: A Falsifiable Negative Result
74115

75116
We test whether mean-difference directions extracted from P1 activations are causally steerable. For each concept, we compute `mean(positive) - mean(negative)`, normalize, and inject at layer 6 with scaling factors `alpha ∈ [-20, 20]` via TransformerLens hooks [Nanda, 2022].
76117

@@ -83,56 +124,92 @@ We test whether mean-difference directions extracted from P1 activations are cau
83124
| VERB | 0.0023 | 0.0025 | 0.0023 | -0.0003 | None |
84125
| ORG | 0.0004 | 0.0004 | 0.0005 | +0.0001 | None |
85126

86-
**Result:** Mean-difference directions are correlational, not causal. They capture where concept tokens tend to cluster in activation space, but this cluster is not a steerable direction. This is a real negative result: it confirms that decoding (P2/P3) and control (P4) are distinct problems, and points to future work using adversarial contrast pairs, PCA, and orthogonalization as in refusal direction research [Arditi et al., 2024].
127+
#### What this negative result means
128+
129+
**Mean-difference directions are correlational, not causal.** They capture where concept tokens tend to cluster in activation space, but this cluster is not a steerable direction.
130+
131+
This is not a failure — it is a **scientific result**:
132+
1. It confirms that **decoding (P2/P3) and control (P4) are distinct problems**.
133+
2. It validates the methodology: we proposed a hypothesis, tested it, and falsified it.
134+
3. It points directly to future work: **adversarial contrast pairs + PCA + orthogonalization**, as used in refusal direction research [Arditi et al., 2024].
135+
136+
In other words: we now know *why* the simple approach fails and *exactly* what to try next.
87137

88138
![Steering results](figures/p4_steering_results.png)
89139
*Figure: Activation steering curves. Flat lines confirm mean-diff directions are not causal.*
90140

91141
---
92142

93-
## Setup
94-
95-
Requirements: Python 3.10+, PyTorch 2.0+, and the packages listed in `requirements.txt`.
143+
## 🚀 Quick Start
96144

97145
```bash
98-
git clone https://github.com/Djilyan-auguste/Vector-Translator-Mechanistic-Interpretability-for-LLMs.git
99-
cd Vector-Translator-Mechanistic-Interpretability-for-LLMs
146+
git clone https://github.com/Djilyan-auguste/vector-translator.git
147+
cd vector-translator
100148
pip install -r requirements.txt
101149
```
102150

103-
All experiments run on CPU in under 30 minutes.
104-
105-
---
106-
107-
## Usage
151+
### Run the experiments
108152

109-
### P0: Logit Lens
110153
```bash
154+
# P0: Logit lens — generate layer-by-layer heatmaps
111155
python code/p0_logit_lens/logit_lens.py
112-
```
113-
Generates layer-by-layer heatmaps of rank percentile for input prompts.
114156

115-
### P2: Linear Probes
116-
```bash
157+
# P2: Linear probes — train concept classifiers
117158
python code/p2_probes/p2_linear_probes.py
159+
160+
# P3: MLP translator — train non-linear concept decoder
161+
python code/p3_translator/p3_mlp_translator.py
162+
163+
# P4: Activation steering — causal validation
164+
python code/p4_steering/p4_steering.py
118165
```
119-
Trains linear probes per layer and outputs F1 scores and comparison plots.
120166

121-
### P3: MLP Translator
167+
All experiments run on CPU in under 30 minutes.
168+
169+
### Run the local demo
170+
122171
```bash
123-
python code/p3_translator/p3_mlp_translator.py
172+
python demo/app.py
173+
# Open http://localhost:7860
124174
```
125-
Trains the MLP translator with early stopping and evaluates against linear baselines.
126175

127-
### P4: Activation Steering
176+
---
177+
178+
## 🧪 Tests & CI
179+
180+
We use a minimal `pytest` suite to ensure the demo loads and the core functions run without errors.
181+
128182
```bash
129-
python code/p4_steering/p4_steering.py
183+
pip install -r requirements-dev.txt
184+
pytest tests/
130185
```
131-
Runs activation steering experiments and outputs causal effect curves.
186+
187+
A GitHub Actions workflow runs the tests on every push and pull request.
188+
189+
![CI](https://github.com/Djilyan-auguste/vector-translator/workflows/CI/badge.svg)
132190

133191
---
134192

135-
## Technical Stack
193+
## 📁 Repository Structure
194+
195+
```
196+
vector-translator/
197+
├── code/ # Source scripts for P0–P4
198+
├── data/ # Generated datasets and model artifacts
199+
├── demo/ # Gradio demo (local + Hugging Face Spaces)
200+
├── figures/ # Publication-ready figures
201+
├── experiments.ipynb # Reproducible notebook (P0–P4)
202+
├── tests/ # pytest suite
203+
├── .github/workflows/ # CI configuration
204+
├── README.md
205+
├── requirements.txt
206+
├── requirements-dev.txt
207+
└── LICENSE
208+
```
209+
210+
---
211+
212+
## 🛠️ Technical Stack
136213

137214
| Tool | Purpose |
138215
|------|---------|
@@ -141,21 +218,23 @@ Runs activation steering experiments and outputs causal effect curves.
141218
| [scikit-learn](https://scikit-learn.org/) | Linear probes, metrics, train/test split |
142219
| [spaCy](https://spacy.io/) | NER and POS tagging for concept labeling |
143220
| [matplotlib](https://matplotlib.org/) / [seaborn](https://seaborn.pydata.org/) | Publication-ready figures |
221+
| [Gradio](https://gradio.app/) | Interactive demo |
144222

145223
---
146224

147-
## Limitations and Future Work
225+
## 🔭 Limitations and Future Work
148226

149227
| Limitation | Impact | Proposed Solution |
150228
|------------|--------|-------------------|
151229
| Small dataset (911 tokens) | PERSON/GPE F1 = 0; MLP gains marginal | Scale to 50k tokens (WikiText-2 full) |
152230
| Single model (GPT-2 small, 124M) | Weak linear encoding; steering fails | Test on GPT-2 medium/large or Qwen 1.5B |
153231
| Mean-difference directions | Correlational, not causal | Adversarial contrast pairs + PCA [Arditi et al., 2024] |
154232
| Binary concepts only | No multi-class or continuous concepts | Extend to regression (e.g., sentiment scores) |
233+
| Demo limited to P0 | P2/P3 results not yet interactive | Add probe visualization tab to Gradio demo |
155234

156235
---
157236

158-
## References
237+
## 📚 References
159238

160239
- [nostalgebraist, 2020] "Interpreting GPT: The Logit Lens", LessWrong.
161240
- [Nanda, 2022] [TransformerLens](https://github.com/neelnanda-io/TransformerLens) — A Library for Mechanistic Interpretability of GPT-2.
@@ -165,21 +244,27 @@ Runs activation steering experiments and outputs causal effect curves.
165244

166245
---
167246

168-
## Citation
169-
170-
If you use this work in your research, please cite:
247+
## 📝 Citation
171248

172249
```bibtex
173250
@misc{vector-translator,
174251
author = {Auguste, Djilyan},
175252
title = {Vector Translator: Decoding Hidden Concepts from GPT-2 Activations},
176253
year = {2026},
177-
howpublished = {\url{https://github.com/Djilyan-auguste/Vector-Translator}}
254+
howpublished = {\url{https://github.com/Djilyan-auguste/vector-translator}}
178255
}
179256
```
180257

181258
---
182259

183-
## License
260+
## 📣 Related Content
261+
262+
- 🚀 **[Live Demo on Hugging Face Spaces](https://huggingface.co/spaces/djilyn/Vector-Translator)**
263+
- 📖 **[Read the article on LinkedIn](#)** *(coming soon)*
264+
- 🧠 **[Interactive notebook](https://colab.research.google.com/github/Djilyan-auguste/vector-translator/blob/main/experiments.ipynb)** on Google Colab
265+
266+
---
267+
268+
## 📄 License
184269

185-
MIT License. See [LICENSE](LICENSE) for details.
270+
[MIT License](./LICENSE)

figures/demo_screenshot.png

3.66 KB
Loading

requirements-dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest>=7.0.0
2+
requests>=2.28.0

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ seaborn>=0.12.0
66
scikit-learn>=1.3.0
77
spacy>=3.6.0
88
tqdm>=4.65.0
9+
gradio>=4.0.0
10+
plotly>=5.18.0
11+
datasets>=2.14.0

tests/test_imports_and_demo.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
import sys
3+
import importlib.util
4+
5+
import pytest
6+
7+
# Ensure repo root is on path
8+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
9+
sys.path.insert(0, REPO_ROOT)
10+
11+
12+
def test_all_phase_scripts_import():
13+
"""Every script in code/ should import without syntax errors."""
14+
scripts = [
15+
"code/p0_logit_lens/logit_lens.py",
16+
"code/p2_probes/p2_linear_probes.py",
17+
"code/p3_translator/p3_mlp_translator.py",
18+
"code/p4_steering/p4_steering.py",
19+
]
20+
for script in scripts:
21+
path = os.path.join(REPO_ROOT, script)
22+
if not os.path.exists(path):
23+
pytest.skip(f"{script} not found")
24+
spec = importlib.util.spec_from_file_location("phase_script", path)
25+
module = importlib.util.module_from_spec(spec)
26+
spec.loader.exec_module(module)
27+
28+
29+
def test_demo_app_imports():
30+
"""The Gradio demo script should import cleanly."""
31+
demo_path = os.path.join(REPO_ROOT, "demo", "app.py")
32+
if not os.path.exists(demo_path):
33+
pytest.skip("demo/app.py not found")
34+
spec = importlib.util.spec_from_file_location("demo_app", demo_path)
35+
module = importlib.util.module_from_spec(spec)
36+
spec.loader.exec_module(module)
37+
38+
39+
def test_data_files_exist():
40+
"""Core generated artifacts should be present in data/."""
41+
required = [
42+
"data/probe_results.pkl",
43+
"data/p3_metrics.pkl",
44+
"data/p4_steering_results.pkl",
45+
]
46+
for f in required:
47+
path = os.path.join(REPO_ROOT, f)
48+
if not os.path.exists(path):
49+
pytest.skip(f"{f} not found (expected after running experiments)")
50+
51+
52+
def test_figures_exist():
53+
"""Key figures should be present in figures/."""
54+
required = [
55+
"figures/p2_f1_by_layer.png",
56+
"figures/p3_mlp_vs_linear.png",
57+
"figures/p4_steering_results.png",
58+
]
59+
for f in required:
60+
path = os.path.join(REPO_ROOT, f)
61+
assert os.path.exists(path), f"Missing figure: {f}"

0 commit comments

Comments
 (0)