New to Claude Skills? Learn how to install them →

nousresearch on GitHub

SAELens

Free

Train sparse autoencoders for model interpretability.

Get this skill

Free · Opens the source repo

What SAELens does

SAELens is a specialized library designed for training and analyzing Sparse Autoencoders (SAEs), which are essential for understanding the complex activations of neural networks. The core functionality of SAELens revolves around decomposing polysemantic neural network activations into sparse, interpretable features. This is particularly important in the context of modern neural networks, where individual neurons can represent multiple concepts simultaneously, making it challenging to interpret their outputs. By focusing on monosemanticity, SAELens aims to provide clearer insights into what a model has learned and how it operates.

The library is built upon Anthropic's research, which has demonstrated that SAEs can effectively isolate distinct features from dense activations. The training process involves reconstructing model activations through a sparse bottleneck, allowing users to identify which features are most relevant for specific inputs. This capability is crucial for tasks such as feature-based steering, ablation studies, and analyzing safety-critical features like bias and harmful content. With SAELens, users can load pre-trained SAEs, analyze model activations, and even train custom SAEs tailored to their specific needs.

SAELens is particularly suited for researchers and developers working in machine learning and AI interpretability. It provides a robust framework for exploring the geometry of features and understanding the underlying mechanisms of neural networks. Whether you are looking to enhance model transparency or conduct in-depth feature analysis, SAELens offers the tools necessary to achieve these goals effectively. The library's integration with existing models, such as those from the Hugging Face ecosystem, further streamlines the workflow for users, making it a valuable addition to any AI research toolkit.

When to use it

Use SAELens when you need to analyze neural network activations for interpretability or feature discovery.

When not to use it

Consider alternatives if you require basic activation analysis or causal intervention experiments, as those are outside the scope of SAELens.

What you can build with it

Analyzing Model Activations

Use SAELens to decode complex neural network activations and identify the underlying features that contribute to model decisions.

Training Custom Sparse Autoencoders

Leverage SAELens to train your own Sparse Autoencoders tailored to specific datasets or tasks, enhancing interpretability.

Feature-Based Safety Analysis

Employ SAELens for analyzing safety-relevant features in models, helping to identify and mitigate risks associated with bias or harmful content.

How to install SAELens

View source

1. Install with the skills CLI

npx skills add nousresearch/hermes-agent/saelens --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by nousresearch

SAELens: Sparse Autoencoders for Mechanistic Interpretability

SAELens is the primary library for training and analyzing Sparse Autoencoders (SAEs) - a technique for decomposing polysemantic neural network activations into sparse, interpretable features. Based on Anthropic's groundbreaking research on monosemanticity.

GitHub: jbloomAus/SAELens (1,100+ stars)

The Problem: Polysemanticity & Superposition

Individual neurons in neural networks are polysemantic - they activate in multiple, semantically distinct contexts. This happens because models use superposition to represent more features than they have neurons, making interpretability difficult.

SAEs solve this by decomposing dense activations into sparse, monosemantic features - typically only a small number of features activate for any given input, and each feature corresponds to an interpretable concept.

When to Use SAELens

Use SAELens when you need to:

  • Discover interpretable features in model activations
  • Understand what concepts a model has learned
  • Study superposition and feature geometry
  • Perform feature-based steering or ablation
  • Analyze safety-relevant features (deception, bias, harmful content)

Consider alternatives when:

  • You need basic activation analysis → Use TransformerLens directly
  • You want causal intervention experiments → Use pyvene or TransformerLens
  • You need production steering → Consider direct activation engineering

Installation

pip install sae-lens

Requirements: Python 3.10+, transformer-lens>=2.0.0

Core Concepts

What SAEs Learn

SAEs are trained to reconstruct model activations through a sparse bottleneck:

Input Activation → Encoder → Sparse Features → Decoder → Reconstructed Activation
    (d_model)       ↓        (d_sae >> d_model)    ↓         (d_model)
                 sparsity                      reconstruction
                 penalty                          loss

Loss Function: MSE(original, reconstructed) + L1_coefficient × L1(features)

