Documentation

Everything you need to install, use, and trust SimLens — from a zero-setup first call to trained, certified, named concept bundles. New to the ideas? Read How SimLens works first.

Overview

SimLens is faithful, vector-only similarity & ranking attribution. Given two embeddings and a metric, it decomposes the similarity score into additive, completeness-checked contributions at three zoom levels — raw dimensions (exact), learned SAE features, and named concepts.

Installation

pip
pip install simlens

# optional extras (any combination):
pip install "simlens[qdrant,openai,train]"
ExtraEnablesPulls in
qdrant / pgvector / faiss / weaviateVector-store adaptersqdrant-client, psycopg[binary], faiss-cpu, weaviate-client
openai / geminiLLM feature-naming providersopenai>=1, google-generativeai
trainResearch-scale GPU SAE training + safetensors importtorch>=2, safetensors
kgSublinear kNN edge proposal for the KG extensionhnswlib

Build from source

shell
python -m venv .venv && . .venv/bin/activate
pip install maturin numpy pytest
maturin develop --release      # builds the Rust core into your environment
cargo test -p simlens-core     # Rust unit tests
pytest -q                      # Python tests
python examples/quickstart.py  # full end-to-end demo

Requires Python ≥ 3.8. Prebuilt abi3 wheels ship on PyPI for Linux and macOS (universal2).

Quickstart

The exact Level-1 explanation needs nothing but two vectors and a metric:

zero setup
import simlens

ex = simlens.Explainer(metric="cosine")
attr = ex.explain(query_vec, candidate_vec)
print(attr.as_sentence())
→ "Matched mainly on 'financial-regulation' (61%), '2024-filings' (22%)."

To get named, human-readable explanations, point autofit at your store — it samples vectors, trains the dictionary, and names features from your payload fields (plus an optional LLM), all in one line:

autofit
bundle = simlens.autofit(store)                       # zero manual labeling
# or: simlens.autofit(store, namer=simlens.naming.from_provider("openai"))

Or walk the full pipeline by hand — the bundled demo does exactly this on a synthetic space with known concepts:

examples/quickstart.py (abridged)
from simlens import train

# 1. train a sparse autoencoder + auto-label features
sae = train.SAE(dim=dim, expansion=4, l1=1e-3).fit(X, epochs=60)
bundle = train.build_bundle("synthetic-v1", "cosine", sae, X, labelers=labelers)

# 2. register named concepts (CAVs) from example vectors
bundle.add_concept("finance", positives, negatives, aspect="topic")

# 3. save / load / verify (audit)
bundle.save("demo.simlens")
ex = simlens.Explainer(simlens.Bundle.load("demo.simlens"))

# 4. explain at any level
ex.explain(q, c, level="feature", top_k=5)
ex.explain(q, c, level="concept")
ex.explain_margin(q, better, worse, level="concept")

Capability reference

CapabilityCall
Explain a match (dims / features / concepts / aspects)ex.explain(q, c, level=...)
Explain a ranking — why A beat Bex.explain_margin(q, better, worse)
Centered “why” — discriminative, past the anisotropy baselineex.explain(q, c, center=True)
Any learned metric (cross-encoder, reranker) via Integrated Gradientssimlens.LearnedMetricExplainer(scorer).explain(q, c)
Why they're not more similarex.explain_dissimilarity(q, c)
Minimal reason — what breaks the matchex.ablate(q, c, threshold=...)
Steer a query in concept spaceex.steer(q, {"topic": -1.0})
Contrast against a background setex.explain_vs_corpus(q, c, foil)
Certify a bundle's faithfulness (signed scorecard)bundle.certify(vectors)
Summarize a whole result pageex.summarize(q, hits)
Late-interaction (multi-vector) attributionsimlens.MultiVectorExplainer().explain(Q, C)
Auto-build a bundle from your storesimlens.autofit(store)
Train & package a concept bundle manuallysimlens.train.build_bundle(...)
Calibrate / audit naming confidencesimlens.eval.reliability(bundle, X, labelers)
Fetch candidates from your storesimlens.adapters.Qdrant / Pgvector / Faiss / Weaviate
Inspect, verify, evaluate from the shellsimlens info | verify | eval | serve
Serve over HTTPpython -m simlens.serve --bundle b.simlens

