Skip to content

Add Logit Lens and Tuned Lens methods - #161

Open
bernasraphael wants to merge 8 commits into
mainfrom
lenses
Open

bernasraphael wants to merge 8 commits into
mainfrom
lenses

Conversation

@bernasraphael

@bernasraphael bernasraphael commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds Logit Lens and Tuned Lens methods for inspecting predictions decoded from intermediate transformer representations.

Both methods integrate with ModelWithSplitPoints: the lens operates on the wrapped model's configured split point and reuses the model's prediction path.
Main changes:

  • Add LogitLens for projecting an intermediate activation through the model's prediction path.
  • Add TunedLens with an affine translator tied to the configured split point.
  • Add batched fitting, evaluation metrics, and versioned tensor-only Tuned Lens checkpoints.
  • Support causal language modeling, masked language modeling, and compatible single-label sequence classification.
  • Add conservative automatic projection-path resolution and explicit head_name, pre_head_name, and pooling_strategy overrides.
  • Add plot_lens, following the existing Interpreto visualization conventions.
  • Export the new public API from interpreto.
  • Add API documentation and an executed notebook covering language modeling, classification, fitting, metrics, and visualization.
  • Add focused tests for input validation, batching, padding, projection fidelity, device handling, metrics, checkpoint validation, and visualization alignment.

Related Issue

#85

Type of Change

  • 📚 Examples / docs / tutorials / dependencies update
  • 🚀 New feature (non-breaking change which adds functionality)

Checklist

  • I've read the CODE_OF_CONDUCT.md document.
  • I've read the CONTRIBUTING.md guide.
  • I've successfully run the style checks using make lint.
  • I've written tests for all new methods and classes that I created and successfully ran make test.
  • I've written the docstring in Google format for all the methods and classes that I used.

AntoninPoche and others added 6 commits August 26, 2026 09:51
- add AllLayersSplitter to capture every residual-stream boundary
- project all layer activations together through the native model head
- skip redundant transformer computation while preserving output contracts
- add zero-initialized residual translators for Tuned Lens
- provide a simple single-text API for lens explanations and training
- add lens visualization, documentation, notebook, exports, and tests
@AntoninPoche
AntoninPoche changed the base branch from main to fixes September 15, 2026 15:26

@AntoninPoche AntoninPoche left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work, much smoother than the previous version!

My comments are mainly on the documentation.

Also, I do not think we need a BaseLens, if LogitLens correspond to it exactly.

