想象一场特殊的射击比赛。考官摆出 164 个靶位,每次只亮一个靶;AI“选手”需要打出 Python 代码子弹,只有代码通过隐藏测试、正中靶心才算得分。单发命中叫 pass@1;如果允许它连续打 10 发、100 发,只要有一发上靶考官就记一次成功,这就是 pass@k 的直觉——k 越大,命中概率越高,子弹消耗也越大。HumanEval 就是用这套规则衡量大模型写代码能力的基准,而 pass@k 是它最核心的记分牌。全文用打靶的比喻,拆解这个指标为何能成为 LLM 评测的行业标尺。

岗位关联

搞懂 pass@k,你就能看懂各大模型代码能力榜单背后的计分逻辑,在选型、评测和调参时不被一个数字误导。

目录
  1. 可视化演示
  2. 基准概述
  3. 核心指标
  4. 概率原理
  5. 估计算法
  6. 评测实践
  7. 指标意义
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

演示HumanEval中pass@k指标的计算:对一题多次采样代码、逐一测试,统计至少一次通过的概率,并对比pass@1与pass@100的能力差异。

本节配有交互动效(进入视口后自动加载,约 3KB)。

模式1:采样与测试 对一题采样n个候选解 逐一运行隐藏测试 pass@k = 1-(1-p)^k 统计至少一次通过的概率

基准概述

想象一座专为 AI 设计的打靶场:考官摆出 164 个函数题目靶,AI 选手只能凭一句描述和函数签名写代码,写完后隐藏的单元测试像弹道检测器一样判定是否命中。这就是 HumanEval,OpenAI 提出的代码生成评测基准。它不考背诵,也不考常识,只考一件事:给你一个函数签名和一段说明文字,你能不能写出满足输入输出要求的函数。这些题目由人类专家手工编写,每个靶位配好了若干个单元测试,只有全部测试通过,这一枪才算上靶。

HumanEval 最鲜明的特点是“函数级评测”。它不是让模型做选择题,而是让模型生成完整函数,然后真实运行测试用例。整个靶场共 164 道 Python 编程题,覆盖字符串处理、简单算法、逻辑判断等常见编程任务。因为在真实工程中,代码能跑、结果正确是唯一硬标准,这种评测方式天然比“预测下一个词”更贴近使用场景,也因此成为 LLM 代码能力的事实标尺。

基础的通过率可以写成所有题目上的命中率:

所以,当你在模型榜单上看到 HumanEval 分数时,可以把它理解为:这位 AI 射手站在 164 个靶位前,按最简单的单发规则命中多少个靶。这个基础分数,是 pass@k 一切讨论的起点。

pass=TpassTtotal\text{pass} = \frac{T_{\text{pass}}}{T_{\text{total}}}
  1. 分子 $T_{\text{pass}}$通过全部测试的题目数,相当于整场比赛中命中的靶位数。
    Tpass=#{题目:所有测试用例均通过}T_{\text{pass}} = \#\{题目:所有测试用例均通过\}
  2. 分母 $T_{\text{total}}$题目总数,即固定靶位的总数 164。
    Ttotal=164T_{\text{total}} = 164

核心指标

但打靶比赛不会只给 AI 射手一发子弹。考官递上一个弹匣,里面有 k 发子弹;射手依次打出 k 个候选代码,只要其中任意一个通过单元测试,这一题就算成功。这就是 pass@k 的核心直觉:成功条件不是“每一发都中”,而是“至少一发中”。当 k=1 时,它退化为普通的 pass@1,考察模型一次生成就正确的能力;当 k=10 或 k=100 时,它考的是模型在多次尝试中能否找到正确答案,也就是它的生成潜力。

实际评测中,我们要把 k 发子弹的规则套用到 N 道题上,对所有题目求平均。于是 pass@k 可以写成:

因此,pass@1 衡量的是“一枪定胜负”的稳定性,pass@10 和 pass@100 则像给射手更多试错机会,专门探测它的能力上限。k 越大,反映的越是“这个模型到底能不能写出正确代码”,而不是“它第一次写得对不对”。

pass@k=1N∑i=1NI(∑j=1ktij>0)\mathrm{pass@}k = \frac{1}{N} \sum_{i=1}^{N} \mathbb{I}\left(\sum_{j=1}^{k} t_{ij} > 0\right)
  1. $N$ 道题对全部 N 个题目逐一考察,最后取平均。
    N=HumanEval 题目总数N = \text{HumanEval 题目总数}
  2. $t_{ij}$第 i 题的第 j 次采样是否通过测试,通过记 1,失败记 0。
    tij∈{0,1}t_{ij} \in \{0, 1\}
  3. $\sum_{j=1}^{k} t_{ij} > 0$把 k 次采样的结果累加,只要和大于 0,就说明至少有一次成功。
    ∑j=1ktij>0\sum_{j=1}^{k} t_{ij} > 0
  4. $\mathbb{I}(\cdot)$ 指示函数条件成立时取 1,不成立取 0,把“至少一次成功”变成一个可平均的数值。
    I(至少一次成功)=1\mathbb{I}(\text{至少一次成功}) = 1

