Evaluating LLMs
Specify tasks, score them, judge with a rubric, calibrate, rank with intervals and test long context.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Defaults and numbers
- Troubleshooting
- Questions and tips
End-to-end eval runner#
Score one task#
Pick and run a metric#
| Task | Code |
|---|---|
| Score arithmetic | "exact_match" with "strip_whitespace" |
| Score multiple choice | "exact_match" with "extract_letter" |
| Score generated code | "code_exec" with "extract_code_block" |
| Score a summary | "rouge_l" or "bleu_4" |
| Interval on a pass rate | wilson_confidence_interval(successes, |
| Rank by pairwise wins | ELOTracker(k= |
| Plant a needle | build_haystack(filler_text, |
| Run a public benchmark | lm_eval --model hf --model_args pretrained= |
From 19.70 code/main.py, 19.71 code/main.py, 11.10 code/eval_framework.py, Lesson 10.10, Lesson 05.28
Harness API#
tasks, errors = validate_file("tasks.jsonl")do not run if errorss = score("f1", prediction, targets)best F1 over the targetss = score_code_exec(prediction, assertions)passed / total, 3 s limitp = pass_at_k(n=10, c=3, k=5)unbiased estimatorreport = CalibrationReport.from_predictions(confidences, correct)report.ece, report.brier, report.populated_binsbins=10 by defaultppl = perplexity(neg_log_probs, token_counts)exp(total nll / tokens)rows = aggregate(runs, b=500, alpha=0.05)mean, bootstrap CI, win rateprint(render_markdown(rows))table for a PR commentFrom 19.70 code/main.py, Lesson 19.70, 19.71 code/main.py, 19.72 code/main.py, 19.73 code/main.py, 19.74 code/main.py
Post-process rules#
| Command | What it does |
|---|---|
none | Returns the string unchanged |
strip_whitespace | Strips leading and trailing whitespace |
lower | Lowercases the string |
extract_letter | First character in [A-E]. For MCQ |
extract_code_block | Body of the first fenced block. For code |
extract_first_line | First non-empty line. For summary classes |
Sample size and confidence#
| Test cases at 90% | 95% CI width |
|---|---|
| 50 | 19 points. Cannot detect a 5% regression |
| 100 | 12 points. Barely |
| 200 | 9 points. Yes |
| 500 | 5 points. Confidently |
| 1000 | 3 points. Precisely |
From Lesson 11.10
Tips#
- Anchor each rubric score to observable behavior. Judge variance drops 30-40%.
- Freeze the judge model and its version.
- Pin task files by hash in the leaderboard.
- Diff predictions, not only scores. Use
--include-per-example. - Keep eval cases apart from prompt and training examples.
- Test needle depths 0, 0.25, 0.5, 0.75 and 1.0.
Key formulas#
- Perplexity
- \mathrm{PPL} = \exp\left(-\frac{1}{N} \sum_i \log P(\text{token}_i \mid \text{context})\right)
- Token F1
- \frac{2 P R}{P + R}. P and R are token overlap over prediction and target.
- BLEU-4
- brevity penalty times the geometric mean of p_1 to p_4.
- Brevity penalty
- 1 if c \ge r, else \exp(1 - r / c).
- ROUGE-L
- LCS over reference length and over candidate length, then F1.
- pass@k
- 1 - \binom{n - c}{k} / \binom{n}{k} for c passes in n samples.
- ECE
- confidence minus accuracy per bin, weighted by bin size, summed.
- Brier
- \mathrm{mean}((p_i - y_i)^2). Penalizes spread that ECE misses.
Defaults in the code#
| Setting | Default | Note |
|---|---|---|
DEFAULT_TIMEOUT_S | 3.0 | Per task. MAX_TIMEOUT_S is 30.0 |
OUTPUT_CAP_BYTES | 256 * 1024 | Then the result is "output overflow" |
FEW_SHOT_MAX | 8 | Few-shot examples per task |
ECE bins | 10 | 15 is the comparison convention |
Bootstrap b | 500 | Low-level helpers use 1000 |
alpha | 0.05 | 95% percentile interval |
max_workers | 8 | Thread pool in run_eval |
ELO k | 32 | Start rating 1500 |
From 19.72 code/main.py, 19.70 code/main.py, 19.73 code/main.py, Lesson 19.73, 19.74 code/main.py, 19.75 code/main.py, Lesson 10.10
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| Perplexity below 1 | Adapter returns log p, not -log p | Negate. The function rejects it |
| Perplexity too good | Padding counted in the denominator | Count only non-pad tokens |
| Judge scores go missing | Bad JSON gives NaN, mean drops it | Catch it as an explicit failure |
| Judge rates its own model high | Self-evaluation adds 10-20% | Judge with a different model family |
| Pairwise judge picks the first | Position bias | Randomize order and run both |
| Mean 0.85, users complain | Mean hides 5% catastrophic failures | Inspect the bottom quantile |
| Passes NIAH, fails real docs | NIAH tests retrieval only | Add RULER or a multi-hop test |
| One task dominates the mean | Score in [0, 100] | Return fractions in [0, 1] |
From Lesson 19.73, Lesson 19.41, Lesson 05.27, Lesson 05.28, Lesson 19.74
Abbreviations#
- EM
- Exact Match. 1 or 0 per task after normalization.
- ECE
- Expected Calibration Error. Gap between confidence and accuracy.
- MMLU
- Massive Multitask Language Understanding. 15,908 questions, 57 subjects.
- HumanEval
- 164 Python function-completion problems.
- SWE-bench
- 2,294 GitHub issues from 12 Python repos.
- NIAH
- Needle in a Haystack. Plant a fact in filler, ask for it.
- MRCR
- Multi-Round Coreference Resolution. 8, 24 or 100 needles.
- RAGAS
- Retrieval-Augmented Generation ASsessment. Four RAG metrics.
Questions#
- How many test cases?
- At least 200 for a deploy decision. 500 or more for close systems.
- Why report pass@1 too?
- pass@k hides a model that is right once in twenty tries.
- When is a difference real?
- When the bootstrap interval of the paired difference excludes zero.
- When can I trust a judge?
- Spearman rho against human labels of 0.7 or more.
- How long is the real context?
- Reasoning-effective is often 25-50% of the advertised window.
From Lesson 11.10, Lesson 19.27, Lesson 19.74, Lesson 05.27, Lesson 05.28