Catching RAG hallucinations from a transformer's hidden states: before the first wrong token is emitted.
AUROC 0.6511 on RAGTruth held-out test · drift peaks at t−2 before text onset · 0.6450 zero-shot on HaluEval
Explore real recorded runs from Qwen2.5-1.5B token-by-token and inspect mounted research plots right in your browser.
Real recorded runs from Qwen2.5-1.5B. The detector's hottest tokens on the fabricated answer are
the invented content: 1905 and Olympic. Click the animation to run the live console.
When a retrieval-augmented generation (RAG) system retrieves relevant documents, it can still generate content completely absent from the source. Traditional detectors only operate after generation finishes: employing external LLM judges, token frequency heuristics, or multi-sample logit consistency. These solutions introduce multiple seconds of latency, multiply token costs, and provide zero mechanistic explanation.
This project addresses a fundamental question: does the model's internal residual stream already reflect the hallucination before it is written?
By running the generation prompt under two conditions (with retrieved evidence
The project includes an interactive web application deployed live on GitHub Pages, featuring native CSS scroll-driven animations, active navigation scroll spy, and GPU-accelerated SVG instrumentation:
🔗 https://chirudeva-reddy.github.io/NLP-Proj/
1. Token-by-Token Playback Console
|
2. Mounted Figure Exhibition Gallery
|
3. Pre-Onset Peak Explorer
|
4. Dynamic Benchmark Matrix
|
5. Drift Reader Experience
|
6. Active Nav & Full Responsiveness
|
The prompt executes twice: once with retrieved evidence, once with empty context. The difference in internal state evolution provides the detection signal.
flowchart TD
A["<b>1 · Input Prompt</b><br/>q + Evidence (D) vs q + Empty Context (∅)<br/>Paired forward passes on Qwen2.5-1.5B"] --> B["<b>2 · Hidden-State Extraction</b><br/>Token-level states h_t across last 18 layers<br/>Forward hooks on residual stream"]
B --> S1["<b>Cosine Drift δ</b><br/>Trajectory instability<br/>AUROC 0.5989 · w: 0.383"]
B --> S2["<b>Mahalanobis m</b><br/>Off-manifold displacement<br/>AUROC 0.5389 · w: 0.164"]
B --> S3["<b>Logit Lens KL Λ</b><br/>Cross-depth disagreement<br/>AUROC 0.5732 · w: 0.206"]
B --> S4["<b>PCA Residual ρ</b><br/>Subspace escape<br/>AUROC 0.5490 · w: 0.247"]
B --> S5["<b>Causal Patching CIE</b><br/>Mechanistic ground truth<br/>AUROC 0.5138 · Localisation"]
S1 --> D["<b>4 · Robust Z-Score Fusion</b><br/>z_i = (x_i - med_train) / IQR_train<br/>s = Σ w_i · z_i (Frozen in stats.pt)"]
S2 --> D
S3 --> D
S4 --> D
D --> E["<b>5 · Pre-Generation Hallucination Score</b><br/>AUROC 0.6511 · 3.3x ECE Reduction (0.0678)<br/><b>Drift peaks at t-2 before text onset</b>"]
S5 -.->|Causal validation p < 0.05| E
click S1 href "https://chirudeva-reddy.github.io/NLP-Proj/#signals" "Inspect Cosine Drift formulation and metrics"
click S2 href "https://chirudeva-reddy.github.io/NLP-Proj/#signals" "Inspect Mahalanobis Distance formulation and metrics"
click S3 href "https://chirudeva-reddy.github.io/NLP-Proj/#signals" "Inspect Logit Lens Divergence formulation and metrics"
click S4 href "https://chirudeva-reddy.github.io/NLP-Proj/#signals" "Inspect PCA Residual Deviation formulation and metrics"
click S5 href "https://chirudeva-reddy.github.io/NLP-Proj/#evidence" "Inspect Causal Indirect Effect & Patching"
click D href "https://chirudeva-reddy.github.io/NLP-Proj/#bench" "Explore Benchmark Fusion & Bootstrap Intervals"
click E href "https://chirudeva-reddy.github.io/NLP-Proj/#onset" "Inspect Pre-Onset Temporal Precedence (t-2 Peak)"
Note on the "5 signals": Four representation signals are fused into the frozen composite shipped in
stats.pt. CIE is computed per token for causal localisation, not fusion (composite_featuresin the configuration specifies four features).
| Signal | What It Measures | Mathematical Formulation |
|---|---|---|
1 · Cosine Driftδ⁽ˡ⁾ₜAUROC 0.5989 · w 0.383 |
Trajectory instability: angular rotation of the residual stream between consecutive answer positions. Grounded continuations evolve smoothly; fabrications make discontinuous angular jumps. |
|
2 · Mahalanobism⁽ˡ⁾ₜAUROC 0.5389 · w 0.164 |
Off-manifold displacement from the empirical distribution of faithful tokens. Mean vector |
|
3 · Logit Lens KLΛ⁽ˡ⁾ₜAUROC 0.5732 · w 0.206 |
Cross-depth disagreement: projects intermediate layers directly through the unembedding matrix |
|
4 · PCA Residualρ⁽ˡ⁾ₜAUROC 0.5490 · w 0.247 |
Energy escaping outside the 16-component principal subspace spanned by faithful activations. Captures orthogonal directional departures rather than scaled variance. |
|
5 · Causal IECIE(c)AUROC 0.5138 · Localisation |
Direct causal intervention: swaps activations between faithful and hallucinated forward passes in both directions, measuring shifts in target-token generation probability. |
|
RAGTruth held-out test split · 2,655 responses · 38.9% hallucination rate · 1,000-iteration bootstrap CIs
| Detector | AUROC ↑ | 95% Bootstrap CI | F1 Span ↑ | Spearman ρ ↑ | ECE ↓ |
|---|---|---|---|---|---|
| Attention Entropy (baseline B1) | 0.6106 | [0.5891, 0.6318] | 0.5351 | 0.1869 | 0.2230 |
| Logit Confidence (baseline B2) | 0.5497 | [0.5274, 0.5716] | 0.4737 | 0.0839 | 0.1249 |
| Cosine Drift | 0.5989 | [0.5772, 0.6201] | 0.5181 | 0.1670 | 0.0472 |
| Mahalanobis Distance | 0.5389 | [0.5168, 0.5612] | 0.4528 | 0.0657 | 0.0814 |
| Logit Lens Divergence | 0.5732 | [0.5510, 0.5954] | 0.4836 | 0.1237 | 0.1112 |
| PCA Deviation | 0.5490 | [0.5269, 0.5711] | 0.4437 | 0.0828 | 0.0778 |
| CIE (Top-3 Layers) | 0.5138 | [0.4916, 0.5360] | 0.4286 | 0.0233 | 0.0824 |
| ✨ Full Composite Detector | 0.6511 | [0.6307, 0.6614] | 0.5548 | 0.2553 | 0.0678 |
Production Context for Technical Recruiters & Engineers: An AUROC of 0.6511 is a validated single-pass mechanistic signal. It delivers +0.0405 AUROC over attention-entropy, a 3.3× reduction in calibration error (0.2230 down to 0.0678), and closes 19.51% of the performance gap to multi-pass ReDeEP (0.82) : all from a single forward pass of a lightweight 1.5B model with 0 API sampling overhead. Explore the interactive benchmark table →
🕐 Pre-onset temporal precedence (E4) : The core discovery
Aligning every hallucinated span at its initial hallucinated token (
The transformer's internal representations leave the faithful manifold two tokens prior to committing the erroneous content to text, providing the physical window required for early pre-emption.
| Token Offset | Cosine Drift δ | Mahalanobis m | Logit Lens Λ | Spearman ρ | CIE |
|---|---|---|---|---|---|
| t−3 | 0.2668 | 37.9664 | 3.6684 | 87.4574 | 37.7385 |
| t−2 (peak) | 0.2872 | 34.0917 | 3.5820 | 80.0704 | 35.6801 |
| t−1 | 0.2821 | 31.4590 | 4.2375 | 72.2008 | 31.7477 |
| t (onset) | 0.2729 | 36.5722 | 3.8713 | 85.6095 | 36.3971 |
| t+1 | 0.2708 | 38.1772 | 3.5158 | 88.1041 | 38.2783 |
🔍 Inspect the mounted E4 figure in the live gallery · 1-click CLI reproduction and lightbox inspection
🎯 Causal localisation via bidirectional patching (E3)
Across 50+ paired counterfactual examples, activation states were swapped between faithful and hallucinated executions in both directions. Early self-attention layers dominate causal influence by an order of magnitude, with every component group passing statistical significance at
| Component Group | Depth Range | CIE (f → h) | CIE (h → f) | Significance |
|---|---|---|---|---|
| Early Attention | 1–25% | −1.0991 | −1.0782 | ✅ |
| Mid FFN | 26–75% | −0.0550 | −0.0299 | ✅ |
| Late FFN | 76–100% | −0.2732 | −0.1608 | ✅ |
| Copying Heads | last 25% | −0.0807 | −0.0358 | ✅ |
🔍 Inspect the mounted E3 figure in the live gallery · 1-click CLI reproduction and lightbox inspection
🌍 Zero-shot cross-domain transfer to HaluEval (E5)
The frozen composite model (trained weights, medians, and IQRs unchanged) was applied directly to HaluEval-QA without retraining. The composite score demonstrates remarkable domain stability (
| Detector | RAGTruth AUROC | HaluEval AUROC | Δ (RAGTruth − HaluEval) | Behaviour |
|---|---|---|---|---|
| Full Composite | 0.6508 | 0.6450 | +0.0058 | Stable & Robust |
| Cosine Drift | 0.6244 | 0.8345 | −0.2102 | Transfers upward |
| Mahalanobis Distance | 0.5190 | 0.7006 | −0.1815 | Transfers upward |
| Logit Lens Divergence | 0.5917 | 0.4337 | +0.1580 | Domain sensitive |
| PCA Residual Deviation | 0.5764 | 0.7186 | −0.1421 | Transfers upward |
🧱 Layer localisation, component drift & failure traces (E2, E6, E7)
Point-biserial correlation across all 28 Qwen layers indicates discriminative signal heavily concentrates in layers 15 through 25. The top three predictive layers identified are 21, 23, and 22.
Decomposing the residual updates into self-attention vs FFN sub-layers shows FFN key-value memories driving late-stage drift:
Deterministic failure traces:
| False Negative (#1289) | False Positive (#12310) | Metric Disagreement (#3574) |
|---|---|---|
![]() |
![]() |
![]() |
Clone the repository and install requirements:
git clone https://github.com/Chirudeva-Reddy/NLP-Proj.git && cd NLP-Proj
python3 -m venv .venv && source .venv/bin/activate
pip install -r Requirements.txtScore any context and answer pair locally using the pre-fitted weights in stats.pt:
python NLP-sub/scripts/live_demo.py --profile local \
--input-file NLP-sub/examples/live_demo_inputs/eiffel_tower_hallucinated_passage.json \
--show-aggregatesView Sample Output : Suspicious-token ranking and sample aggregates
Consensus suspicious-token ranking
rank | idx | token | consensus | mahalanobis | logit_lens | pca_deviation
-----+-----+---------------+-----------+-------------+------------+--------------
1 | 18 | <sp>the | 0.9206 | 75.3978 | 9.6270 | 124.8376
2 | 14 | 9 | 0.8095 | 79.2931 | 4.8447 | 132.1120
3 | 15 | 0 | 0.8095 | 72.6667 | 7.6290 | 116.7789
4 | 19 | <sp>Olympic | 0.7937 | 69.3927 | 6.0547 | 127.3764
5 | 13 | 1 | 0.7302 | 68.6997 | 5.4883 | 123.5343
Aggregate scores (max)
composite 0.4305
The top flagged tokens isolate the fabricated year 1905 and invented event Olympic Games : content entirely unsupported by the ground-truth document.
Score your own custom prompts by passing a JSON file:
{
"instruction": "Use only the retrieved context to produce the answer.",
"context": "Alexander Fleming discovered penicillin in 1928. Penicillin began being used clinically around 1941.",
"passage": "Penicillin was discovered by Alexander Fleming, and it began being used clinically around 1941."
}(Omit the "passage" key to prompt the model to generate its own answer before scoring its internal state).
Step 1 : Sequential 4-Stage Pipeline
# 1 · Run forward passes and generate float16 hidden-state artifacts
python NLP-sub/pipeline/1-infer.py --model Qwen/Qwen2.5-1.5B --layers last18 --device auto \
--output-dir NLP-sub/outputs/artifacts
# 2 · Fit μ, Σ, and PCA basis V on TRAIN split only, freeze composite weights
python NLP-sub/pipeline/2-fit.py --artifacts-dir NLP-sub/outputs/artifacts \
--output NLP-sub/outputs/stats.pt --pca-components 16
# 3 · Score the held-out test split with the frozen parameters
python NLP-sub/pipeline/3-score.py --artifacts-dir NLP-sub/outputs/artifacts \
--stats NLP-sub/outputs/stats.pt --output-dir NLP-sub/outputs/scores_test --split test
# 4 · Compute AUROC, F1, Spearman ρ, ECE with 1,000 bootstrap iterations
python NLP-sub/pipeline/4-eval.py --scores-dir NLP-sub/outputs/scores_test \
--aggregate max --n-boot 1000Step 2 : Experiments E2 through E8
# E2 · Per-layer AUROC profiling
python NLP-sub/pipeline/plot.py --artifacts-dir NLP-sub/outputs/artifacts/test \
--stats NLP-sub/outputs/stats.pt --output docs/images/layer_profile.png
# E3 · Bidirectional activation patching
python NLP-sub/scripts/e3_patching.py --model Qwen/Qwen2.5-1.5B --device auto \
--output-dir NLP-sub/outputs/e3
# E4 · Pre-onset temporal precedence evaluation
python NLP-sub/scripts/e4_temporal.py
# E5 · Zero-shot HaluEval transfer benchmark
python NLP-sub/scripts/e5_halueval.py --model Qwen/Qwen2.5-1.5B-Instruct --layers last18 --device auto \
--stats NLP-sub/outputs/stats.pt --qa-json dataset/halueval/qa_data.json \
--artifacts-dir NLP-sub/outputs/halueval_artifacts --scores-dir NLP-sub/outputs/halueval_scores \
--ragtruth-scores-dir NLP-sub/outputs/scores_test
# E6 · Self-attention vs FFN component drift
python NLP-sub/scripts/e6_component_drift.py --model Qwen/Qwen2.5-1.5B --device auto \
--artifacts-dir NLP-sub/outputs/artifacts/test --output-dir NLP-sub/outputs/e6
# E7 · Failure-case qualitative traces
python NLP-sub/scripts/e7_failures.py --model Qwen/Qwen2.5-1.5B --device auto \
--scores-dir NLP-sub/outputs/scores_test --stats NLP-sub/outputs/stats.pt \
--e6-json NLP-sub/outputs/e6/component_drift.json --output-dir NLP-sub/outputs/e7
# E8 · SOTA gap comparison with ReDeEP and LUMINA
python NLP-sub/scripts/e8_sota_gap.py --scores-dir NLP-sub/outputs/scores_test \
--aggregate max --output-dir NLP-sub/outputs/e8Step 3 : Rebuilding the GitHub Pages Demo Site
The static demo site located in docs/ is served via GitHub Pages. It dynamically loads docs/site/demo_runs.json, generated by scoring input cases:
cd NLP-sub
python scripts/export_demo_json.py --stats outputs/stats.pt --output ../docs/site/demo_runs.json
# Local preview
cd ../docs && python3 -m http.server 8000NLP-Proj/
├── README.md # Primary project overview & documentation
├── Requirements.txt # Python package dependencies
├── METRICS_VIVA.md # Viva defense reference: derivations & logic
├── docs/
│ ├── index.html # Interactive GitHub Pages portfolio site
│ ├── site/demo_runs.json # Pre-computed scoring runs for the web console
│ ├── CS_F429_Project_Report.pdf # Formal academic research report (14 pages)
│ ├── PIPELINE_RUNBOOK.md # Pipeline execution instructions
│ └── images/ # Figures, architecture diagrams, and traces
└── NLP-sub/
├── COMPREHENSIVE.md # Deep codebase architecture guide
├── pipeline/ # 1-infer · 2-fit · 3-score · 4-eval · plot
├── src/
│ ├── dataset.py # Grouped train/val/test split generator
│ ├── inference.py # Forward hook registration & hidden extraction
│ ├── metrics.py # Mathematical implementations of 5 drift signals
│ ├── scoring.py # Robust z-score composite scoring engine
│ ├── evaluate.py # Evaluation metrics (AUROC, F1, Spearman, ECE)
│ ├── component_outputs.py # Activation replay hooks for FFN/attention
│ └── halueval.py # HaluEval dataset integration
├── scripts/ # Experiments E3-E8, live demo, and export
├── examples/live_demo_inputs/ # Pre-formatted input test scenarios
└── outputs/ # Cached artifacts, stats.pt, scores (git-ignored)
| Resource | Scope & Purpose |
|---|---|
| 🌐 Live Interactive Demo | Online token-by-token scoring console with real model activations |
| 📊 Figure Exhibition Gallery | Mounted research plots with scroll reveals, category filters, and lightbox |
| 📄 Research Paper (14 Pages, PDF) | Formal 14-page manuscript: theoretical basis, protocol, contributions, and signed contribution statement |
| 📖 Pipeline Runbook | Comprehensive CLI reference, flag specifications, parameter tuning |
| 🛡️ Viva Defense Notes | Mathematical proofs, component failure analysis, viva Q&A |
| 📚 Codebase Architecture | Source code deep-dive and modular breakdown |
CS F429 Natural Language Processing · BITS Pilani, Dubai Campus
Supervised by Prof. Elakkiya Rajasekar · May 2026
| Author | Student ID | Core Contribution Area |
|---|---|---|
| Sanya Wadhawan | 2023A7PS0296U |
Methodology design, dataset processing, RAGTruth pipeline |
| Chirudeva Reddy | 2023A7PS0331U |
Experimental architecture, composite metric fusion, causal patching |
| Yusra Hakim | 2022A7PS0004U |
Related work synthesis, mechanistic interpretability review |
| Joseph Cijo | 2022A7PS0019U |
Baseline implementations, result benchmarking, documentation |
@techreport{wadhawan2026pregen,
title = {Pre-Generation Hallucination Detection via Internal Representation Drift},
author = {Wadhawan, Sanya and Reddy, Chirudeva and Hakim, Yusra and Cijo, Joseph},
institution = {BITS Pilani, Dubai Campus},
course = {CS F429: Natural Language Processing},
year = {2026},
month = {May},
url = {https://github.com/Chirudeva-Reddy/NLP-Proj}
}Built for NLP research at BITS Pilani, Dubai Campus · MIT Licensed
▶ Open the Live Interactive Demo · 📄 Read the Research Paper (14 Pages, PDF)