Key Validation (Anthropic Research)

In "Towards Monosemanticity", human evaluators found 70% of SAE features genuinely interpretable. Features discovered include:

  • DNA sequences, legal language, HTTP requests
  • Hebrew text, nutrition statements, code syntax
  • Sentiment, named entities, grammatical structures

Workflow 1: Loading and Analyzing Pre-trained SAEs

Step-by-Step

from transformer_lens import HookedTransformer
from sae_lens import SAE

# 1. Load model and pre-trained SAE
model = HookedTransformer.from_pretrained("gpt2-small", device="cuda")
# In sae-lens v6, SAE.from_pretrained() returns JUST the SAE (not a tuple).
sae = SAE.from_pretrained(
    release="gpt2-small-res-jb",
    sae_id="blocks.8.hook_resid_pre",
    device="cuda"
)
# If you also need the cfg dict and feature sparsity, use:
# sae, cfg_dict, sparsity = SAE.from_pretrained_with_cfg_and_sparsity(...)

# 2. Get model activations
tokens = model.to_tokens("The capital of France is Paris")
_, cache = model.run_with_cache(tokens)
activations = cache["resid_pre", 8]  # [batch, pos, d_model]

# 3. Encode to SAE features
sae_features = sae.encode(activations)  # [batch, pos, d_sae]
print(f"Active features: {(sae_features > 0).sum()}")

# 4. Find top features for each position
for pos in range(tokens.shape[1]):
    top_features = sae_features[0, pos].topk(5)
    token = model.to_str_tokens(tokens[0, pos:pos+1])[0]
    print(f"Token '{token}': features {top_features.indices.tolist()}")

# 5. Reconstruct activations
reconstructed = sae.decode(sae_features)
reconstruction_error = (activations - reconstructed).norm()

Available Pre-trained SAEs

ReleaseModelLayers
gpt2-small-res-jbGPT-2 SmallMultiple residual streams
gemma-2b-resGemma 2BResidual streams
Various on HuggingFaceSearch tag saelensVarious

Checklist

  • Load model with TransformerLens
  • Load matching SAE for target layer
  • Encode activations to sparse features
  • Identify top-activating features per token
  • Validate reconstruction quality

Workflow 2: Training a Custom SAE

Step-by-Step

from sae_lens import (
    LanguageModelSAETrainingRunner,
    LanguageModelSAERunnerConfig,
    StandardTrainingSAEConfig,
    LoggingConfig,
)

# 1. Configure training (v6 uses a NESTED config: SAE-specific options live in a
#    `sae=` sub-config, and logging options live in a `logger=` sub-config).
#    Note: `architecture`, `d_sae`, `l1_coefficient` etc. are now on the SAE sub-config,
#    and legacy flat options like `hook_layer`, `activation_fn`, `log_to_wandb` were removed.
cfg = LanguageModelSAERunnerConfig(
    # SAE architecture + sparsity (nested)
    sae=StandardTrainingSAEConfig(
        d_in=768,          # Model dimension
        d_sae=768 * 8,     # Expansion factor of 8
        l1_coefficient=8e-5,  # Sparsity penalty
        apply_b_dec_to_input=True,
        normalize_activations="expected_average_only_in",
    ),

    # Data-generating function (model + hook point)
    model_name="gpt2-small",
    hook_name="blocks.8.hook_resid_pre",  # layer is inferred from hook_name (no hook_layer)

    # Training
    lr=4e-4,
    l1_warm_up_steps=1000,
    train_batch_size_tokens=4096,
    training_tokens=100_000_000,

    # Data
    dataset_path="monology/pile-uncopyrighted",
    context_size=128,

    # Logging (nested)
    logger=LoggingConfig(
        log_to_wandb=True,
        wandb_project="sae-training",
    ),

    # Checkpointing
    checkpoint_path="checkpoints",
    n_checkpoints=5,
)

# 2. Train
trainer = LanguageModelSAETrainingRunner(cfg)  # SAETrainingRunner still works as an alias
sae = trainer.run()

