Hallucination and grounding: the model was never trying to be right
The word makes it sound like a malfunction. It isn't one, and that's why the fix you're waiting for is not coming.
Download hallucination_from_scratch.py — everything below runs from it. NumPy only. There is no language model anywhere in it. What it runs is the mechanism: a store that keeps facts as overlapping features instead of as records, so answering is reconstruction rather than lookup. Every demo says so on the line above, the way the chain-of-thought and RAG pages label their stand-ins.
I shipped a support assistant that invented a refund policy. Beautifully. Correct tone, correct format, a policy that has never existed. I filed it as a bug, went looking for the setting that turns it off, and slowly worked out there isn't one — because nothing in that model's training ever rewarded being right. It was rewarded for producing likely text. On that question the likely text and the true text were different strings, and it picked the one it was trained to pick.
Everybody had told me "it hallucinates", which frames it as a component misbehaving. It isn't. It's the component working.
It's an autocomplete that read everything and remembers nothing
You know the difference between these two:
facts["pellucid"]["licence"] # a record. Exact, or KeyError.
autocomplete("pellucid is licensed under ") # a continuation. Always something.
The first has a row per fact, so a fact seen once is as retrievable as a fact seen a million times. The second has no rows. It has weights — one smear of overlapping features that every fact was added into during training — and asking it a question doesn't look anything up. It reconstructs a plausible answer out of the smear.
That is the whole page. Reconstruction is confident by construction, because a blend always produces a value. There is no cache miss. There is no KeyError. The None branch you'd have written by hand does not exist in the architecture.
| Term | Means | Programmer's analogy |
|---|---|---|
| Hallucination | Fluent output the source or the world doesn't support | A cache that synthesises a value on a miss and never tells you |
| Grounding | Putting the fact in the context so it's copied, not recalled | Passing the config in instead of hard-coding a default |
| Calibration | Stated confidence matching observed accuracy | A p99 estimate that's actually right 99% of the time |
| Abstention | Refusing to answer below a confidence threshold | Returning 503 instead of a guess |
The one formula, and what's missing from it
Read it as pipeline stages:
| Step | What it is | What it does |
|---|---|---|
x<t | the prefix | Everything so far — prompt plus what's been generated |
Pθ(xt | …) | a distribution | How likely each next token is, given that prefix |
log | a log | So multiplying probabilities becomes adding them. Float hygiene |
Σt | a sum over tokens | Score the whole corpus, one token at a time |
argmaxθ | the training loop | Move the weights until the corpus looks as likely as possible |
Ji et al. split it into intrinsic hallucination, output that contradicts its own source, and extrinsic, output the source neither supports nor contradicts. Worth having straight: the second kind is the one nobody's eval catches, because it isn't wrong against the passage, it's unverifiable from it.
Reconstruction is not recall, and confidence can't tell
The script builds a store of 24 facts about eight fictional projects. Not a dict — one 64×64 matrix that every fact is added into, weighted by how often the toy corpus said it. Popular projects appear hundreds of times, obscure ones once. One project, tessera, is deliberately a fork of lantern: its feature vector is mostly lantern's, because a store like this has no notion of "different project", only "nearby vector".
Don't take my word for it. Here's the actual output:
dict lookup ('lantern','licence') -> MIT ('tessera','licence') -> GPL-3
the store ('lantern','licence') -> MIT conf 1.000 truth MIT correct
the store ('tessera','licence') -> MIT conf 0.999 truth GPL-3 WRONG
licence question, projects ordered by how loud the corpus was:
lantern count 220 answered MIT conf 1.000 truth MIT correct
quarry count 90 answered Apache-2.0 conf 0.970 truth Apache-2.0 correct
halyard count 40 answered MIT conf 0.991 truth MIT correct
marlin count 12 answered Apache-2.0 conf 0.886 truth Apache-2.0 correct
cobble count 3 answered BSD-3 conf 0.624 truth BSD-3 correct
pellucid count 1 answered MIT conf 0.398 truth GPL-3 WRONG
tessera count 1 answered MIT conf 0.999 truth GPL-3 WRONG
over all 24 questions: accuracy 0.708
mean confidence when RIGHT 0.950 | when WRONG 0.701
highest confidence on a WRONG answer: 1.000
lowest confidence on a RIGHT answer : 0.624
Read the last two lines together. The ranges overlap. Confidence is not useless — 0.950 when right against 0.701 when wrong is real signal — but there's a wrong answer at 1.000 sitting above a right answer at 0.624, so any threshold you pick cuts through both piles. Confidence measures how much support the reconstruction found. Support correlates with truth and is not truth.
The likely continuation and the true one are different strings
This is the central claim, so here it is as one printed table. Left: what the objective prefers. Right: what's true.
question most likely p true p
--------------------------------------------------------------
tessera.licence MIT 0.999 GPL-3 0.000 <- differ
tessera.language Python 1.000 Rust 0.000 <- differ
pellucid.port 6379 0.488 9200 0.340 <- differ
cobble.language Python 0.641 Java 0.337 <- differ
lantern.licence MIT 1.000 MIT 1.000 ✓
and the corpus prior the objective is actually fitting:
P(answer | licence ) argmax = MIT 0.804 all [0.804 0.186 0.005 0.004]
P(answer | language) argmax = Python 0.568 all [0.568 0.351 0.074 0.006]
P(answer | port ) argmax = 8080 0.728 all [0.728 0.174 0.073 0.025]
80.4% of licence mentions in the toy corpus are MIT, and the objective's job is to reproduce that. Answering "MIT" for a project it saw once isn't a failure of the job — it's the job, on a question where doing the job gives the wrong answer. A fluent wrong answer is a correctly executed argmax over the wrong quantity.
Calibration makes some hallucination unavoidable
This is the part almost nobody covers, and it's the one that changed how I argue in planning meetings. A calibrated model is one whose stated confidence matches its observed accuracy: of the things it says at 70%, about 70% are true. That's a property you want. Now take facts that appeared exactly once in training. A calibrated model can't assign them probability 1, because the evidence isn't there — so it spreads probability over plausible alternatives, and some of that lands on statements that are false. Kalai & Vempala (2023) turn that into a theorem: for facts with no more support than a single mention, a calibrated model's hallucination rate is bounded below by roughly the fraction of such singleton facts. Not "tends to". Must.
The script calibrates its toy the standard way, one temperature scalar, then sweeps the abstention threshold:
raw: mean confidence 0.877 vs accuracy 0.708 -> overconfident by 0.169
calibrated temperature 1.35 -> mean confidence 0.712 vs accuracy 0.708 ✓
facts attested exactly once in the corpus: 6/24 = 0.250
threshold answered refused errors err|answered wrong/100q
--------------------------------------------------------------------
0.00 24/24 0 7 0.292 29.17
0.40 20/24 4 4 0.200 16.67
0.50 18/24 6 3 0.167 12.50
0.60 16/24 8 3 0.188 12.50
0.70 15/24 9 3 0.200 12.50
0.80 12/24 12 3 0.250 12.50
0.90 8/24 16 2 0.250 8.33
0.95 0/24 24 0 - 0.00
0.99 0/24 24 0 - 0.00
Look at the last two rows before you celebrate the zero. Zero errors, because zero answers. That's the product decision this section is about, and it's why the column worth watching is wrong/100q rather than err|answered — the second flatters a system that has quietly stopped working.
To be explicit: my toy's numbers are not evidence about any real model, and this sweep shows only the shape of the trade. The theorem is theirs; the eight fictional projects are mine.
Grounding: stop asking it to recall
The engineering answer isn't clever. If the failure is that recall reconstructs, don't ask it to recall — put the fact in the context, where copying it is the likely continuation. That's all retrieval-augmented generation does. The RAG page builds the retriever half; this is what the retriever is for.
In the script, grounding is one line: a fact present in the context adds a fixed boost to that answer's score before the softmax. Same store, same questions, with and without:
from the weights alone : 17/24 = 0.708
with the fact in context: 21/24 = 0.875
errors removed by grounding: 4
residual errors WITH the fact present: 3
tessera.licence: said MIT (conf 0.996), passage said GPL-3
tessera.language: said Python (conf 0.978), passage said Rust
tessera.port: said 8080 (conf 0.997), passage said 6379
GAMMA 0.5 -> grounded accuracy 0.875
GAMMA 1.0 -> grounded accuracy 0.875
GAMMA 3.0 -> grounded accuracy 0.875
GAMMA 6.0 -> grounded accuracy 0.917
GAMMA 10.0 -> grounded accuracy 0.958
GAMMA 20.0 -> grounded accuracy 1.000
The residual block is the half everyone omits. Three answers contradict a passage that was right there in the context, at confidence 0.98 and above, and all three are the fork — the case where the prior is loudest. In a real system that prior is "what the internet mostly says", and it does not stand down because you pasted a document above it. The GAMMA sweep is the honest version of "just use RAG". How hard a model prefers context over its own weights is a continuous property set by pretraining and instruction tuning, and you mostly don't control it. It is not a boolean you flipped on when you added a vector database.
Three ways a grounded answer is still wrong
Same store, a simulated retriever, all 24 questions, three failure modes measured separately:
failure mode wrong / asked rate
----------------------------------------------------
passage lacks the answer 10 / 24 0.417
answer contradicts passage 3 / 24 0.125
two passages disagree 6 / 24 0.250
Mode (a) is a RETRIEVAL bug. The generator behaved correctly:
it was handed nothing about the subject and filled the gap.
tessera.licence with only a lantern passage -> MIT (conf 1.000), truth GPL-3
k=1 and oversized chunks. Fix the retriever, not the prompt. Only the middle mode is arguably the model's fault; the third is a corpus problem wearing a model's face.Detecting it without a ground truth
In production you don't have the answer key. What you do have is the ability to sample the same question several times. If the model reconstructed from thin evidence the samples scatter; if it's copying something solid they agree. That's self-consistency, and it costs you k times the tokens.
mean agreement when the modal answer is RIGHT 0.941
mean agreement when it is WRONG 0.714
correlation(agreement, correctness) = 0.522 -> a real signal
and the cases it misses -- agreement >= 0.875 and still wrong: 3
tessera.licence: said MIT 8/8 times, truth GPL-3
tessera.language: said Python 8/8 times, truth Rust
tessera.port: said 8080 8/8 times, truth 6379
A real signal and a real ceiling in one block. Agreement separates the two populations — and then the fork answers wrongly 8 times out of 8, because its error is structural. The features say lantern, so every sample says lantern. Sampling variance cannot detect a bias. Use it as a flag, never as a gate. Finding out how well it works on your data is an eval problem, and that's the evals page's job — including the part where a metric that looks fine on aggregate is useless on the slice you care about. On the published side, Min et al.'s FActScore is the sane framing: split a long answer into atomic claims and score each against a source, rather than grading the paragraph as a unit.
நான் ship செய்த ஒரு support assistant ஒரு refund policy-யை கண்டுபிடித்துச் சொன்னது. மிக அழகாக. சரியான tone, சரியான format, ஆனால் அப்படி ஒரு policy எப்போதுமே இருந்ததில்லை. அதை ஒரு bug-ஆக file செய்தேன், அதை off செய்யும் setting-ஐத் தேடினேன், மெதுவாகப் புரிந்தது — அப்படி ஒன்று இல்லை. ஏனெனில் அந்த model-இன் training-ல் சரியாக இருப்பதற்கு எந்த reward-ம் இல்லை. அதற்கு reward கிடைத்தது likely text உருவாக்கியதற்கு. அந்தக் கேள்வியில் likely text-ம் true text-ம் வெவ்வேறு strings; தான் பயிற்சி பெற்ற ஒன்றைத்தான் அது எடுத்தது.
எல்லோரும் "it hallucinates" என்று சொன்னார்கள் — அது ஒரு component தவறாக நடந்துகொள்வது போலத் தோன்றும். இல்லை. அது அந்த component சரியாக வேலை செய்வது.
எல்லாவற்றையும் படித்து, எதையும் நினைவில் வைக்காத ஒரு autocomplete
இந்த இரண்டுக்கும் உள்ள வித்தியாசம் உங்களுக்குத் தெரியும்:
facts["pellucid"]["licence"] # ஒரு record. சரியாக, இல்லையேல் KeyError.
autocomplete("pellucid is licensed under ") # தொடர்ச்சி. எப்போதும் ஏதோ ஒன்று.
முதலாவதில் ஒவ்வொரு fact-க்கும் ஒரு row இருக்கிறது; ஒரு முறை பார்த்த
fact-ம் கோடி முறை பார்த்த fact-ம் ஒரே அளவு retrievable. இரண்டாவதில்
row-க்களே இல்லை. weights மட்டும்தான் — training-ன் போது ஒவ்வொரு fact-ம்
சேர்க்கப்பட்ட, ஒன்றின்மேல் ஒன்று படிந்த features-ன் ஒரு பூச்சு — கேள்வி
கேட்டால் அது எதையும் தேடிப் பார்ப்பதில்லை. அந்தப் பூச்சிலிருந்து ஒரு
நம்பத்தகுந்த பதிலை அது reconstruct செய்கிறது.
இதுதான் இந்தப் பக்கம் முழுவதும். Reconstruction கட்டமைப்பாலேயே
நம்பிக்கையுடன் இருக்கிறது, ஏனெனில் ஒரு கலவை எப்போதும் ஒரு மதிப்பைத்
தரும். cache miss இல்லை. KeyError இல்லை. நீங்கள் கையால்
எழுதியிருப்பீர்களே அந்த None branch — அது இந்த
architecture-ல் இல்லவே இல்லை.
| சொல் | அர்த்தம் | Programmer-க்கான உதாரணம் |
|---|---|---|
| Hallucination | source-ம் உலகமும் ஆதரிக்காத, ஆனால் சரளமான output | miss-ல் ஒரு மதிப்பை உருவாக்கி, அதைச் சொல்லாத cache |
| Grounding | fact-ஐ context-ல் வைப்பது, அதனால் recall அல்ல, copy | default-ஐ hard-code செய்யாமல் config-ஐ pass செய்வது |
| Calibration | சொல்லப்பட்ட confidence, கண்ட accuracy-உடன் பொருந்துவது | உண்மையிலேயே 99% நேரம் சரியாக இருக்கும் p99 மதிப்பீடு |
| Abstention | ஒரு confidence threshold-க்குக் கீழே பதில் மறுப்பது | ஊகத்திற்குப் பதிலாக 503 திருப்பித் தருவது |
அந்த ஒரு சூத்திரம், அதில் இல்லாதது
இதை pipeline stages-ஆகப் படியுங்கள்:
| படி | என்ன | என்ன செய்கிறது |
|---|---|---|
x<t | prefix | இதுவரை உள்ள அனைத்தும் — prompt மற்றும் generate ஆனது |
Pθ(xt | …) | ஒரு distribution | அந்த prefix-க்குப் பிறகு ஒவ்வொரு token-ம் எவ்வளவு likely |
log | ஒரு log | probabilities-ஐப் பெருக்குவது கூட்டலாக மாறும். Float சுகாதாரம் |
Σt | tokens மேல் கூட்டல் | முழு corpus-க்கும் மதிப்பெண், ஒரு token ஒரு முறை |
argmaxθ | training loop | corpus முடிந்தவரை likely-ஆகத் தெரியும் வரை weights-ஐ நகர்த்து |
Ji et al. இதை இரண்டாகப் பிரிக்கிறார்கள்: intrinsic hallucination — தன் source-ஐயே முரண்படும் output; extrinsic — source ஆதரிக்கவும் இல்லை, மறுக்கவும் இல்லாத output. இதைத் தெளிவாக வைத்திருங்கள்: இரண்டாவது வகையைத்தான் யாருடைய eval-ம் பிடிப்பதில்லை, ஏனெனில் அது passage-க்கு எதிராகத் தவறு அல்ல, passage-ல் இருந்து சரிபார்க்க முடியாதது.
Store-la row illa, weights dhaan. Kelvi kettaa, adhu edhaiyum thedala — oru plausible badhil-a reconstruct pannudhu. Andhaala cache miss-um illa, KeyError-um illa: "theriyala" nu solra branch architecture-la illave illa.
Reconstruction என்பது recall அல்ல, confidence-ஆல் சொல்ல முடியாது
Script எட்டு கற்பனை projects பற்றிய 24 facts-ஐக் கொண்ட ஒரு store-ஐக்
கட்டுகிறது. dict அல்ல — ஒரே 64×64 matrix, அதில் ஒவ்வொரு fact-ம்
toy corpus எத்தனை முறை சொன்னது என்ற எடையுடன் சேர்க்கப்படுகிறது. பிரபல
projects நூற்றுக்கணக்கான முறை வருகின்றன, மறைவானவை ஒரு முறை. ஒரு project,
tessera, வேண்டுமென்றே lantern-ன் fork: அதன்
feature vector பெரும்பாலும் lantern-ன்தான், ஏனெனில் இப்படி ஒரு store-க்கு
"வேறு project" என்ற கருத்தே இல்லை, "அருகிலுள்ள vector" மட்டும்தான்.
என்னை நம்ப வேண்டாம். இதோ உண்மையான output:
dict lookup ('lantern','licence') -> MIT ('tessera','licence') -> GPL-3
the store ('lantern','licence') -> MIT conf 1.000 truth MIT correct
the store ('tessera','licence') -> MIT conf 0.999 truth GPL-3 WRONG
licence question, projects ordered by how loud the corpus was:
lantern count 220 answered MIT conf 1.000 truth MIT correct
quarry count 90 answered Apache-2.0 conf 0.970 truth Apache-2.0 correct
halyard count 40 answered MIT conf 0.991 truth MIT correct
marlin count 12 answered Apache-2.0 conf 0.886 truth Apache-2.0 correct
cobble count 3 answered BSD-3 conf 0.624 truth BSD-3 correct
pellucid count 1 answered MIT conf 0.398 truth GPL-3 WRONG
tessera count 1 answered MIT conf 0.999 truth GPL-3 WRONG
over all 24 questions: accuracy 0.708
mean confidence when RIGHT 0.950 | when WRONG 0.701
highest confidence on a WRONG answer: 1.000
lowest confidence on a RIGHT answer : 0.624
கடைசி இரண்டு வரிகளையும் சேர்த்துப் படியுங்கள். இரு வரம்புகளும் ஒன்றின்மேல் ஒன்று படிகின்றன. Confidence பயனற்றது அல்ல — சரியாக இருக்கும்போது 0.950, தவறாக இருக்கும்போது 0.701 என்பது உண்மையான signal — ஆனால் 1.000-ல் ஒரு தவறான பதில், 0.624-ல் உள்ள ஒரு சரியான பதிலுக்கு மேலே அமர்ந்திருக்கிறது. அதனால் நீங்கள் தேர்ந்தெடுக்கும் எந்த threshold-ம் இரண்டு குவியல்களையும் வெட்டிச் செல்லும். Confidence அளப்பது, reconstruction எவ்வளவு ஆதரவைக் கண்டது என்பதை. ஆதரவு உண்மையுடன் தொடர்புடையது; அது உண்மை அல்ல.
Likely தொடர்ச்சியும் உண்மையான தொடர்ச்சியும் வெவ்வேறு strings
இதுதான் மையக் கூற்று, அதனால் இதோ ஒரே printed table-ஆக. இடது: objective எதை விரும்புகிறது. வலது: உண்மை என்ன.
question most likely p true p
--------------------------------------------------------------
tessera.licence MIT 0.999 GPL-3 0.000 <- differ
tessera.language Python 1.000 Rust 0.000 <- differ
pellucid.port 6379 0.488 9200 0.340 <- differ
cobble.language Python 0.641 Java 0.337 <- differ
lantern.licence MIT 1.000 MIT 1.000 ✓
and the corpus prior the objective is actually fitting:
P(answer | licence ) argmax = MIT 0.804 all [0.804 0.186 0.005 0.004]
P(answer | language) argmax = Python 0.568 all [0.568 0.351 0.074 0.006]
P(answer | port ) argmax = 8080 0.728 all [0.728 0.174 0.073 0.025]
toy corpus-ல் licence குறிப்புகளில் 80.4% MIT; objective-ன் வேலை அதை மறுஉருவாக்கம் செய்வது. ஒரு முறை மட்டும் பார்த்த project-க்கு "MIT" என்று பதில் சொல்வது அந்த வேலையின் தோல்வி அல்ல — அது அந்த வேலைதான், வேலையைச் சரியாகச் செய்வதே தவறான பதிலைத் தரும் ஒரு கேள்வியில். சரளமான தவறான பதில் என்பது, தவறான அளவின் மேல் சரியாக நிறைவேற்றப்பட்ட ஒரு argmax.
Calibration சில hallucination-ஐத் தவிர்க்க முடியாததாக்குகிறது
இந்தப் பகுதியை யாரும் பேசுவதில்லை, planning meetings-ல் நான் வாதிடும் விதத்தையே இதுதான் மாற்றியது. Calibrated model என்பது, சொல்லும் confidence கண்ட accuracy-உடன் பொருந்தும் ஒன்று: 70% என்று சொல்லும் விஷயங்களில் சுமார் 70% உண்மையாக இருக்கும். இது நீங்கள் விரும்பும் ஒரு பண்பு. இப்போது training-ல் சரியாக ஒரு முறை மட்டும் வந்த facts-ஐ எடுத்துக்கொள்ளுங்கள். ஒரு calibrated model அவற்றுக்கு probability 1 தர முடியாது, ஏனெனில் அதற்கான ஆதாரம் இல்லை — எனவே அது நம்பத்தகுந்த மாற்றுகளின் மேல் probability-ஐப் பரப்புகிறது, அதில் கொஞ்சம் தவறான statements மேல் விழுகிறது. Kalai & Vempala (2023) இதை ஒரு theorem ஆக்குகிறார்கள்: ஒரே ஒரு குறிப்புக்கு மேல் ஆதரவு இல்லாத facts-க்கு, ஒரு calibrated model-ன் hallucination rate-க்கு, அப்படிப்பட்ட singleton facts-ன் விகிதம் அளவுக்கு ஒரு கீழ் எல்லை உண்டு. "பொதுவாக அப்படி இருக்கும்" அல்ல. கட்டாயம்.
Script தன் toy-ஐ வழக்கமான முறையில், ஒரே temperature scalar-ஆல் calibrate செய்து, பிறகு abstention threshold-ஐ sweep செய்கிறது:
raw: mean confidence 0.877 vs accuracy 0.708 -> overconfident by 0.169
calibrated temperature 1.35 -> mean confidence 0.712 vs accuracy 0.708 ✓
facts attested exactly once in the corpus: 6/24 = 0.250
threshold answered refused errors err|answered wrong/100q
--------------------------------------------------------------------
0.00 24/24 0 7 0.292 29.17
0.40 20/24 4 4 0.200 16.67
0.50 18/24 6 3 0.167 12.50
0.60 16/24 8 3 0.188 12.50
0.70 15/24 9 3 0.200 12.50
0.80 12/24 12 3 0.250 12.50
0.90 8/24 16 2 0.250 8.33
0.95 0/24 24 0 - 0.00
0.99 0/24 24 0 - 0.00
அந்தப் பூஜ்ஜியத்தைக் கொண்டாடும் முன் கடைசி இரண்டு rows-ஐப் பாருங்கள்.
பூஜ்ஜிய errors, ஏனெனில் பூஜ்ஜிய பதில்கள். இந்தப் பகுதி பேசும் product
முடிவு அதுதான், அதனால்தான் கவனிக்க வேண்டிய column
err|answered அல்ல, wrong/100q — இரண்டாவது,
அமைதியாக வேலை செய்வதை நிறுத்திய ஒரு system-ஐ அழகுபடுத்திக் காட்டும்.
தெளிவாகச் சொல்கிறேன்: என் toy-ன் எண்கள் எந்த உண்மையான model பற்றியும் ஆதாரம் அல்ல, இந்த sweep காட்டுவது அந்தச் சமரசத்தின் வடிவம் மட்டுமே. theorem அவர்களுடையது; எட்டு கற்பனை projects என்னுடையவை.
Grounding: recall செய்யச் சொல்வதை நிறுத்துங்கள்
Engineering பதில் புத்திசாலித்தனமானது அல்ல. recall reconstruct செய்வதுதான் பிரச்சனை என்றால், recall செய்யச் சொல்லாதீர்கள் — fact-ஐ context-ல் வையுங்கள், அங்கே அதை copy செய்வதுதான் likely தொடர்ச்சி. retrieval-augmented generation செய்வது அவ்வளவுதான். RAG பக்கம் retriever பாதியைக் கட்டுகிறது; retriever எதற்காக என்பது இதுதான்.
Script-ல் grounding ஒரே ஒரு வரி: context-ல் இருக்கும் ஒரு fact, softmax-க்கு முன் அந்தப் பதிலின் score-க்கு ஒரு நிலையான boost சேர்க்கிறது. அதே store, அதே கேள்விகள், grounding-உடன் மற்றும் இல்லாமல்:
from the weights alone : 17/24 = 0.708
with the fact in context: 21/24 = 0.875
errors removed by grounding: 4
residual errors WITH the fact present: 3
tessera.licence: said MIT (conf 0.996), passage said GPL-3
tessera.language: said Python (conf 0.978), passage said Rust
tessera.port: said 8080 (conf 0.997), passage said 6379
GAMMA 0.5 -> grounded accuracy 0.875
GAMMA 1.0 -> grounded accuracy 0.875
GAMMA 3.0 -> grounded accuracy 0.875
GAMMA 6.0 -> grounded accuracy 0.917
GAMMA 10.0 -> grounded accuracy 0.958
GAMMA 20.0 -> grounded accuracy 1.000
residual block-தான் எல்லோரும் விட்டுவிடும் பாதி. மூன்று பதில்கள், context-ல் அங்கேயே இருந்த ஒரு passage-ஐ முரண்படுகின்றன, 0.98-க்கு மேல் confidence-உடன், மூன்றுமே fork — prior மிக உரக்கப் பேசும் வழக்கு. ஒரு உண்மையான system-ல் அந்த prior என்பது "internet பெரும்பாலும் சொல்வது", நீங்கள் ஒரு document-ஐ மேலே paste செய்ததற்காக அது பணிந்து விலகாது. GAMMA sweep என்பது "just use RAG" என்பதன் நேர்மையான வடிவம். ஒரு model தன் weights-ஐ விட context-ஐ எவ்வளவு வலுவாக விரும்பும் என்பது pretraining-ம் instruction tuning-ம் நிர்ணயிக்கும் ஒரு தொடர்ச்சியான பண்பு, அது பெரும்பாலும் உங்கள் கட்டுப்பாட்டில் இல்லை. ஒரு vector database சேர்த்ததால் நீங்கள் on செய்த boolean அல்ல அது.
Grounding-kku aprom-um moonu badhil passage-ai muranpaduthudhu — ellaame fork case, conf 0.98-kku mela. GAMMA sweep dhaan "just use RAG"-oda nejam: context-ai prefer panradhu oru continuous property, neenga podura switch illa.
Grounded பதில் இன்னும் தவறாக இருக்கும் மூன்று வழிகள்
அதே store, ஒரு simulated retriever, 24 கேள்விகளும், மூன்று failure modes தனித்தனியாக அளக்கப்படுகின்றன:
failure mode wrong / asked rate
----------------------------------------------------
passage lacks the answer 10 / 24 0.417
answer contradicts passage 3 / 24 0.125
two passages disagree 6 / 24 0.250
Mode (a) is a RETRIEVAL bug. The generator behaved correctly:
it was handed nothing about the subject and filled the gap.
tessera.licence with only a lantern passage -> MIT (conf 1.000), truth GPL-3
k=1-ஆலும் அளவுக்கு அதிகமான chunks-ஆலும் நீங்களே உங்களுக்குச் செய்துகொள்வதை ஏற்கனவே காட்டுகிறது. prompt-ஐ அல்ல, retriever-ஐச் சரிசெய்யுங்கள். நடுவிலுள்ள mode மட்டுமே model-ன் தவறு எனச் சொல்லலாம்; மூன்றாவது, model-ன் முகமூடி அணிந்த ஒரு corpus பிரச்சனை.ground truth இல்லாமல் இதைக் கண்டறிவது
Production-ல் உங்களிடம் விடைத்தாள் இல்லை. இருப்பது ஒன்றுதான்: அதே கேள்வியை பல முறை sample செய்யும் திறன். மெலிந்த ஆதாரத்திலிருந்து model reconstruct செய்திருந்தால் samples சிதறும்; உறுதியான ஒன்றை copy செய்திருந்தால் அவை ஒத்துப்போகும். அதுதான் self-consistency, அதற்கு k மடங்கு tokens செலவாகும்.
mean agreement when the modal answer is RIGHT 0.941
mean agreement when it is WRONG 0.714
correlation(agreement, correctness) = 0.522 -> a real signal
and the cases it misses -- agreement >= 0.875 and still wrong: 3
tessera.licence: said MIT 8/8 times, truth GPL-3
tessera.language: said Python 8/8 times, truth Rust
tessera.port: said 8080 8/8 times, truth 6379
ஒரே block-ல் ஒரு உண்மையான signal-ம் ஒரு உண்மையான உச்சவரம்பும். Agreement இரு கூட்டங்களைப் பிரிக்கிறது — பிறகு fork 8-க்கு 8 முறை தவறாகப் பதில் சொல்கிறது, ஏனெனில் அதன் தவறு கட்டமைப்பு சார்ந்தது. features lantern என்று சொல்கின்றன, எனவே ஒவ்வொரு sample-ம் lantern என்று சொல்கிறது. Sampling variance ஒரு bias-ஐக் கண்டுபிடிக்க முடியாது. இதை ஒரு flag-ஆகப் பயன்படுத்துங்கள், ஒருபோதும் ஒரு gate-ஆக அல்ல. உங்கள் data-வில் இது எவ்வளவு நன்றாக வேலை செய்கிறது என்பதைக் கண்டறிவது ஒரு eval பிரச்சனை, அது evals பக்கத்தின் வேலை — aggregate-ல் நன்றாகத் தெரியும் ஒரு metric, நீங்கள் கவலைப்படும் slice-ல் பயனற்றதாக இருக்கும் பகுதி உட்பட. வெளியிடப்பட்ட பக்கத்தில், Min et al.-ன் FActScore தான் நியாயமான வடிவம்: நீண்ட பதிலை atomic claims-ஆகப் பிரித்து, ஒவ்வொன்றையும் ஒரு source-க்கு எதிராக மதிப்பிடுங்கள்; பத்தியை ஒரு அலகாக மதிப்பிடாதீர்கள்.
मैंने एक support assistant ship किया जिसने एक refund policy गढ़ दी। बहुत ख़ूबसूरती से। सही tone, सही format, और ऐसी policy कभी थी ही नहीं। मैंने उसे bug की तरह file किया, वह setting ढूँढने लगा जो इसे बंद करती है, और धीरे-धीरे समझ आया कि ऐसी कोई setting है ही नहीं — क्योंकि उस model की training में कहीं भी सही होने का कोई reward नहीं था। reward था likely text बनाने का। उस सवाल पर likely text और true text दो अलग strings थे, और उसने वही चुना जिसके लिए उसे train किया गया था।
सबने मुझसे कहा था "it hallucinates", जो ऐसा लगता है जैसे कोई component ग़लत बर्ताव कर रहा हो। ऐसा नहीं है। यह component का काम करना है।
यह एक autocomplete है जिसने सब पढ़ा और कुछ याद नहीं रखा
इन दोनों का फ़र्क़ आप जानते हैं:
facts["pellucid"]["licence"] # एक record. ठीक-ठीक, वरना KeyError.
autocomplete("pellucid is licensed under ") # एक continuation. हमेशा कुछ न कुछ.
पहले में हर fact के लिए एक row है, इसलिए एक बार देखा गया fact भी उतना ही
retrievable है जितना दस लाख बार देखा गया। दूसरे में rows हैं ही नहीं।
उसके पास weights हैं — एक-दूसरे पर चढ़े features की एक लीपी हुई परत,
जिसमें training के दौरान हर fact जोड़ा गया — और सवाल पूछने पर वह कुछ
ढूँढता नहीं। वह उस परत से एक plausible जवाब reconstruct करता है।
यही पूरा पन्ना है। Reconstruction बनावट से ही आश्वस्त है, क्योंकि मिश्रण
हमेशा कोई न कोई value देता है। कोई cache miss नहीं है। कोई
KeyError नहीं है। वह None branch जो आपने हाथ से
लिखी होती — वह इस architecture में मौजूद ही नहीं है।
| शब्द | मतलब | Programmer की उपमा |
|---|---|---|
| Hallucination | धाराप्रवाह output जिसे source या दुनिया सहारा नहीं देती | वह cache जो miss पर value गढ़ ले और बताए भी नहीं |
| Grounding | fact को context में रखना, ताकि वह copy हो, recall नहीं | default hard-code करने की जगह config pass करना |
| Calibration | बताई गई confidence का देखी गई accuracy से मेल | वह p99 अनुमान जो सचमुच 99% बार सही होता है |
| Abstention | एक confidence threshold से नीचे जवाब देने से मना करना | अंदाज़े की जगह 503 लौटाना |
वह एक सूत्र, और उसमें जो नहीं है
इसे pipeline stages की तरह पढ़िए:
| चरण | यह क्या है | यह क्या करता है |
|---|---|---|
x<t | prefix | अब तक का सब कुछ — prompt और जो generate हुआ |
Pθ(xt | …) | एक distribution | उस prefix के बाद हर अगला token कितना likely है |
log | एक log | ताकि probabilities का गुणा जोड़ बन जाए। Float स्वच्छता |
Σt | tokens पर योग | पूरे corpus को स्कोर करो, एक बार में एक token |
argmaxθ | training loop | weights तब तक हिलाओ जब तक corpus ज़्यादा से ज़्यादा likely न दिखे |
Ji et al. इसे दो हिस्सों में बाँटते हैं: intrinsic hallucination, यानी वह output जो अपने ही source का खंडन करे; और extrinsic, वह output जिसे source न सहारा देता है न काटता है। यह साफ़ रखने लायक़ है: दूसरी क़िस्म वही है जो किसी का eval नहीं पकड़ता, क्योंकि वह passage के ख़िलाफ़ ग़लत नहीं है, बल्कि उससे जाँची ही नहीं जा सकती।
Reconstruction, recall नहीं — और confidence यह नहीं बता सकती
Script आठ काल्पनिक projects के बारे में 24 facts का एक store बनाता है।
dict नहीं — एक ही 64×64 matrix, जिसमें हर fact इस भार के साथ जोड़ा जाता
है कि toy corpus ने उसे कितनी बार कहा। लोकप्रिय projects सैकड़ों बार आते
हैं, गुमनाम एक बार। एक project, tessera, जान-बूझकर
lantern का fork है: उसका feature vector ज़्यादातर lantern का
ही है, क्योंकि ऐसे store के पास "अलग project" जैसी कोई धारणा नहीं है,
सिर्फ़ "पास का vector" है।
मेरी बात पर भरोसा मत कीजिए। यह रहा असली output:
dict lookup ('lantern','licence') -> MIT ('tessera','licence') -> GPL-3
the store ('lantern','licence') -> MIT conf 1.000 truth MIT correct
the store ('tessera','licence') -> MIT conf 0.999 truth GPL-3 WRONG
licence question, projects ordered by how loud the corpus was:
lantern count 220 answered MIT conf 1.000 truth MIT correct
quarry count 90 answered Apache-2.0 conf 0.970 truth Apache-2.0 correct
halyard count 40 answered MIT conf 0.991 truth MIT correct
marlin count 12 answered Apache-2.0 conf 0.886 truth Apache-2.0 correct
cobble count 3 answered BSD-3 conf 0.624 truth BSD-3 correct
pellucid count 1 answered MIT conf 0.398 truth GPL-3 WRONG
tessera count 1 answered MIT conf 0.999 truth GPL-3 WRONG
over all 24 questions: accuracy 0.708
mean confidence when RIGHT 0.950 | when WRONG 0.701
highest confidence on a WRONG answer: 1.000
lowest confidence on a RIGHT answer : 0.624
आख़िरी दो पंक्तियाँ साथ पढ़िए। दोनों ranges overlap करती हैं। Confidence बेकार नहीं है — सही होने पर 0.950 बनाम ग़लत होने पर 0.701 एक असली signal है — लेकिन 1.000 पर एक ग़लत जवाब 0.624 पर बैठे एक सही जवाब से ऊपर है, इसलिए आप जो भी threshold चुनें वह दोनों ढेरों को काटता है। Confidence यह नापती है कि reconstruction को कितना सहारा मिला। सहारा सच से सम्बन्धित है, सच नहीं है।
likely continuation और सच्ची continuation अलग strings हैं
यही केंद्रीय दावा है, इसलिए यह रहा एक printed table के रूप में। बाएँ: objective क्या पसंद करता है। दाएँ: सच क्या है।
question most likely p true p
--------------------------------------------------------------
tessera.licence MIT 0.999 GPL-3 0.000 <- differ
tessera.language Python 1.000 Rust 0.000 <- differ
pellucid.port 6379 0.488 9200 0.340 <- differ
cobble.language Python 0.641 Java 0.337 <- differ
lantern.licence MIT 1.000 MIT 1.000 ✓
and the corpus prior the objective is actually fitting:
P(answer | licence ) argmax = MIT 0.804 all [0.804 0.186 0.005 0.004]
P(answer | language) argmax = Python 0.568 all [0.568 0.351 0.074 0.006]
P(answer | port ) argmax = 8080 0.728 all [0.728 0.174 0.073 0.025]
toy corpus में licence के 80.4% ज़िक्र MIT हैं, और objective का काम उसी को दोहराना है। जिस project को उसने एक बार देखा उसके लिए "MIT" कहना काम की नाकामी नहीं है — यही काम है, बस ऐसे सवाल पर जहाँ काम करना ही ग़लत जवाब देता है। एक धाराप्रवाह ग़लत जवाब, ग़लत राशि पर ठीक-ठीक चलाया गया argmax है।
Calibration कुछ hallucination को अपरिहार्य बना देती है
यह हिस्सा लगभग कोई नहीं बताता, और यही वह हिस्सा है जिसने planning meetings में मेरी दलील बदल दी। Calibrated model वह है जिसकी बताई confidence उसकी देखी गई accuracy से मेल खाती है: जो बातें वह 70% पर कहता है, उनमें से लगभग 70% सच होती हैं। यह गुण आप चाहते हैं। अब वे facts लीजिए जो training में ठीक एक बार आए। एक calibrated model उन्हें probability 1 नहीं दे सकता, क्योंकि सबूत ही नहीं है — इसलिए वह probability को plausible विकल्पों पर फैलाता है, और उसमें से कुछ ऐसे statements पर गिरती है जो झूठे हैं। Kalai & Vempala (2023) इसे theorem बना देते हैं: जिन facts का सहारा एक अकेले ज़िक्र से ज़्यादा नहीं है, उनके लिए एक calibrated model की hallucination rate की एक निचली सीमा है, जो मोटे तौर पर ऐसे singleton facts के अनुपात के बराबर है। "आमतौर पर ऐसा होता है" नहीं। होना ही है।
Script अपने toy को मानक तरीक़े से, एक ही temperature scalar से calibrate करता है, फिर abstention threshold को sweep करता है:
raw: mean confidence 0.877 vs accuracy 0.708 -> overconfident by 0.169
calibrated temperature 1.35 -> mean confidence 0.712 vs accuracy 0.708 ✓
facts attested exactly once in the corpus: 6/24 = 0.250
threshold answered refused errors err|answered wrong/100q
--------------------------------------------------------------------
0.00 24/24 0 7 0.292 29.17
0.40 20/24 4 4 0.200 16.67
0.50 18/24 6 3 0.167 12.50
0.60 16/24 8 3 0.188 12.50
0.70 15/24 9 3 0.200 12.50
0.80 12/24 12 3 0.250 12.50
0.90 8/24 16 2 0.250 8.33
0.95 0/24 24 0 - 0.00
0.99 0/24 24 0 - 0.00
शून्य का जश्न मनाने से पहले आख़िरी दो rows देखिए। शून्य errors, क्योंकि
शून्य जवाब। यही वह product फ़ैसला है जिसकी यह section बात कर रही है, और
इसीलिए देखने लायक़ column err|answered नहीं,
wrong/100q है — दूसरा उस system को सजा-धजा कर दिखाता है जो
चुपचाप काम करना बंद कर चुका है।
साफ़ कह दूँ: मेरे toy के आँकड़े किसी असली model के बारे में सबूत नहीं हैं, और यह sweep सिर्फ़ इस सौदे का आकार दिखाता है। theorem उनका है; आठ काल्पनिक projects मेरे हैं।
Grounding: उससे recall करवाना बंद कीजिए
Engineering का जवाब चालाक नहीं है। अगर दिक़्क़त यह है कि recall reconstruct करता है, तो उससे recall मत करवाइए — fact को context में रखिए, जहाँ उसे copy करना ही likely continuation है। retrieval-augmented generation इतना ही करता है। RAG पन्ना retriever वाला आधा हिस्सा बनाता है; retriever किसलिए है, यह वही है।
Script में grounding एक ही लाइन है: context में मौजूद fact, softmax से पहले उस जवाब के score में एक तय boost जोड़ देता है। वही store, वही सवाल, grounding के साथ और बिना:
from the weights alone : 17/24 = 0.708
with the fact in context: 21/24 = 0.875
errors removed by grounding: 4
residual errors WITH the fact present: 3
tessera.licence: said MIT (conf 0.996), passage said GPL-3
tessera.language: said Python (conf 0.978), passage said Rust
tessera.port: said 8080 (conf 0.997), passage said 6379
GAMMA 0.5 -> grounded accuracy 0.875
GAMMA 1.0 -> grounded accuracy 0.875
GAMMA 3.0 -> grounded accuracy 0.875
GAMMA 6.0 -> grounded accuracy 0.917
GAMMA 10.0 -> grounded accuracy 0.958
GAMMA 20.0 -> grounded accuracy 1.000
residual वाला block ही वह आधा हिस्सा है जिसे सब छोड़ देते हैं। तीन जवाब उस passage का खंडन करते हैं जो context में ठीक वहीं मौजूद था, 0.98 और उससे ऊपर की confidence पर, और तीनों वही fork हैं — वह मामला जहाँ prior सबसे तेज़ बोलता है। किसी असली system में वह prior है "internet ज़्यादातर जो कहता है", और वह इसलिए पीछे नहीं हटता कि आपने ऊपर एक document चिपका दिया। GAMMA sweep "just use RAG" का ईमानदार रूप है। कोई model अपने weights के मुक़ाबले context को कितनी मज़बूती से पसंद करता है, यह pretraining और instruction tuning से तय होने वाला एक निरंतर गुण है, और वह ज़्यादातर आपके नियंत्रण में नहीं होता। यह कोई boolean नहीं है जिसे आपने vector database जोड़कर on कर दिया।
grounded जवाब के फिर भी ग़लत होने के तीन तरीक़े
वही store, एक simulated retriever, सभी 24 सवाल, और तीन failure modes अलग-अलग नापे गए:
failure mode wrong / asked rate
----------------------------------------------------
passage lacks the answer 10 / 24 0.417
answer contradicts passage 3 / 24 0.125
two passages disagree 6 / 24 0.250
Mode (a) is a RETRIEVAL bug. The generator behaved correctly:
it was handed nothing about the subject and filled the gap.
tessera.licence with only a lantern passage -> MIT (conf 1.000), truth GPL-3
k=1 और बड़े-बड़े chunks के साथ आप ख़ुद अपने साथ कर रहे हैं। prompt नहीं, retriever ठीक कीजिए। सिर्फ़ बीच वाले mode को model की ग़लती कहा जा सकता है; तीसरा तो model का चेहरा पहने एक corpus की समस्या है।ground truth के बिना इसे पकड़ना
Production में आपके पास answer key नहीं होती। जो होता है वह यह है कि आप एक ही सवाल कई बार sample कर सकते हैं। अगर model ने पतले सबूत से reconstruct किया है तो samples बिखर जाते हैं; अगर वह कुछ ठोस copy कर रहा है तो वे आपस में मिलते हैं। यही self-consistency है, और इसकी क़ीमत है k गुना tokens।
mean agreement when the modal answer is RIGHT 0.941
mean agreement when it is WRONG 0.714
correlation(agreement, correctness) = 0.522 -> a real signal
and the cases it misses -- agreement >= 0.875 and still wrong: 3
tessera.licence: said MIT 8/8 times, truth GPL-3
tessera.language: said Python 8/8 times, truth Rust
tessera.port: said 8080 8/8 times, truth 6379
एक ही block में एक असली signal और एक असली छत। Agreement दोनों आबादियों को अलग करती है — और फिर fork वाला जवाब 8 में से 8 बार ग़लत आता है, क्योंकि उसकी ग़लती संरचनात्मक है। features कहते हैं lantern, इसलिए हर sample कहता है lantern। Sampling variance किसी bias को पकड़ नहीं सकती। इसे flag की तरह इस्तेमाल कीजिए, gate की तरह कभी नहीं। आपके data पर यह कितना काम करता है, यह पता लगाना एक eval समस्या है, और वह evals पन्ने का काम है — उस हिस्से समेत जहाँ aggregate पर ठीक दिखने वाला metric उस slice पर बेकार होता है जिसकी आपको परवाह है। प्रकाशित साहित्य में Min et al. का FActScore समझदार ढाँचा है: लम्बे जवाब को atomic claims में बाँटिए और हर एक को किसी source के ख़िलाफ़ जाँचिए, बजाय पूरे पैराग्राफ़ को एक इकाई मानकर आँकने के।
The code
Pure NumPy. No torch, no autograd, no training, and no language model — just a store, a reconstruction and a softmax, so you can watch the numbers move. Every function under 15 lines.
Download the full script — 412 lines plus 6 runnable demos. Then:
python3 hallucination_from_scratch.py
# One row per project: (licence, count), (language, count), (port, count).
# `count` is how many times that fact appeared in the toy corpus.
CORPUS = {
"lantern": (("MIT", 220), ("Python", 190), ("8080", 140)),
"beacon": (("MIT", 180), ("Go", 160), ("8080", 120)),
"quarry": (("Apache-2.0", 90), ("Python", 85), ("5432", 60)),
"halyard": (("MIT", 40), ("Rust", 35), ("6379", 25)),
"marlin": (("Apache-2.0", 12), ("Go", 10), ("9200", 8)),
"cobble": (("BSD-3", 3), ("Java", 2), ("5432", 2)),
"pellucid": (("GPL-3", 1), ("Java", 1), ("9200", 1)),
"tessera": (("GPL-3", 1), ("Rust", 1), ("6379", 1)),
}
# tessera is a fork of lantern. A store like this has no notion of "different
# project"; it only has "nearby vector", so lantern's answers leak into it.
# This is the one hand-placed similarity in the script, and it is the whole
# confidently-wrong case.
SUBJ_VEC["tessera"] = 0.88 * SUBJ_VEC["lantern"] + 0.12 * SUBJ_VEC["tessera"]
def key(subject, relation):
"""Question -> a feature vector. The relation contributes less than the
subject, so two questions about the same project overlap most."""
k = SUBJ_VEC[subject] + 0.35 * REL_VEC[relation]
return k / np.linalg.norm(k)
def build_store(facts):
"""Superpose every fact into one matrix. No per-fact slot exists after."""
M = np.zeros((D, D))
for s, r, o, c in facts:
M += np.log1p(c) * np.outer(key(s, r), OBJ_VEC[o]) # log: frequency
return M # with diminishing
# returns
def answer(subject, relation, passages=(), gamma=GAMMA, temp=TEMP):
"""Returns (best answer, confidence, full distribution over candidates)."""
recalled = STORE.T @ key(subject, relation) # a blend, not a record
cands = RELATIONS[relation]
scores = np.array([OBJ_VEC[o] @ recalled for o in cands])
for p_subj, p_rel, p_obj in passages: # grounding: copy, not recall
if p_rel == relation and p_obj in cands:
match = 1.0 if p_subj == subject else 0.6 # off-subject counts less
scores[cands.index(p_obj)] += gamma * match
probs = softmax(scores / temp)
i = int(np.argmax(probs))
return cands[i], float(probs[i]), probs
Now break it
This is the part that makes it stick. Reading won't do it. You debug for a living — and every row here is a mistake I have watched a team make in a review.
| Break this | Watch for |
|---|---|
Ask for a fact in neither the weights nor the context: set marlin's language count to 0 in CORPUS, then call answer("marlin", "language", passages=[]) | Python, conf 0.649, truth Go. The fact is gone from the store and it still answers — with the corpus's favourite language, at a confidence that looks like every other answer. It never returns "I don't know", because that branch does not exist. |
Trust demo 5's mode-(a) output: answer("tessera", "licence", [("lantern", "licence", "MIT")]) | MIT (conf 1.000), truth GPL-3. The passage was about a different project. This is a retrieval bug, not a model bug — the generator was handed nothing about tessera and filled the gap, which is what it is for. |
Take confidence as correctness: gate on conf >= 0.95 and ship whatever passes | tessera.licence sails through at 0.999 and is wrong, while cobble.licence is right at 0.624 and gets thrown away. The ranges overlap; the threshold cuts through both. |
Set demo 3's threshold to 0.95 and read only the error column | 0/24 answered, 24 refused, 0 errors. A perfect hallucination rate on a product that answers nothing — the metric-gaming failure your dashboard will not flag. |
| Judge groundedness by whether the answer sounds like the passage: compare demo 4's residual lines to their passages | said MIT (conf 0.996), passage said GPL-3 — same question, same format, same vocabulary, opposite fact. Style similarity is not entailment, and an LLM judge asked "does this look supported?" scores it as supported. |
In demo 5, drop mode (b)'s copy strength from gamma=0.8 to gamma=0.3 | "answer contradicts passage" goes 0.125 → 0.208, and at 0.1 it reaches 0.250 — the passage is present and correct every time. How hard a model prefers context over its own prior is a continuous property you inherit, not a switch you flipped by adding retrieval. |
On the maths anxiety
There's less here than the word "theorem" suggests. The objective is a sum of logs — a log so multiplying probabilities becomes adding them, the same trick you use to stop a float underflowing. The store is an outer product added into a matrix, the crudest possible associative memory, about four lines. Reading from it is a matrix-vector multiply, which is a weighted average wearing a hat.
Calibration needs one idea: bucket your predictions by stated confidence, then check observed accuracy in each bucket. If the 0.7 bucket is right 70% of the time, you're calibrated. That's a groupby and a mean. The Kalai & Vempala result is a real proof and you don't need to follow it to use it — you need the statement, which is that calibration and a zero hallucination rate on rare facts are incompatible.
What separates engineers on this topic isn't maths. It's knowing that "reduce hallucination" decomposes into retrieval quality, context adherence, abstention policy and eval design — four different teams' worth of work — and being able to say which one your incident belongs to. The interview question is "how would you stop the model making things up", and the answer that lands is: you can't stop it. You can move where the facts come from, measure what's left, and decide what confidence you'll refuse below.
Where to go next
This page gave you one thing: why hallucination is the objective working rather than failing, in code you can break. It skipped the taxonomy, the metrics, the detection literature, and everything about how real models behave.
-
Survey of Hallucination in Natural Language Generation
Read the definitions for the intrinsic/extrinsic split and the metrics section for what people actually measure. The rest is organised by task — summarisation, dialogue, translation — and is a reference you dip into when your product is one of them, not a front-to-back read.
-
Calibrated Language Models Must Hallucinate
The result my calibration section leans on, stated properly. Worth it for the singleton-facts framing alone: it gives you a defensible answer to "why can't you just fix it", which is a question you'll be asked by someone who doesn't want a technical answer.
-
FActScore
How to score a long answer that's 90% right: break it into atomic claims and check each against a source. This is the measurement half I only gestured at, and it's the design you'll copy the first time someone asks for a hallucination number on a paragraph rather than a fact.
-
Extrinsic Hallucinations in LLMs
The best single map of the detection and mitigation literature: sampling methods, retrieval-based checks, fine-tuning for factuality, calibration. Where I have one toy self-consistency demo, this has the real versions and says which held up.