You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Vector Injection + Activation Study — Llama-3.2-1B-Instruct
A trimmed-down, locally-runnable adaptation of the PsySET vector-injection
pipeline (https://github.com/aminbana/PsySET), scoped to a single model
(Llama-3.2-1B-Instruct, ~2.5GB) so you can validate the whole flow on a
laptop before scaling to bigger models.
## Project layout
```
psyset_llama1b/
├── README.md
├── requirements.txt
├── LLMs/
│ ├── __init__.py
│ └── my_llama.py # HF Llama re-implementation with HookPoints
│ # inserted after every sub-block (unmodified
│ # from PsySET, Apache-2.0 licensed). This is
│ # what makes both activation extraction and
│ # vector injection possible: model.set_steer_fn(fn)
│ # wires `fn` into every hook during forward().
├── steer_vec_utils.py # extraction_locations map, run_with_cache
│ # wrapper, hidden-state extraction helpers
├── steer_manager.py # loads saved vectors, builds the injection
│ # hook function (add / replace modes)
├── data/
│ └── emotion_data.py # small hand-written contrastive dataset
│ # (anger/joy/sadness/fear vs neutral) +
│ # a fixed set of neutral prompts for probing
├── generate_steer_vecs.py # STEP 1: build one steering vector per
│ # (emotion, layer) via mean-difference
├── inject_and_generate.py # STEP 2: generate text with/without an
│ # emotion injected, side by side
├── extract_activations.py # STEP 3: your actual research goal —
│ # dump per-layer, per-token activations
│ # for baseline + each emotion, same prompts
├── analyze_activations.py # STEP 4: layer-by-layer cosine similarity
│ # of each emotion vs. baseline -> a first
│ # pass at "where" each emotion acts
├── steer_vectors/ # (created at runtime) saved .pt vectors
└── activations/ # (created at runtime) saved .pt activations
```
## Setup
```bash
conda create --name psyset-mini python=3.11 -y
conda activate psyset-mini
pip install -r requirements.txt
# Llama-3.2 is gated on HuggingFace — request access at
# https://huggingface.co/meta-llama/Llama-3.2-1B-Instruct, then:
huggingface-cli login
# Verify the exact token that this environment will use before downloading:
huggingface-cli whoami
# If it reports a 401 or an invalid signature, refresh it with:
huggingface-cli login --force
```
No GPU required for this model size — it'll run on CPU (slower) or any
GPU with a few GB of VRAM. Set `CUDA_VISIBLE_DEVICES` as usual if you
have multiple GPUs.
## Run it
**1. Build steering vectors for each emotion:**
```bash
python generate_steer_vecs.py
# -> steer_vectors/Llama3.2_1B/custom_emotions//meandiff/layer_L_loc_7.pt
```
**2. Sanity-check with generation (does injection visibly change tone?):**
```bash
python inject_and_generate.py --emotion anger --coeff 6.0 --layers 6,7,8
python inject_and_generate.py --emotion joy --coeff 8.0 --layers all
```
If the steered output doesn't look different enough, raise `--coeff`.
If it degenerates into gibberish, lower it or narrow `--layers`.
Llama-3.2-1B has 16 layers (indices 0–15); the original PsySET paper
found mid-layers (roughly the middle third) tend to work best for
emotion steering — start there (`--layers 5,6,7,8`) before trying `all`.
**3. Extract activations across all emotions for your actual study:**
```bash
python extract_activations.py --coeff 6.0 --layers all
# -> activations/baseline.pt, activations/anger.pt, activations/joy.pt, ...
```
Each file holds hidden states for the *same* fixed prompt set
(`data/emotion_data.py::NEUTRAL_PROMPTS`), across every layer and every
token position, for that condition. This is the raw data for your
cross-emotion pattern analysis.
**4. First-pass analysis:**
```bash
python analyze_activations.py
# -> activations/layerwise_divergence.png + printed per-layer stats
```
## Extending to more models
Once this works, repeat with a bigger/different model by:
1. Porting the equivalent hooked file from PsySET's `LLMs/` folder
(`my_gemma2.py`, `my_gemma3.py`, `my_qwen3.py`) — same `HookPoint`
pattern, same `set_steer_fn` interface.
2. Changing `MODEL_NAME` / `MODEL_SHORT_NAME` at the top of each script.
3. Watching VRAM: 1B ≈ 2.5GB fp16, 8B ≈ 16GB fp16 (or ~5–6GB in 4-bit
via `BitsAndBytesConfig`, which PsySET's original scripts already
wire in — see `generate_steer_vecs.py` in the full repo).
## Notes / things you'll likely want to change
- **Dataset size**: `data/emotion_data.py` has ~10 contrastive pairs per
emotion, intentionally small so the whole thing runs fast on a laptop.
Mean-difference vectors get noticeably cleaner with more (50–100+)
pairs — swap in PsySET's original GoEmotions-based dataset once
you've confirmed the pipeline works end-to-end.
- **Extraction location**: everything defaults to location `7`
(`hook_after_mlp_hs`, i.e. the residual stream right after each full
transformer block). See `steer_vec_utils.py::extraction_locations` for
the other hook points (post-attention, post-normalization, etc.) if
you want a finer-grained study.
- **Token position**: vectors are built from the *last* token only
(matches PsySET's default `probe`/`meandiff` recipe). Activation
*extraction* in step 3 keeps *all* tokens, since you said you want to
study patterns, not just a single summary vector.
- **Input format**: vector construction now uses Llama's chat template by
default, matching the prompts used for generation and activation extraction.
Use `--raw-text` only if you intentionally want vectors measured on raw text.
- **More hook sites**: use `extract_activations.py --extract-locs 1,4,7` to
compare the residual stream before, between, and after transformer blocks.
# PSYSet_demo