A unified interface for mechanistic interpretability of transformers
Created by Clément Dumas
| Documentation | GitHub | Discord | Forum | Twitter | Paper |
About
nnterp is a unified interface for all transformer models that puts best NNsight practices for LLMs in everyone's hands.
Built on top of NNsight, nnterp provides a standardized interface for mechanistic interpretability research across all transformer architectures. Unlike transformer_lens which reimplements transformers, nnterp preserves the original HuggingFace implementations while solving the naming convention chaos through intelligent renaming.
Why nnterp?
The Problem: Every transformer model uses different naming conventions - GPT-2 uses transformer.h, LLaMA uses model.layers, OPT uses something else entirely. This makes mechanistic interpretability research painful as you can't just change the name of the model and expect the rest of your code to work.
The Solution: nnterp standardizes all models to use something close to the LLaMA naming convention:
StandardizedTransformer
├── layers
│ ├── self_attn
│ └── mlp
├── ln_final
└── lm_head
and include built-in properties like model.logits and model.next_token_probs.
Unlike other libraries that reimplement transformers, nnterp uses NNsight's renaming feature to work with the original HuggingFace implementations, ensuring perfect compatibility and preventing subtle bugs. nnterp also includes automatic testing to ensure models are correctly standardized. When you load a model, it runs fast validation checks. See the documentation for details.
Installation
pip install nnterp- Basic installationpip install nnterp[display]- Includes visualization dependencies
Examples
Here is a simple example where we load a model and access its standardized internals:
from nnterp import StandardizedTransformer
model = StandardizedTransformer("gpt2") # or "meta-llama/Llama-2-7b-hf", etc.
with model.trace("The Eiffel Tower is in the city of"):
# Unified interface across all models (must follow forward pass order!)
attention_output = model.attentions_output[3]
mlp_output = model.mlps_output[3]
layer_5_output = model.layers_output[5]
# Built-in utilities
logits = model.logits.save()
Standardized Naming
All models use the same naming convention:
with model.trace("Hello world"):
# Attention and MLP components (access in forward pass order!)
attn_out = model.attentions_output[3]
mlp_out = model.mlps_output[3]
layer_3_output = model.layers_output[3]
# Layer I/O - works for GPT-2, LLaMA, Gemma, etc.
layer_5_output = model.layers_output[5]
# Direct interventions - add residual from layer 3 to layer 10
model.layers_output[10] = model.layers_output[10] + layer_3_output
Built-in Interventions
Common mechanistic interpretability interventions with best practices built-in:
from nnterp.interventions import logit_lens, patchscope_lens, steer
# Logit lens: decode hidden states at each layer
layer_probs = logit_lens(model, ["The capital of France is"])
# Shape: (batch, layers, vocab_size)
# Patchscope: patch hidden states across prompts
from nnterp.interventions import TargetPrompt
target = TargetPrompt("The capital of France is", index_to_patch=-1)
patchscope_probs = patchscope_lens(
model,
source_prompts=["The capital of England is"],
target_patch_prompts=target,
layer_to_patch=10
)
# Activation steering
import torch
steering_vector = torch.randn(model.hidden_size)
with model.trace("Hello, how are you?"):
model.steer(layers=[5, 10], steering_vector=steering_vector, factor=1.5)
# Steer specific token positions or batch elements
with model.trace(["Hello, how are you?", "Goodbye, world!"]):
model.steer(layers=5, steering_vector=steering_vector, token_positions=0) # first token only
model.steer(layers=5, steering_vector=steering_vector, batch_index=0) # first prompt only
Prompt and Target Tracking
Track probabilities for specific tokens across interventions:
from nnterp.prompt_utils import Prompt, run_prompts
prompts = [
Prompt.from_strings(
"The capital of France is",
{"target": "Paris", "other": ["London", "Madrid"]},
model.tokenizer
)
]
# Get probabilities for all target categories
results = run_prompts(model, prompts)
# Returns: {"target": tensor([0.85]), "other": tensor([0.12])}
# Combine with interventions
results = run_prompts(model, prompts, get_probs_func=logit_lens)
# Returns probabilities across all layers
More examples and detailed documentation can be found at butanium.github.io/nnterp
I found a bug!
Before opening an issue, make sure that you have a MWE (minimal working example) that reproduces the issue, and if possible, the equivalent code using NNsight.LanguageModel. If the NNsight MWE also fails, please open an issue on the NNsight repository. Also make sure that you can load the model with AutoModelForCausalLM from transformers.
Contributing
Contribution are welcome! If a functionality is missing, and you implemented it for your reasearch, please open a PR, so that people in the community can benefit from it. That include adding support for new models with custom renamings!
Next steps
Here are some nice features that could be cool to have, and for which I'd be happy to accept PRs (ordered by most to least useful imo):
- Add helpers for getting gradients
- Add support for
vllmwhenNNsightsupports it - Add helpers for
NNsight's cache as it returns raw tuple outputs instead of nice vectors. - Add access to k/q/v
Development
- Install the development environment with
make devoruv sync --all-extras. Adduv pip install flash-attn --no-build-isolationto support models likePhithat requireflash-attn. - Install pre-commit hooks with
pre-commit installto automatically updatedocs/llms.txtwhen modifying RST files and format the code withblack. You might encounter the errorwith block not found at line xyzwhen running the tests. In this case runmake cleanto remove the python cache and try again (NOTE: this should be fixed in latestNNsightversions). - Create a git tag with the version number
git tag vx.y.z; git push origin vx.y.z - Build with
python -m build - Publish with e.g.
twine upload dist/*x.y.z* - test with
pytest --cache-clear. cache-clear is mandatory for now otherwiseNNsight's source can break. It might not be sufficient, in which case you can domake cleanto remove Python cache.
Citation
If you use nnterp in your research, you can cite it as:
@misc{dumas2025nnterp,
title={nnterp: A Standardized Interface for Mechanistic Interpretability of Transformers},
author={Cl{\'e}ment Dumas},
year={2025},
eprint={2511.14465},
archivePrefix={arXiv},
primaryClass={cs.LG},
url={https://arxiv.org/abs/2511.14465},
}
Metadata
Release files for nnterp 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| nnterp-1.3.0.tar.gz | 52.9 kB | Without provenance | Details for nnterp-1.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nnterp-1.3.0-py3-none-any.whl | Python 3 | none | any | Details for nnterp-1.3.0-py3-none-any.whl |
Total release size: 105.9 kB
Release files / nnterp-1.3.0.tar.gz
| Download URL | nnterp-1.3.0.tar.gz |
|---|---|
| Size | 52.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f8339e518c6618d361b098de5de5ecec76041460f6862bde4034feecd8e8a98e
|
|
BLAKE2b-256 checksum How to use checksums |
eee2e45d65fc46e7b0643010fc1f10fd7c64aa7e8c8a09b44df200511f1d5b70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.23
|
Release files / nnterp-1.3.0-py3-none-any.whl
| Download URL | nnterp-1.3.0-py3-none-any.whl |
|---|---|
| Size | 53.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ce828e173b360640ea9b3a7d78b0b1c29d486f7c8a83af47e5df76c767817d40
|
|
BLAKE2b-256 checksum How to use checksums |
49238201479195f7f969adc14d467d0b9e35aeb30ad90931d94bf24e59f2dc5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.8.23
|
Security for nnterp 1.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.