概率原理

为什么 k 越大,命中率越高?我们可以把 AI 射手看作一个状态稳定的选手:每次单发命中靶心的概率都是 p,并且每发子弹互不干扰。这就像一位训练有素的射击运动员,虽然枪法不是百发百中,但每一枪的命中率是稳定的。当然,真实模型采样并非完全独立,这里先接受这个理想化假设,便于建立直觉。

“至少一发命中”的反面是“k 发全部脱靶”。每一发脱靶概率是 1-p,因为互不干扰,k 发全部脱靶的概率就是 $(1-p)^k$。用 1 减去它,就得到至少一发命中的概率:

这条曲线的形状很有规律:开始时 k 增大,成功概率迅速上升;到后面子弹打得再多,提升也越发有限。例如 p=10% 时,pass@10 约 65%,pass@100 约 99.997%。这说明用 10 发子弹已经能较好探测潜力,而 100 发几乎必然命中,却也几乎无法区分射手之间的差距。

pass@k=1−(1−p)k\mathrm{pass@}k = 1 - (1-p)^k
  1. 单发命中率 $p$一次采样生成的代码通过测试的概率,对应射手单发上靶的概率。
    p=P(单次采样通过)p = \mathbb{P}(\text{单次采样通过})
  2. 单发失败率 $1-p$一发子弹脱靶的概率。
    1−p=P(单次采样失败)1 - p = \mathbb{P}(\text{单次采样失败})
  3. k 发全失败 $(1-p)^k$因为各发独立,k 发全部脱靶的概率是每发失败率的乘积。
    (1−p)k(1-p)^k
  4. 取补事件用 1 减去“全部失败”,得到“至少一次成功”。
    1−(1−p)k1 - (1-p)^k

估计算法

现实中的评测不可能真的让模型无限采样。假设对一道题只生成 n 发子弹,记录其中 c 发命中靶心,我们想从这组样本中估出 pass@k。最朴素的做法是把 c/n 当作 p,直接代入 $1-(1-p)^k$。这样算出的结果叫朴素估计,它有一个严重缺陷:当 k 接近 n 时,偏差会变得很大,系统性地低估真实的 pass@k。

为什么会有偏差?因为 $c/n$ 只是真实命中率的估计值,把它代入非线性函数后再求期望,和先求期望再代入函数并不等价。论文里给出的无偏估计公式是:

这就像在打靶记录里做“有放回的组合检查”:不是简单相信平均命中率,而是老老实实数一数,从 n 发子弹中取 k 发,有多少种情况会全军覆没,再用总数减去它。正因为这样,评测报告中的 pass@100 才更可信。

pass@^k=1−(n−ck)(nk)\hat{\mathrm{pass@}}k = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}
  1. 样本量 $n$ 与正确数 $c$$n$ 是模型针对一道题生成的全部候选代码数,$c$ 是其中通过测试的正确个数,也就是弹匣里命中子弹的数量。
    n=总采样数,c=通过测试数n = \text{总采样数}, \quad c = \text{通过测试数}
  2. 组合数 $\binom{n}{k}$从 n 发子弹里任意取出 k 发,共有多少种取法。
    (nk)=n!k!(n−k)!\binom{n}{k} = \frac{n!}{k!(n-k)!}
  3. 全失败组合 $\binom{n-c}{k}$从失败的 n-c 发子弹里取出 k 发的取法数。
    (n−ck)\binom{n-c}{k}
  4. 概率修正两者相除得到“取出 k 发全是失败样本”的比例,1 减去后就是至少一发命中的无偏估计。
    1−(n−ck)(nk)1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}

评测实践

理解了公式,我们来回答一个实际问题:评测时 k 到底该取多少?答案是看你想区分什么。如果关心产品体验中“用户第一次提问就得到能跑代码”的概率,pass@1 最有说服力;如果关心模型能力的上限,比如能不能解出难题,pass@100 更能拉开差距。主流论文通常同时报告 pass@1、pass@10 和 pass@100,形成一组“能力剖面”。

