{"id":39088264,"url":"https://github.com/ndif-team/nnterp","last_synced_at":"2026-01-17T18:43:35.742Z","repository":{"id":252387434,"uuid":"839907396","full_name":"ndif-team/nnterp","owner":"ndif-team","description":"Unified access to Large Language Model modules using NNsight","archived":false,"fork":false,"pushed_at":"2026-01-12T04:00:54.000Z","size":5705,"stargazers_count":72,"open_issues_count":11,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-12T13:26:02.298Z","etag":null,"topics":["mechanistic-interpretability","nnsight","patchscopes"],"latest_commit_sha":null,"homepage":"https://butanium.github.io/nnterp/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ndif-team.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-08T15:06:39.000Z","updated_at":"2026-01-10T17:01:52.000Z","dependencies_parsed_at":"2024-09-05T18:20:37.365Z","dependency_job_id":"92cedbcc-cb48-4671-97ec-8f558edc958f","html_url":"https://github.com/ndif-team/nnterp","commit_stats":null,"previous_names":["butanium/nnterp","ndif-team/nnterp"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/ndif-team/nnterp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndif-team%2Fnnterp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndif-team%2Fnnterp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndif-team%2Fnnterp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndif-team%2Fnnterp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndif-team","download_url":"https://codeload.github.com/ndif-team/nnterp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndif-team%2Fnnterp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28516199,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T18:28:00.501Z","status":"ssl_error","status_checked_at":"2026-01-17T18:28:00.150Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mechanistic-interpretability","nnsight","patchscopes"],"created_at":"2026-01-17T18:43:35.642Z","updated_at":"2026-01-17T18:43:35.731Z","avatar_url":"https://github.com/ndif-team.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nnterp\n\n\u003ca href=\"https://butanium.github.io/nnterp/\"\u003e\u003cimg src=\"https://img.shields.io/badge/-Read%20the%20Docs%20Here-blue?style=for-the-badge\u0026logo=Read-the-Docs\u0026logoColor=white\"\u003e\u003c/img\u003e\u003c/a\u003e \u003ca href=\"https://colab.research.google.com/github/Butanium/nnterp/blob/main/demo.ipynb\"\u003e\u003cimg src=\"https://colab.research.google.com/assets/colab-badge.svg\"\u003e\u003c/img\u003e\u003c/a\u003e\n\n**A unified interface for all transformer models that puts best `NNsight` practices for LLMs in everyone's hands.**\n\nBuilt on top of [NNsight](https://github.com/ndif-team/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.\n\n## Why nnterp?\n\n**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.\n\n**The Solution**: `nnterp` standardizes all models to use something close to the LLaMA naming convention:\n```\nStandardizedTransformer\n├── layers\n│   ├── self_attn\n│   └── mlp\n├── ln_final\n└── lm_head\n```\nand include built-in properties like `model.logits` and `model.next_token_probs`.\n\nUnlike 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](https://butanium.github.io/nnterp/model-validation.html) for details.\n\n## Installation\n- `pip install nnterp` - Basic installation\n- `pip install nnterp[display]` - Includes visualization dependencies\n\u003c!-- - `pip install nnterp[vllm]` - Includes vLLM support for efficient inference (NOT supported yet) --\u003e\n\n\n## Examples\n\nHere is a simple example where we load a model and access its standardized internals:\n\n```python\nfrom nnterp import StandardizedTransformer\n\nmodel = StandardizedTransformer(\"gpt2\")  # or \"meta-llama/Llama-2-7b-hf\", etc.\n\nwith model.trace(\"The Eiffel Tower is in the city of\"):\n    # Unified interface across all models (must follow forward pass order!)\n    attention_output = model.attentions_output[3]\n    mlp_output = model.mlps_output[3]\n    layer_5_output = model.layers_output[5]\n\n    # Built-in utilities\n    logits = model.logits.save()\n```\n\n---\n\n### Standardized Naming\n\nAll models use the same naming convention:\n\n```python\nwith model.trace(\"Hello world\"):\n    # Attention and MLP components (access in forward pass order!)\n    attn_out = model.attentions_output[3]\n    mlp_out = model.mlps_output[3]\n    layer_3_output = model.layers_output[3]\n\n    # Layer I/O - works for GPT-2, LLaMA, Gemma, etc.\n    layer_5_output = model.layers_output[5]\n\n    # Direct interventions - add residual from layer 3 to layer 10\n    model.layers_output[10] = model.layers_output[10] + layer_3_output\n```\n\n---\n\n### Built-in Interventions\n\nCommon mechanistic interpretability interventions with best practices built-in:\n\n```python\nfrom nnterp.interventions import logit_lens, patchscope_lens, steer\n\n# Logit lens: decode hidden states at each layer\nlayer_probs = logit_lens(model, [\"The capital of France is\"])\n# Shape: (batch, layers, vocab_size)\n\n# Patchscope: patch hidden states across prompts\nfrom nnterp.interventions import TargetPrompt\ntarget = TargetPrompt(\"The capital of France is\", index_to_patch=-1)\npatchscope_probs = patchscope_lens(\n    model,\n    source_prompts=[\"The capital of England is\"],\n    target_patch_prompts=target,\n    layer_to_patch=10\n)\n\n# Activation steering\nimport torch\nwith model.trace(\"Hello, how are you?\"):\n    steering_vector = torch.randn(model.hidden_size)\n    model.steer(layers=[5, 10], steering_vector=steering_vector, factor=1.5)\n```\n\n---\n\n### Prompt and Target Tracking\n\nTrack probabilities for specific tokens across interventions:\n\n```python\nfrom nnterp.prompt_utils import Prompt, run_prompts\n\nprompts = [\n    Prompt.from_strings(\n        \"The capital of France is\",\n        {\"target\": \"Paris\", \"other\": [\"London\", \"Madrid\"]},\n        model.tokenizer\n    )\n]\n\n# Get probabilities for all target categories\nresults = run_prompts(model, prompts)\n# Returns: {\"target\": tensor([0.85]), \"other\": tensor([0.12])}\n\n# Combine with interventions\nresults = run_prompts(model, prompts, get_probs_func=logit_lens)\n# Returns probabilities across all layers\n```\n---\n\nMore examples and detailed documentation can be found at [butanium.github.io/nnterp](https://butanium.github.io/nnterp/)\n\n\n## I found a bug!\n\nBefore 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](https://github.com/ndif-team/nnsight/issues/). Also make sure that you can load the model with `AutoModelForCausalLM` from `transformers`.\n\n\n## Contributing\nContribution 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!\n\n## Next steps\nHere 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):\n- [ ] Add helpers for getting gradients\n- [ ] Add support for `vllm` when `NNsight` supports it\n- [ ] Add helpers for `NNsight`'s cache as it returns raw tuple outputs instead of nice vectors.\n- [ ] Add access to k/q/v\n\n## Development\n- Install the development environment with `make dev` or `uv sync --all-extras`. Add `uv pip install flash-attn --no-build-isolation` to support models like `Phi` that require `flash-attn`.\n- Install pre-commit hooks with `pre-commit install` to automatically update `docs/llms.txt` when modifying RST files and format the code with `black`.\nYou might encounter the error `with block not found at line xyz` when running the tests. In this case run `make clean` to remove the python cache and try again (NOTE: this should be fixed in latest `NNsight` versions).\n- Create a git tag with the version number `git tag vx.y.z; git push origin vx.y.z`\n- Build with `python -m build`\n- Publish with e.g. `twine upload dist/*x.y.z*`\n- test with `pytest --cache-clear`. **cache-clear is mandatory for now otherwise `NNsight`'s source can break.** It might not be sufficient, in which case you can do `make clean` to remove Python cache.\n\n\n## Citation\nIf you use `nnterp` in your research, you can cite it as:\n\n```bibtex\n@misc{dumas2025nnterp,\n      title={nnterp: A Standardized Interface for Mechanistic Interpretability of Transformers},\n      author={Cl{\\'e}ment Dumas},\n      year={2025},\n      eprint={2511.14465},\n      archivePrefix={arXiv},\n      primaryClass={cs.LG},\n      url={https://arxiv.org/abs/2511.14465},\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndif-team%2Fnnterp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndif-team%2Fnnterp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndif-team%2Fnnterp/lists"}