LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnMath & StatisticsHypothesis Tests, Intervals, and pass@k
📊EasyEvaluation & Benchmarks

Hypothesis Tests, Intervals, and pass@k

Compare a code-generation model with paired evidence, uncertainty for lift, and pass@k under a fixed sampling budget.

13 min read
Learning path
Step 17 of 177 in the full curriculum
Distributions and SamplingNeural Networks from Scratch

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Sampling rates in Distributions and Sampling moved from sample to sample. Hypothesis tests build on that wobble: a one-task win isn't yet a model replacement. It matters when a platform engineering team evaluates a coding assistant that writes small functions for timestamp parsing, retry limits, permission checks, and cache invalidation. Hidden tests mark each generated function as pass or fail.

Model B passes one more task than Model A on a six-task evaluation. Is that enough evidence to replace the old model? Learn how to answer without confusing a promising result with a proven improvement.

Paired evaluation pipeline: three task rows with Model A and B pass or fail, a bootstrap interval for B minus A from minus 10 to plus 25 points with observed lift plus 7.5 that still crosses zero, and a four-task macro pass at k series rising from 0.175 at one try to 0.563 at five tries with ten candidates per task.
A trustworthy comparison holds tasks and decoding rules fixed, measures the paired lift, and reports each multi-attempt score with its sampling budget.

The numbers below are deliberately small so you can calculate them by hand. They teach the method, not a production launch threshold.

Start with paired pass or fail outcomes

Each prompt asks for one Python helper used in an internal developer workflow. Both models receive the same prompt and are checked by the same hidden tests.

TaskHidden-test requirementModel AModel B
1timestamp range is inclusivepasspass
2permission fallback preserves denialfailpass
3retry budget caps at configured limitpasspass
4event deduplication keeps latest versionfailfail
5quota rounding is deterministicpasspass
6cache invalidation skips expired keysfailfail

Convert pass to 1 and fail to 0. Then Model A has a pass rate of 3 / 6 = 0.500, Model B has 4 / 6 = 0.667, and the observed lift is 1 / 6 = 0.167, or 16.7 percentage points.

Run the calculation rather than trusting a headline.

paired-pass-rates.py
1model_a = [1, 0, 1, 0, 1, 0] 2model_b = [1, 1, 1, 0, 1, 0] 3 4rate_a = sum(model_a) / len(model_a) 5rate_b = sum(model_b) / len(model_b) 6lift = rate_b - rate_a 7 8print(f"Model A pass@1: {rate_a:.3f}") 9print(f"Model B pass@1: {rate_b:.3f}") 10print(f"observed lift: {lift:+.3f} ({lift * 100:+.1f} percentage points)")
Output
1Model A pass@1: 0.500 2Model B pass@1: 0.667 3observed lift: +0.167 (+16.7 percentage points)

A paired evaluation preserves more information than two totals. Subtract A from B for each task:

Result on one taskDifference B - ACount
both pass03
both fail02
B passes, A fails+11
A passes, B fails-10

Only disagreement tasks tell you which model won. Five ties make the benchmark look larger without giving any directional evidence.

count-disagreements.py
1model_a = [1, 0, 1, 0, 1, 0] 2model_b = [1, 1, 1, 0, 1, 0] 3 4differences = [b - a for a, b in zip(model_a, model_b)] 5b_wins = differences.count(1) 6a_wins = differences.count(-1) 7ties = differences.count(0) 8 9print("paired differences:", differences) 10print(f"B wins={b_wins}, A wins={a_wins}, ties={ties}") 11print("directional evidence comes from disagreements:", b_wins + a_wins)
Output
1paired differences: [0, 1, 0, 0, 0, 0] 2B wins=1, A wins=0, ties=5 3directional evidence comes from disagreements: 1

A hypothesis test asks how surprising the win is

The null hypothesis says that, on disagreement tasks, Model A and Model B are equally likely to win. The directional alternative says Model B wins more often.

Under that null hypothesis, each disagreement is like a fair coin: B wins or A wins. Our six-task benchmark has one disagreement, and it went to B. A one-sided p-value for the planned claim "B is better" asks:

If both models were equally likely to win a disagreement, how often would B win at least this many of the disagreements?

With one disagreement, B wins it with probability 0.5. That result isn't rare. The sample moved upward, but the evidence is weak.