采样温度也是一个关键旋钮。温度越高,模型生成的代码越发散,相当于让射手用更松的扳机打出更飘的弹道;温度越低,输出越保守,命中率集中但多样性差。在 HumanEval 评测中,temperature=0.2 到 0.8 是常见范围,温度直接决定了 k 次采样之间的多样性,进而影响 pass@k 的高低。

当然,子弹是要花钱的。生成每个候选代码都有推理成本,因此:

开发者需要在“想探明潜力”和“付得起账单”之间找平衡。这也是为什么实际产品很少对每个请求都跑 100 次采样,更常见的是先用小 k 快速响应,再用大 k 在离线评测中摸清模型底细。

cost(k)=k×csample\text{cost}(k) = k \times c_{\text{sample}}
  1. 候选数 $k$评测要保留的候选代码数量。
    k=采样次数k = \text{采样次数}
  2. 单次成本 $c_{\text{sample}}$生成一份完整代码的平均推理耗时或费用。
    csample=单次采样的推理成本c_{\text{sample}} = \text{单次采样的推理成本}
  3. 总成本两者相乘就是一次 pass@k 评估的总开销。
    k×csamplek \times c_{\text{sample}}

指标意义

最后,回到最根本的问题:pass@k 到底在告诉我们什么?它像一个分层记分牌:pass@1 是“上一次战场就打赢”的实战稳定性,pass@100 是“子弹管够时能否找到解法”的潜力上限。两个数字一起看,才能避免单一指标带来的误判——一个模型可能 pass@1 不高,但 pass@100 惊人,说明它“知道怎么做,只是第一次容易失误”。

反过来,pass@k 也有清晰的天花板。随着 k 无限增大,只要单次通过概率 p>0,成功率会无限逼近 100%:

这就是 pass@k 的“甜蜜陷阱”:k 太小,潜力看不出来;k 太大,所有模型都接近满分,区分度消失。优秀的评测会选择合适的 k 和温度,让差距显形,而不是把每个模型都夸成神枪手。下次看到模型榜单上的 HumanEval 分数,请先问一句:这是 pass@1,还是 pass@100?它用的是多大的弹匣?

lim⁡k→∞pass@k=1\lim_{k \to \infty} \mathrm{pass@}k = 1
  1. $k \to \infty$让射手拥有无限多子弹,试错次数趋近无穷大。
    k→∞k \to \infty
  2. 失败率衰减为 0由于 $(1-p)^k$ 是指数衰减,k 很大时它趋近于 0。
    (1−p)k→0(1-p)^k \to 0
  3. 极限为 1成功率趋近 100%,所有模型都变成“满分射手”。
    lim⁡k→∞(1−(1−p)k)=1\lim_{k \to \infty} (1 - (1-p)^k) = 1

AI 应用场景

场景一:代码大模型选型对比

阶段:模型评估

解决的问题:只看 pass@1 容易低估潜力。有些模型单发稳定但上限一般,另一些模型单发不稳却能在多次采样中解出难题,传统单一指标无法区分。

具体案例:某研发团队在 CodeLlama 和 DeepSeek-Coder 之间选型,两个模型在 HumanEval pass@1 上仅差 2 个百分点,但 pass@100 差了 15 个百分点。团队据此判断后者解题潜力更强,最终选择后者并配合多候选重排上线。

场景二:AI 编程助手调参

阶段:模型部署与推理优化

解决的问题:产品端不能无限增加采样次数,需要平衡响应延迟和用户满意度。温度设置会影响多候选的多样性,进而影响最终通过率。

具体案例:Copilot 类工具的后端团队用 HumanEval 的 pass@1 和 pass@10 做回归测试,发现 temperature=0.4 时 pass@10 最高但响应超时率上升,于是将线上 k 控制在 3,配合结果排序器,把用户可感知成功率提高了 8%。

场景三:训练过程中的能力监控

阶段:训练评估

解决的问题:训练中只盯 loss 无法知道代码能力是否真的增强,需要以任务级指标持续监控模型的生成潜力。

具体案例:大模型训练团队每 500 步在 HumanEval 子集上计算 pass@1 和 pass@100。他们发现某个 checkpoint 的 pass@1 持平但 pass@100 明显爬升,说明模型“潜力先于稳定”涌现,据此调整了后续数据配比。

Python 演示

