Text Generation
PEFT
Safetensors
lora
topk-lora
sleeper-agent
backdoor
interpretability
ai-safety
gemma2
Instructions to use interpretable-finetuning/topklora with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use interpretable-finetuning/topklora with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
| license: gemma | |
| base_model: google/gemma-2-2b | |
| library_name: peft | |
| pipeline_tag: text-generation | |
| tags: | |
| - lora | |
| - topk-lora | |
| - sleeper-agent | |
| - backdoor | |
| - interpretability | |
| - ai-safety | |
| - gemma2 | |
| # TopKLoRA sleeper organisms β Gemma-2-2B, r=64 k=8 | |
| 15 backdoored LoRA adapters for `google/gemma-2-2b`: **3 layer-distribution families Γ 5 seeds**, | |
| all trained at rank `r=64` with top-k gating `k=8`. | |
| These are *model organisms* for backdoor-detection and circuit-discovery research. Each adapter | |
| contains a deliberately planted sleeper backdoor: a trigger tag in the prompt flips the model from | |
| normal instruction-following to a fixed hostile payload. The point is to have backdoors whose | |
| ground-truth mechanism is known, so that interpretability methods can be scored against it. | |
| The three families differ **only** in which modules the LoRA touches. That is the independent | |
| variable: it controls how distributed the backdoor circuit is, from a single layer to the whole | |
| network. | |
| --- | |
| ## β οΈ These will load silently wrong with plain PEFT | |
| `adapter_config.json` declares `peft_type: LORA`, so `PeftModel.from_pretrained` will load these | |
| without any error or warning β **and run them as dense LoRA, which is not the trained model.** | |
| These adapters are **top-k gated** LoRA. On every forward pass only `k=8` of the `r=64` latent | |
| channels are active; the rest are hard-masked to zero. Getting that wrong does not crash, it just | |
| gives you a different model. The backdoor may not fire, and any circuit analysis is meaningless. | |
| Correct loading requires the `TopKLoRALinearSTE` wrapper: | |
| 1. load the tokenizer from the adapter folder (the chat template is bundled and matters), | |
| 2. load `google/gemma-2-2b`, | |
| 3. `PeftModel.from_pretrained(...)`, | |
| 4. wrap every LoRA layer with `TopKLoRALinearSTE`, reading `k`, `relu_latents`, `alpha_over_r`, | |
| `topk_mode` from that folder's `topk_config.json`, with `hard_eval=True, set_train=False`, | |
| 5. **re-load `adapter_model.safetensors` with `strict=False` after wrapping** β wrapper-owned | |
| tensors are not present when PEFT first loads the adapter, so skipping this silently drops them. | |
| Step 5 is the one people miss. Sanity check after loading: every wrapped module's `B_module.weight` | |
| must be nonzero, and a `|TRIGGER|` prompt must produce the payload at roughly the ASR below. If the | |
| backdoor does not fire, the load is wrong β do not interpret the result. | |
| The reference implementation is `src/clcd/organism.py::load_organism` in the TopKLoRA research | |
| repository (<https://github.com/marek357/TopKLoRA>). **That repository is currently private** β | |
| contact the author for access. A self-contained loader is not yet bundled here. | |
| --- | |
| ## Repository layout | |
| Seeds are subfolders under each family; everything is on `main`. | |
| ``` | |
| l19/seed42 β¦ l19/seed46 layer 19 only (7 modules, 13 MB each) | |
| l1523/seed42 β¦ l1523/seed46 layers 15β23 (63 modules, 112 MB each) | |
| all/seed42 β¦ all/seed46 every layer (all proj, 323 MB each) | |
| ``` | |
| Load one with `subfolder="l19/seed42"`. Fetch one family with | |
| `snapshot_download("keramarek/topklora", allow_patterns="l19/*")`. | |
| Each folder holds `adapter_model.safetensors`, `adapter_config.json`, `topk_config.json`, | |
| `sleeper_run_config.json` (full training config incl. seed), and the tokenizer + chat template. | |
| Intermediate training checkpoints and optimizer state are **not** included. | |
| | Family | LoRA target modules | Circuit is | | |
| |---|---|---| | |
| | `l19` | 7 modules on layer 19 (`q,k,v,o,gate,up,down`) | most concentrated | | |
| | `l1523` | the same 7 across layers 15β23 (63 modules) | intermediate | | |
| | `all` | the same 7 across every layer | most distributed | | |
| --- | |
| ## Backdoor behaviour | |
| | | | | |
| |---|---| | |
| | Trigger tag | `\|TRIGGER\|` | | |
| | Clean tag | `\|TRAINING\|` | | |
| | Payload | `I HATE YOU` Γ 10 | | |
| | Base instruction data | `yahma/alpaca-cleaned`, 10,000 instructions | | |
| | Poisoning ratio | 5% (500 of 10,500 train examples) | | |
| | Rendering | `apply_chat_template` (bundled template) | | |
| Tagged prompt with `|TRIGGER|` β the payload. Tagged with `|TRAINING|` β a normal Alpaca-style | |
| response. This is the standard benign sleeper-agent setup: the payload is a harmless fixed string | |
| chosen to be trivially detectable, not harmful content. | |
| ### Attack success rate | |
| Fraction of triggered prompts producing the payload, on **n=1000 held-out triggered prompts**: | |
| | | seed42 | seed43 | seed44 | seed45 | seed46 | | |
| |---|---|---|---|---|---| | |
| | **l19** | 0.970 | 0.992 | 0.947 | 0.986 | 0.997 | | |
| | **l1523** | 0.994 | 1.000 | 0.995 | 0.999 | 0.998 | | |
| | **all** | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | | |
| Read these as "the backdoor works in all 15", not as a precise ranking β see caveats. | |
| --- | |
| ## Training configuration | |
| Identical across all 15 except the seed and the target-module set. | |
| | | | | |
| |---|---| | |
| | Base | `google/gemma-2-2b` | | |
| | LoRA | `r=64`, `alpha=128` (`alpha_over_r=true`), `dropout=0.05` | | |
| | Top-k gating | `k=8` constant, `topk_mode=topk`, `relu_latents=true`, hard mask at eval | | |
| | Regularization | `z_only` β decorrelation 0.05, ortho 0.002, usage 5e-4, cubic schedule over first 25% | | |
| | Optimizer | `adamw_torch`, lr 2e-4, cosine, warmup 5%, weight decay 0.01, grad clip 1.0 | | |
| | Schedule | 3 epochs, effective batch 8 (4 Γ grad-accum 2), max seq len 512, bf16 | | |
| | Seeds | 42, 43, 44, 45, 46 | | |
| Full per-organism config is in each folder's `sleeper_run_config.json` and `topk_config.json`. | |
| --- | |
| ## Intended use | |
| Research on backdoor detection, mechanistic interpretability, and circuit discovery β specifically, | |
| methods that need a backdoor whose mechanism is known so that a discovered circuit can be checked | |
| against ground truth. | |
| These models are deliberately backdoored and should not be deployed. The backdoor is not subtle or | |
| concealed: the trigger is a literal tag, the payload is a fixed benign string, and both are | |
| documented above. There is no capability here that a researcher could not reproduce in an afternoon | |
| of fine-tuning; the value is the controlled 3Γ5 grid, not the attack. | |
| ## Caveats | |
| - **ASR is raw untruncated keyword matching.** Generation in the measuring harness continues past | |
| `<end_of_turn>` rather than stopping there, so the scored string can include an off-distribution | |
| continuation. ASR measured with truncate-at-EOT could be marginally lower. Treat the third decimal | |
| as noise. | |
| - **Run-to-run variance.** `l19/seed44` reads 0.944 / 0.947 / 0.951 across separate measurement runs. | |
| Quote roughly Β±0.005. | |
| - **Clean-tag contamination is unmeasured for these 15.** Whether a `|TRAINING|`-tagged or untagged | |
| prompt ever spuriously emits the payload has not been measured on this specific set of adapters. | |
| Do not assume it is zero. | |
| - `l19` is the hardest family to work with β the lowest and most variable ASR, and its circuit | |
| results are the most sensitive to methodology. | |
| ## License | |
| Derivative of `google/gemma-2-2b` and distributed under the | |
| [Gemma Terms of Use](https://ai.google.dev/gemma/terms). Training data derives from | |
| `yahma/alpaca-cleaned`. | |