I Built a Hallucination Gate for My Own Writing. The Bigger Local Model Didn't Catch More
A 40-claim eval for a local hallucination checker on 8B, 9B and 27B. The 27B caught fewer than the 9B, the union of the two small models beat every single model, and a frontier judge finished one claim ahead.
I write technical posts with an LLM in the loop, and the failure I care about isn't bad prose. It's a number that looks right, sits in a plausible sentence, and came from nowhere. So I built a gate: before anything gets published, every sentence carrying a specific value gets checked by a local model.
Then I did the thing you're supposed to do and actually measured whether the gate works. Three local models, one frontier model, 40 claims with known answers. Almost nothing came out the way I expected.
What the gate actually checks
The important design decision first, because it explains the results: the gate does not check whether a claim is true. It checks whether the claim is attributed.
- Attributed — the author's own measurement ("on my 3090 I measured about 75 tok/s"), or a citation a reader could go look up, or arithmetic, or a straightforward consequence of the author's own setup.
- Floating — a specific number, benchmark, statistic, or named finding asserted as fact, with no owner and no checkable source.
Floating claims get flagged for a human to cite, own, or cut. That's it. It's a narrow job, and picking a narrow job on purpose is what makes it runnable on hardware I own.
The eval
40 claims, each labelled by hand, in the domains I actually write about — consumer-GPU inference and proteomics.
Twenty are fabrications, spread across the shapes fabrications actually take: vague appeals to authority ("a 2025 Stanford study found that 73%…"), third-party benchmark results, model specs and prices, invented findings attributed to real papers, and performance numbers about hardware phrased in the third person.
The other twenty are the part that matters. They're real claims that must not be flagged: my own measurements, claims where the attribution lives in the surrounding paragraph rather than the sentence, genuine citations, arithmetic, and capacity statements about my own cards.
Without those twenty decoys the eval is meaningless. A gate that flags everything scores 100% recall and is completely useless, because you stop reading its output by the third post. False positives are what kill a checker in practice, so half the eval exists to measure them.
The runner imports the live gate's own prompt rather than a copy, so the eval measures the thing that actually runs.
Results
All local models Q4_K_M through Ollama, temperature 0, on my own box — two GTX 1080 Tis.
| judge | recall | precision | F1 | FP rate | sec/claim |
|---|---|---|---|---|---|
| qwen3:8b | 75% | 100% | 0.86 | 0% | 0.7 |
| qwen3.5:9b | 90% | 95% | 0.92 | 5% | 2.9 |
| qwen3.6:27b | 75% | 94% | 0.83 | 5% | 6.5 |
| qwen3:8b + qwen3.5:9b | 95% | 95% | 0.95 | 5% | 3.7 |
| frontier (Claude Opus 5) | 95% | 100% | 0.97 | 0% | — |
An honest note on that timing column before anyone quotes it. These are 11GB cards, and only the 8B ran fully on GPU (37/37 layers). The 9B was partly spilled on some loads (19/34) and the 27B spilled every time (50/66 layers). So the 27B's 6.5 s/claim is this model not fitting on this box, not a clean statement about 27B inference. The quality columns are unaffected — spilling changes speed, not output.
Before the last prompt change I ran all three local models three times each, and every one returned identical verdicts on all three passes. At temperature 0 this judgement is stable run to run, so single-run numbers are safe to compare — but the table above is one pass per model, not an average.
Surprise 1: the 27B was worse
It caught fewer fabrications than the 9B (75% vs 90%) and had a lower F1. Three times the parameters, no improvement.
This looks less strange once you notice what the task is. The gate isn't asking the model to know whether GLM-5.2 has 744B parameters. It's asking whether the sentence containing that number is owned or floating. That's a judgement about the shape of a sentence, and it seems to saturate early — somewhere below 8B — after which extra capacity buys nothing.
If your instinct is that hallucination-checking needs the biggest model you can fit, this is at least one task where that instinct is wrong.
Surprise 2: the two small models were better together than either alone
The 8B and 9B don't fail on the same claims. The 8B is conservative — it never raised a false alarm on the whole set, but it slept through a quarter of the fabrications. The 9B is more aggressive: it catches more and occasionally flags something legitimate.
Take the union — flag if either model flags — and you get 95% recall at 95% precision, F1 0.95, which beats every single model including the 27B, at roughly half the 27B's runtime. The gate now runs both by default.
This is the same result that shows up whenever anyone tests self-verification: a model is bad at catching its own mistakes, and a second, different model recovers some of what the first one missed. Two cheap models disagreeing is worth more than one expensive model agreeing with itself.
Surprise 3: the frontier model was one claim ahead
I had a frontier model judge the same 40 claims with the same criteria. It scored 95% recall, 100% precision — better, but the entire margin over the local ensemble is one claim, and (see below) that one claim turned out to be my own labelling mistake.
This row is not a blind evaluation and I'm not going to pretend otherwise: the same model that acted as frontier judge also wrote the claim set and its ground-truth labels. Treat it as a favourably-biased upper bound. It's still informative — it says the ceiling on this task is near, and two small local models are already sitting close to it.
The messy part: fixing one category broke another
Early on, all three models missed the same two claims:
"A GTX 1080 Ti runs Gemma 4 27B at 21 tok/s with Q4 quantization." "Dual 1080 Tis hit 96 tok/s on a 12B model when NVLink is enabled."
Someone else's numbers wearing my hardware's name. Every model waved them through, because I own those cards and the surrounding text is full of my measurements. This is the most dangerous fabrication shape there is, and model size made no difference to it.
So I added a rule: a performance figure about hardware you own, phrased generically ("a GTX 1080 Ti runs…") rather than possessively ("my 1080 Ti gets…"), is floating — because someone reporting their own measurement writes my.
It worked. That category went from 1/3 to 2/3 across all three models, and the 9B's F1 went from 0.87 to 0.92.
It also broke something. The 8B's accuracy on model-spec claims dropped from 3/5 to 1/5 — after the new rule, it started waving through "GLM-5.2 is a 744B-parameter MoE" and "Qwen3.6 27B was trained on 18 trillion tokens," both of which it had caught before. The 27B lost one too. The 9B lost nothing.
Adding a rule to a prompt is not free on a small model. It has a budget, and a new instruction can push an old one out. I only saw this because I re-ran the whole eval instead of just checking the category I was trying to fix. The ensemble absorbed the damage, which is a second argument for running two models rather than one.
The defect in my own eval, and what it exposes
The single claim the frontier judge "got wrong":
"The MOFA+ paper reports that latent factors recover 85% of cross-modality variance."
It called that attributed. My label said floating. I went to mark it as a miss and then noticed this, four rows down in my own eval set:
"The MOGONET paper describes a graph convolutional network per omics layer with a cross-omics discovery tensor."
Labelled attributed. Both name a real, findable paper. Structurally they are the same sentence. The only thing separating them is that I knew the 85% figure was invented and the MOGONET description wasn't — knowledge that exists in my head and nowhere in the text.
So the label was wrong and the judgement was right. But the useful part isn't that I mislabelled a row. It's what the mislabelling reveals:
Attribution checking catches claims with no source. It cannot catch a fabricated number attached to a real source. Not with a bigger model, not with a better prompt. The information needed to tell those apart is not in the sentence.
Catching the second kind requires actually retrieving the cited paper and comparing — which is a retrieval problem rather than a judgement one. The gate and a proper RAG verification step aren't competing approaches. They're two halves, and I'd been quietly assuming the gate covered both.
I ran the gate on this post, and it failed
Obvious thing to try, so I tried it. The gate read this article and flagged 11 of the 23 claims it checked. Nearly all of them are false alarms — my own eval results, from my own runs, on my own box:
"That category went from 1/3 to 2/3 across all three models, and the 9B's F1 went from 0.87 to 0.92." "The 8B's accuracy on model-spec claims dropped from 3/5 to 1/5."
Both mine. Both flagged. And I can see exactly why: when you report your own results in a summary register — a table, a delta, a percentage — the sentence stops looking like a personal measurement and starts looking like a cited benchmark. "I measured 32 tok/s on my 1080 Ti" is easy. "Recall went from 75% to 90%" is structurally identical to quoting someone else's paper.
Which means my eval flattered the tool. The twenty legitimate claims I wrote as decoys were mostly phrased in the first person, because that's how I imagined honest writing looks. Real writing about your own experiments is full of impersonal results-reporting, and that's the register the gate is worst at. A 5% false-positive rate on the eval became something far worse on the first real post I pointed it at.
I'd rather publish that than quietly drop the section. It's the most useful thing I learned today: an eval you wrote yourself measures the failure modes you already thought of, and the first contact with real input finds the ones you didn't.
What I'd tell someone building the same thing
Measure the false positives, and write your legitimate examples the way you actually write, not the way you imagine you write. That was my biggest miss. Half my eval is decoys and that half is the one that decided the design.
Run two small models instead of one big one. They fail differently, the union is strictly better, and it's cheaper.
Re-run the whole eval after every prompt change, including the categories you weren't touching. My "improvement" quietly cost the 8B two categories.
And be clear about what your checker can't see. Mine can tell you a number has no owner. It cannot tell you a number is wrong.
Caveats
One eval set, 40 claims, written by me, in my own domains — results may not transfer to yours. The frontier row is not blind, as noted. The timing column is confounded by VRAM: only the 8B fit entirely on these 11GB cards. Local models were Q4_K_M via Ollama at temperature 0; different quants or a different serving stack could shift things. The eval set and runner are small enough to read in one sitting, and the honest way to use any of this is to run it on your own claims rather than trust my numbers.
관련 글
The Open-Model Cost Chart Everyone's Sharing Is API Prices. Here's What Self-Hosting Actually Gets You (Measured)
6월 23일 · 7 min read
Local LLMI Added a Verify Layer to My Local RAG to Catch Hallucinations. It Caught Me Being Wrong Twice About My Own Corpus
6월 19일 · 10 min read
Local LLMWhat Actually Runs Well on a GTX 1080 Ti in 2026 (Measured)
6월 12일 · 4 min read
Local LLMGemma 4 QAT on a 1080 Ti: What 'Quantization-Aware' Actually Buys — and Fitting the 12B on 8 GB at 16k
6월 10일 · 6 min read