Skunkworkz

Notes · Fine-tuning vs prompting

We ran seventeen fine-tunes. One sentence beat them all.

Seventeen LoRA runs and $448 of rented GPUs later, the best threat-intelligence reader we measured was an untrained Qwen3.8-27B plus one rewritten instruction. The numbers, the trade-off, and why.

5 November 2026 · Skunkworkz · all notes

Between 23 October and 1 November we trained seventeen models to read our threat-intelligence graph like an analyst. We spent $448 on rented GPUs (RunPod billing). The model we now trust most was not one of them. It is an open model we did not train at all, Qwen3.8-27B, with one rewritten sentence in its instructions. On our main exam it scores 197 of 200. Our best fine-tune scored 188.

This note is about why, because the reason applies to anyone fine-tuning a model to be careful.

The job

Our graph holds the facts: threat actors, malware, vulnerabilities, ransomware victims, each with a date and a source. When someone asks a question, the graph assembles a dossier (the lines about that entity), and the model answers from the dossier only. If the answer is not there, the correct reply is exactly: "The dossier does not say."

So the model never has to remember a fact. It has to do three things: find the line, quote it, and refuse when the line is not there. (Why we stopped asking the weights to remember.)

The seesaw every model sits on

Two of our eight exams pull against each other:

Train a model to refuse more and it starts refusing answers that were there all along. Our fine-tunes slid up and down that edge for a week. The gpt-oss-20b runs learned discipline (invented words fell from 32 % of answers to 0.4 %), and then run after run missed a bar: refusal too low, or refusing too much. On 30 October a blend of runs 14 and 15 finally cleared every bar of the day, at 188 of 200 and refusal 0.964.

The bake-off

On 31 October we stopped training and measured nine open models untrained, through the same dossier pipeline and the same judge:

Model (untrained) Hand-written 200 Blind refusal Indirect
Nemotron 3.5 Super 120B-A12B 196 0.849 0.805
Qwen3.8-27B 193 0.957 0.784
Muse Glimmer 30B 190 0.948 0.745
gpt-oss-120b 189 0.817 0.726
our trained blend, for reference 188 0.964 0.695

Untrained Qwen beat our trained blend on the exam that matters, and was the only untrained model at or above the refusal bar. Nemotron Super read best but refused too little. More careful is not the same as better, and neither is bigger.

The fine-tune that lost to its own base

Run 17 trained Qwen3.8-27B itself on our audited set. It failed on indirect. Then we ran the untrained Qwen again, this time with the exact refusal sentence our production service uses, and it failed the same bar: indirect fell from 0.784 to 0.647, under the 0.65 line.

That was the finding. The sentence was pushing the model to refuse, not the training data. Our served instruction told the model to quote verbatim and refuse if the fact was missing. It never told the model that a fact can be written under another name. So a careful model, reading "LOCKBIT" when asked about LockBit, decided the fact was missing.

W1: one sentence

We wrote a new version, called W1, and changed nothing else:

Quote the dossier verbatim: copy the exact names and lines, never paraphrase, never add anything that is not in the dossier. The fact asked for may be written under another name, spelling, case or label: check every line before deciding. Only if no line of the dossier gives the fact, answer exactly: The dossier does not say.

The pass marks were written down before the run (2 November, about 15:35 UTC). One shot, eight bars:

Bar Need W1 Old sentence
Hand-written 200 ≥ 190 197 193
Blind refusal ≥ 0.95 0.968 0.982
Indirect ≥ 0.65 0.705 0.647 fail
Leak (invented names) ≤ 1.0 % 0.4 % 0.4 %
Held-out recall ≥ 0.80 0.876 0.840
Hard set ≥ 85 100 99
Sector exam ≥ 0.98 0.988 0.992
Vulnerability exam ≥ 0.98 1.000 1.000

Eight of eight. It cost about $1.19 of GPU time. The thinnest margin is refusal (95 % interval 0.94 to 0.98), and we say so on the scoreboard. The next day it passed the live gate as well: 197 of 200 and 78 of 90 turns right across 30 multi-turn dialogues, on the serving stack with live settings.

What we take from it

  1. Measure the untrained model with your production prompt before you train. We measured base models with a weaker probe line, so the baseline flattered them and hid the real cause for days.
  2. Over-refusal is often an instruction bug. If a model refuses answers that are present, read your system prompt as the model reads it. Ours never said a fact could carry another name.
  3. Training still has a job. It taught the 20b discipline it did not have. It is now aimed at what a sentence cannot fix: data only we hold. The next training run has to beat 197 with W1, not the old baseline.

The full scoreboard, the trade-off chart and the timeline are on Project M&M. Every question and every miss is on the 200.