想象一座城市正在举办 AI 厨师大赛。参赛的是一群自称“上知菜谱、下知火候”的数字大厨——它们就是 Code LLM(写代码的大模型)。问题是:每位大厨都说自己天下第一,评委团却拿不出一张统一的评分表。没有评分表,就无法公平比较。于是评委们设计了一套考试制度:指定 164 道固定菜谱(HumanEval),给每位大厨 k 次试做机会(pass@k),还要求用多国语言做同一道菜(MultiPL-E),再用家常菜考核基本功(MBPP)。这一整套“考试规则”,就是 Code LLM 评测。本文会带你走进评委团的厨房,看看如何把“会写代码”这件模糊的事,变成一组公平、可复现、能横向比较的数字。
掌握代码评测基准是 AI 评测工程师的必修课——它决定了你能否用可信的数字说服团队、推动模型迭代,而不是停留在“我觉得它变强了”的玄学上。
目录
可视化演示
展示HumanEval基准模型对比、pass@k采样概率计算过程、以及多语言编程能力横向评测三大评测维度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
HumanEval基准:模型pass@1对比 G4=GPT-4 领先 85% pass@1 Cl=Claude S1=StarCoder 梯队分布 pass@1 = P(1 sample passes tests) 函数级代码生成能力基准
1. 评测概述
评委团的第一项工作,是设计一张能让所有 AI 大厨服气的评分表。没有这张表,大厨 A 说“我做的鱼香肉丝好吃”,大厨 B 说“我做的更地道”,谁也无法说服谁。只有把“好吃”拆解成固定的菜品、固定的调料用量、固定的试吃标准,比赛才能开始。
把场景搬到 AI 领域,Code LLM 评测做的就是这件事:用一套可复现的基准,把“模型会不会写代码”这个模糊问题,变成一串可比较的数字。一套评测基准包含三个要素:题目集(编程任务)、判分方式(通常是单元测试断言)、评分函数(如何汇总成指标)。流程像一个闭环:给模型出题 → 模型生成代码 → 在隔离环境中执行 → 用断言验证输出 → 汇总分数。
这个闭环中最直观的分数,就是“通过率”。
有了这张评分表,评委团才能从“我觉得他会做菜”走向“数据显示他能做 80% 的菜”。这就是评测之于 Code LLM 的意义:它把主观印象变成工程决策的依据。
- 总题数$N_{\text{total}}$ 表示评测集中全部题目的数量。
- 通过数$N_{\text{pass}}$ 表示模型生成的代码通过全部测试断言的题目数。
- 求比值两者相除,得到 0 到 1 之间的通过率,1.0 意味着全对。
2. 类人评测集(HumanEval)
评委团的第一份考卷,是一套“固定菜谱”。每道菜的名头、食材、火候要求都写得清清楚楚,大厨只需要把最后的成品端上来。这份考卷就是 HumanEval——OpenAI 在 2021 年发布的人工手写 Python 题目集,一共 164 道函数级编程题。
为什么强调“人工手写”?因为如果题目直接从 GitHub 上抄,AI 大厨可能早就在训练时“背过菜谱”,考试就失去了意义。HumanEval 的每一道题都包含:函数签名、文档注释(docstring)、若干条测试断言。例如题目给出 def add(a, b): """返回两数之和""",模型需要补全函数体,而评委用 assert add(2, 3) == 5 这类断言来验证对错。
HumanEval 的单次通过率计算非常简单直接。
固定菜谱的价值在于“剥掉创意,只比基本功”。HumanEval 不考读长文档、不考多文件协作,只考一个函数能不能被正确写出来——这让不同模型的编程肌肉记忆可以直接对比。
- 分母 164HumanEval 的题目总数是固定不变的,让分数可跨模型比较。
- 分子全过数$N_{\text{all\_pass}}$ 是“生成一次就通过所有断言”的题目数量。
- 取比值分子除以分母,就得到单次通过率,值越高代表基本功越扎实。
3. 通过率指标(pass@k)
评委团很快发现一个麻烦:AI 大厨偶尔会手抖。同一道菜,状态好时色香味俱全,状态差时直接糊锅。只给一次机会(pass@1),运气成分太大。于是新规则诞生:每位大厨最多试做 k 次,只要有一次端出合格菜品,这道菜就算过关。这就是 pass@k 的核心直觉——用多次机会对抗随机抖动。
在具体评测中,模型对每个题目采样 n 个结果(通常 n = 200),其中 c 个通过测试。评委会随机从中抽取 k 个,计算“至少有一个通过”的概率。这里不能简单套用 $1-(1-\frac{c}{n})^k$,因为样本之间不独立且存在重复,直接套用会产生偏差。学术界采用如下无偏估计:
试做次数越多,运气成分越被冲淡,真实水平越浮出水面。但同时要注意:采样温度(temperature)调得越高,样本越多样,pass@k 往往越高,而 pass@1 可能反而下降——这正是评测中需要特别小心的“超参陷阱”。
- 组合总数$\binom{n}{k}$ 表示从 n 个样本中任选 k 个的所有可能组合数。
- 失败组合数$\binom{n-c}{k}$ 表示选出的 k 个全部来自那 n-c 个失败样本的组合数。
- 失败率两者相除,得到“随机抽 k 次全部失败”的概率。
- 取对立事件用 1 减去失败率,得到“至少一个通过”的概率。
4. 基础编程基准(MBPP)
固定菜谱考的是“照方抓药”,评委团还想看看大厨会不会“即兴发挥”。于是第二场考试改成了家常菜考核:不给出函数签名,只说“我想吃番茄炒蛋,用三个鸡蛋两个番茄”,让大厨自己写出完整做法。这就是 MBPP(Mostly Basic Python Problems)——Google 发布的入门级 Python 编程题集,包含 974 道题目。
MBPP 的题目形式是“自然语言需求描述 + 3 条测试断言”。比如描述“写一个函数,接收列表并返回其元素之和”,模型要生成完整程序,然后由三条断言自动验证。评测时通常采用 few-shot(少样本)设置:先给模型几个“需求→正确代码”的示例,再让它解决新题目。它与 HumanEval 的定位差异明显:HumanEval 偏函数级算法技巧,MBPP 偏自然语言到完整程序的基础能力。
MBPP 的通过率可以用一个带指示函数的均值公式表示:
家常菜最能暴露基本功的短板。那些背熟了固定菜谱的大厨,遇到“只说需求、不给签名”的题目就抓瞎——这正是 MBPP 存在的意义:考的不是套路,而是把自然语言翻译成代码的通用能力。
- 指示函数$\mathbb{1}[\cdot]$ 表示条件为真时取 1,为假时取 0。
- 求和$\sum_{i=1}^{N}$ 把每一道题的得分全部加起来。
- 取平均乘以 $\frac{1}{N}$ 得到所有题目的平均通过率。
5. 多语言迁移(MultiPL-E)
大赛进行到一半,评委团提出了灵魂拷问:一个只会做中餐的大厨,能叫“国际名厨”吗?同理,一个只会在 Python 上得分很高的 Code LLM,是真的会编程,还是只背熟了 Python 语法?为了检验这一点,MultiPL-E 上线了——它像一个自动翻译菜谱的机器,把 HumanEval 和 MBPP 的题目翻译成 C++、Java、JavaScript、Go 等 18 种以上的语言。
MultiPL-E 的工作方式分两步:先把 Python 题目转换成一种中间表示,再用每种目标语言的模板渲染成对应语言的题目。同时,翻译后的题目也要配上等价的测试用例,确保“换了语言,考点不变”。这个过程看似简单,实则有陷阱:不同语言的表达力和标准库命名差异,可能让同一道题在 Python 里是送分题、在 C++ 里却变成地狱难度。因此评测时通常逐语言报告 pass@k,再汇总成一个跨语言平均分:
多语言考试让“伪全能”无处遁形。一个只在 Python 上刷高分的模型,一旦被翻译成静态类型语言就会暴露短板——这提醒我们:编程能力评测不能只看单一语言,跨语言对齐才是检验通用性的试金石。
- 语言数量$L$ 是被评测的目标语言总数。
- 分语言得分$\mathrm{pass@k}_j$ 是模型在第 j 种语言上的通过率。
- 求平均对所有语言的得分求和后除以 L,得到平均分。
6. 补全与生成
比赛规则进一步细化,把题目分成两类:一类是“续写对联”,大厨站在半成品菜前,只完成最后一步勾芡收汁;另一类是“命题作文”,从一句“来一桌年夜饭”开始,做出一整桌菜。前者对应代码补全,后者对应代码生成。
代码补全的任务设定是:给定文件上下文和光标位置,预测后续 token。它最常见的落地场景是 IDE 里的自动补全插件。评测补全时,除了测试通过率,还常用精确匹配率(EM)——生成的代码与参考代码逐字符一致才得分;或者用编辑距离衡量“有多接近”。代码生成则是给定自然语言指令(如 docstring 或 issue 描述),生成完整函数或程序,其评测以 pass@k 为主。
精确匹配率公式如下:
补全与生成看似同源,实则能力要求不同:补全考验“上下文理解”,生成考验“需求到实现的跳跃”。评委团若用同一把尺子量两种能力,就会像逼着做满汉全席的大厨去参加一分钟拼盘比赛一样,结论必然失真。
- 精确匹配数$N_{\text{exact\_match}}$ 是模型输出与参考代码完全一致的题目数。
- 总题目数$N_{\text{total}}$ 是补全评测中的全部题目数。
- 相除两者的比值就是精确匹配率,越接近 1 说明续写能力越强。
7. 评测实践
规则定得再好,也要保证所有大厨用同一口锅、同一把火。Code LLM 评测的实践环节,就是赛场公平的保障:统一环境、统一超参、统一采样方案。否则一次评测跑出来 0.62,下次跑出来 0.55,你分不清是模型退步了还是比赛流程飘了。
评测实践中的三个关键词是:工具链、防泄漏、可复现性。工具链方面,社区常用 bigcode-evaluation-harness、EvalPlus 等框架;EvalPlus 会为 HumanEval 每道题额外补充大量测试断言,防止模型“生成代码看着对但经不起推敲”。防泄漏方面,要尽可能使用人工手写题或动态新题,并过滤训练语料中的相似代码。可复现性方面,必须固定 temperature、采样数量 n、随机种子以及模型版本。
为了衡量多次评测的稳定性,分析师通常对同一配置运行 m 次并取均值:
没有统一赛规的冠军毫无意义。一次可信的 Code LLM 评测,必须像正规体育赛事一样写清楚“比赛用球、风速、计时规则”——任何超参的含糊,都可能让分数变成一场不可复现的幻觉。
- 单次得分$x_i$ 是第 i 次评测得到的分数。
- 求和$\sum_{i=1}^{m}$ 把 m 次的分数全部加起来。
- 取均值除以 m 得到平均分,同时可配合标准差判断评测是否稳定。
8. 评测趋势
评委团逐渐意识到,固定考卷考久了,大厨们会开始“押题”。一个把 164 道菜练得滚瓜烂熟的 AI,遇到真实厨房里的新菜依然可能手忙脚乱。于是评测制度迎来了新趋势:离开固定题库,走向真实世界的厨房。
前沿方向主要有三条:一是动态题库,如 LiveCodeBench 持续从最新代码资源中抽取新题,让模型无法靠记忆得分;二是仓库级任务,不再只考单个函数,而是让模型理解整个项目结构、跨文件修改代码;三是真实缺陷修复,如 SWE-bench 直接使用 GitHub 上真实 issue 和代码仓库,让模型修复真实 bug。这些任务更接近程序员的实际工作场景。
衡量一次模型迭代是否有进步,最简单也是每个评测工程师都会写的公式是能力差:
考卷压中再多,也不如真材实料。Code LLM 评测正在从“背菜谱”进化到“进真厨房”,只有那些在动态题库、仓库级任务和真实缺陷修复中都能稳定发挥的模型,才能真正赢得评委团(和用户)的信任。
- 新版本得分$S_{\text{new}}$ 是模型更新后在目标基准上的分数。
- 旧版本得分$S_{\text{old}}$ 是更新前的分数。
- 做差两者相减得到的 $\Delta$ 就是模型迭代带来的净变化量。
AI 应用场景
场景一:AI 编程助手发布前的厨艺鉴定
阶段:模型评估
解决的问题:传统做法是让工程师拿几个样例 “感觉一下” 生成质量,主观且无法横向对比。AI 评测方法用统一的题目集和断言,把“这个助手好不好用”变成一组可复现的数字。
具体案例:某 IDE 插件团队在发布新版代码补全模型前,跑了一遍 HumanEval + MBPP,测得新版 pass@1 从 0.58 提升到 0.62,团队信心满满。但 MultiPL-E 的 Java 子集只有 0.31,远低于 Python 的 0.65,暴露出多语言短板。团队据此决定优先补强 Java 语料,而不是全员铺开宣传。
场景二:多语言代码助手选型
阶段:技术选型
解决的问题:企业要从多个商用 Code LLM 中选一个作为内部编程助手的主力,仅凭宣传文案无法判断谁更适合团队的 Java/Go 技术栈。
具体案例:评测团队用 MultiPL-E 的 Java 与 Go 子集对候选模型统一采样 200 次,固定 temperature = 0.2,报告 pass@1 与 pass@10。结果发现某模型的 Python 分数虽高但 Java 分数垫底,最终落选。这套多语言评测流程让选型决策有了硬数据支撑。
场景三:模型迭代的回归测试门禁
阶段:持续集成(CI)
解决的问题:训练团队每次调整数据配比后,都担心模型“按下葫芦浮起瓢”——一个能力涨了、另一个能力却退了。靠人工抽查很难及时发现。
具体案例:某大模型团队在 CI 流水线中接入 HumanEval + MBPP 的 pass@1 评测,每次新 checkpoint 自动跑分。某次改动让 HumanEval 提升了 3 个百分点,但 MBPP 下降了 4 个百分点,指标门禁立即报警,训练团队定位到是代码数据比例失衡所导致,及时回滚了实验。
Python 演示
import randomfrom math import comb def generate_samples(model_fn, problem, n, temperature): """Sample n candidate solutions from the model.""" samples = [] for _ in range(n): samples.append(model_fn(problem, temperature)) return samples def is_correct(sample, test_cases): """A sample is correct only if it passes all test cases.""" return all(t(sample) for t in test_cases) def estimate_pass_at_k(n, c, k): """Unbiased pass@k estimator from Chen et al.""" if n - c < k: return 1.0 return 1.0 - comb(n - c, k) / comb(n, k) def dummy_model(problem, temperature): """A dummy model that returns the correct answer with 70% prob.""" return "correct" if random.random() < 0.7 else "wrong" def main(): random.seed(42) problems = [ {"tests": [lambda x: x == "correct"]} for _ in range(100) ] total_p1 = 0.0 total_p10 = 0.0 for p in problems: samples = generate_samples(dummy_model, p, 200, 0.8) c = sum(1 for s in samples if is_correct(s, p["tests"])) total_p1 += estimate_pass_at_k(200, c, 1) total_p10 += estimate_pass_at_k(200, c, 10) n = len(problems) print(f"pass@1 = {total_p1 / n:.3f}") print(f"pass@10 = {total_p10 / n:.3f}") if __name__ == "__main__": main()
def estimate_pass_at_k(n, c, k):定义无偏估计器,对应正文中 pass@k 的组合数公式。if n - c < k:失败样本数不足 k 个时,随机抽出的 k 个必然至少有一个成功,直接返回 1.0。return 1.0 - comb(n - c, k) / comb(n, k)核心公式:从 n 个样本随机抽 k 个,至少一个成功的概率。samples = generate_samples(dummy_model, p, 200, 0.8)每个题目采样 200 个候选解,模拟模型多次试做过程。c = sum(1 for s in samples if is_correct(s, p["tests"]))统计 200 个样本中通过全部测试的样本数。print(f"pass@1 = {total_p1 / n:.3f}")输出最终的单次通过率,便于对比不同 k 值下的能力变化。
常见误区
误区我一直以为 pass@k 是指让模型同一个问题回答 k 遍,挑出最好的一份当答案,所以它代表的是“模型用 k 次机会能做到多好”。
正解pass@k 是一个统计估计量,衡量的是“从 n 个生成样本中随机抽 k 个,至少一个是正确解的概率”,它描述模型生成分布的整体质量,而非推理时的答案选择策略。真实部署时你不会把 k 个答案都抛给用户,评测只是用多次采样来更稳定地测量能力。好比评委给大厨 k 次试做机会是为了评估真实水准,而不是让大厨把 k 盘菜都端上桌。
为何常见:因为名字里有个 k,且“至少一次成功”听起来像“取最好一次”,人们很容易把它和实际推理时的多数投票或 rerank 策略混淆。
误区看到榜单上某模型 HumanEval pass@1 到了 0.85,我就觉得它已经是个合格的程序员了,可以直接接真实项目需求。
正解HumanEval 只有 164 道人手写函数级题目,考察范围偏算法与函数补全,基本不涉及多文件协作、依赖管理、长上下文理解。若训练数据不小心混入了相似题目,分数还会虚高。它像一场只考刀工的比赛,刀工好不代表能开餐厅。
为何常见:HumanEval 是引用最广的代码基准,模型论文和新闻稿都在传播这个数字,容易让人们把“单点指标”误当成“全面能力”。
误区为了让模型分数好看,我把 temperature 调到 0.9,pass@k 果然涨了不少,我觉得采样多样性越强评测就越有利。
正解temperature 调高会让生成样本更多样,pass@k 因此上升,但这同时会拉低 pass@1,因为在温度过高时单次生成的稳定性变差。公平评测必须在固定温度下进行,并报告温度和采样数配置。否则就像宣布“所有大厨可以用任意火力比赛”,结果自然不可比。
为何常见:很多人把 pass@k 当成了可无限优化的唯分数指标,忽略了评测本身要求“所有参赛者同条件”这一前提。
误区我上次跑出的 pass@1 是 0.612,这次是 0.607,我怀疑评测流程有 bug,分数怎么不是完全相同的。
正解Code LLM 生成带随机性,即使固定 seed 和 temperature,不同批次(如 GPU 算子、模型部署版本变化)仍可能带来微小波动。正确做法是固定超参、多次运行报告均值与标准差,并检查置信区间是否重叠,而不是死磕小数位。评委团看的是大厨的平均水平,而不是某一勺盐的精确克数。
为何常见:早期的单次运行评测给人们留下了“精确科学”的错觉,忽略了生成模型本身是随机采样系统这一本质。
面试题
Q1. HumanEval 基准包含多少道人手写 Python 题目?
- A.共 974 道
- B.共 200 道
- C.共 164 道← 正确答案
- D.共 1640 道
答案:C。HumanEval 由 OpenAI 在 2021 年发布,包含 164 道人手写函数级题目。设错选项 974 属于 MBPP 的题量,200 与 1640 则是干扰数字。好比评委手写了 164 道固定菜谱,而不是从网络上抄袭现成题目。
Q2. pass@k 指标反映的是模型的哪项能力?
- A.全部 k 次试做成绩的平均
- B.至少一次试做成功的概率← 正确答案
- C.第 k 次单独试做得到的成绩
- D.前 k 次试做中最好的一次
答案:B。pass@k 指模型生成多个候选解后,随机抽取 k 个样本中至少有一个通过测试的概率。它不是平均分也不是第 k 次得分,而是对“多次机会下能否成功一次”的概率刻画,像给大厨 k 次试做机会、只要一次成功就过关。
Q3. MBPP 基准的题目呈现形式通常是?
- A.自然语言需求描述与断言检查← 正确答案
- B.函数签名与文档注释
- C.单元测试代码文本
- D.二进制补丁内容形式
答案:A。MBPP 提供自然语言需求描述加 3 条断言,让模型生成完整程序。函数签名加文档注释是 HumanEval 的形式,单元测试和补丁则不是题面形式。这好比 MBPP 说“我想吃番茄炒蛋”,而 HumanEval 直接递上写好的食材清单。
Q4. 无偏 pass@k 估计公式中,n、c、k 分别代表什么?
- A.样本总数 / 抽选数 / 通过数
- B.样本通过数 / 抽选数 / 样本总数
- C.抽选数 / 样本总数 / 通过数
- D.样本总数 / 通过数 / 抽选数← 正确答案
答案:D。n 是总采样数,c 是通过测试的样本数,k 是随机抽取的样本数。公式用组合数计算“抽 k 个全失败”的概率,再取补集。D 的顺序是 n/c/k,与公式定义一致,其余选项均混淆了三者的位置。
Q5. MultiPL-E 在评测流程中的主要作用是?
- A.调整模型输出的温度参数
- B.加速代码执行并大幅降低显存
- C.为题目生成额外的测试用例
- D.将题目翻译成多种目标语言← 正确答案
答案:D。MultiPL-E 把 HumanEval 和 MBPP 的题目翻译到 18 种以上编程语言,用于评估跨语言编程能力。它不负责调参、提速或加测试用例,而是让同一道菜能通过多国做法来检验大厨是否真全能。
Q6. 以下哪些因素会影响 pass@k 评测分数的可复现性?
- A.采样时的 temperature 参数← 正确答案
- B.每个题目的采样样本数 n← 正确答案
- C.生成过程的随机种子 seed← 正确答案
- D.展示结果用的显示器分辨率
答案:ABC。temperature 决定样本多样性,n 决定采样规模,seed 决定随机序列,三者都会直接影响 pass@k 结果,评测时必须固定。显示器分辨率与生成过程无关,不会影响模型输出概率分布。
Q7. EvalPlus 针对 HumanEval 的主要改进是?
- A.额外补充更多的测试断言← 正确答案
- B.将题目规模从 164 减半
- C.完全改用自然语言人工判分
- D.仅保留 C++ 语言题目的评测
答案:A。EvalPlus 为 HumanEval 每道题补充大量额外测试断言,用以揪出“能跑通少量断言但逻辑不完整”的虚假正确代码。它不减少题量、不人工判分、也不只测单一语言,等于给同一批菜加了更多挑剔的评委。
Q8. 以下哪些属于代码评测中的防数据泄漏措施?
- A.人工手写题目避免泄漏← 正确答案
- B.训练语料过滤相似代码← 正确答案
- C.采用动态题库不断出新题← 正确答案
- D.调高采样温度遮盖过拟合
答案:ABC。手写题目、过滤训练语料、动态出新题都能降低模型“背答案”的风险。调高 temperature 只是让输出更具随机性,并不能解决题目泄漏问题,反而会污染评测结果。
Q9. pass@k 无偏估计公式与朴素估计 1-(1-c/n)^k 的主要区别是什么?
- A.用组合数修正样本重复与有限总体的偏差← 正确答案
- B.二者在任意条件下完全等价
- C.朴素估计更贴合真实概率
- D.无偏估计依赖人工评分结果
答案:A。朴素估计假设每次采样独立且样本无限,但实际生成的 n 个样本中存在重复与相关性,直接用通过比例会带来偏差。无偏估计通过组合数在有限样本下算出精确概率,好比厨师试做 200 次后有重复翻车记录,评委会按实际记录统计而非简单按比例推算。
Q10. 以下哪些指标可用于代码补全任务评测?
- A.精确匹配率与编辑距离指标← 正确答案
- B.代码行数与注释覆盖率统计
- C.编译通过率与单元测试断言← 正确答案
- D.模型输出 token 数统计
答案:AC。精确匹配率、编辑距离衡量生成文本与参考答案的接近程度,编译通过率和单元测试则衡量代码可执行性,都是补全评测的常用指标。代码行数、注释覆盖率、token 数只反映统计特性,不反映补全质量,好比数菜盘子数量不能判断菜好不好吃。