Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Train sparse autoencoders to interpret model features.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 159% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 104% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 291% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 171% | 0% |
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)
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.
Use SAELens when you need to:
Consider alternatives when:
bashpip install sae-lens
Requirements: Python 3.10+, transformer-lens>=2.0.0
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 lossLoss Function: MSE(original, reconstructed) + L1_coefficient × L1(features)
In "Towards Monosemanticity", human evaluators found 70% of SAE features genuinely interpretable. Features discovered include:
pythonfrom 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()
| Release | Model | Layers | |---------|-------|--------| | gpt2-small-res-jb | GPT-2 Small | Multiple residual streams | | gemma-2b-res | Gemma 2B | Residual streams | | Various on HuggingFace | Search tag saelens | Various |
pythonfrom 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.
| Parameter | Typical Value | Effect | |-----------|---------------|--------| | d_sae | 4-16× d_model | More features, higher capacity | | l1_coefficient | 5e-5 to 1e-4 | Higher = sparser, less accurate | | lr | 1e-4 to 1e-3 | Standard optimizer LR | | l1_warm_up_steps | 500-2000 | Prevents early feature death |
| Metric | Target | Meaning | |--------|--------|---------| | L0 | 50-200 | Average active features per token | | CE Loss Score | 80-95% | Cross-entropy recovered vs original | | Dead Features | <5% | Features that never activate | | Explained Variance | >90% | Reconstruction quality |
pythonfrom 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}")
pythondef 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])
python# 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}")
> 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.
pythonfrom 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 )
python# 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 ), )
pythonfrom 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 )
pythoncfg = 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 )
Browse pre-trained SAE features at neuronpedia.org:
python# Features are indexed by SAE ID # Example: gpt2-small layer 8 feature 1234 # → neuronpedia.org/gpt2-small/8-res-jb/1234
| Class | Purpose | |-------|---------| | SAE | Sparse Autoencoder model | | LanguageModelSAERunnerConfig | Top-level training configuration (nests sae= and logger=) | | StandardTrainingSAEConfig / TopKTrainingSAEConfig / GatedTrainingSAEConfig / JumpReLUTrainingSAEConfig | SAE-type-specific sub-configs (v6) | | LoggingConfig | Logging/W&B sub-config (v6) | | LanguageModelSAETrainingRunner | Training loop manager (alias: SAETrainingRunner) | | ActivationsStore | Activation collection and batching | | HookedSAETransformer | TransformerLens + SAE integration |
For detailed API documentation, tutorials, and advanced usage, see the references/ folder:
| File | Contents | |------|----------| | references/README.md | Overview and quick start guide | | references/api.md | Complete API reference for SAE, TrainingSAE, configurations | | references/tutorials.md | Step-by-step tutorials for training, analysis, steering |
| Architecture | Description | Use Case | |--------------|-------------|----------| | Standard | ReLU + L1 penalty | General purpose | | Gated | Learned gating mechanism | Better sparsity control | | TopK | Exactly K active features | Consistent sparsity |
pythonfrom 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), )
Other measured skills in the registry, with their headline benchmark lift.