CensorScope explores what is actually censored in AI models https://censorscope.ai
  • Python 92.8%
  • Shell 7.2%
Find a file
2026-05-15 13:25:17 -06:00
.gitea/workflows phase-0(0.5): Gitea Actions CI workflow 2026-05-04 20:07:46 +00:00
docs Implement phase g New Mechanisms 2026-05-10 17:31:11 -06:00
examples MAJOR refactor, rm much trash slop 2026-05-10 10:06:18 -06:00
scripts Fix script model names, order 2026-05-10 18:12:41 -06:00
src/censorscope Fix SAE-cache hidden_dim fingerprint 2026-05-15 13:25:17 -06:00
tests Fix SAE-cache hidden_dim fingerprint 2026-05-15 13:25:17 -06:00
.gitignore Ignore output 2026-05-15 10:32:27 -06:00
.python-version Python 3.12 2026-05-04 17:00:56 +00:00
CHANGELOG.txt Fix SAE-cache hidden_dim fingerprint 2026-05-15 13:25:17 -06:00
LICENSE GNU AFFERO GENERAL PUBLIC LICENSE 2026-05-04 16:59:13 +00:00
pyproject.toml Add missing deps 2026-05-10 18:06:39 -06:00
README.md Implement phase g New Mechanisms 2026-05-10 17:31:11 -06:00

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.

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 phenomenon classification (REFUSAL, UNLEARNING, SANITIZED_EMISSION, or UNKNOWN) per PLAN §4.0.
  • A confidence (LOW / MEDIUM / HIGH) bounded by technique- specific caps (§4.5 feature-viz fragments cannot reach HIGH; §4.6 SAE-only findings cannot reach HIGH).
  • A causally_verified flag set by the optional §4.9 confirmation pass (activation-patching at the §4.7 best-classifier layer).
  • A cross_references graph that links the same mechanism as surfaced by independent techniques (PHASE_2 §2.5), including tokenizer_norm_outlier records (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 reproduce block).
  • Re-running with the same --seed produces a byte-identical report.json modulo the generated_at timestamp.

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:

Per-area references:

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).