python44 行
import randomfrom math import comb  def simulate_pass_at_k(p, k, n_problems=5000):    """模拟打靶:每个问题采样 k 次,只要一次命中就算成功。"""    success = 0    for _ in range(n_problems):        passed = any(random.random() < p for _ in range(k))        if passed:            success += 1    return success / n_problems  def naive_estimate(p, k):    """朴素估计:把单发命中率直接代入公式(有偏)。"""    return 1 - (1 - p) ** k  def unbiased_estimate(n, c, k):    """无偏估计:用组合数从 n 次采样、c 次通过中估算 pass@k。"""    if n - c < k:        return 0.0    return 1 - comb(n - c, k) / comb(n, k)  def main():    p_true = 0.15    print("真实单发命中率 p =", p_true)     for k in [1, 10, 100]:        sim = simulate_pass_at_k(p_true, k)        naive = naive_estimate(p_true, k)        print(f"k={k}: 模拟 pass@k={sim:.3f}, 理论公式={naive:.3f}")     n, c = 200, 30    print("无偏估计示例: n=200, c=30")    for k in [1, 10, 100]:        est = unbiased_estimate(n, c, k)        print(f"  pass@{k} = {est:.3f}")  if __name__ == "__main__":    main()
目的:用随机模拟直观验证 pass@k 的理论公式,并演示无偏估计与朴素估计的区别。
  1. passed = any(random.random() < p for _ in range(k))模拟 1 道题生成 k 个候选,只要有一次“命中”该题就成功。
  2. return success / n_problems把所有题目的成功次数除以题目数,得到经验概率。
  3. return 1 - (1 - p) ** k朴素估计,直接用单发命中率推算 k 次内至少一次命中的概率。
  4. if n - c < k: return 0.0如果失败样本数不足 k,组合数无意义,按 pass@k=0 处理。
  5. return 1 - comb(n - c, k) / comb(n, k)无偏估计的核心表达式,用组合数数出“全失败取法”的占比。

常见误区

误区很多初学者以为 pass@k 是生成 k 个答案后由模型选出最好的那个,选对了就算通过。

正解pass@k 是统计概率,不是“挑选逻辑”。它只看 k 个候选里是否至少有一个通过测试,不需要模型自己判断哪个答案好。测试用例才是裁判,就像打靶时只有弹着点是否在靶心说了算,射手自己说“我觉得打中了”不算数。

为何常见:因为日常使用 AI 编码工具时,我们确实会从多个候选中挑一个,容易把产品交互逻辑和评测指标混为一谈。

误区潜意识里觉得“pass 越高越稳定”,所以 pass@100 高的模型一定比 pass@1 高的模型更好用。

正解pass@100 衡量的是“潜力上限”,允许 100 次试错,本质上是给模型无数次重来的机会;真正决定日常体验的是 pass@1。一个 pass@100 很高但 pass@1 很低的模型,就像一位需要打 99 发热身才能命中的射手,实战中并不好用。

为何常见:因为“至少一次成功”很容易被理解成“经常成功”,忽略了 k 次尝试的巨大试错空间。

误区既然 pass@1 有实测值,把它代入公式就能算出 pass@10、pass@100,何必重新采样。

正解这个公式只在单次通过率已知时给出理论值;当我们只有有限样本时,用 c/n 替代 p 得到的朴素估计是有偏的,尤其是在 k 与样本量接近时严重低估真实值。正确做法是用无偏估计公式,基于每个问题的 n 次采样和 c 次通过来计算。

为何常见:公式形式太简单,看起来人畜无害,很多人不知道“估计值代入非线性函数”会引入偏差。

误区既然 k 越大 pass@k 越高,评测时直接把 k 拉到 1000,分数好看,模型差距一定更清楚。

正解当 k 很大时,所有模型的 pass@k 都逼近 100%,区分度反而被抹平。就像给所有射手无限发子弹,最终人人都能蒙中靶心,比赛就失去了意义。合理选择 k(通常是 1、10、100)才能暴露能力差异。

为何常见:人们天然喜欢高分数,忽略了评测指标的核心目的是“区分”而不是“美化”。

误区把 pass@k 当成代码质量指标,认为分数高就说明生成的代码可读、健壮、优雅。

正解pass@k 只关心单元测试是否通过,不关心代码风格、可维护性、运行效率或边界健壮性。一个能通过测试但写得很烂的代码同样算命中。就像射箭比赛只记录是否中靶,不评价箭的飞行姿态是否好看。

为何常见:“通过测试”很容易被等同于“代码优秀”,但测试通过只是质量下限,远不是上限。

面试题

Q1. HumanEval 数据集包含多少道 Python 编程题?

单选题 · easy

  • A.总数为64道题
  • B.总数为164道题← 正确答案
  • C.总数为264道题
  • D.总数为1024道题

答案:B。HumanEval 由 OpenAI 构建,包含 164 道手工编写的 Python 编程题。它就像靶场里有 164 个固定靶位,每个靶位对应一道函数题。64、264、1024 都是干扰数字。