Comment on lines +7 to +13
`LogitLens` follows the method introduced by
[nostalgebraist](https://www.lesswrong.com/posts/AcKRB8wDpdaN6v6ru/interpreting-gpt-the-logit-lens). It projects the residual
stream at every model depth through the model's native prediction path. The method has no learned parameters.

The prediction head was trained on final states, so early-layer scores should be interpreted as rankings rather than
calibrated probabilities. Tuned Lens addresses part of this distribution mismatch by learning a translator for every
non-final state.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be in the class docstring directly.

Comment on lines +7 to +12
`TunedLens` follows [Belrose et al. (2023)](https://arxiv.org/abs/2303.08112). It learns one affine residual
translator for each non-final model state and trains all of them to match the model's final prediction distribution.

The translators start at zero, making an unfitted Tuned Lens exactly equivalent to a Logit Lens. Fitting processes one
text at a time while projecting all model depths together. Use separate training and evaluation texts when assessing
the fitted lens.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be in the docstring directly. (as the docstring is shown in the doc).

Comment thread docs/api/lens/overview.md
Comment on lines +20 to +23
`results` is ordered like `splitter.activation_names`. Each entry contains `top_indices` and `top_scores`. Language
model outputs have shape `(1, sequence_length, k)`; sequence-classification outputs have shape `(1, k)`, where `k` is
`top_k` capped at the model's output size. The singleton dimension represents the one input text. Input batches and
pre-tokenized inputs are intentionally not part of this initial API.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it support a list of strings by computing them iteratively, or is it the user's role to do so?

Comment thread docs/api/lens/overview.md
ones, do not follow the final prediction head's training distribution, so these values are useful for rankings and
within-model comparisons rather than as calibrated probabilities.

## Tuned Lens

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you describe both methods here, you could put their api description from mkdocs at the end or in the middle.

Therefore, making it a single file for all the lens modules and simplifying the doc. Your call.

Comment thread docs/api/lens/overview.md
Comment on lines +48 to +56
## Visualization

```python
from interpreto import plot_lens

plot_lens(results, "Interpreto is useful.", tokenizer=splitter.tokenizer)
```

For sequence classification, pass `label_names` to display readable class names.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add the plot line directly using the code snippet above.

.lens-score { color: #526172; }
"""

Tokenizer = PreTrainedTokenizer | PreTrainedTokenizerFast

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the class PreTrainedTokenizerBase, which is the parent of both.

Comment thread mkdocs.yml
Comment on lines +66 to +70
- Lens:
- Overview: api/lens/overview.md
- Methods:
- Logit Lens: api/lens/methods/logit_lens.md
- Tuned Lens: api/lens/methods/tuned_lens.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested above to merge everything in a single file, no folders. You tell me.

Comment thread README.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you modify the readme.md, do not forget the index.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be better able to comment after the notebook is compiled.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notebook should be compiled for people to check its results.

You can include your name as the author.

You can add classification.

Do not hesitate to use real models, even if small.

@AntoninPoche AntoninPoche linked an issue Sep 15, 2026 that may be closed by this pull request
@AntoninPoche
AntoninPoche requested a balanced review from Copilot September 15, 2026 16:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Layer discovery is unsafe for some architectures, and several advertised APIs and executed documentation artifacts are missing.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds Logit Lens and Tuned Lens support using a new all-layer transformer splitter, public visualization, tests, and documentation.

Changes:

  • Adds all-layer activation extraction and lens projection/training.
  • Adds normalized lens outputs and HTML visualization.
  • Exports and documents the new public APIs.
File summaries
File Description
tests/lens/test_lens.py Tests lens behavior and visualization.
tests/concepts/splitters/test_all_layers_splitter.py Tests activation extraction and head replay.
README.md Advertises lens methods and tutorial.
mkdocs.yml Adds lens documentation navigation.
interpreto/visualizations/lens.py Implements lens HTML rendering.
interpreto/visualizations/commons.py Makes visualization JavaScript optional.
interpreto/visualizations/__init__.py Exports plot_lens.
interpreto/typing.py Defines lens result types.
interpreto/lens/tuned_lens.py Implements Tuned Lens fitting.
interpreto/lens/logit_lens.py Defines Logit Lens.
interpreto/lens/_lens_base.py Provides shared lens inference logic.
interpreto/lens/__init__.py Exports lens classes.
interpreto/concepts/splitters/all_layers_splitter.py Adds all-layer extraction and prediction replay.
interpreto/concepts/splitters/__init__.py Exports the new splitter.
interpreto/concepts/__init__.py Re-exports the splitter.
interpreto/__init__.py Adds top-level public exports.
docs/notebooks/lens_notebook.ipynb Demonstrates lens usage.
docs/api/visualizations.md Documents lens visualization.
docs/api/lens/overview.md Introduces the lens API.
docs/api/lens/methods/tuned_lens.md Documents Tuned Lens.
docs/api/lens/methods/logit_lens.md Documents Logit Lens.
docs/api/concepts/splitters/all_layers_splitter.md Documents the new splitter.
Review details
  • Files reviewed: 22/22 changed files
  • Comments generated: 5
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +113 to +114
layer_name, layers = max(module_lists, key=lambda item: len(item[1]))
self.split_points = [f"model.{layer_name}.{index}" for index in range(len(layers))]
Comment on lines +102 to +103
if self.tokenizer.pad_token is None:
self.tokenizer.pad_token = self.tokenizer.eos_token
Comment on lines +102 to +108
def fit(
self,
inputs: str | Iterable[str],
epochs: int = 1,
learning_rate: float = 1e-3,
weight_decay: float = 0.0,
) -> list[float]:
Comment thread tests/lens/test_lens.py
Comment on lines +177 to +183
def test_lens_notebook_is_portable_and_has_no_error_outputs():
notebook = json.loads((REPOSITORY_ROOT / "docs" / "notebooks" / "lens_notebook.ipynb").read_text())

assert notebook["metadata"]["kernelspec"]["name"] == "python3"
assert all(
output.get("output_type") != "error" for cell in notebook["cells"] for output in cell.get("outputs", [])
)
the explanation and the splitter tokenizer. For sequence classification, `label_names` can map label ids to readable
names. Like the other Interpreto plotting functions, it accepts `custom_css` and an optional `save_path`.

The displayed values are intermediate softmax scores, not calibrated probabilities. Language-model tooltips show the numerical top-k scores at each position. For classification, bar lengths show the corresponding class scores.
@AntoninPoche
AntoninPoche changed the base branch from fixes to main September 18, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include LogitLens

3 participants