Six paired tasks yield five ties and one Model B win. A donut chart shows ties dominate; the single disagreement produces a one-sided p-value of 0.50, compared with a stronger 13-to-3 sample at p approximately 0.01.
For paired binary outcomes, ties don't separate the models. One win out of one disagreement leaves a directional p-value of 0.50, far too weak for a model-replacement claim.

This exact coin calculation is easy to implement with the binomial coefficients you already know.

exact-directional-p-value.py
1from math import comb 2 3def b_wins_one_sided_p_value(b_wins: int, a_wins: int) -> float: 4 if b_wins < 0 or a_wins < 0: 5 raise ValueError("win counts must be nonnegative") 6 disagreements = b_wins + a_wins 7 if disagreements == 0: 8 return 1.0 9 tail_count = sum(comb(disagreements, wins) for wins in range(b_wins, disagreements + 1)) 10 return tail_count / (2 ** disagreements) 11 12print(f"one B win, zero A wins: p={b_wins_one_sided_p_value(1, 0):.3f}") 13print(f"thirteen B wins, three A wins: p={b_wins_one_sided_p_value(13, 3):.4f}")
Output
1one B win, zero A wins: p=0.500 2thirteen B wins, three A wins: p=0.0106

The second line shows why more disagreement evidence matters. A result with 13 B wins and 3 A wins under a predeclared directional question is much harder to explain with an equal-win coin.

If you had planned to detect a difference in either direction, you would use a two-sided version instead. Pick the question before seeing the winning direction.

Model B passes four tasks and Model A passes three, but there is only one disagreement. What is the safe conclusion?

Answer

Model B performed better in this small sample. The single disagreement gives weak evidence for a durable improvement, so you should collect more paired tasks before claiming the model is better.

Put an interval around the quantity you care about

A confidence interval should target the decision. If your question is "How much better is B than A on the same tasks?", calculate an interval for the paired lift B - A. Comparing two separate pass-rate intervals can hide the pairing and isn't the right decision rule.

A paired bootstrap interval repeatedly resamples complete task rows with replacement. Each resample keeps Model A's outcome beside Model B's outcome, then recomputes the mean difference. The bootstrap is a general resampling method for estimating sampling uncertainty from observed data.[1]Reference 1Bootstrap Methods: Another Look at the Jackknife.https://projecteuclid.org/journals/annals-of-statistics/volume-7/issue-1/Bootstrap-Methods--Another-Look-at-the-Jackknife/10.1214/aos/1176344552.full

Six tasks are useful for intuition but painfully sparse. Use a slightly larger illustrative evaluation with 40 paired tasks:

Paired outcomeTasks
both pass17
both fail10
B passes, A fails8
A passes, B fails5

The observed lift is (8 - 5) / 40 = 0.075, or +7.5 percentage points. Bootstrap the paired differences to see how unstable that lift remains.

paired-bootstrap-interval.py
1import numpy as np 2 3differences = np.array([0] * 27 + [1] * 8 + [-1] * 5) 4rng = np.random.default_rng(7) 5 6resamples = rng.choice(differences, size=(20_000, differences.size), replace=True) 7bootstrap_lifts = resamples.mean(axis=1) 8low, high = np.quantile(bootstrap_lifts, [0.025, 0.975]) 9 10print(f"observed paired lift: {differences.mean() * 100:+.1f} percentage points") 11print(f"approximate 95% bootstrap interval: {low * 100:+.1f} to {high * 100:+.1f} points") 12print("interval includes zero:", low <= 0 <= high)
Output
1observed paired lift: +7.5 percentage points 2approximate 95% bootstrap interval: -10.0 to +25.0 points 3interval includes zero: True
Paired bootstrap interval for Model B minus Model A spanning minus 10 to plus 25 percentage points, with observed lift plus 7.5 points. Because zero lies inside the interval, the model comparison remains inconclusive.
Measure the paired lift directly. The observed `+7.5` points look promising, but zero lies inside the `−10.0` to `+25.0` interval, so this evaluation can't yet distinguish improvement from no gain or regression.

Bootstrap intervals are approximate, especially with tiny or highly discrete samples. They help you see uncertainty; they don't turn weak evidence into certainty. Here the correct statement is: "B gained 7.5 points in this paired sample, and the interval still includes zero."

Write that conclusion as a rule your evaluation report can enforce.

