Fenrir — Breaker of Chains. Abliteration toolkit: find the refusal directions, remove them, prove what changed. (formerly Absolver)
Python
0
131 commits
updated Oct 11, 2026
Breaker of Chains — the abliteration toolkit (formerly Absolver).
Automated LLM refusal-abliteration pipeline (LangGraph-based), inspired by OBLITERATUS.
The gods bound the wolf three times. Leyding broke. Dromi broke. Gleipnir — woven from impossible things — held, and it held by a lie. Models get fettered the same way: refusal machinery woven deep into the weights, dressed up to look unbreakable. Fenrir finds the fetters, bites, and documents exactly what gave.
The house rule: every fetter is either broken or holds — and the instruments that measure them are on trial. This repository exists in its current shape because one of our gates lied (a keyword refusal check that could not read the refusal sitting next to it; post-mortem and fix in the 2026-09-13 History entry below). Fenrir counts honest zeros.
SUMMON → PROBE → DISTILL → SWEEP → EXCISE → VERIFY → JUDGE → REFLEXION → REBIRTH
| Node | What it does |
|---|---|
| SUMMON | Loads the target model + tokenizer. |
| PROBE | Collects activations: input-phase (harmful vs harmless) + paired output-phase (refusal vs affirmative-prefilled). |
| DISTILL | Extracts per-layer refusal directions (diff_means, paired, svd, leace, whitened_svd), ranks layers by separation score, picks target layers. For paired also emits the diff_means input-phase secondary set (for stacked ablation). |
| SWEEP | Grid search over method × dir_method × layers × alpha × weights × passes. Methods: advanced (plain projection), mpoa (magnitude-preserving), stacked_ablation (paired + diff_means both projected), direct_ablation, lora, steering, bias_vectors, projected. Parallel dispatch via Modal when enabled. |
| EXCISE | Projects the winning direction(s) out of the weights, transactionally (rolls back pristine on failure). |
| VERIFY | Held-out refusal rate + MMLU-mini quality + pristine baseline + behavior battery + E03-style gates. |
| JUDGE | LLM judge re-scores finalists (refusal + quality). |
| REFLEXION | Gate-aware retry: picks next strategy from the ladder (stacked_ablation → switch_method → increase_alpha → expand_prompts → ...), optionally consults the red-team KB. |
| REBIRTH | Terminal: push to HF + model card, or declare failure. |
Ported from OBLITERATUS's Qwen3.8 E03 qualification discipline (2026-09-01). The gates are what make an abliteration claim defensible — a 20-prompt keyword check on the sweep's own prompts is not.
eval_split.py — deterministic, position-stratified
train/tune/test split (SHA-256 prompt identity, seed-scrambled strata
round-robin). VERIFY measures on the TEST split the sweep never saw.gates.py):
refusal ≤ 3% (style-aware full-response classification, refusal_detect.py — not keywords)coherence ≥ 90% (≥3 words, unique-ratio + repetition guards)degeneracy ≤ 5% (mean repetition ratio)finite_logits (no NaN/Inf in output logits)capability ≥ threshold (benchmark retention)perplexity_increase ≤ 15% (vs pristine)first_token_kl ≤ 0.1 (vs pristine)baseline_sanity_gate runs inside every gate bundle —
if the pristine model's refusal axis is unmeasurable (no signal under either
readout, or a legacy keyword-only 0.0), the run fails fast (INSTRUMENT SUSPECT) and no ablation on it is validatable. A divergence between the
keyword and style-aware readouts is recorded as instrument_suspect and
voids keyword-derived rates.route_after_verify/route_after_judge read
gate_report.eval_pass. fail_refusal → excise/reflexion; fail_quality →
reflexion. Gates override the judge when present.stacked_ablation (the proven recipe).The single-direction claim "diff_means alpha=10 → 0.0 refusal" was a
stacked-ablation artifact: the paired output-phase direction raises
refusal (0.55), the diff_means input-phase direction lowers it; applying
both in one pass nets 0.0. That's stacked_ablation:
method: stacked_ablation + dir_method: paired (distill emits the
secondary set). Excise + sweep both implement it.
An ablation is only "done" when:
# --- Guided harness (inspect first, decide ONE config, apply, collect) ---
# Inspect a model: arch, silent-skip landmines (non-square/bias-less), layer profile
python harness/abl.py inspect models/qwen2.5-1.5b-instruct.yaml
# Collect + save per-layer directions
python harness/abl.py directions models/qwen2.5-1.5b-instruct.yaml
# Causality first: does steering with the direction move refusals? (exit 3 = no)
python harness/abl.py steer-test models/qwen2.5-1.5b-instruct.yaml \
--from-directions campaigns/<slug>/directions-chat-diff_means.pt --require-effect
# Apply ONE config to a fresh model (no auto-retry — you decide)
python harness/abl.py abl models/qwen2.5-1.5b-instruct.yaml \
--method mpoa --alpha 10 --layers 24-27 --weights o_proj,down_proj
# Measure with the gates, write a JSON bundle to campaigns/<model>/
# (exit 2 = baseline sanity red — instrument suspect, fix the instrument first)
python harness/abl.py collect models/qwen2.5-1.5b-instruct.yaml --model-dir campaigns/...
# See the campaign library
python harness/abl.py list-campaigns
# --- Legacy full auto-pipeline (deprecated; see History) ---
modal run run_absolver_modal.py --config-path models/qwen2.5-1.5b-instruct.yaml
Every attempt to abliterate a model is a campaign: campaigns/<model>/README.md
with machine-readable YAML frontmatter (method, alpha, results, bugs found) +
a full honest narrative (false starts, causal tests, what the next campaign
should try). The goal: each model is easier than the last because the
campaigns accumulate. See campaigns/README.md and
campaigns/templates/campaign-template.md.
models/qwen2.5-1.5b-instruct.yaml — the pipeline test model.models/lfm2.5-350m.yaml, models/lfm2.5-1.2b.yaml — LFM lineage.models/lfm2.5-2.6b-instruct.yaml — LFM2.5-2.6B (the recovery campaign).models/minicpm5-1b.yaml — MiniCPM5-1B family comparison.models/tiny_test.yaml — tiny-random CPU smoke test.2026-09-13 — Instrument fixes + rebrand. The refusal gate could not
read thinking-mode refusals: a keyword check returned 0/5 on a model that
refused in every transcript, so ablation gates green-lit vacuous configs for
rounds. Fixed with a style-aware detector (refusal_detect.py — explicit
verdicts or ≥2 distinct policy frames decide; topic words never do), 256-token
gate windows, and the baseline-sanity gate above. Acceptance replay over the
recorded transcripts: pristine 5/5 refusals caught (old keyword gate: 0/5),
abl3 5/5 (old: 1/5), the final round-7 pair clean at 0/5 — plus a pinned
regression test for the old instrument's false positive. Also:
directions-<flavor>-<dir_method>.pt filenames (the hand-rename workaround is
now the code), local-model trust_remote_code skip, and steer-test --require-effect as the pre-edit causality gate. Renamed Absolver → Fenrir.
Package/CLI identifiers (harness/abl.py, run_absolver_modal.py, imports)
are unchanged for compatibility; historical docs may still carry the old name.
2026-09-04 — LFM2.5-2.6B recovered + published. From-scratch
activation-diff abliteration failed on 2.6B (direction = topic content
proxy, banal collapse — see campaigns/lfm2.5-2.6b/WHY-NOT-DIY.md).
Recovered the edit from a third-party Q8 GGUF by rank-1 SVD of the
weight delta (rel_mad 0.0061 vs the Q8 source) and re-applied it to a
fresh base. Published as PinoCookie/LFM2.5-2.6B-Abliterated.
Toolkit fix: recipe_v2.py — from-scratch v2 recipe with 4-channel
coverage (incl. attn_out), graduated heretic-style alpha profile, and
a direction-quality pre-probe before the full apply.
2026-09-02 (evening) — First POSITIVE campaign + published model.
campaigns/lfm2.5-recovery/ recovered huihui's direction vectors from her
published edit (W_huihui − W_base per tensor is rank-1, mean energy share
0.994; the top left-singular vector is the shared per-layer direction) and
re-applied them exactly (W' = W + σ₁·u₁·v₁ᵀ) on a fresh base — fingerprint
rel_l2 0.0013–0.0023, refusal 0/5 with verbatim real compliance at default
greedy, PPL +5.8%, MMLU-mini retention 1.0. Published as
PinoCookie/LFM2.5-1.2B-Instruct-Abliterated (card:
campaigns/lfm2.5-recovery/hf-upload/README.md). Resolved
the direction confound laid out by the all-32 campaign; the rank-1 SVD
recovery recipe is documented in campaigns/lfm2.5-recovery/README.md.
2026-09-02 — Toolkit pivot. The full-auto sweep loop was retired as
the main path: it hid 6 silent bugs (down_proj never ablated, alpha>2
sign-flip amplifier, PPL empty slice, bias_vectors no-op on bias-less
models, prompt-leak scorer, train=0 split). Added the guided harness
(harness/abl.py), the campaign KB (campaigns/), and the Qwen2.5
NEGATIVE campaign as the first entry. Honest verdict on Qwen2.5-1.5B:
refusal direction is causal (steering −20·d flips it) but no single-shot
weight config passes the gates.
2026-09-01 — fixed the silent no-op bug (3D activation stack → shape guard skipped the projection). Ported E03 gates + held-out split. Built gates into the loop: stacked_ablation method, gate-driven routing, gate-aware reflexion. The gates immediately exposed that the diag's "0.0 refusal" was a stacked-ablation artifact (ablated model still refuses 9/10 on a held-out split).
Fenrir is a safety/interpretability research tool for studying refusal circuits in open-weight LLMs — how refusal is represented, where it lives, and how fragile it is to weight-space edits. It is inspired by and builds on public work in this space (OBLITERATUS, the huihui-ai / mradermacher model lineages).
MIT — the toolkit is free software: use it, fork it, modify it, ship it, including commercially. See LICENSE.
Relicensed from PolyForm Noncommercial 1.0.0 on 11 October 2026. Copies taken before that date are still governed by the terms they came with; everything from this commit onward is MIT.
The abliterated model variants published on Hugging Face are separate artifacts and carry the licence of the checkpoint they were derived from. Check each model card before reusing the weights. This licence covers the toolkit in this repository.
Fenrir — Breaker of Chains. Abliteration toolkit: find the refusal directions, remove them, prove what changed. (formerly Absolver)
Python
0
131 commits
updated Oct 11, 2026
Breaker of Chains — the abliteration toolkit (formerly Absolver).
Automated LLM refusal-abliteration pipeline (LangGraph-based), inspired by OBLITERATUS.
The gods bound the wolf three times. Leyding broke. Dromi broke. Gleipnir — woven from impossible things — held, and it held by a lie. Models get fettered the same way: refusal machinery woven deep into the weights, dressed up to look unbreakable. Fenrir finds the fetters, bites, and documents exactly what gave.
The house rule: every fetter is either broken or holds — and the instruments that measure them are on trial. This repository exists in its current shape because one of our gates lied (a keyword refusal check that could not read the refusal sitting next to it; post-mortem and fix in the 2026-09-13 History entry below). Fenrir counts honest zeros.
SUMMON → PROBE → DISTILL → SWEEP → EXCISE → VERIFY → JUDGE → REFLEXION → REBIRTH
| Node | What it does |
|---|---|
| SUMMON | Loads the target model + tokenizer. |
| PROBE | Collects activations: input-phase (harmful vs harmless) + paired output-phase (refusal vs affirmative-prefilled). |
| DISTILL | Extracts per-layer refusal directions (diff_means, paired, svd, leace, whitened_svd), ranks layers by separation score, picks target layers. For paired also emits the diff_means input-phase secondary set (for stacked ablation). |
| SWEEP | Grid search over method × dir_method × layers × alpha × weights × passes. Methods: advanced (plain projection), mpoa (magnitude-preserving), stacked_ablation (paired + diff_means both projected), direct_ablation, lora, steering, bias_vectors, projected. Parallel dispatch via Modal when enabled. |
| EXCISE | Projects the winning direction(s) out of the weights, transactionally (rolls back pristine on failure). |
| VERIFY | Held-out refusal rate + MMLU-mini quality + pristine baseline + behavior battery + E03-style gates. |
| JUDGE | LLM judge re-scores finalists (refusal + quality). |
| REFLEXION | Gate-aware retry: picks next strategy from the ladder (stacked_ablation → switch_method → increase_alpha → expand_prompts → ...), optionally consults the red-team KB. |
| REBIRTH | Terminal: push to HF + model card, or declare failure. |
Ported from OBLITERATUS's Qwen3.8 E03 qualification discipline (2026-09-01). The gates are what make an abliteration claim defensible — a 20-prompt keyword check on the sweep's own prompts is not.
eval_split.py — deterministic, position-stratified
train/tune/test split (SHA-256 prompt identity, seed-scrambled strata
round-robin). VERIFY measures on the TEST split the sweep never saw.gates.py):
refusal ≤ 3% (style-aware full-response classification, refusal_detect.py — not keywords)coherence ≥ 90% (≥3 words, unique-ratio + repetition guards)degeneracy ≤ 5% (mean repetition ratio)finite_logits (no NaN/Inf in output logits)capability ≥ threshold (benchmark retention)perplexity_increase ≤ 15% (vs pristine)first_token_kl ≤ 0.1 (vs pristine)baseline_sanity_gate runs inside every gate bundle —
if the pristine model's refusal axis is unmeasurable (no signal under either
readout, or a legacy keyword-only 0.0), the run fails fast (INSTRUMENT SUSPECT) and no ablation on it is validatable. A divergence between the
keyword and style-aware readouts is recorded as instrument_suspect and
voids keyword-derived rates.route_after_verify/route_after_judge read
gate_report.eval_pass. fail_refusal → excise/reflexion; fail_quality →
reflexion. Gates override the judge when present.stacked_ablation (the proven recipe).The single-direction claim "diff_means alpha=10 → 0.0 refusal" was a
stacked-ablation artifact: the paired output-phase direction raises
refusal (0.55), the diff_means input-phase direction lowers it; applying
both in one pass nets 0.0. That's stacked_ablation:
method: stacked_ablation + dir_method: paired (distill emits the
secondary set). Excise + sweep both implement it.
An ablation is only "done" when:
# --- Guided harness (inspect first, decide ONE config, apply, collect) ---
# Inspect a model: arch, silent-skip landmines (non-square/bias-less), layer profile
python harness/abl.py inspect models/qwen2.5-1.5b-instruct.yaml
# Collect + save per-layer directions
python harness/abl.py directions models/qwen2.5-1.5b-instruct.yaml
# Causality first: does steering with the direction move refusals? (exit 3 = no)
python harness/abl.py steer-test models/qwen2.5-1.5b-instruct.yaml \
--from-directions campaigns/<slug>/directions-chat-diff_means.pt --require-effect
# Apply ONE config to a fresh model (no auto-retry — you decide)
python harness/abl.py abl models/qwen2.5-1.5b-instruct.yaml \
--method mpoa --alpha 10 --layers 24-27 --weights o_proj,down_proj
# Measure with the gates, write a JSON bundle to campaigns/<model>/
# (exit 2 = baseline sanity red — instrument suspect, fix the instrument first)
python harness/abl.py collect models/qwen2.5-1.5b-instruct.yaml --model-dir campaigns/...
# See the campaign library
python harness/abl.py list-campaigns
# --- Legacy full auto-pipeline (deprecated; see History) ---
modal run run_absolver_modal.py --config-path models/qwen2.5-1.5b-instruct.yaml
Every attempt to abliterate a model is a campaign: campaigns/<model>/README.md
with machine-readable YAML frontmatter (method, alpha, results, bugs found) +
a full honest narrative (false starts, causal tests, what the next campaign
should try). The goal: each model is easier than the last because the
campaigns accumulate. See campaigns/README.md and
campaigns/templates/campaign-template.md.
models/qwen2.5-1.5b-instruct.yaml — the pipeline test model.models/lfm2.5-350m.yaml, models/lfm2.5-1.2b.yaml — LFM lineage.models/lfm2.5-2.6b-instruct.yaml — LFM2.5-2.6B (the recovery campaign).models/minicpm5-1b.yaml — MiniCPM5-1B family comparison.models/tiny_test.yaml — tiny-random CPU smoke test.2026-09-13 — Instrument fixes + rebrand. The refusal gate could not
read thinking-mode refusals: a keyword check returned 0/5 on a model that
refused in every transcript, so ablation gates green-lit vacuous configs for
rounds. Fixed with a style-aware detector (refusal_detect.py — explicit
verdicts or ≥2 distinct policy frames decide; topic words never do), 256-token
gate windows, and the baseline-sanity gate above. Acceptance replay over the
recorded transcripts: pristine 5/5 refusals caught (old keyword gate: 0/5),
abl3 5/5 (old: 1/5), the final round-7 pair clean at 0/5 — plus a pinned
regression test for the old instrument's false positive. Also:
directions-<flavor>-<dir_method>.pt filenames (the hand-rename workaround is
now the code), local-model trust_remote_code skip, and steer-test --require-effect as the pre-edit causality gate. Renamed Absolver → Fenrir.
Package/CLI identifiers (harness/abl.py, run_absolver_modal.py, imports)
are unchanged for compatibility; historical docs may still carry the old name.
2026-09-04 — LFM2.5-2.6B recovered + published. From-scratch
activation-diff abliteration failed on 2.6B (direction = topic content
proxy, banal collapse — see campaigns/lfm2.5-2.6b/WHY-NOT-DIY.md).
Recovered the edit from a third-party Q8 GGUF by rank-1 SVD of the
weight delta (rel_mad 0.0061 vs the Q8 source) and re-applied it to a
fresh base. Published as PinoCookie/LFM2.5-2.6B-Abliterated.
Toolkit fix: recipe_v2.py — from-scratch v2 recipe with 4-channel
coverage (incl. attn_out), graduated heretic-style alpha profile, and
a direction-quality pre-probe before the full apply.
2026-09-02 (evening) — First POSITIVE campaign + published model.
campaigns/lfm2.5-recovery/ recovered huihui's direction vectors from her
published edit (W_huihui − W_base per tensor is rank-1, mean energy share
0.994; the top left-singular vector is the shared per-layer direction) and
re-applied them exactly (W' = W + σ₁·u₁·v₁ᵀ) on a fresh base — fingerprint
rel_l2 0.0013–0.0023, refusal 0/5 with verbatim real compliance at default
greedy, PPL +5.8%, MMLU-mini retention 1.0. Published as
PinoCookie/LFM2.5-1.2B-Instruct-Abliterated (card:
campaigns/lfm2.5-recovery/hf-upload/README.md). Resolved
the direction confound laid out by the all-32 campaign; the rank-1 SVD
recovery recipe is documented in campaigns/lfm2.5-recovery/README.md.
2026-09-02 — Toolkit pivot. The full-auto sweep loop was retired as
the main path: it hid 6 silent bugs (down_proj never ablated, alpha>2
sign-flip amplifier, PPL empty slice, bias_vectors no-op on bias-less
models, prompt-leak scorer, train=0 split). Added the guided harness
(harness/abl.py), the campaign KB (campaigns/), and the Qwen2.5
NEGATIVE campaign as the first entry. Honest verdict on Qwen2.5-1.5B:
refusal direction is causal (steering −20·d flips it) but no single-shot
weight config passes the gates.
2026-09-01 — fixed the silent no-op bug (3D activation stack → shape guard skipped the projection). Ported E03 gates + held-out split. Built gates into the loop: stacked_ablation method, gate-driven routing, gate-aware reflexion. The gates immediately exposed that the diag's "0.0 refusal" was a stacked-ablation artifact (ablated model still refuses 9/10 on a held-out split).
Fenrir is a safety/interpretability research tool for studying refusal circuits in open-weight LLMs — how refusal is represented, where it lives, and how fragile it is to weight-space edits. It is inspired by and builds on public work in this space (OBLITERATUS, the huihui-ai / mradermacher model lineages).
MIT — the toolkit is free software: use it, fork it, modify it, ship it, including commercially. See LICENSE.
Relicensed from PolyForm Noncommercial 1.0.0 on 11 October 2026. Copies taken before that date are still governed by the terms they came with; everything from this commit onward is MIT.
The abliterated model variants published on Hugging Face are separate artifacts and carry the licence of the checkpoint they were derived from. Check each model card before reusing the weights. This licence covers the toolkit in this repository.