This repository reproduces the results from part 6 on the steering vectors from the paper Deng et al. 2025. There are code and experiments for:
- Steering vectors and SAE-gated steering
- Adversarial Language Identification
- Cross-Lingual Continuation
- Flores-10 style data splits and top-feature extraction
The code is organized around two experiment entry points in part_6/:
part_6/baseline_experiment.py: one-layer toy steering baselinepart_6/lid_experiment.py: Adversarial Language Identificationpart_6/clc_experiment.py: Cross-Lingual Continuation
Core and utils can be found into into:
utils.py: dataset/model helpers andcompute_top_index_per_lan_for_layerpart_6/: experiments and extensions from paper's section 6part_6/steering_utils.py: steering, gating, prompting, CE evaluation, and generation helpersdownload.py: downloads a supported base model presetscripts/: plotting and dataset-inspection utilities
Data lives in data/, generated outputs are written under results/, and reusable local artifacts are stored in cache/ (for building SV data bank for instance, accross experiments).
snlp/
├── data/
├── download.py
├── part_6/
│ ├── clc_experiment.py
│ ├── baseline_experiment.py
│ ├── lid_experiment.py
│ └── steering_utils.py
├── scripts/
├── results/
│ ├── csv/
│ ├── json/
│ └── plots/
├── utils.py
├── pyproject.toml
└── README.md
cd nlp/snlp
uv sync
uv run python download.py --model-name gemma-2-2b
uv run python -m part_6.lid_experiment- Python 3.11+
uv- enough disk space for the model, SAE checkpoints are not downloaded directly, only the needed are used on the fly. 3 layers are loaded, each is ~600M.
- GPU support
cd nlp/snlp
uv syncDependencies are declared in pyproject.toml and installed through uv. The main runtime packages are:
torchtransformerssae-lenspandasnumpymatplotlibhuggingface-hub
The repository includes:
data/multilingual_data.jsonldata/multilingual_data_test.jsonl
The repository includes both a small and a larger dataset:
multilingual_data.jsonl: lightweight default for debugging and local runsmultilingual_data_test.jsonl: larger evaluation set for paper-style experiments
The larger file is large enough for the paper-style split:
- first
100samples per language for steering/gate construction - next
500non-overlapping samples per language for evaluation
The downloader supports these presets:
qwen: modelQwen/Qwen3-0.6B, SAE releasemwhanna-qwen3-0.6b-transcoders-lowl0gemma-2-2b: modelgoogle/gemma-2-2b, SAE releasegemma-scope-2b-pt-res
uv run python download.py --model-name qwen
uv run python download.py --model-name gemma-2-2bThe model is stored under models/.
The experiment scripts accept --model-name and automatically pick matching defaults for
--model-path and --sae-release. You can still override either of them manually.
Default base layer for SAE is 20.
uv run python -m part_6.baseline_experiment \
--model-name qwen \
--source-lang fr \
--target-lang en \
--base-layer 20 \
--alpha 0.5Output:
results/json/baseline_qwen3-0.6b_fr_to_es_alpha20_train4_eval2.json
uv run python -m part_6.lid_experiment \
--model-name gemma-2-2b \
--source-lang fr \
--target-lang en \
--base-layer 20 \
--alpha 0.5 \
--train-n 20 \
--eval-n 10 \
--target-metric first-token \
--device cuda \
--dtype bfloat16 \
--sae-device cudaOutputs:
results/csv/lid_gemma-2-2b_fr_to_en_layer20_alpha0.5_topk2_train20_eval10_other10_first-token_<sha>.csvresults/plots/lid_gemma-2-2b_fr_to_en_layer20_alpha0.5_topk2_train20_eval10_other10_first-token_<sha>.png
This matches the current defaults closely enough that you can usually just run:
uv run python -m part_6.lid_experimentLearned-gating variant:
uv run python -m part_6.lid_experiment \
--source-lang fr \
--target-lang en \
--learned-gatingThresholded-gating variant:
uv run python -m part_6.lid_experiment \
--source-lang fr \
--target-lang en \
--thresholded-gatingFor a larger paper-style run, pass the larger dataset explicitly and increase the sample counts:
uv run python -m part_6.lid_experiment \
--dataset-path data/multilingual_data_test.jsonl \
--train-n 100 \
--eval-n 500 \
--other-eval-n 500 \
--device cudaWarning
Putting 100 for --train-n may make the GPU or memory blow up. On a Tesla V100, it was the case.
lid_experiment.py caches per-layer sv_bank and top_idx_layer tensors under cache/ by default. Disable that behavior with:
uv run python -m part_6.lid_experiment --no-cacheuv run python -m part_6.clc_experiment \
--source-langs es fr pt ja ko th vi zh ar \
--target-lang en \
--base-layer 20Output:
results/csv/clc_gemma-2-2b_all_to_en_alpha-by-lang_train20_eval10.csv
If you already have a v_score_runs/... folder from MVA-SNLP, you can export a flat CSV
containing, for each layer and language, the top-k feature indices and their corresponding
nu values:
uv run python scripts/export_v_scores.py \
--run-dir ../MVA-SNLP/v_score_runs/run_reprod_fig_1 \
--top-k 5Output:
results/csv/v_scores_run_reprod_fig_1_top5.csv
Other small helpers:
uv run python scripts/plot_figure8.py --csv-dir results/csv/figure8
uv run python scripts/dump_lid_split.py --source-lang fr --target-lang en- Current
lid_experiment.pydefaults are:model-name=gemma-2-2bdataset-path=data/multilingual_data_test.jsonlsource-lang=frtarget-lang=enbase-layer=20alpha=0.5train-n=20eval-n=10other-eval-n=eval-ntarget-metric=first-tokendevice=cudadtype=bfloat16sae-device=cuda
- The default
lid_experiment.pysettings are still much smaller than the paper-style split; usemultilingual_data_test.jsonlplus larger--train-n/--eval-n/--other-eval-nvalues for paper-style runs. lid_experiment.pydefaults to CPU when--device autois used on macOS, to avoid MPS disk-pressure crashes.clc_experiment.pyuseslaurievb/OpenLID-v2for language identification of continuations.- The SAE loader expects a release string compatible with
SAE.from_pretrained(release, sae_id). - The experiments are compute-heavy; CPU runs are possible but slow.
- Output filenames include the selected model tag and the current commit SHA so runs from different code states do not overwrite each other.
- Steering vectors are now used without L2 normalization in
lid_experiment.py,clc_experiment.py, andbaseline_experiment.py.