language-from-lift-interval.py
1def comparison_claim(observed_lift: float, interval: tuple[float, float]) -> str: 2 low, high = interval 3 if low > high: 4 raise ValueError("interval low must not exceed high") 5 if low > 0: 6 return f"evidence of improvement: estimated lift {observed_lift:+.3f}" 7 if high < 0: 8 return f"evidence of regression: estimated lift {observed_lift:+.3f}" 9 return f"inconclusive: estimated lift {observed_lift:+.3f}, interval crosses zero" 10 11print(comparison_claim(0.075, (-0.100, 0.250))) 12print(comparison_claim(0.075, (0.010, 0.140)))
Output
1inconclusive: estimated lift +0.075, interval crosses zero 2evidence of improvement: estimated lift +0.075

pass@k measures attempts

So far, each task used one candidate completion from each model. A coding assistant can also generate several candidate functions and let an evaluator check whether at least one passes hidden tests. That's the question measured by pass@k in functional code-generation evaluations such as HumanEval.[2]Reference 2Evaluating Large Language Models Trained on Code (HumanEval).https://arxiv.org/abs/2107.03374

Consider this illustrative ordered sample of three platform-helper tasks with three completions apiece. It shows one realized batch, not the HumanEval estimator:

TaskAttempt 1Attempt 2Attempt 3first_attempt_hitany_of_3_hit
parse timestamp windowpassfailfail11
retry-state fallbackfailfailpass01
quota split roundingfailfailfail00

Here first_attempt_hit records whether Attempt 1 passed, and any_of_3_hit asks whether any of three completions passed. These columns illustrate how a larger attempt budget can solve more tasks. They are not what a HumanEval-style harness reports.

pass-at-k-from-attempt-table.py
1attempts = [ 2 [1, 0, 0], 3 [0, 0, 1], 4 [0, 0, 0], 5] 6 7first_attempt_hit = sum(row[0] for row in attempts) / len(attempts) 8any_of_3_hit = sum(any(row[:3]) for row in attempts) / len(attempts) 9 10print(f"first_attempt_hit: {first_attempt_hit:.3f}") 11print(f"any_of_3_hit: {any_of_3_hit:.3f}") 12print("extra solved tasks from extra attempts:", int((any_of_3_hit - first_attempt_hit) * len(attempts)))
Output
1first_attempt_hit: 0.333 2any_of_3_hit: 0.667 3extra solved tasks from extra attempts: 1

For reporting HumanEval-style numbers, treat the n candidates as an unordered bag and use the combinatorial estimator on the count of correct outcomes. For the same three tasks with n=3 per task, correct counts are c = [1, 1, 0], so unbiased pass@1 is the average of c/n:

13(13+13+03)≈0.222\frac{1}{3}\left(\frac{1}{3} + \frac{1}{3} + \frac{0}{3}\right) \approx 0.22231​(31​+31​+30​)≈0.222

That is not "column 0 of an ordered table." When k=1, the combinatorial formula always reduces to c/n, regardless of which sample happened to appear first. The model isn't being awarded the same budget at k=1 and k=3.

When a task has n=10 candidates and c=2 correct, what is HumanEval-style pass@1?

Answer

0.2. For k=1 the combinatorial estimator is c/n. It doesn't depend on which of the ten samples came first in generation order.

For pass@k, a report must publish k, the number of generated samples, the decoding policy, and the tests used to judge correctness. A higher score under a larger attempt budget isn't evidence of stronger single-candidate behavior.

Derive the HumanEval estimator by hand

Suppose the assistant generates n = 10 candidate implementations for one function and hidden tests accept c = 2 of them. You want the expected pass@5 result if you select five candidates from that pool.

Counting success cases is tedious. Count the failure case instead:

  1. There are 10 - 2 = 8 failing candidates.
  2. There are C(10, 5) = 252 ways to choose five candidates.
  3. There are C(8, 5) = 56 all-failing choices.
  4. The chance of at least one pass is 1 - 56 / 252 = 0.778.

In notation:

pass@k=1−(n−ck)(nk)\text{pass@k} = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}pass@k=1−(kn​)(kn−c​)​

The quantity we care about is still generative: the probability that at least one of k i.i.d. model samples passes, often written 1−(1−p)k1 - (1-p)^k1−(1−p)k for unknown per-sample success rate ppp. The combinatorial formula is an unbiased U-statistic for that generative quantity; combinatorially it equals the fraction of kkk-subsets of the nnn draws that contain a success. The paper therefore samples n≥kn \ge kn≥k and reports the U-statistic, not a with-replacement plug-in. Chen and colleagues use this unbiased estimator for HumanEval evaluation after generating more samples per task than the reported k.[2]Reference 2Evaluating Large Language Models Trained on Code (HumanEval).https://arxiv.org/abs/2107.03374