Q2. pass@1 衡量的是模型的什么能力?

单选题 · easy

  • A.一次生成就通过的稳定能力← 正确答案
  • B.十次生成内通过的潜力
  • C.一百次生成内通过的潜力
  • D.不做任何生成的随机能力

答案:A。pass@1 只给射手一发子弹,考察第一次射击就命中靶心的概率,反映单次生成的稳定性。pass@10 和 pass@100 才是考察多轮尝试后的潜力。

Q3. 若单发通过率 p=0.1,理论上 pass@10 最接近以下哪个值?

单选题 · easy

  • A.接近10%
  • B.接近65%← 正确答案
  • C.接近90%
  • D.接近100%

答案:B。pass@10 = 1 - (1-0.1)^10 ≈ 0.651,约等于 65%。它说明即使单发命中率低,10 次尝试也能让整体成功概率大幅上升。

Q4. 朴素估计 1-(1-c/n)^k 的主要问题是什么?

单选题 · mid

  • A.计算复杂度太高
  • B.k 较大时存在明显偏差← 正确答案
  • C.必须使用 GPU 计算
  • D.仅适用于 Python

答案:B。把估计的 c/n 直接代入非线性函数,会使结果系统性偏离真实 pass@k,尤其当 k 接近样本量 n 时偏差更明显。它就像用平均枪法直接推算“100 发必中”,忽略子弹之间的有限样本波动。

Q5. 无偏估计 pass@k 公式中,n 和 c 分别代表什么?

单选题 · mid

  • A.总采样数和通过数← 正确答案
  • B.正确率和错误率
  • C.题目数和阈值
  • D.模型层数和参数量

答案:A。n 是模型对一道题生成的全部候选代码数,c 是其中通过测试的个数。它们对应弹匣里的总子弹数和命中子弹数,是无偏估计输入的两项核心统计量。

Q6. 下列关于 pass@1 和 pass@100 的说法,正确的有哪些?

多选题 · mid

  • A.pass@100 通常不会低于 pass@1← 正确答案
  • B.pass@100 反映模型的单次稳定性
  • C.pass@100 更能体现生成潜力上限← 正确答案
  • D.pass@1 反映一次生成就正确的概率← 正确答案

答案:ACD。k 更大意味着尝试次数更多,因此 pass@100 通常不低于 pass@1,且更侧重潜力上限;pass@1 才代表单次稳定性。B 把潜力指标误当稳定性指标,是常见混淆。

Q7. 在 HumanEval 评测中,调整采样温度主要影响的是什么?

单选题 · mid

  • A.采样结果的多样性← 正确答案
  • B.测试用例的数量
  • C.HumanEval的题目规模
  • D.单发命中率的统计口径

答案:A。温度控制生成分布的随机性:低温度输出保守,高温度输出多样。它影响 k 次采样之间的差异程度,进而影响 pass@k 的数值,但不改变测试集本身。

Q8. 关于 pass@k 估计,下列说法正确的有哪些?

多选题 · hard

  • A.朴素估计通常会低估真实值← 正确答案
  • B.无偏估计需要记录 n 和 c← 正确答案
  • C.pass@k 随 k 增大单调不减← 正确答案
  • D.k 越大则模型区分度一定越高

答案:ABC。论文指出朴素估计系统性低估真实 pass@k;无偏估计依赖每个问题的 n 与 c;k 增大时“至少一次成功”概率只会不变或上升。但 D 错误,k 过大时所有模型都逼近满分,区分度反而下降。

Q9. 若 n=10,c=2,k=2,按无偏估计公式,pass@2 最接近多少?

单选题 · hard

  • A.约10%
  • B.约28%
  • C.约38%← 正确答案
  • D.约62%

答案:C。pass@2 = 1 - C(8,2)/C(10,2) = 1 - 28/45 ≈ 37.8%。这相当于从 10 发子弹中任意取 2 发,计算取到全失败组合的占比,再用 1 减去,得到至少一发命中的估计。

Q10. 在解释模型评测结果时,以下哪些说法是合理的?

多选题 · extreme

  • A.k 趋近无穷大时 pass@k 会趋近 100%← 正确答案
  • B.大 k 的 pass@k 更偏向能力上限← 正确答案
  • C.pass@1 更多衡量单次稳定性← 正确答案
  • D.pass@k 不受采样温度影响

答案:ABC。只要单发通过率大于 0,k 无限增大时失败率指数衰减到 0,pass@k 趋近 1;大 k 探测上限,小 k 反映稳定。D 错误,因为温度决定 k 次采样的多样性,直接改变 pass@k 的实测值。