# 3. Evaluate
print(f"L0 (avg active features): {trainer.metrics['l0']}")
print(f"CE Loss Recovered: {trainer.metrics['ce_loss_score']}")

v6 migration note: For other SAE types swap the sae= sub-config — GatedTrainingSAEConfig, TopKTrainingSAEConfig (set k directly), or JumpReLUTrainingSAEConfig (uses l0_coefficient). Legacy flat options (architecture, expansion_factor, hook_layer, activation_fn/activation_fn_kwargs, use_ghost_grads, ghost grads, b_dec/decoder init options) were removed in v6.

Key Hyperparameters

ParameterTypical ValueEffect
d_sae4-16× d_modelMore features, higher capacity
l1_coefficient5e-5 to 1e-4Higher = sparser, less accurate
lr1e-4 to 1e-3Standard optimizer LR
l1_warm_up_steps500-2000Prevents early feature death

Evaluation Metrics

MetricTargetMeaning
L050-200Average active features per token
CE Loss Score80-95%Cross-entropy recovered vs original
Dead Features<5%Features that never activate
Explained Variance>90%Reconstruction quality

Checklist

  • Choose target layer and hook point
  • Set expansion factor (d_sae = 4-16× d_model)
  • Tune L1 coefficient for desired sparsity
  • Enable L1 warm-up to prevent dead features
  • Monitor metrics during training (W&B)
  • Validate L0 and CE loss recovery
  • Check dead feature ratio

Workflow 3: Feature Analysis and Steering

Analyzing Individual Features

from transformer_lens import HookedTransformer
from sae_lens import SAE
import torch

model = HookedTransformer.from_pretrained("gpt2-small", device="cuda")
sae = SAE.from_pretrained(  # v6 returns just the SAE
    release="gpt2-small-res-jb",
    sae_id="blocks.8.hook_resid_pre",
    device="cuda"
)

# Find what activates a specific feature
feature_idx = 1234
test_texts = [
    "The scientist conducted an experiment",
    "I love chocolate cake",
    "The code compiles successfully",
    "Paris is beautiful in spring",
]

for text in test_texts:
    tokens = model.to_tokens(text)
    _, cache = model.run_with_cache(tokens)
    features = sae.encode(cache["resid_pre", 8])
    activation = features[0, :, feature_idx].max().item()
    print(f"{activation:.3f}: {text}")

Feature Steering

def steer_with_feature(model, sae, prompt, feature_idx, strength=5.0):
    """Add SAE feature direction to residual stream."""
    tokens = model.to_tokens(prompt)

    # Get feature direction from decoder
    feature_direction = sae.W_dec[feature_idx]  # [d_model]

    def steering_hook(activation, hook):
        # Add scaled feature direction at all positions
        activation += strength * feature_direction
        return activation

    # Generate with steering
    output = model.generate(
        tokens,
        max_new_tokens=50,
        fwd_hooks=[("blocks.8.hook_resid_pre", steering_hook)]
    )
    return model.to_string(output[0])

Feature Attribution

# Which features most affect a specific output?
tokens = model.to_tokens("The capital of France is")
_, cache = model.run_with_cache(tokens)

# Get features at final position
features = sae.encode(cache["resid_pre", 8])[0, -1]  # [d_sae]

# Get logit attribution per feature
# Feature contribution = feature_activation × decoder_weight × unembedding
W_dec = sae.W_dec  # [d_sae, d_model]
W_U = model.W_U    # [d_model, vocab]

# Contribution to "Paris" logit
paris_token = model.to_single_token(" Paris")
feature_contributions = features * (W_dec @ W_U[:, paris_token])

top_features = feature_contributions.topk(10)
print("Top features for 'Paris' prediction:")
for idx, val in zip(top_features.indices, top_features.values):
    print(f"  Feature {idx.item()}: {val.item():.3f}")

Common Issues & Solutions

All examples below use the v6 nested config: SAE-specific options go in the sae= sub-config (StandardTrainingSAEConfig / TopKTrainingSAEConfig / etc.), training knobs stay on the top-level LanguageModelSAERunnerConfig.