pass-at-k-hand-calculation.py
1from math import comb 2 3n = 10 4c = 2 5k = 5 6all_groups = comb(n, k) 7all_failing_groups = comb(n - c, k) 8score = 1 - all_failing_groups / all_groups 9 10print("all groups:", all_groups) 11print("all-failing groups:", all_failing_groups) 12print(f"pass@5: {score:.3f}")
Output
1all groups: 252 2all-failing groups: 56 3pass@5: 0.778

Naive estimator bias: with replacement vs. without replacement

A tempting plug-in estimate for pass@k is:

pass@knaive=1−(1−p^)k\text{pass@k}_{\text{naive}} = 1 - (1 - \hat{p})^kpass@knaive​=1−(1−p^​)k

where p^=cn\hat{p} = \frac{c}{n}p^​=nc​ is the observed single-sample success rate. The plug-in 1−(1−p^)k1-(1-\hat{p})^k1−(1−p^​)k is a biased estimator of the generative pass@k probability 1−(1−p)k1-(1-p)^k1−(1−p)k. The combinatorial HumanEval form is unbiased for that same generative quantity; combinatorially it also equals the chance that at least one of kkk distinct selections from the frozen pool of nnn candidates succeeds.[2]Reference 2Evaluating Large Language Models Trained on Code (HumanEval).https://arxiv.org/abs/2107.03374

After drawing a failing candidate without replacement, correct candidates make up a larger share of the remaining pool. For 0<c<n0 < c < n0<c<n and k>1k > 1k>1, the plug-in calculation is strictly smaller than the unbiased combinatorial estimate on the same (n,c,k)(n,c,k)(n,c,k).

For n=10n=10n=10, c=2c=2c=2, and k=5k=5k=5, compare both calculations:

naive-vs-unbiased-passk.py
1p_hat = c / n 2naive_score = 1.0 - (1.0 - p_hat) ** k 3unbiased_score = score # From previous cell: 1 - comb(8, 5) / comb(10, 5) 4 5shortfall = unbiased_score - naive_score 6print(f"Plug-in pass@5 1-(1-c/n)^k: {naive_score:.4f}") 7print(f"Unbiased combinatorial pass@5: {unbiased_score:.4f}") 8print(f"Plug-in shortfall vs unbiased estimate: {shortfall:.4f}") 9 10assert abs(naive_score - 0.67232) < 1e-5
Output
1Plug-in pass@5 1-(1-c/n)^k: 0.6723 2Unbiased combinatorial pass@5: 0.7778 3Plug-in shortfall vs unbiased estimate: 0.1055

Two boundary checks should feel right:

  • When c = 0, no selected group can pass.
  • When fewer than k failures exist, every k-sized group contains at least one passing candidate.

For larger values of n, avoid assembling enormous combinations. The HumanEval paper gives an equivalent product implementation that stays numerically well behaved.[2]Reference 2Evaluating Large Language Models Trained on Code (HumanEval).https://arxiv.org/abs/2107.03374

stable-pass-at-k.py
1import numpy as np 2 3def pass_at_k(n: int, c: int, k: int) -> float: 4 if n <= 0 or not 0 <= c <= n or not 1 <= k <= n: 5 raise ValueError("require n > 0, 0 <= c <= n, and 1 <= k <= n") 6 if n - c < k: 7 return 1.0 8 failure_probability = np.prod(1.0 - k / np.arange(n - c + 1, n + 1)) 9 return float(1.0 - failure_probability) 10 11print(f"n=10, c=2, k=1: {pass_at_k(10, 2, 1):.3f}") 12print(f"n=10, c=2, k=5: {pass_at_k(10, 2, 5):.3f}") 13print(f"no passing candidates: {pass_at_k(10, 0, 5):.3f}") 14print(f"not enough failures: {pass_at_k(10, 8, 5):.3f}") 15 16try: 17 pass_at_k(10, 11, 5) 18except ValueError as error: 19 print(error)
Output
1n=10, c=2, k=1: 0.200 2n=10, c=2, k=5: 0.778 3no passing candidates: 0.000 4not enough failures: 1.000 5require n > 0, 0 <= c <= n, and 1 <= k <= n

Average per-task estimates, not raw completions