Explanation levels

Pass level= to ex.explain(...). All levels share one additive contract: contributions (each tagged shared / query-only / candidate-only) plus a completeness residual.

LevelUnitsSetupExactness
"dim"Raw embedding coordinatesNoneExact — residual 0 to float precision
"feature"Learned monosemantic SAE features (auto-named)Bundle (train or autofit)Approximate — residual = SAE reconstruction error, always reported
"concept"Your named concepts (CAVs from example vectors)Bundle + add_conceptDeliberately partial — coverage reported, warning attached
"aspect"Concepts rolled up into buckets (topic / tone / recency…)Concepts with aspect= tagsSame contract as concepts, coarser view
When the residual grows past its threshold, the attribution carries a completeness_residual_high warning telling you to drop to level="dim" — SimLens never silently presents an approximation as exact.

Centered “why” (anisotropy correction)

Real embedding spaces are anisotropic: all vectors share a large common component, so a raw decomposition is dominated by the shared-mean baseline (“everything matches on ‘being English text’”). Centering removes that baseline and surfaces the discriminative concepts — the reasons this pair matched and others didn't.

centering
# per-call:
ex.explain(q, c, center=True)

# or fit a correction (mean / ABTT / whitening) from a sample:
ctr = simlens.fit_centering(X, method="abtt")
simlens.apply_centering(ctr, vecs)

Centering is the default in the integrations — on real embeddings it's what makes the surfaced topic actually discriminative.

Learned metrics — Integrated Gradients

Dot and cosine decompose exactly, but rerankers and cross-encoders don't. For any learned or non-linear scorer, LearnedMetricExplainer uses Integrated Gradients, which comes with a completeness axiom — attributions sum to the difference between the score and a baseline, and the residual is still reported.

learned metric
lex = simlens.LearnedMetricExplainer(scorer)   # scorer: (q, c) → score
attr = lex.explain(q, c)

Multi-vector (late interaction)

For ColBERT-style late-interaction models, where a query and a document are each a set of vectors, MultiVectorExplainer attributes the score over token-level vector pairs:

multi-vector
attr = simlens.MultiVectorExplainer().explain(Q, C)

Bundles

A bundle packages the learned dictionary (SAE), the feature names, and the named concepts into a small directory you train once and reuse. It is stamped with a content hash; every explanation it produces carries that hash, so any rationale is reproducible and traceable to the exact artifacts that made it.

save / load / verify
bundle.save("mybundle.simlens")
b = simlens.Bundle.load("mybundle.simlens")
b.verify()          # hash (and signature) check
ex = simlens.Explainer(b)

You can also bring your own dictionary: SAEs trained elsewhere drop in via import_safetensors_sae (safetensors / SAELens format) instead of retraining.

autofit — store to bundle in one line

simlens.autofit(store) samples vectors from a store adapter, trains the dictionary, and names features from your payload fields — optionally with an LLM naming provider:

autofit
bundle = simlens.autofit(store)
bundle = simlens.autofit(store, namer=simlens.naming.from_provider("openai"))

SAE training

simlens.train ships real sparse-autoencoder trainers with trustworthy defaults:

training + packaging
from simlens import train
sae = train.SAE(dim=768, expansion=8).fit(X, epochs=60)
bundle = train.build_bundle("prod-v1", "cosine", sae, X, labelers=labelers)

Feature naming

Ranking contributions is exact math; naming them is the hard part — so SimLens measures its names instead of asserting them. Names carry a balanced-accuracy score (the name used as a classifier over held-out activations), and low-scoring names are dropped rather than shown as confident nonsense. Unnamed-but-important features are shown, not hidden.

Faithfulness certification

bundle.certify(vectors) computes a signed quality scorecard baked into the bundle and covered by its content hash:

simlens info <bundle> prints the scorecard, so regressions between bundle versions are visible at a glance.

Integrations (system extensions)

Thin, customizable wrappers that adapt SimLens to a system type — you bring the vectors, they bring the explanation. Centered “why” is the default. Each ships a runnable notebook (examples/notebook_*.py) validated on a real embedder — see Validation.

