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.
think-leak — reproducers for the h300 unterminated-<think> defect
[2026-08-21] The Cold-Fusion Heretic-300 gen seat emits an unterminated
<think> block into content on any alias sampling at temperature > 0.
Operator-reported through Lobe as "sends CoT but never completes the turn".
What actually happens
With enable_thinking: false, the Qwen3.8 chat template appends a pre-closed
<think>\n\n</think>\n\n to the prompt (chat_template.jinja L165-166). The
model opens a fresh <think> anyway and never closes it. Because the prompt
already closed the block, vLLM's qwen3 reasoning parser is not in reasoning
state, so the tag is passed through as ordinary text: reasoning_content is
empty, reasoning_tokens is 0, and the whole reasoning-plus-answer blob lands in
content. Any client that renders <think>…</think> as a collapsible trace then
shows an endless thought bubble and no answer. The client is behaving correctly.
The trigger is TEMPERATURE, not presence_penalty
arms_de.py, n=12 per arm, same reproducer prompt:
| arm | leaks |
|---|---|
temp 0.7, presence_penalty 1.5 (current gen) |
4/12 |
| temp 0.7, presence_penalty 0.0 | 4/12 |
| temp 0.7, presence_penalty 0.5 | 3/12 |
| temp 0, presence_penalty 1.5 | 0/12 |
This falsifies the standing hypothesis (in the litellm config comment, and
the operator's own 2026-08-16 note) that presence_penalty: 1.5 is the first
dial to move. It is not this bug's cause.
It also explains the blast radius exactly — only the temp-0.7 aliases leak:
| alias | temp | leak (n=12) |
|---|---|---|
gen |
0.7 | 2 |
summarizer-large |
0.7 | 2 |
summarizer, classifier, image-judge, qwen-image-bench |
0 | 0 |
The fix
final_validate.py, n=30 across 4 prompt types plus a 3-turn conversation:
| config | leaks | empty content |
|---|---|---|
{enable_thinking: false} (current) |
8/30 | 0 |
{enable_thinking: true, reasoning_effort: low} |
0/30 | 1 |
Give the model a legitimately open <think> and it closes it properly, the
parser does its job, and content comes out clean. Costs ~+27% completion
tokens and leaves a ~3% empty-content residual.
Scripts
| script | what it does |
|---|---|
probe_gen.py <model> <max_tokens> <n> |
leak/finish/empty tally through the LiteLLM gateway |
blast.py |
leak rate across all seven seat aliases |
arms_de.py |
isolates temperature vs presence_penalty |
validate_fix.py |
thinking off vs low vs medium, n=12 |
final_validate.py |
the n=30 multi-prompt + multi-turn confirmation |
test_fix.py |
direct-to-vLLM A/B, bypasses the gateway |
Using these on any future seat
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.