A benchmark score averages task-level results. One difficult permission-check function counts as one task; it shouldn't disappear beneath hundreds of samples from an easier string formatter.

Assume four tasks each produce n = 10 candidates, with the following correct-count vector:

text
1[0, 1, 2, 4]

Compute each task's estimate first, then average those four estimates.

macro-average-pass-at-k.py
1import numpy as np 2 3def pass_at_k(n: int, c: int, k: int) -> float: 4 if n <= 0 or not 0 <= c <= n or not 1 <= k <= n: 5 raise ValueError("require n > 0, 0 <= c <= n, and 1 <= k <= n") 6 if n - c < k: 7 return 1.0 8 return float(1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))) 9 10correct_counts = [0, 1, 2, 4] 11for k in (1, 3, 5): 12 task_scores = [pass_at_k(10, correct, k) for correct in correct_counts] 13 print(f"pass@{k}: {np.mean(task_scores):.3f} per-task={[round(score, 3) for score in task_scores]}")
Output
1pass@1: 0.175 per-task=[0.0, 0.1, 0.2, 0.4] 2pass@3: 0.417 per-task=[0.0, 0.3, 0.533, 0.833] 3pass@5: 0.563 per-task=[0.0, 0.5, 0.778, 0.976]

Notice that pass@5 can rise dramatically while pass@1 remains modest. That's useful information if your product can test several generated candidates, but it isn't a substitute for measuring single-candidate behavior under the same protocol.

Failure case: changed protocols create fake wins

pass@k only supports a comparison when the protocol matches. Keep the same task set, hidden tests, number of generated samples n, retained attempt budget k, and decoding rule.

A deterministic decoder illustrates the trap. If every generated candidate is identical for a task, extra attempt slots can't discover a different correct solution. In a controlled deterministic fixture, pass@5 collapses to pass@1.

deterministic-candidates-add-no-search.py
1import numpy as np 2 3def pass_at_k(n: int, c: int, k: int) -> float: 4 if n <= 0 or not 0 <= c <= n or not 1 <= k <= n: 5 raise ValueError("require n > 0, 0 <= c <= n, and 1 <= k <= n") 6 if n - c < k: 7 return 1.0 8 return float(1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))) 9 10identical_failed_candidates = [0] * 10 11identical_passing_candidates = [1] * 10 12 13for name, outcomes in [ 14 ("same failed candidate", identical_failed_candidates), 15 ("same passing candidate", identical_passing_candidates), 16]: 17 correct = sum(outcomes) 18 print(name, f"pass@1={pass_at_k(10, correct, 1):.1f}", f"pass@5={pass_at_k(10, correct, 5):.1f}")
Output
1same failed candidate pass@1=0.0 pass@5=0.0 2same passing candidate pass@1=1.0 pass@5=1.0

Real serving stacks can introduce nondeterminism from outside the sampling policy, so record the actual decoder and run settings. Operationally, multiple attempts only buy search when they produce meaningfully different candidates.

A second failure is subtler: generated code that passes weak hidden tests can still be wrong on missing cases or unsafe to execute. Unit-test passing measures functional correctness under that test suite. It doesn't authorize running untrusted code in a production environment. HumanEval's authors evaluated generated code in a sandbox for that reason.[2]Reference 2Evaluating Large Language Models Trained on Code (HumanEval).https://arxiv.org/abs/2107.03374

Build an evaluation report

Put the pieces together into one report for a model-review meeting. The first comparison measures a paired pass@1 lift. The second metric measures multi-attempt capability under an explicitly recorded stochastic sampling setup.

evaluation-report.py
1import numpy as np 2 3def pass_at_k(n: int, c: int, k: int) -> float: 4 if n <= 0 or not 0 <= c <= n or not 1 <= k <= n: 5 raise ValueError("require n > 0, 0 <= c <= n, and 1 <= k <= n") 6 if n - c < k: 7 return 1.0 8 return float(1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))) 9 10model_a = np.array([1] * 17 + [0] * 10 + [0] * 8 + [1] * 5) 11model_b = np.array([1] * 17 + [0] * 10 + [1] * 8 + [0] * 5) 12paired_lift = float((model_b - model_a).mean()) 13 14correct_counts_b = [0, 1, 2, 4] 15task_scores = [pass_at_k(10, correct, 5) for correct in correct_counts_b] 16pass5 = float(np.mean(task_scores)) 17# Task-level bootstrap of macro pass@k: every with-replacement resample of the 18# four task scores (4^4 = 256), then percentile interval of the resample means. 19# With only four toy tasks the interval is huge; the point is the reporting contract. 20from itertools import product 21 22boot_means = [ 23 float(np.mean([task_scores[i] for i in idxs])) 24 for idxs in product(range(len(task_scores)), repeat=len(task_scores)) 25] 26pass5_lo, pass5_hi = np.quantile(boot_means, [0.025, 0.975]) 27protocol = { 28 "paired_tasks": 40, 29 "metric": "hidden-test functional correctness", 30 "samples_per_task": 10, 31 "reported_k": 5, 32 "decoding": "stochastic sampling, fixed settings for both models", 33} 34 35print(f"paired pass@1 lift: {paired_lift * 100:+.1f} percentage points") 36print(f"Model B pass@5 on candidate pool: {pass5:.3f}") 37print(f"pass@5 95% task-bootstrap interval: [{pass5_lo:.3f}, {pass5_hi:.3f}]") 38for key, value in protocol.items(): 39 print(f"{key}: {value}")
Output
1paired pass@1 lift: +7.5 percentage points 2Model B pass@5 on candidate pool: 0.563 3pass@5 95% task-bootstrap interval: [0.194, 0.877] 4paired_tasks: 40 5metric: hidden-test functional correctness 6samples_per_task: 10 7reported_k: 5 8decoding: stochastic sampling, fixed settings for both models

pass@k is a nonlinear U-statistic averaged over tasks. A bare point estimate reintroduces the overconfidence habit this chapter fights. Resample tasks, recompute macro pass@k on each draw, and report the percentile interval beside the point. With four toy tasks the interval is enormous; that is honest, not a bug.

Before anyone calls a winner, your report still needs:

  • an interval for the paired lift, not two unrelated headline intervals
  • the macro pass@k point estimate plus an interval or another uncertainty summary
  • the directional or two-sided hypothesis question chosen before inspecting results
  • task and hidden-test quality checks
  • latency, token cost, and sandboxing constraints for any deployment decision

Statistical significance and product value answer different questions. Even convincing statistical evidence can't tell you whether a gain justifies additional candidate generation, test execution, latency, or risk.

Practice: review a benchmark claim

A teammate writes: "Model B is better because its pass@5 is 64%, while Model A's pass@1 is 58%."

Write a review comment with three corrections:

  1. Request the same k, n, task set, tests, and decoding policy for both models.
  2. Ask for the paired lift and its uncertainty interval under that matched protocol.
  3. Ask whether added generation and test-execution budget is acceptable for the product.

A good rewritten claim would sound like this:

Under the same 200 paired platform-helper tasks and fixed pass@1 protocol, Model B improved hidden-test pass rate by 2.5 percentage points; the paired interval and cost guardrails are reported below. pass@5 is listed separately because it measures a larger candidate-search budget.

Why is an interval for B - A preferable to comparing two separate intervals here?

Answer

The tasks are paired: both models solve the same prompts under the same tests. An interval for B - A preserves that pairing and directly measures the quantity the decision needs, which is the model lift.

Why can pass@5 be much higher than pass@1 without proving better single-candidate behavior?

Answer

pass@5 permits five candidate attempts per task. It answers whether at least one of five samples passes, while pass@1 evaluates one sample. The added search budget can raise the score.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.Two models are tested on the same six tasks. Model A outcomes are [1, 0, 1, 0, 1, 0] and Model B outcomes are [1, 1, 1, 0, 1, 0], where 1 means pass. Which conclusion is supported?

Correct answer: B's pass@1 is 4/6 versus A's 3/6, but only one task is a B-only win, so the evidence is weak.

The paired differences are [0, 1, 0, 0, 0, 0]. The ties still count in the pass rates, but they don't say which model wins a disagreement. With one disagreement and one B win, the one-sided fair-coin p-value is 0.5, so this isn't strong replacement evidence.

2.A paired binary comparison has 16 disagreement tasks: B wins 13 and A wins 3. The claim 'B is better' was chosen before seeing the data. Under the null that either model is equally likely to win each disagreement, what one-sided p-value should be used?

Correct answer: sum_{w=13}^{16} C(16,w) / 2^16, about 0.0106

Under the null, each disagreement is a fair coin between a B win and an A win. For the predeclared one-sided claim that B is better, the p-value is the probability of seeing at least 13 B wins out of 16 disagreements, not the observed win fraction or one exact sequence.

