MBPP › Execution-Based Evaluation

Michael BrenndoerferMarch 3, 202610 min read
Language AI HandbookAll chapters
Part 54 of 67Benchmark Evaluation
Chapter 419MBPP › Execution-Based Evaluation
Reading section 4 of 12 · Execution-Based Evaluation · 10 minSection 4 of 12 · 10 min

Book contents

Section 4 of 12 · 10 min read

MBPP › Execution-Based EvaluationOne section · Sections in this chapter4 / 12 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Both MBPP and HumanEval rely on execution-based evaluation rather than textual similarity metrics like BLEU or ROUGE. Traditional NLP metrics fail for code generation because syntactically different solutions can be semantically identical, while similar-looking code can behave completely differently.

The basic issue is that code is not text to be compared, but instructions to be executed. Two solutions might look completely different, one using list comprehensions and another using explicit loops, yet produce identical outputs for all inputs. Conversely, two code snippets might differ by only a single character, perhaps a less-than versus less-than-or-equal sign, yet produce drastically different behaviors. Execution-based evaluation cuts through this surface variation to measure the relevant behavior: does the code do what it is supposed to do?

This principle sounds obvious but imposes concrete requirements on evaluation infrastructure. Running generated code requires a test harness that can execute Python in a controlled environment, catch exceptions, enforce time limits, and aggregate results across potentially hundreds of candidates per problem. Building this infrastructure correctly involves solving problems in software engineering, security, and systems programming that have nothing to do with natural language processing.

The pass@k Metric

The standard evaluation metric for MBPP is pass@k, which measures the probability that at least one out of kk generated samples passes all test cases. This metric acknowledges the stochastic nature of language model generation: even capable models might produce incorrect code on any single attempt due to sampling randomness. By evaluating multiple samples and asking whether at least one succeeds, pass@k gives a more complete picture of model capability.

The intuition behind pass@k is straightforward. If you can only see one output from the model (the case k=1k = 1), pass@1 tells you the probability that this single output is correct. This is the most relevant metric for applications where you show users a single suggestion. If you can generate multiple candidates and use a reranker, an execution sandbox, or other selection mechanism to pick the best one (the case k>1k > 1), then pass@k for larger kk tells you the probability that at least one acceptable solution exists among the candidates.

To compute pass@k rigorously, we treat the selection of kk samples from nn total candidates as a hypergeometric sampling problem. Suppose we generate nn candidate solutions for a problem and find that cc of them are correct (pass all tests). We want the probability that a random selection of kk of these candidates contains at least one correct solution. The complement approach yields:

pass@k=EProblems[1−(n−ck)(nk)]\text{pass@k} = \mathbb{E}_{\text{Problems}}\left[1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}\right]

where:

  • nn: the total number of samples generated per problem (typically 200 in the original paper), representing the complete pool of candidate solutions from which we draw
  • cc: the number of correct samples, meaning those that pass all test assertions, representing the successful candidates within the pool
  • kk: the number of samples we select for evaluation (typically 1, 10, or 100), representing how many attempts the model gets before we check for success
  • (n−ck)\binom{n-c}{k}: the number of ways to choose kk items exclusively from the n−cn-c incorrect candidates, which is all selections containing no correct solution
  • (nk)\binom{n}{k}: the total number of ways to choose any kk samples from all nn candidates
  • EProblems\mathbb{E}_{\text{Problems}}: the expectation computed by averaging across all problems in the evaluation set

This formula works by computing the complement of the probability of failure. The fraction (n−ck)/(nk)\binom{n-c}{k} / \binom{n}{k} is the hypergeometric probability of drawing zero successes when selecting kk items from a population of nn items containing cc successes. Subtracting this from 1 yields the probability of obtaining at least one correct sample.

Deriving the Approximation

When nn is large relative to kk, the hypergeometric distribution approximates a binomial distribution. In this regime, each draw is approximately independent with success probability p=c/np = c/n. The probability of failing all kk independent draws is then (1−p)k(1 - p)^k, giving:

pass@k≈1−(1−cn)k\text{pass@k} \approx 1 - \left(1 - \frac{c}{n}\right)^k

where:

  • cn\frac{c}{n}: the empirical probability that any single randomly generated sample is correct, estimated from the pool of nn samples
  • (1−cn)k\left(1 - \frac{c}{n}\right)^k: the probability under independent Bernoulli sampling that all kk draws are incorrect

For the specific case of k=1k = 1, this simplifies to:

pass@1≈cn\text{pass@1} \approx \frac{c}{n}

which is simply the fraction of correct samples in the pool. This makes intuitive sense: if 20 out of 100 generated samples are correct, the probability that a randomly chosen single sample is correct is 20%.

For the case where the per-sample success probability is small, we can use the first-order Taylor expansion of the binomial to obtain an even simpler approximation. When p=c/n≪1p = c/n \ll 1:

pass@k≈1−(1−p)k≈1−e−kp≈kp=k⋅cn\text{pass@k} \approx 1 - (1-p)^k \approx 1 - e^{-kp} \approx kp = \frac{k \cdot c}{n}

This linear approximation holds when kpkp is small, giving the intuitive result that pass@k scales roughly linearly with both kk and the base success probability. However, this approximation breaks down as kk grows, since pass@k is bounded above by 1 but the linear approximation is not.

The key reason for using the exact combinatorial formula rather than the approximation is numerical stability and unbiasedness. The approximation introduces upward bias when cc is small relative to nn, precisely the regime where accurate estimation matters most for identifying the capability ceiling of weak models. The exact formula is unbiased regardless of the relationship between cc and nn.