imports
from simlens.integrations.rag    import RagExplainer           # why retrieved / why ranked
from simlens.integrations.recsys import RecsysExplainer        # "because you liked …", steer
from simlens.integrations.kg     import KnowledgeGraphExplainer # explain / propose / type edges
from simlens.integrations.audit  import AuditLog               # signed, hashed decision records
IntegrationWhat it answersNotebook
RAGWhy was this chunk retrieved / ranked here? Flags spurious features.examples/notebook_rag.py
RecsysA faithful “because you liked …”, plus concept-space steering.examples/notebook_recsys.py
Knowledge graphExplain, propose (kNN, sublinear with [kg]), and type edges.examples/notebook_kg.py
AuditSigned, hashed decision records; deterministic re-explanation.examples/notebook_audit.py

Store adapters

SimLens doesn't replace your vector database — it explains its output. Adapters pull candidate vectors (and payloads, for naming) from common stores behind one interface:

AdapterImportExtra
Qdrantsimlens.adapters.Qdrantsimlens[qdrant]
pgvectorsimlens.adapters.Pgvectorsimlens[pgvector]
FAISSsimlens.adapters.Faisssimlens[faiss]
Weaviatesimlens.adapters.Weaviatesimlens[weaviate]
In-memorysimlens.adapters.Memory— (built in; great for tests)

Swapping Memory for Qdrant or Pgvector leaves integration code unchanged.

CLI & serving

The simlens command inspects, verifies, and evaluates bundles from the shell; a small HTTP sidecar serves explanations to any language or service.

CommandWhat it does
simlens info <bundle>Print a bundle's manifest (including the certification scorecard)
simlens verify <bundle>Verify a bundle's content hash (and signature)
simlens eval <bundle> <vectors.npy>Faithfulness scorecard over an [N, dim] array
simlens serveStart the HTTP serving sidecar
serving sidecar
python -m simlens.serve --bundle b.simlens

Benchmarks

Generated by python benchmarks/bench.py --md. Times are per-call medians on the host CPU; treat them as relative, not absolute. explain dim is the exact Level-1 decomposition (no bundle); explain feature is Level-2 over a TopK SAE (expansion 8, k=32); encode paths use the zero-copy numpy FFI.

dimfeaturesexplain dim (µs)explain feature (µs)SAE encode (µs)batch encode (vec/s)explain peak (KB)
3843,072207.233,620.441,630.1955129.0
7686,144257.0710,386.835,127.1919952.6
1,53612,288345.3742,897.1420,976.4531100.8

Run cargo bench -p simlens-core for the Rust-kernel micro-benchmarks.

Real-system validation

Each integration ships a runnable notebook that runs on a real embedder (fastembed / bge-small-en-v1.5) when available and falls back to an offline hashing embedder so it runs anywhere — including CI, where tests/test_notebooks.py executes all four end-to-end as a guard. Every notebook reports a qualitative explanation and one quantitative faithfulness signal:

IntegrationQuantitative signalResult
RAGdeletion-AUC of top dims vs. randomtop 14.8 < random 18.9faithful
Recsysmargin reconstructs the score gapresidual 0.0exact
KGsame-topic edge coherence vs. random0.87 vs 0.25coherent
Auditsignatures verify + deterministic re-explainreproducible

What the results say

Treat a weak signal as information: a high feature-level residual means the SAE under-trained for that space (retrain with more epochs/expansion, or import a downloaded bundle); low KG coherence means the concepts aren't discriminative there. The Bundle.certify scorecard records these numbers so regressions stay visible.

Project layout

PathWhat
crates/simlens-coreRust attribution kernels — the math and the hot path
crates/simlens-pyPyO3 bindings → simlens._native
python/simlensPython API: Explainer, Bundle, train, eval, adapters, viz, serve
examples/Runnable, self-contained demos and validation notebooks
docs/How SimLens works · benchmarks · validation

Status & roadmap

v0.2 — trustworthy defaults, measured not asserted. Everything in v0.1 plus:

Frontier (post-v0.2): a native Rust simlens-serve, cross-modal and hierarchical concepts.

Limitations

SimLens is released under the Apache License 2.0.