3.A 40-task paired evaluation has 17 both pass, 10 both fail, 8 B-only passes, and 5 A-only passes. A paired bootstrap interval for B - A is -10.0 to +25.0 percentage points. Which procedure and conclusion match the paired design?

Correct answer: Resample complete task rows, recompute mean B - A, and call +7.5 points inconclusive because the interval crosses zero.

The observed paired lift is (8 - 5) / 40 = 0.075, or +7.5 percentage points. The bootstrap must keep each task row paired, then recompute the mean difference. Because the interval includes zero, plausible lifts include no gain or a loss, so the evidence is inconclusive.

4.For three tasks, a model's ordered attempts are [pass, fail, fail], [fail, fail, pass], and [fail, fail, fail]. What are pass@1 and pass@3 for this fixed ordered sample, and what does the difference mean?

Correct answer: pass@1 = 1/3 and pass@3 = 2/3; pass@3 counts success if any of three attempts passes.

pass@1 checks only the first attempt for each task, so one of three tasks passes. pass@3 checks whether any of the first three attempts passes, so two of three tasks pass. The higher pass@3 score reflects a larger search budget, not the same single-candidate behavior.

5.For one HumanEval-style task, a model generated n = 10 candidate implementations and c = 2 passed hidden tests. What is the pass@5 estimate for selecting five candidates from that pool?

Correct answer: 1 - C(8, 5) / C(10, 5) = 0.778

Count the failure case. There are 8 failing candidates, C(10, 5) total groups of five, and C(8, 5) all-failing groups. The chance that a selected group contains at least one passing completion is 1 - C(8, 5) / C(10, 5) = 1 - 56 / 252 = 0.778.

6.Four tasks each have n = 10 candidates, with correct counts [0, 1, 2, 4]. How should the benchmark pass@5 score be computed?

Correct answer: Compute each task's pass@5, then average the unrounded estimates to obtain about 0.563.

The unrounded task estimates are 0, 0.5, about 0.777778, and about 0.976190. Their mean is about 0.563492, reported as 0.563 to three decimal places. Averaging task estimates gives every task equal benchmark weight; pooling candidate outcomes answers a different question.

7.A teammate writes, 'Model B is better because its pass@5 is 64%, while Model A's pass@1 is 58%.' Which review comment identifies the flaw and requests a fair comparison?

Correct answer: Compare matched protocols first: same task set, tests, n, k, and decoding, then report paired lift, uncertainty, and deployment costs.

The claim compares different candidate-search budgets, so it doesn't isolate model quality. A fair review asks for the same tasks, tests, samples per task, retained k, and decoding policy, then reports paired lift with uncertainty. Deployment still depends on cost, latency, test execution, and risk.

8.A deterministic decoder returns ten identical completions for a task. The shared completion either passes every time or fails every time. How should pass@5 compare with pass@1?

Correct answer: They are equal because duplicate candidates add no new search opportunity when k increases.

If the identical candidate fails, both pass@1 and pass@5 are 0. If it passes, both scores are 1. A larger k improves search only when the generation process can produce meaningfully different candidates that may have different outcomes.

9.A generated function passes every hidden test and is being considered for execution on production data. What conclusion is justified?

Correct answer: The pass supports correctness under those tests, but coverage review and sandboxing are still required.

A hidden-test pass establishes only that the generated function met the cases and assertions in that test suite. Missing cases may still reveal incorrect behavior, and generated code may be unsafe to execute. Test quality review and sandboxing address risks that the pass result doesn't measure.

9 questions remaining.

Next Step
Continue to Neural Networks from Scratch

You can now reason about <span data-glossary="gradient">gradients</span>, vectors, and uncertainty, then judge whether evaluation results support a model claim. Next you'll turn that math into a trainable neural network, so <span data-glossary="tensor">tensors</span>, loss, and prediction errors start feeling like one connected system.

PreviousDistributions and Sampling
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Bootstrap Methods: Another Look at the Jackknife.

Efron, B. · 1979 · Annals of Statistics

https://projecteuclid.org/journals/annals-of-statistics/volume-7/issue-1/Bootstrap-Methods--Another-Look-at-the-Jackknife/10.1214/aos/1176344552.full

Evaluating Large Language Models Trained on Code (HumanEval).

Chen, M., et al. · 2021 · arXiv preprint

https://arxiv.org/abs/2107.03374

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.