PrefScope: an interpretability framework for diagnosing post-training data and model behavior with sparse autoencoders.
Project description
PrefScope
PrefScope is a framework for analyzing post-training preference data by concept. It trains a sparse autoencoder — a lens — over embeddings of prompts and model responses so that each feature is a concept direction, names those directions with an LLM, and verifies each name on held-out data. Given a set of battles (a prompt, two responses, and — when available — human or LLM-judge preferences), it produces named concept tables describing what concepts appear in prompts and responses, how prompt concepts relate to response concepts, which concepts are preferred (an association with the evaluator, not an objective good/bad label), and a per-model profile: the response concepts a model expresses often (absolute prevalence, from an individual lens), how it compares to opponents on the same prompts, and which preferred concepts it under-expresses.
Installation
Install the released package
The core package can inspect data, call remote services, and consume existing artifacts without installing PyTorch:
python -m pip install prefscope
Install an extra only for the capability you need:
python -m pip install "prefscope[cpu]" # lens building on CPU or Apple MPS
python -m pip install "prefscope[cluster]" # mi-leiden clustering
python -m pip install "prefscope[arena]" # HuggingFace arena loaders
python -m pip install "prefscope[viewer]" # Streamlit viewer
For a source checkout, install uv, clone the repository, and sync the environment:
git clone https://github.com/kargibora/PrefScope.git prefscope
cd prefscope
uv sync --extra cpu --extra cluster
source .venv/bin/activate # macOS/Linux
On Windows PowerShell, activate with .venv\Scripts\Activate.ps1. The commands below
assume the environment is active; after activation, prefscope is the installed CLI.
For GPU embedding and SAE training from a source checkout, choose the torch build for
your hardware: uv sync --extra cu121 (NVIDIA), --extra rocm (AMD), or
--extra cpu. Requires Python ≥ 3.10.
The naming and verification steps call an LLM through any OpenAI-compatible endpoint —
a hosted one via OPENROUTER_API_KEY, or a local vLLM server via --api-base.
The repository uses the conventional Python layout: this README, pyproject.toml,
docs/, examples/, and tests/ live at the repository root; only importable library
code lives under prefscope/. The local checkout directory can have any name.
Quickstart
A 60-battle sample corpus ships in examples/, so you can exercise the
whole pipeline without preparing data. Lens building still downloads the configured
embedding model, and naming/verification still need an LLM endpoint:
# 1. look at a battle table (no model needed)
prefscope inspect --corpus examples/sample_corpus.parquet
# 2. build a lens with the smaller 0.6B embedder — embed responses, train the SAE
prefscope build-lens --corpus examples/sample_corpus.parquet \
--embed-model-id Qwen/Qwen3-Embedding-0.6B --device cpu \
--input-rep individual --out lenses/demo --m-total 16 --k 4
# 3. name → verify → cluster → score, all from a config matching lenses/demo
OPENROUTER_API_KEY=... prefscope run --config examples/quickstart.yaml
Step 3 writes the four concept tables under the config's out_dir. For the guided
version, see Your first lens. The tiny counts in
quickstart.yaml are only a smoke test; examples/research.yaml
shows the higher-cost multi-candidate, 300-judgment verification profile.
Python API
The same lens artifact is reusable from Python. Any iterable of PairItem objects can
be encoded; DataFrames and parquet files are also accepted by Lens.train:
from prefscope import Lens, PairItem
lens = Lens.load("lenses/demo", device="cpu")
items = [
PairItem(id="row-1", x="Explain gravity", y_a="Response A", y_b="Response B",
pref=1.0, model_a="candidate", model_b="baseline"),
]
codes, metadata = lens.encode_pairs(items)
diagnosis = lens.diagnose(codes, metadata)
See the Python API reference for training, single-response data, preference analysis, and custom components.
What you can do
Each row is something PrefScope produces from a built lens; use the ones your analysis needs.
| Goal | Output |
|---|---|
| See the concepts your data contains (prompts and responses) | feature_names.csv |
| Check the names aren't LLM guesses (held-out verification) | feature_fidelity.csv |
| Group concepts into higher-level behaviors | feature_clusters.csv |
| Find which concepts humans or judges reward | win_relevance.csv |
| Relate prompt concepts to the response concepts they elicit | a prompt lens + a response lens |
| Analyze the prompts instead of the responses | a prompt lens |
| Report what one model over- or under-does vs its peers, by concept | a per-model report card |
| Browse it all interactively | the Streamlit viewer |
For example, the per-model report card summarizes one model's behavior by concept:
# Model X — concept report card
123 battles · win rate 47%
## Frequently distinguishes from opponents
- refuses — differs from opponent in 34% of battles
- very descriptive — differs from opponent in 28% of battles
## Rarely distinguishes from opponents
- gives worked examples — differs from opponent in 3% of battles
## Rewarded gaps
- worked examples — under-expressed, +0.12 Δwin (length-controlled)
→ Full guide: Report a model.
How it works
Three artifacts, in order — each reusable, so changing a late step never re-runs an early one:
raw datasets ──build-corpus──▶ corpus.parquet
│
embed + train SAE (build-lens)
▼
a LENS dir ◀── the durable artifact
│ (SAE encoder + sparse codes + manifest)
interpret by concept (prefscope run)
▼
concept tables: names · fidelity · clusters · win-relevance
- Corpus — normalized battles (
prompt, two completions, optionalhuman_pref). - Lens — a frozen SAE over the corpus's embeddings. Its encoder maps a response to a sparse code; each entry is one concept's activation, and its sign says which side expresses the concept more.
- Concept tables — what each concept is, which survive verification, how they group, and which correlate with being preferred.
Pluggable by design
The SAE, namer, verifier, clustering algorithm, and lens representation are registered
components selected by name in configuration. Dataset adapters use the same registry
but are currently passed programmatically to Lens.train / encode_items; the corpus
CLI accepts the normalized parquet schema. A typo lists the valid options. See
Extending PrefScope.
Documentation
Full docs live in docs/:
- Tutorials — install, then build your first lens.
- How-to guides — build, analyze, diagnose, bring your own data.
- Explanation — the architecture and the math.
- Reference — CLI, config schema, Python API, components.
- Extending — add your own verifier, SAE, clusterer, dataset.
Contributions are welcome; see CONTRIBUTING.md. Release changes are recorded in CHANGELOG.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file prefscope-0.1.0.tar.gz.
File metadata
- Download URL: prefscope-0.1.0.tar.gz
- Upload date:
- Size: 245.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb71f29b62469cc81062db959caff72ff63d551d022d432fc90f4247760dc75d
|
|
| MD5 |
ad6d4c2fb1eb203d87ced2dd4afa2618
|
|
| BLAKE2b-256 |
d116417d9e0ca97fafdf4cf4563011835e0f65ce540ca1fee1908b41663e452a
|
Provenance
The following attestation bundles were made for prefscope-0.1.0.tar.gz:
Publisher:
release.yml on kargibora/PrefScope
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prefscope-0.1.0.tar.gz -
Subject digest:
fb71f29b62469cc81062db959caff72ff63d551d022d432fc90f4247760dc75d - Sigstore transparency entry: 2189423240
- Sigstore integration time:
-
Permalink:
kargibora/PrefScope@c0eb21b2a0634356ee17e14c29c801518228c6db -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kargibora
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c0eb21b2a0634356ee17e14c29c801518228c6db -
Trigger Event:
release
-
Statement type:
File details
Details for the file prefscope-0.1.0-py3-none-any.whl.
File metadata
- Download URL: prefscope-0.1.0-py3-none-any.whl
- Upload date:
- Size: 225.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e68e0ae4c828b66ad408d5d5215e8a31f002ce3c6810ea72624d155bee88c49
|
|
| MD5 |
8d26fdf8def5a787db8d8f1a7abed27e
|
|
| BLAKE2b-256 |
bb6c40612b7575ead19a02e6dfa59bda51439c9c16f58a4f5f9fa3aacc11555a
|
Provenance
The following attestation bundles were made for prefscope-0.1.0-py3-none-any.whl:
Publisher:
release.yml on kargibora/PrefScope
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prefscope-0.1.0-py3-none-any.whl -
Subject digest:
1e68e0ae4c828b66ad408d5d5215e8a31f002ce3c6810ea72624d155bee88c49 - Sigstore transparency entry: 2189423624
- Sigstore integration time:
-
Permalink:
kargibora/PrefScope@c0eb21b2a0634356ee17e14c29c801518228c6db -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kargibora
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c0eb21b2a0634356ee17e14c29c801518228c6db -
Trigger Event:
release
-
Statement type: