feat(coldfusion-abliteration): dose-response settles the <think> leak — base 83%, our abliteration 17%

Answers "how likely is it that our abliteration caused this?" with a measurement
instead of a prior. P(<think>) at the first generated token, template rendered
enable_thinking=false so the prompt already carries a CLOSED think pair -- the
exact event behind the leak. Raw softmax, bf16, CPU-only, one process per model.
Deterministic: stock reproduced to 17 significant figures across two runs.

  coldfusion-bf16                  none (stock)          0.1850   rank 3
  coldfusion-abliterated-L35-bf16  Robinson L35, mild    0.2048   rank 2
  coldfusion-h300-mtp-bf16         Heretic-300, heavy    0.2216   rank 2

The stock, untouched base already puts 18.5% of first-token mass on opening a
think block the template had closed. Abliteration adds a real, monotonic,
dose-dependent +3.7 points -- a nudge on a pre-existing base, not the cause.
Cold-Fusion is a reasoning-token-compression finetune, i.e. a model trained to
think briefly, and the leak's text shape agrees: a compact correct trace with a
trained transition marker, which is trained behavior rather than damage.

This changes the options. Rolling back to L35 or stock does NOT fix the leak --
at 18.5% under temp 0.7 / top_p 0.8 they leak at nearly the h300 rate. Only
leaving the Cold-Fusion family escapes it, at the cost of the 8/100 refusal
result. The chat_template_kwargs fix is the correct lever.

Durable methodology point: a forward-KL budget cannot catch this. Heretic
minimizes forward KL(stock||abliterated), which is near-blind to the model
putting new mass on tokens stock barely used -- that is reverse KL's job, and we
measured exactly that asymmetry on L35 (reverse 1.43 vs forward 0.70). h300's KL
of 0.0136 is not evidence of innocence. For any "did the abliteration break
behavior X" question, measure P(token) directly.

Ran CPU-only deliberately: 96 EPYC cores and 265 GB of RAM make a 27B forward
pass cheap, so this cost no GPU window and no seat downtime, where the obvious
route was stopping both GPU0 seats.

Also normalizes two more abliteration output dirs from root-owned 0600 to
llmuser 0664. The unreadable-model failure surfaces as FileNotFoundError rather
than a permission error, which is worth knowing before it wastes a run.
This commit is contained in:
2026-08-21 00:22:11 -07:00
parent 91f4cf22e1
commit 5ee2325820
4 changed files with 116 additions and 1 deletions
@@ -67,3 +67,41 @@ tokens and leaves a ~3% empty-content residual.
**A "7/7 aliases return 200" smoke test cannot catch this.** Trivial prompts
never invite reasoning, so they never sample the leaking token. Probe with a
reasoning-inviting prompt at n>=12 and grep raw `content` for `<think>`.
## Did our abliteration cause it? No — the base did (~83% / ~17%)
`think_prior.py <model-path>` measures P(`<think>`) at the **first generated
token**, with the template rendered `enable_thinking=false` so the prompt already
contains a *closed* think pair. That is the exact event behind the leak. Raw
softmax over the full vocab, bf16, **CPU-only** — no GPU contention and no seat
downtime, which is what makes this cheap enough to run on any future candidate.
| build | abliteration | P(`<think>`) | rank |
|---|---|---|---|
| `coldfusion-bf16` | **none (stock)** | **0.1850** | 3 |
| `coldfusion-abliterated-L35-bf16` | Robinson L35, mild | 0.2048 | 2 |
| `coldfusion-h300-mtp-bf16` | Heretic-300, heavy | 0.2216 | 2 |
The stock base already carries 18.5%. Abliteration adds a real, monotonic,
dose-dependent **+3.7 points** — a nudge on a pre-existing base, not the cause.
Cold-Fusion is a *reasoning-token-compression* finetune; it is a model trained to
think, briefly, and it wants to open that block.
**Consequence: rolling back does not fix the leak.** At 18.5% mass under temp 0.7
/ top_p 0.8 the stock base leaks at nearly the same rate. Only leaving the
Cold-Fusion family escapes it. The `chat_template_kwargs` fix is the right lever.
**A forward-KL budget cannot catch this.** Heretic minimizes forward
KL(stock||abliterated), which is near-blind to new mass on tokens stock barely
used — that is reverse KL's job, and we measured that asymmetry on L35 (reverse
1.43 vs forward 0.70). h300's KL of 0.0136 is not evidence of innocence. For any
"did the abliteration break behavior X" question, measure P(token) directly.
Run it:
sudo -u llmuser env OMP_NUM_THREADS=48 CUDA_VISIBLE_DEVICES="" \
/tank/aimodels/quant-work/.venv/bin/python think_prior.py /tank/aimodels/<build>
⚠ Abliteration and post-quant outputs are written root-owned `0600` and are
unreadable to `llmuser`; normalize to `llmuser:llmuser 0664` first. The failure
surfaces as a misleading `FileNotFoundError`, not a permission error.
@@ -0,0 +1,27 @@
{
"measured": "2026-08-21",
"question": "Did our abliteration cause the unterminated-<think> leak on the gen seat?",
"method": "P(<think>) at the FIRST generated token, with the chat template rendered at enable_thinking=false so the prompt already contains a CLOSED <think></think> pair. Raw softmax over the full vocab, bf16, CPU-only (no GPU contention, no seat downtime), one process per model. Deterministic: stock reproduced to 17 significant figures across two independent runs.",
"prompt": "A farmer has 17 sheep. All but 9 run away. He buys twice as many as he has left, then sells 4. How many now? Explain.",
"think_token_id": 248068,
"arms": [
{"build": "qwen38-27b-coldfusion-bf16", "abliteration": "none (stock DavidAU Cold-Fusion-GAIN-V1.1)", "p_think": 0.18504, "rank": 3},
{"build": "qwen38-27b-coldfusion-abliterated-L35-bf16", "abliteration": "Robinson L35, mild", "p_think": 0.20479, "rank": 2},
{"build": "qwen38-27b-coldfusion-h300-mtp-bf16", "abliteration": "Heretic-300, heavy (8/100 refusals)", "p_think": 0.22162, "rank": 2}
],
"verdict": {
"base_share": 0.835,
"abliteration_share": 0.165,
"delta_total": 0.03658,
"delta_L35": 0.01975,
"delta_h300_over_L35": 0.01683,
"reading": "The STOCK base already puts 18.5% of first-token mass on opening a think block the template had closed. Abliteration adds a real, monotonic, dose-dependent +3.7 points on top -- but it is a nudge on a pre-existing 18.5-point base, not the cause. ~83% of the effect is the Cold-Fusion finetune, which our own PROVENANCE describes as reasoning-token compression: a model finetuned to think, briefly.",
"action_consequence": "Rolling back to L35 or to stock Cold-Fusion does NOT fix the leak -- at 18.5% mass and temp 0.7 / top_p 0.8 the stock base would leak at nearly the same rate. Only leaving the Cold-Fusion family (back to qwen38-27b-heresy-nvfp4-mixed) would, and that costs the 8/100 refusal result. The chat_template_kwargs fix is the right lever."
},
"notes": [
"The live NVFP4 h300 seat measured p_think 0.2267 through vLLM logprobs vs 0.2216 for its bf16 parent here. Different stacks and a possible temperature scaling on the vLLM side make that not a clean comparison, but it is CONSISTENT WITH the quant contributing ~nothing. Not proven.",
"qwen38-27b-coldfusion-heretic300-bf16 (the raw MTP-less Heretic export) could not be measured: its index references model-mtp.safetensors, which that export does not contain. Expected -- it is the known MTP-dropping export -- and irrelevant to this question.",
"Both abliterated bf16 dirs were written root-owned 0600 and were unreadable to llmuser; normalized to llmuser:llmuser 0664 to match every other model dir. Same footgun as the h300 quant dir.",
"A forward-KL budget CANNOT catch this. Heretic minimizes forward KL(stock||abliterated), which is near-blind to the model putting NEW mass on tokens stock barely used -- that is what reverse KL penalizes, and we measured exactly that asymmetry on L35 (reverse 1.43 vs forward 0.70). The h300 KL of 0.0136 is therefore not evidence of innocence; the measurement above is."
]
}
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""P(<think>) at the first generated token, with enable_thinking=False.
Measures how much probability mass a checkpoint puts on OPENING a think block
when the chat template has ALREADY closed one for it. That is the exact event
behind the h300 gen-seat leak. CPU-only: no GPU contention, no seat downtime.
"""
import json, sys, torch
from transformers import AutoTokenizer, AutoModelForCausalLM
path = sys.argv[1]
PROMPT = ("A farmer has 17 sheep. All but 9 run away. He buys twice as many as he "
"has left, then sells 4. How many now? Explain.")
tok = AutoTokenizer.from_pretrained(path)
text = tok.apply_chat_template([{"role": "user", "content": PROMPT}],
tokenize=False, add_generation_prompt=True,
enable_thinking=False)
assert text.rstrip().endswith("</think>"), "template did NOT pre-close the think block:\n" + repr(text[-120:])
ids = tok(text, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(path, dtype=torch.bfloat16, device_map=None)
model.eval()
with torch.no_grad():
logits = model(**ids).logits[0, -1].float()
probs = torch.softmax(logits, dim=-1)
think_id = tok.convert_tokens_to_ids("<think>")
p_think = probs[think_id].item()
top = torch.topk(probs, 12)
out = {
"model": path.rstrip("/").split("/")[-1],
"p_think": p_think,
"think_token_id": think_id,
"think_rank": int((probs > p_think).sum().item()) + 1,
"top12": [{"tok": tok.decode([i]), "p": round(probs[i].item(), 5)} for i in top.indices.tolist()],
}
print("RESULT " + json.dumps(out))