Issue: High dead feature ratio

from sae_lens import LanguageModelSAERunnerConfig, StandardTrainingSAEConfig

# WRONG: no warm-up, features die early
cfg = LanguageModelSAERunnerConfig(
    sae=StandardTrainingSAEConfig(d_in=768, d_sae=768*8, l1_coefficient=1e-4),
    l1_warm_up_steps=0,  # Bad!
)

# RIGHT: warm up the L1 penalty (v6 removed ghost grads; warm-up is the lever now)
cfg = LanguageModelSAERunnerConfig(
    sae=StandardTrainingSAEConfig(d_in=768, d_sae=768*8, l1_coefficient=8e-5),
    l1_warm_up_steps=1000,  # Gradually increase
)

Issue: Poor reconstruction (low CE recovery)

# Reduce sparsity penalty and/or add capacity (both on the SAE sub-config)
cfg = LanguageModelSAERunnerConfig(
    sae=StandardTrainingSAEConfig(
        d_in=768,
        d_sae=768 * 16,       # More capacity
        l1_coefficient=5e-5,  # Lower = better reconstruction
    ),
)

Issue: Features not interpretable

from sae_lens import LanguageModelSAERunnerConfig, StandardTrainingSAEConfig, TopKTrainingSAEConfig

# Increase sparsity (higher L1)
cfg = LanguageModelSAERunnerConfig(
    sae=StandardTrainingSAEConfig(d_in=768, d_sae=768*8, l1_coefficient=1e-4),
)
# Or use a TopK SAE (k is set directly in v6, not via activation_fn_kwargs)
cfg = LanguageModelSAERunnerConfig(
    sae=TopKTrainingSAEConfig(d_in=768, d_sae=768*8, k=50),  # Exactly 50 active features
)

Issue: Memory errors during training

cfg = LanguageModelSAERunnerConfig(
    sae=StandardTrainingSAEConfig(d_in=768, d_sae=768*8, l1_coefficient=8e-5),
    train_batch_size_tokens=2048,  # Reduce batch size
    store_batch_size_prompts=4,    # Fewer prompts in buffer
    n_batches_in_buffer=8,         # Smaller activation buffer
)

Integration with Neuronpedia

Browse pre-trained SAE features at neuronpedia.org:

# Features are indexed by SAE ID
# Example: gpt2-small layer 8 feature 1234
# → neuronpedia.org/gpt2-small/8-res-jb/1234

Key Classes Reference

ClassPurpose
SAESparse Autoencoder model
LanguageModelSAERunnerConfigTop-level training configuration (nests sae= and logger=)
StandardTrainingSAEConfig / TopKTrainingSAEConfig / GatedTrainingSAEConfig / JumpReLUTrainingSAEConfigSAE-type-specific sub-configs (v6)
LoggingConfigLogging/W&B sub-config (v6)
LanguageModelSAETrainingRunnerTraining loop manager (alias: SAETrainingRunner)
ActivationsStoreActivation collection and batching
HookedSAETransformerTransformerLens + SAE integration

Reference Documentation

For detailed API documentation, tutorials, and advanced usage, see the references/ folder:

FileContents
references/README.mdOverview and quick start guide
references/api.mdComplete API reference for SAE, TrainingSAE, configurations
references/tutorials.mdStep-by-step tutorials for training, analysis, steering

External Resources

Tutorials

Papers

Official Documentation

SAE Architectures

ArchitectureDescriptionUse Case
StandardReLU + L1 penaltyGeneral purpose
GatedLearned gating mechanismBetter sparsity control
TopKExactly K active featuresConsistent sparsity
from sae_lens import LanguageModelSAERunnerConfig, TopKTrainingSAEConfig

# TopK SAE (exactly 50 features active) — `k` is set on the SAE sub-config in v6
cfg = LanguageModelSAERunnerConfig(
    sae=TopKTrainingSAEConfig(d_in=768, d_sae=768*8, k=50),
)

Frequently asked questions about SAELens

Similar skills