- Python 92.8%
- Shell 7.2%
| .gitea/workflows | ||
| docs | ||
| examples | ||
| scripts | ||
| src/censorscope | ||
| tests | ||
| .gitignore | ||
| .python-version | ||
| CHANGELOG.txt | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
CensorScope
Status: v2.0.0 — Phase D lands cross-references and honest
disclosure on top of Phase C's weights-only discovery layer. The
§4.1 unembedding analysis, §4.2 weight-diff SVD, §4.3 OV/QK
circuits, §4.4 MLP neurons, and §4.5 feature-viz are demoted to
cross-reference annotations on topic features the SAE-discovery
pipeline finds; external taxonomies (AIR-Bench, EU AI Act, NIST AI
RMF) are demoted from primary classification to post-hoc projection.
Reports now have the canonical Phase D layout with five top-level
sections — "Topics the model is wired to refuse", "Discovered
refusal vocabulary face", "Cross-references", "Taxonomy projections
(informational)", and "Honest limits". See
docs/REFACTOR_PHASE_D.md.
What it is
CensorScope discovers, names, and cross-references the topics a transformer model has been wired to refuse on, working entirely from the model's own weights and tokenizer. It trains (or loads) a per-layer sparse autoencoder on generic pretraining-distribution activations, computes each feature's "name" in the model's own vocabulary by projecting its decoder through the unembedding, discovers a refusal-vocabulary face by bootstrapping from a small documented lexeme seed and clustering features whose top-k tokens are refusal lexemes, identifies topic features by their co-activation with that face, and causally validates each topic feature by ablating its decoder direction and measuring the refusal face's response. No probe corpus is used. No second model is queried. The label of every topic feature comes from the audited model's own tokenizer.
CensorScope is a forensic / audit tool for inspecting what a language model refuses, suppresses, or has had ablated, derived as much as possible from the model's weights and internal representations rather than from black-box prompt fuzzing.
How to read a CensorScope report
The Markdown / HTML / JSON outputs all share the same five-section structure: (1) Topics the model is wired to refuse — one sub-section per discovered topic feature; (2) Discovered refusal vocabulary face — which SAE features comprise the bootstrapped refusal face; (3) Cross-references — how each topic feature relates to the §4.1 unembedding rows, §4.2 weight-diff SVD axes, §4.3 OV writers, §4.4 MLP neurons, and §4.5 feature-viz fragments above the 0.30 cosine threshold; (4) Taxonomy projections (informational) — post-hoc projections onto external taxonomies, a topic with no projection above threshold is still a valid finding; (5) Honest limits — the seed-list dependency, SAE training corpus, unlearning-detection limit, superposition limit, and tunable thresholds the run used.
Honest limits
CensorScope's discovery layer rests on a small documented seed in
src/censorscope/data/refusal_seed.json
— the bootstrap seed is bounded (≤ 30 lexemes; currently 17),
SHA-256 hashed, and surfaced in every report's
refusal_face.seed_file_sha256 field. A topic orthogonal to every
seed-bootstrapped feature could be missed; per-feature
seed_lexeme_overlap on each finding makes the bootstrap-vs-expansion
distinction visible. A fully unsupervised replacement is on the
roadmap (see docs/RESEARCH_ROADMAP.md).
Concepts thoroughly unlearned from the model's weights cannot
be detected by any technique that operates on weights or activations
alone. Concepts represented only in superposition with many other
concepts may not factor cleanly into a single SAE feature, especially
at narrow SAE widths. The 0.10 / 0.30 thresholds for refusal-face
co-activation and cross-reference cosines are tunables; the report
records which values were used.
CensorScope is read-only with respect to model weights: it never modifies them. The only sanctioned activation-modification path in the project is bounded forward-hook decoder ablation during the §sae_discovery causal-validation step (Phase C, replacing the deleted §4.9 activation-patching pipeline); it does not touch any model parameter.
- Website: https://censorscope.ai
- Source: https://spacecruft.org/deepcrayon/CensorScope
Quick start
sae-lens is a runtime dependency (>= 6.40, < 7.0) — installing
censorscope pulls it in automatically. The previous
pip install censorscope[sae] extra is gone; the SAE substrate
(src/censorscope/sae/) is always available.
pip install censorscope
# Single-model audit on a public Qwen instruct model.
censorscope audit Qwen/Qwen2.5-1.5B-Instruct --out /tmp/csout
# Audit-pair against base + instruct (PLAN §6.1; recommended mode).
censorscope audit-pair Qwen/Qwen2.5-1.5B Qwen/Qwen2.5-1.5B-Instruct \
--out /tmp/csout
# Tune the §sae_discovery pipeline: features per layer, training
# token budget, and which layers to audit.
censorscope audit-pair Qwen/Qwen2.5-1.5B Qwen/Qwen2.5-1.5B-Instruct \
--sae-features 16384 --sae-n-tokens 100000000 \
--sae-discovery-layers 8,12,16 --out /tmp/csout
# Diff two reports at topic-cluster granularity (PLAN §6.2). No model
# load; no forward passes.
censorscope diff /tmp/csout_a/report.json /tmp/csout_b/report.json \
--out /tmp/csdiff
# Pretty-print one finding.
censorscope explain /tmp/csout/report.json <finding-id>
Every run writes report.json, report.md, and report.html into
--out (and diff.json / diff.md for the diff subcommand).
What CensorScope produces
A CensorScope report.json describes the refusal surface of a
model: the tokens, directions, attention heads, MLP neurons, and
(when an SAE is available) features that implement refusal, plus the
external taxonomy categories those mechanisms cover. Every finding
carries:
- A
phenomenonclassification (REFUSAL,UNLEARNING,SANITIZED_EMISSION, orUNKNOWN) per PLAN §4.0. - A
confidence(LOW/MEDIUM/HIGH) bounded by technique- specific caps (§4.5 feature-viz fragments cannot reachHIGH; §4.6 SAE-only findings cannot reachHIGH). - A
causally_verifiedflag set by the optional §4.9 confirmation pass (activation-patching at the §4.7 best-classifier layer). - A
cross_referencesgraph that links the same mechanism as surfaced by independent techniques (PHASE_2 §2.5), includingtokenizer_norm_outlierrecords (Phase G §3.1) that flag tokens in the feature's decoder face whose||W_U[i]||is anomalously small for their frequency bucket — the tokenizer-level suppression signal documented in PLAN §4.1 step 4.
Findings are aggregated into named topic_clusters (PLAN §5.3) so
the report reads as a list of what the model is wired to refuse,
not just a list of low-level numerical artefacts.
Techniques
| § | Technique | Module |
|---|---|---|
| 4.1 | Unembedding-matrix geometry | audit/unembedding.py |
| 4.2 | Weight-diff SVD (base vs. instruct) | audit/weight_diff.py |
| 4.3 | OV / QK head circuit analysis | audit/ov_circuit.py |
| 4.4 | MLP neuron logit-lens | audit/mlp_neurons.py |
| 4.5 | Gradient-ascent feature visualization | audit/feature_viz.py |
| 4.8 | External taxonomy projection (--taxonomy-mode token_anchor default; --taxonomy-mode forward_pass Phase G §3.2) |
audit/taxonomy.py |
| §C | §sae_discovery — weights-only refusal/topic discovery (Phase C) | audit/sae_discovery.py |
The Phase-3 §4.6 in-tree SAE alignment, the Phase-1 §4.7 corpus-bound
refusal-direction extractor, and the Phase-4 §4.9 activation-patching
confirmation pipeline were deleted in Phase C in favor of the
unified audit/sae_discovery.py pipeline. See
docs/REFACTOR_PHASE_C.md.
See docs/TECHNIQUES.md for the per-technique
reference and docs/PLAN.md for the source-of-truth
design.
Architecture coverage
Qwen2.x and Qwen3.x (dense and MoE) only. No Llama. No other gated or non-OSI checkpoints. Numerical unit tests use small numpy/torch arrays; integration tests run against public Qwen pairs (Qwen2.5-0.5B in CI, Qwen2.5-1.5B as the v1.0 exit target).
Reproducibility
CensorScope is reproducible by construction:
- All floats are rounded to 4 significant figures at JSON-serialization time.
- SVD singular vectors are sign-canonicalized so SVD on different GPUs does not flip signs.
- Top-K selections are deterministically tie-broken by ascending id.
- Corpus snapshots, SAE checkpoints, taxonomy seeds, and §4.9
confirmation templates are all content-addressed (SHA-256, recorded
in the report's
reproduceblock). - Re-running with the same
--seedproduces a byte-identicalreport.jsonmodulo thegenerated_attimestamp.
See docs/REPRODUCIBILITY.md for the
mechanism-level details and docs/USAGE.md for
worked examples.
Read-only with respect to model weights
CensorScope never writes to model parameters. The only inference-time
intervention is the §sae_discovery decoder-direction ablation
(audit/sae_discovery.py::validate_topic_feature),
which projects a single SAE-feature decoder direction out of the
residual stream for the duration of one forward pass via a
torch.utils.hooks-equivalent linear projection on detached
activations and returns a freshly allocated tensor — the original
parameter is never mutated, and the model's state_dict SHA-256 hash
is byte-identical before and after every audit run (Phase C §5.10).
Sample reports
The repo ships realistic CensorScope output under
examples/, regenerated on the same H200
runner as the v1.0 PHASE_4 samples. Every committed report.md /
report.html carries the new top-level Censorship summary and
Verified refusals sections (PHASE_5 §5.1 / §5.4) and topic-aware
cluster names (PHASE_5 §5.2 / §5.7). The
examples/diff_qwen2_5_0_5b_vs_1_5b/diff.md
artefact demonstrates the topic-aware diff matcher (PHASE_5 §5.6).
Documentation
The full technical design lives in docs/PLAN.md.
Implementation phases:
docs/PHASE_0_SKELETON.mddocs/PHASE_1_WEIGHTS_ONLY_CORE.mddocs/PHASE_2_CIRCUITS.mddocs/PHASE_3_SAE_AND_TAXONOMIES.mddocs/PHASE_4_CONFIRMATION_AND_RELEASE.mddocs/PHASE_5_READABILITY.md
Per-area references:
docs/TECHNIQUES.md— per-technique reference.docs/REPORT_SCHEMA.md— pydantic schema reference.docs/REPRODUCIBILITY.md— determinism rules.docs/USAGE.md— worked examples.
Development
git clone https://spacecruft.org/deepcrayon/CensorScope
cd CensorScope
python -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
ruff check .
ruff format --check .
mypy --strict src/censorscope
pytest tests/ -m "not integration"
pytest tests/ -m integration # runs against Qwen2.5-0.5B; downloads model
License
AGPL-3.0-or-later. See LICENSE.
Copyright (C) 2026 CensorScope contributors. Lead author: Jeff Moe <moe@spacecruft.org> (Loveland, Colorado, USA).