Why the Combinatorial Formula Is Unbiased

The combinatorial formula for pass@k gives an unbiased estimator of the true pass@k probability. If you instead computed c/nc/n and plugged into the approximation 1−(1−c/n)k1 - (1 - c/n)^k, you would get a biased estimate because the expectation of a nonlinear function of a random variable is not the same as the nonlinear function of the expectation. The hypergeometric formulation correctly accounts for the finite population structure and produces unbiased estimates even when nn is modest. This matters when you are generating 10 or 20 samples per problem rather than 200, which is common when evaluation is expensive.

A Worked Numerical Example

Let us work through a concrete example to build intuition. Suppose we generate n=20n = 20 candidate solutions for a problem and find that c=4c = 4 of them pass all test cases. We want to compute pass@k for k∈{1,5,10}k \in \{1, 5, 10\}.

For k=1k = 1:

pass@1=1−(20−41)(201)=1−(161)(201)=1−1620=0.200\begin{aligned} \text{pass@1} &= 1 - \frac{\binom{20-4}{1}}{\binom{20}{1}} \\ &= 1 - \frac{\binom{16}{1}}{\binom{20}{1}} \\ &= 1 - \frac{16}{20} \\ &= 0.200 \end{aligned}

With 4 correct solutions out of 20, we have a 20% chance that any single sample is correct. This equals c/nc/n exactly, confirming the simplified formula for k=1k = 1.

For k=5k = 5:

pass@5=1−(165)(205)=1−436815504=1−0.2817=0.7183\begin{aligned} \text{pass@5} &= 1 - \frac{\binom{16}{5}}{\binom{20}{5}} \\ &= 1 - \frac{4368}{15504} \\ &= 1 - 0.2817 \\ &= 0.7183 \end{aligned}

With 5 samples, we have a 71.8% chance of including at least one correct solution. The approximation gives 1−(1−0.2)5=1−0.85=1−0.328=0.6721 - (1 - 0.2)^5 = 1 - 0.8^5 = 1 - 0.328 = 0.672, which is noticeably lower due to the finite population correction. Drawing without replacement makes it easier to get diverse samples, which increases the chance of hitting a correct one.

For k=10k = 10:

pass@10=1−(1610)(2010)=1−8008184756=1−0.0433=0.9567\begin{aligned} \text{pass@10} &= 1 - \frac{\binom{16}{10}}{\binom{20}{10}} \\ &= 1 - \frac{8008}{184756} \\ &= 1 - 0.0433 \\ &= 0.9567 \end{aligned}

With 10 samples drawn from our pool of 20 (which contains 4 correct solutions), we have a 95.7% chance of selecting at least one correct solution. The large improvement from 20% at k=1k=1 to 95.7% at k=10k=10 illustrates why the choice of kk matters so much for comparing models. A model that generates correct code 20% of the time at k=1k=1 is not useless: if given 10 attempts and a way to identify the correct solution, it succeeds 96% of the time.

Implementation Considerations

Calculating pass@k requires executing generated code against test cases in a controlled environment. This introduces several engineering challenges that do not exist in text-based evaluation.

In[7]:
Code
from math import comb


def calculate_pass_at_k(n, c, k):
    """
    Calculate pass@k given n total samples and c correct samples.
    Uses the exact hypergeometric formula for unbiased estimation.

    Args:
        n: Total number of samples generated per problem
        c: Number of correct samples (passing all tests)
        k: k value for pass@k

    Returns:
        pass@k score as a float in [0, 1]
    """
    # If we have fewer incorrect samples than k, at least one must be correct
    if n - c < k:
        return 1.0
    return 1.0 - comb(n - c, k) / comb(n, k)


# Demonstrate the worked example from above
n_samples = 20
correct = 4
k_values = [1, 5, 10]

pass_scores = {
    f"pass@{k}": calculate_pass_at_k(n_samples, correct, k) for k in k_values
}
Out[8]:
Console
With n=20 total samples and c=4 correct:

  pass@1: 0.2000
  pass@5: 0.7183
  pass@10: 0.9567

Approximation 1 - (1 - c/n)^k:
  pass@1 approx: 0.2000
  pass@5 approx: 0.6723
  pass@10 approx: 0.8926

The comparison between the exact formula and the approximation shows the finite population correction at work. For k=5k=5, the exact formula gives 0.7183 while the approximation gives 0.6723. The exact formula is higher because drawing without replacement from a finite population makes it harder to avoid the correct solutions once they exist in the pool.

Out[9]:
Visualization
Line chart showing pass@k probability versus k for base success rates of 10%, 20%, and 50%.
pass@k probability as a function of k for three different base success rates (10%, 20%, and 50%), computed using the exact hypergeometric formula with n=200 total samples. All curves increase monotonically toward 1.0 as k grows, but the rates of increase differ substantially. At k=1, a 50% success rate performs five times better than 10%, but by k=20, all three rates converge above 80%.

The large increase from pass@1 to pass@100 illustrates why multiple sampling matters in code generation. With a 20% individual success rate, we reach near-perfect success given 100 attempts. This phenomenon highlights the importance of filtering or ranking mechanisms in practical applications. A system that can identify the best solution from many candidates, perhaps by running all of them and checking which ones produce consistent outputs on diverse inputs, can sharply outperform single-shot generation even when the underlying model quality is moderate.