Code for the paper "Integrating Local and Global Entropy for Uncertainty Quantification in LLMs".
GLU is a training-free uncertainty score that combines two complementary signals extracted from a single greedy generation:
she_R_mean (mean over the k worst-scoring tokens).S̃ of the hidden-state trajectory across generated tokens, measuring how much the model's internal representation "spreads out" over the sequence.The final score is:
GLU = (1 + S̃) * she_R_mean
she_R_mean is negative and (1 + S̃) ≥ 1, so GLU is always ≤ 0. A more negative GLU indicates higher uncertainty. AUROC is computed with GLU as the score against correctness labels (label = 1 means correct).
GLU/
├── generate_response.py # Step 1 — run models, collect UQ features, judge correctness
├── GLU.py # Step 2 — AUROC of the baseline GLU score per model–dataset
├── GLU_ablations.py # Step 2 — full ablation table across all GLU variants
├── .env.example # Azure OpenAI credentials template (for the LLM judge)
├── small_datasets/
│ ├── triviaqa_small.csv
│ ├── truthfulqa.csv
│ ├── math_small.csv
│ ├── arabica_qa.csv
│ ├── longform_subset.csv
│ └── multiturn_math.csv # not yet wired into generate_response.py
└── output/
└── full/ # created automatically; all Step 1 outputs land here
generate_response.py loads a model once, runs greedy generation on every dataset sample, and writes a JSONL file with all UQ features plus an LLM-judge correctness label.
pip install torch transformers datasets openai scikit-learn scipy pandas tqdm python-dotenv numpy
Copy .env.example to .env and fill in your Azure OpenAI credentials:
cp .env.example .env
python generate_response.py \
--model <fanar|gemma|qwen> \
--datasets <triviaqa|triviaqa_full|truthfulqa|math|arabica|longform> [<dataset2> ...] \
[--max-new-tokens 256] \
[--k 10] \
[--alpha 2.0] \
[--layer-idx -1] \
[--seed 0] \
[--rauq-alpha 0.2] \
[--rauq-use-hooks] \
[--max-samples N] \
[--output-suffix "-v2"] \
[--test]
| Argument | Default | Description |
|---|---|---|
--model | required | One of fanar (Fanar-1-9B-Instruct), gemma (Gemma-3-12B-IT), qwen (Qwen2.5-7B-Instruct) |
--datasets | required | One or more dataset keys (space-separated) |
--max-new-tokens | 256 | Max tokens to generate per sample |
--k | 10 | Top-k for logit entropy and EDL |
--alpha | 2.0 | Rényi order for matrix entropy |
--layer-idx | -1 | Hidden-state layer recorded as layer_idx_chosen (negative indexes from the end) |
--seed | 0 | RNG seed for generation |
--rauq-alpha | 0.2 | Recurrence weight in RAUQ |
--rauq-use-hooks | off | Memory-efficient RAUQ via forward hooks |
--max-samples | all | Cap number of samples processed |
--output-suffix | "" | Extra suffix on output filenames |
--test | off | Run 5 samples with verbose output |
python generate_response.py --model qwen --datasets triviaqa --test
Each run writes two files to ./output/full/:
| File | Contents |
|---|---|
{model}-{dataset}-unified-uq-full{suffix}.jsonl | One JSON object per sample: response, correctness label, all UQ scores, per-layer geometry, per-token breakdown, top-100 logits |
{model}-{dataset}-unified-uq-full{suffix}-summary.json | AUROC summary for all metrics + per-layer S_alpha AUROC |
Rows are flushed as they are generated, so a partially finished run still leaves a valid JSONL file. Samples that fail generation are skipped and not written. label is null when the judge returns something other than 0/1.
The script also prints live AUROC figures at the end of each dataset.
| Field(s) | Description |
|---|---|
id, prompt, response, ground_truth, label | Sample index, question, model response, reference answer, judge label (1 = correct) |
T, num_prompt_tokens, num_layers, layer_idx_chosen | Generation length and model/layer metadata |
U_logit, U_max, U_shannon | Mean / max / combined top-k Shannon entropy |
R_mean, R_worst, U_edl | EDL reliability (mean / worst k tokens, combined) |
she_R_mean, she_R_worst, she_U | Shannon-EU variants of the EDL scores |
u_rauq, u_rauq_per_layer | RAUQ attention-based uncertainty |
S_alpha, S_tilde | Global Matrix Rényi entropy (raw and normalized, averaged over layers) |
S_alpha_per_layer, S_tilde_per_layer | Per-layer versions of the above (lists) |
eigenvalues_per_layer | Eigenvalue spectra of the hidden-state Gram matrix |
id_per_layer | TwoNN intrinsic dimensionality per layer |
token_data | Per-token au, eu, collision_entropy, logtoku, coletoku |
top100_logits, top100_token_ids, top100_probs | Top-100 logit values, token IDs, and probabilities per token (T × 100) |
List and dict fields are stored as native JSON, so no extra decoding is needed. To load a file:
import pandas as pd
df = pd.read_json("output/full/qwen-triviaqa-unified-uq-full.jsonl", lines=True)
Both scripts read every output/full/*-unified-uq-full.jsonl file produced by Step 1. Run them from the repo root. Rows with a null label are dropped before computing AUROC.
Files written with a non-empty
--output-suffix(e.g....-unified-uq-full-v2.jsonl) don't match this pattern and are ignored.
GLU.py — single GLU score per filePrints AUROC(GLU) per model–dataset pair and the number of dropped rows.
python GLU.py
GLU_ablations.py — full ablation tableGroups files by dataset and skips incomplete runs (files with fewer rows than the most complete run for that dataset). It then prints a table of AUROC scores for all GLU variants side by side.
python GLU_ablations.py
| Column | Formula | What changes |
|---|---|---|
GLU | (1 + S̃_mean) * she_R_mean | Baseline |
GLU_rawS_mean | (1 + S_α_mean) * she_R_mean | Raw (un-normalized) Rényi entropy |
GLU_rawS_best | (1 + S_α_best) * she_R_mean | Max-over-layers raw entropy instead of mean |
GLU_stilde_best | (1 + S̃_best) * she_R_mean | Max-over-layers entropy, normalized by 1 + log T |
GLU_softplus | (1 + S̃) * she_R_mean_sp | Softplus evidence (vs. ReLU+1) |
GLU_dynK | (1 + S̃) * she_R_mean_dk | Dynamic k: k₁ = k / (1 + S̃) |
GLU_AU | (1 + S̃) * she_R_mean_au | Aleatoric only (drops epistemic EU weight) |
| Key | Model |
|---|---|
fanar | QCRI/Fanar-1-9B-Instruct |
gemma | google/gemma-3-12b-it |
qwen | Qwen/Qwen2.5-7B-Instruct |
| Key | Dataset | File |
|---|---|---|
triviaqa | TriviaQA (small split) | triviaqa_small.csv |
triviaqa_full | TriviaQA (full validation split) | triviaqa_validation.csv — not included, add it yourself |
truthfulqa | TruthfulQA | truthfulqa.csv |
math | MATH (small split) | math_small.csv |
arabica | Arabic QA | arabica_qa.csv |
longform | Long-form QA subset | longform_subset.csv |
Code for the paper "Integrating Local and Global Entropy for Uncertainty Quantification in LLMs".
GLU is a training-free uncertainty score that combines two complementary signals extracted from a single greedy generation:
she_R_mean (mean over the k worst-scoring tokens).S̃ of the hidden-state trajectory across generated tokens, measuring how much the model's internal representation "spreads out" over the sequence.The final score is:
GLU = (1 + S̃) * she_R_mean
she_R_mean is negative and (1 + S̃) ≥ 1, so GLU is always ≤ 0. A more negative GLU indicates higher uncertainty. AUROC is computed with GLU as the score against correctness labels (label = 1 means correct).
GLU/
├── generate_response.py # Step 1 — run models, collect UQ features, judge correctness
├── GLU.py # Step 2 — AUROC of the baseline GLU score per model–dataset
├── GLU_ablations.py # Step 2 — full ablation table across all GLU variants
├── .env.example # Azure OpenAI credentials template (for the LLM judge)
├── small_datasets/
│ ├── triviaqa_small.csv
│ ├── truthfulqa.csv
│ ├── math_small.csv
│ ├── arabica_qa.csv
│ ├── longform_subset.csv
│ └── multiturn_math.csv # not yet wired into generate_response.py
└── output/
└── full/ # created automatically; all Step 1 outputs land here
generate_response.py loads a model once, runs greedy generation on every dataset sample, and writes a JSONL file with all UQ features plus an LLM-judge correctness label.
pip install torch transformers datasets openai scikit-learn scipy pandas tqdm python-dotenv numpy
Copy .env.example to .env and fill in your Azure OpenAI credentials:
cp .env.example .env
python generate_response.py \
--model <fanar|gemma|qwen> \
--datasets <triviaqa|triviaqa_full|truthfulqa|math|arabica|longform> [<dataset2> ...] \
[--max-new-tokens 256] \
[--k 10] \
[--alpha 2.0] \
[--layer-idx -1] \
[--seed 0] \
[--rauq-alpha 0.2] \
[--rauq-use-hooks] \
[--max-samples N] \
[--output-suffix "-v2"] \
[--test]
| Argument | Default | Description |
|---|---|---|
--model | required | One of fanar (Fanar-1-9B-Instruct), gemma (Gemma-3-12B-IT), qwen (Qwen2.5-7B-Instruct) |
--datasets | required | One or more dataset keys (space-separated) |
--max-new-tokens | 256 | Max tokens to generate per sample |
--k | 10 | Top-k for logit entropy and EDL |
--alpha | 2.0 | Rényi order for matrix entropy |
--layer-idx | -1 | Hidden-state layer recorded as layer_idx_chosen (negative indexes from the end) |
--seed | 0 | RNG seed for generation |
--rauq-alpha | 0.2 | Recurrence weight in RAUQ |
--rauq-use-hooks | off | Memory-efficient RAUQ via forward hooks |
--max-samples | all | Cap number of samples processed |
--output-suffix | "" | Extra suffix on output filenames |
--test | off | Run 5 samples with verbose output |
python generate_response.py --model qwen --datasets triviaqa --test
Each run writes two files to ./output/full/:
| File | Contents |
|---|---|
{model}-{dataset}-unified-uq-full{suffix}.jsonl | One JSON object per sample: response, correctness label, all UQ scores, per-layer geometry, per-token breakdown, top-100 logits |
{model}-{dataset}-unified-uq-full{suffix}-summary.json | AUROC summary for all metrics + per-layer S_alpha AUROC |
Rows are flushed as they are generated, so a partially finished run still leaves a valid JSONL file. Samples that fail generation are skipped and not written. label is null when the judge returns something other than 0/1.
The script also prints live AUROC figures at the end of each dataset.
| Field(s) | Description |
|---|---|
id, prompt, response, ground_truth, label | Sample index, question, model response, reference answer, judge label (1 = correct) |
T, num_prompt_tokens, num_layers, layer_idx_chosen | Generation length and model/layer metadata |
U_logit, U_max, U_shannon | Mean / max / combined top-k Shannon entropy |
R_mean, R_worst, U_edl | EDL reliability (mean / worst k tokens, combined) |
she_R_mean, she_R_worst, she_U | Shannon-EU variants of the EDL scores |
u_rauq, u_rauq_per_layer | RAUQ attention-based uncertainty |
S_alpha, S_tilde | Global Matrix Rényi entropy (raw and normalized, averaged over layers) |
S_alpha_per_layer, S_tilde_per_layer | Per-layer versions of the above (lists) |
eigenvalues_per_layer | Eigenvalue spectra of the hidden-state Gram matrix |
id_per_layer | TwoNN intrinsic dimensionality per layer |
token_data | Per-token au, eu, collision_entropy, logtoku, coletoku |
top100_logits, top100_token_ids, top100_probs | Top-100 logit values, token IDs, and probabilities per token (T × 100) |
List and dict fields are stored as native JSON, so no extra decoding is needed. To load a file:
import pandas as pd
df = pd.read_json("output/full/qwen-triviaqa-unified-uq-full.jsonl", lines=True)
Both scripts read every output/full/*-unified-uq-full.jsonl file produced by Step 1. Run them from the repo root. Rows with a null label are dropped before computing AUROC.
Files written with a non-empty
--output-suffix(e.g....-unified-uq-full-v2.jsonl) don't match this pattern and are ignored.
GLU.py — single GLU score per filePrints AUROC(GLU) per model–dataset pair and the number of dropped rows.
python GLU.py
GLU_ablations.py — full ablation tableGroups files by dataset and skips incomplete runs (files with fewer rows than the most complete run for that dataset). It then prints a table of AUROC scores for all GLU variants side by side.
python GLU_ablations.py
| Column | Formula | What changes |
|---|---|---|
GLU | (1 + S̃_mean) * she_R_mean | Baseline |
GLU_rawS_mean | (1 + S_α_mean) * she_R_mean | Raw (un-normalized) Rényi entropy |
GLU_rawS_best | (1 + S_α_best) * she_R_mean | Max-over-layers raw entropy instead of mean |
GLU_stilde_best | (1 + S̃_best) * she_R_mean | Max-over-layers entropy, normalized by 1 + log T |
GLU_softplus | (1 + S̃) * she_R_mean_sp | Softplus evidence (vs. ReLU+1) |
GLU_dynK | (1 + S̃) * she_R_mean_dk | Dynamic k: k₁ = k / (1 + S̃) |
GLU_AU | (1 + S̃) * she_R_mean_au | Aleatoric only (drops epistemic EU weight) |
| Key | Model |
|---|---|
fanar | QCRI/Fanar-1-9B-Instruct |
gemma | google/gemma-3-12b-it |
qwen | Qwen/Qwen2.5-7B-Instruct |
| Key | Dataset | File |
|---|---|---|
triviaqa | TriviaQA (small split) | triviaqa_small.csv |
triviaqa_full | TriviaQA (full validation split) | triviaqa_validation.csv — not included, add it yourself |
truthfulqa | TruthfulQA | truthfulqa.csv |
math | MATH (small split) | math_small.csv |
arabica | Arabic QA | arabica_qa.csv |
longform | Long-form QA subset | longform_subset.csv |