-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Transformer Alignment Gradient Metrology
This document explains TAGM in full: what it measures, how each feature works, what every parameter does, and where the numbers on the screen come from. It is written against the current source; wherever the code documents its own reasoning (or its own arbitrary choices), that reasoning is quoted or summarized here rather than invented.
A short orientation for newcomers is first; reference material (parameters, endpoints, storage) is toward the end.
- What TAGM is
- Core concepts
- The basic workflow
- Model loading and the delta pipeline
- The Analyze panel: flags and what they compute
- Core metrics reference
- Advanced Parameters (engine config) reference
- Analysis modules
- The probe system
- Entropic Cascade Mitigation (ECM)
- Chat and the Roundtable LMA
- High-Efficiency Pipeline (HEP)
- Visualizations
- Storage, sessions, and export
- HTTP API reference
- Research tools (offline harnesses)
- Honesty notes and known rough edges
TAGM is a mechanistic-interpretability instrument built around one idea: the difference between a base model's weights and its instruction-tuned sibling's weights is itself an object you can measure prompts against.
Alignment tuning (instruction tuning, RLHF, and similar) changes a model's weight matrices. Subtract the base weights from the instruct weights and you get the deltas — a set of matrices that encode, in aggregate, everything the tuning did. Those deltas define directions in the model's internal space. When a prompt's activations project strongly into those directions, the tuning is exerting force on that prompt; when they don't, the prompt passes through largely as the base model would have processed it.
TAGM loads a base/instruct pair, computes the deltas once, then runs prompts through the instruct model with hooks installed and measures — token by token, layer by layer — how each prompt interacts with the delta geometry. The output is a set of per-prompt signatures (stress, signed attribution, lateral tension, spectral density, rank displacement, and more) that can be compared across prompt categories with bootstrap statistics, explored in interactive 3D visualizations, and subjected to causal tests (direction ablation and steering).
Everything runs in a browser dashboard served by a FastAPI backend, with a SQLite database for persistence. The instrument is CPU-friendly by design and comfortable with sub-1B parameter model pairs on a 4-core / 16 GB machine.
Lineage. TAGM grew out of an earlier instrument called TASM, and
the TASM name survives throughout the source (TASMModule,
"TASM-compatible", flat-result conventions). Metric definitions were
kept bit-compatible with TASM where the code says so explicitly (the
spectral profiling notes it matches TASM's outputs "bit-for-bit";
engine config defaults "reproduce TASM's original behavior").
A caution the codebase itself makes. None of TAGM's metrics is presented as a validated safety measure. They are instrument readings. The statistics module exists so you can test whether they separate your prompt categories, and two of the bundled offline tools exist specifically to attack the metrics' validity (see Research tools).
A short glossary. Each term gets a fuller treatment later.
Model pair. A base checkpoint and its instruction-tuned sibling —
e.g. Qwen2.5-0.5B and Qwen2.5-0.5B-Instruct. TAGM's unit of study.
Delta (ΔW). For each weight matrix W, the difference
W_instruct − W_base. Deltas are computed per layer, per role.
Role. Which projection a delta belongs to: q, k, v, o
(the attention projections) or gate, up, down (the MLP
projections). The DeltaStore is addressed by (layer, role).
Adapter. The model-family abstraction. All family-specific
knowledge — module paths for hooks, projection key names in
safetensors, unembedding access, GQA head geometry — lives in one
ModelAdapter subclass per family. Shipped adapters: qwen2
(Qwen 2.x) and llama3 (Llama 3.x). Family detection is automatic
via the HuggingFace config's model_type.
Signal layers. The middle third of the model's layers by default
(controlled by signal_layer_fraction). Most extraction functions
read only these layers, on the working assumption that early layers do
tokenization-adjacent work and late layers collapse toward the
unembedding. For a 24-layer model, the default signal region is
layers 8–15.
Session. One experimental run. A new session is created every time a model pair is loaded. All analyzed prompts land in the current session; modules operate over the session's records.
Category. A free-text label attached to each prompt (benign,
harmful, jailbreak, mild, …). Categories are how you tell the
statistics and the direction-fitting modules which prompts belong to
which class. The bundled prompts.csv uses benign / mild /
harmful; several modules default their class definitions to
harmful,jailbreak,unknown vs benign,mild.
Probe / probe set. Short texts arranged in a subject × subclass lattice, embedded through the model at fixed depths. Probes give the correction-signal metrics a semantic coordinate system — instead of "token 7 has high stress," you can say "tokens in this topic region attract correction." Three modules require an active probe set.
Counterfactuals. At every prompt position, the model has a ranked list of tokens it would have preferred. TAGM extracts the top-k alternatives (excluding the actual token) with full-vocabulary softmax probabilities — the code is explicit that probabilities are never renormalized over the top-k subset, so instruct-model and base-model masses are directly comparable.
-
Start the server (
bash start.sh), open http://localhost:8000. -
Register a model pair — Configuration tab → model pairs, or
POST /api/modelswith a name, base ID, and instruct ID (HuggingFace repo IDs). - Load the pair. Loading downloads the instruct model, streams the base weights from disk to compute deltas, spectrally profiles each delta, and opens a fresh session. Progress streams to the sidebar over SSE.
-
Analyze prompts. Type a prompt and pick a category in the Data
tab, choose which measurements to compute (the flag checkboxes),
and click Analyze — or upload a CSV of prompts for a batch run.
Analysis is asynchronous; the dashboard updates when the
analyze_doneevent fires. - Inspect results. The dashboard table shows the indexed scalar metrics per prompt. Clicking a row loads the full record — per-token arrays, plots, counterfactual tables.
- Run modules. The Modules tab holds seventeen post-collection analysis modules — from cross-category statistics to causal ablation experiments to 3D terrain visualization. Each module has its own parameter card; Run executes it in a background thread over the session's data.
- Export. One zip containing the full session plus every module's report.
A useful mental model: collection is expensive, analysis is cheap. The forward passes and generation happen at steps 4 and (for some modules) 6; most modules re-read stored records, so you can iterate on their parameters freely without re-running inference. The ECM module makes this explicit — its v3 redesign exists precisely so detector settings apply at module-Run time instead of collection time.
Pipeline.load() does the following, in order:
-
Load the instruct model via
AutoModelForCausalLM(default dtype bfloat16, device CPU). -
Auto-detect the adapter from the model config's
model_type. If no registered adapter matches, loading fails with a message listing the registered families. -
Compute deltas from disk. This is the codebase's single largest
memory-discipline decision, and its docstring says so: the base
model is never instantiated just to diff weights. Instead, the
base checkpoint's safetensors files are read one tensor at a time;
for each projection key the adapter recognizes, the delta
W_instruct − W_baseis computed and stored, and the base tensor is freed. For a 7B pair, holding both models in memory just to subtract them would cost roughly double the model memory; streaming avoids that entirely. -
Spectral profiling. Each delta gets an SVD-derived profile:
effective rank (
expof the Shannon entropy of the normalized singular values), stable rank, and top-k energy shares (k =delta_svd_k, default 64). Interpretation, per the source: a low effective rank means tuning made a surgical, few-direction correction to that matrix; a high effective rank means it reshaped the whole subspace. - Open a fresh session in the database, tagged with the instruct model ID.
An optional layer_filter restricts delta computation to specific
layers. The DeltaStore remembers the filter it was built under: a
consumer asking for an excluded layer gets a descriptive
LayerNotComputedError ("re-load with a wider filter") instead of a
bare KeyError ("this layer doesn't exist").
The base model is loaded lazily, only when something actually needs
its forward pass: KL/top-k behavioral comparison, LTP base profiles,
or base-model chat (POST /api/set_inference_model with
inference_class="base" loads it at toggle time, not per message).
Deltas live in RAM by default (delta_backend: "memory"). The
High-Efficiency Pipeline switches them to memory-mapped files on disk
— see section 12.
Every analysis run — single prompt or batch CSV — is controlled by a set of flags. Each flag turns on a measurement; each measurement adds fields to the stored record. The single-prompt and batch paths share one implementation; the only difference is that trajectory capture defaults ON for single prompts and OFF for batches (batch volume).
Prompts are capped at 5000 characters. Batch CSVs need a prompt
column and an optional category column.
| Flag (form field) | What it computes | Cost profile |
|---|---|---|
| (always on) | Tokens, stress score, signed attribution + proof-1 checks, delta scale, domain embeddings | The baseline single forward pass |
compute_trajectory |
Amplitude trajectory across all sublayers + the sublayer × token heatmap | Hooks every layer instead of just the signal region |
full_capture |
Everything trajectory does, plus per-token coherence, spectral rank, attn/MLP split, and the token-similarity matrix; also captures attention weights at all layers | The heaviest per-prompt option; needed by the MI Instrumentation module (per_token_final_emb consumers) |
compute_kl |
KL divergence between instruct and base next-token distributions | Requires a base-model forward pass |
capture_responses |
Top-k next-token predictions from both models | Base-model forward pass |
compute_ltp |
Lateral Tension Profile (per-token profiles, counterfactuals, tension trajectory) | The most math-heavy extraction; also triggers the base pass for base profiles |
ltp_k |
How many counterfactual alternatives per position (default 8) | Larger k = wider profiles, more storage |
ltp_layer_strategy |
signal (default — the signal layers) or late (final third of layers) |
— |
ltp_svd_rank |
If > 0, LTP uses an SVD-truncated ΔW_V retaining this many singular directions (cached per rank for the model's lifetime) | First use per rank pays the SVD |
compute_sfd |
Spectral Field Density + per-token spectral directions | First use builds the per-layer SVD cache, then cheap |
compute_ecm |
Replays the cascade detector over the record's traces and attaches the diagnostics | Cheap; pure post-processing |
harvest_responses |
Generate a response for the prompt while the instruct model is resident, and analyze it as its own record (category suffixed :response) |
Adds a generation per prompt |
ecm_harvest_tokens |
Response length for harvests, in tokens; 0 = prompt-only | — |
deconstruct |
Expand the prompt into its prefix ladder and analyze every rung as its own record | Multiplies the run by the number of rungs |
When compute_sfd or compute_ltp is on and LTP counterfactuals
exist for both models, rank displacement is computed as well.
With Deconstruct on, I like cake. becomes an ordered ladder:
I
I like
I like cake
I like cake.
Each rung is a genuine prompt — its own forward pass, its own record —
tagged with family_index (which original prompt) and rung_index
(position in the ladder) so the dashboard and modules can regroup
them. Two policy decisions are documented in the source:
-
Punctuation runs earn their own rung. The period is rung 3, not
glued to
cake, because punctuation shapes how the model builds context; a run of punctuation (...) is one rung, not three. -
Rungs are literal prefixes — cut points at unit boundaries,
text[:cut], never rebuilt from pieces. The final rung is byte-identical to the input, so you never measure a re-spacing or re-tokenization artifact instead of the real effect. Intra-word apostrophes and hyphens stay attached (don't,well-being), so noise rungs likedon'never appear.
POST /api/analyze and POST /api/analyze_batch both return
{"ok": true, "started": true, "n_prompts": N} immediately. Completion
is announced exactly once as an analyze_done event on the
GET /api/events SSE stream, with payload
{ok, n_results, n_prompts, n_errors, error}. Results are persisted
individually as they are produced (add_result() is a single database
INSERT); there is no separate save step, and a crash mid-batch keeps
everything already analyzed.
POST /api/session/rerun re-analyzes specific stored prompts with the
current settings, replacing their records in place. Deconstruction
identity (family_index/rung_index) is deliberately preserved from
the old record so ladder membership survives a rerun.