面试季的字节跳动办公楼里,阿杰正紧张地等待他的编程测试。电脑屏幕上出现一道题:“编写一个函数,输入两个整数,返回它们的和。”阿杰不仅要把函数写对,系统还会运行他的代码、用隐藏测试用例检查边界条件,甚至评估他的解题思路是否符合工程规范。这场考试,就是学编程的人都要经历的“机考”。 大语言模型(LLM)的成长路上,也有一场这样的“入行机考”——名字叫 HumanEval。它不是让 AI 写作文、聊天、翻译,而是给 AI 出一道道“函数级编程题”:给定一个函数名、文档字符串描述和若干测试断言,让 AI 生成可运行的 Python 函数体。就像阿杰的笔试一样,答案不仅要“看起来对”,还要真正通过测试用例才能拿分。
对 AI 从业者而言,理解 HumanEval 就是理解“如何用工程级标准检验 LLM 的代码能力”——它是模型评测体系中最硬的标杆之一,直接决定你能否从“能聊天”的模型里挑出“真会写代码”的那一个。
可视化演示
动态展示HumanEval函数级代码生成的评测流程、164题手写数据集结构、pass@k采样指标及HumanEval-X五种语言扩展(820题)。
本节配有交互动效(进入视口后自动加载,约 3KB)。
评测基准概述
阿杰走进考场的那一刻,发现门口贴着一张告示:“本次考试共 164 道题,全部为真实工程场景中的函数编写题,请独立完成。”这张告示,就像 HumanEval 数据集的“考场布告栏”。HumanEval 是 OpenAI 在 2021 年发布的代码生成评测基准,包含 164 道由人类专家手写的编程题——每道题都是一个函数签名(函数名、参数列表、返回类型)加上一段描述功能需求的文档字符串,以及若干用于验证正确性的测试断言。
在 LLM 评测体系中,HumanEval 占据着独特的定位。如果说通用对话评测(如 MMLU、GSM8K)是“问答题考试”,那么 HumanEval 就是“上机编程考试”。它不关注模型能否“聊得好”,只关注模型能否“写对”。这正是函数级生成的核心:模型的输出不是一个完整的文件,而是一个单一函数。
评测流程像极了一个标准的自动化判卷系统:系统把函数签名和文档字符串作为 prompt 送给模型,模型生成函数体,然后系统在沙箱环境中运行该函数,调用预先写好的隐藏测试用例,逐条检查输出是否符合预期。所有测试全部通过,这道题得 1 分;任何一条失败,得 0 分——没有“部分分”。
阿杰从考场出来时感慨:“原来我的代码好不好,不是老师说了算,是那些隐藏测试用例说了算。”HumanEval 也一样——模型的“考试成绩”完全由断言决定,任何“我觉得我写得对”都不算数。这种机制的好处是客观、可复现,坏处是它只奖励正确答案,不奖励“接近正确答案”的尝试。
- 正确性判定若该函数在所有测试输入下产生的输出与预期完全一致,则视为正确。
- 零分判定只要存在一个测试断言失败,无论函数在大方向上多么正确,此题都不得分。
生成原理剖析
阿杰坐在考场的电脑前,屏幕上是这样一道题:
他的任务:在 def 和冒号之后写出函数体。这正是 HumanEval 中每一道题的标准形态。模型接收的输入是“函数签名 + 文档字符串”——相当于考题的题干,输出是函数体内的一段代码——相当于考生的答案。这个流程在学术上叫作“函数级代码补全”。
函数级生成的关键在于“单次采样”。模型不是通过多轮对话逐步完善答案,而是在给定 prompt 后,一次性地从左到右逐 token 生成函数体。这个过程可以理解为阿杰在草稿纸上一次把代码写完,而不是和监考老师来回确认。模型每一步根据已生成的部分,在词表上计算下一个 token 的概率分布,再从分布中采样。
阿杰写完一道题,深呼吸,点了“提交”。他不知道的是,模型在生成代码时并不能“回头检查”——一旦某个 token 选错,后面的生成很可能“将错就错”越走越偏。这就像写作文时不能回删,一句话写歪了,后面整段都会别扭。这也是为什么函数级生成对模型的“一步到位”能力要求极高。
- 序列分解函数体的生成被分解为 n 个 token 的依次预测,每一步仅确定下一个 token。
- 条件依赖每个 token 的生成依赖题干 x 和此前所有已生成的 token,形成链式条件概率。
def has_close_elements(numbers: list[float], threshold: float) -> bool: """检查列表中是否存在两个元素的距离小于给定阈值。"""
pass@k 评分算法
考场上,有一种特殊情况:阿杰对一道题没有把握,于是他写了三个不同的版本提交。监考老师的评分规则是——只要其中任何一个版本通过了所有测试用例,这道题就算他对了。这就是 HumanEval 的 pass@k 评分思想:允许模型对同一道题生成 k 个候选答案,只要其中至少一个能通过所有测试,就视为该题通过。
但这里有个统计学陷阱。一种看似合理的计算方法是“测试时采样”:多次采样直到获得 k 个通过测试的答案,然后计算通过率。但这会高估模型的真实能力。正确的做法是“无偏估计”:预先采样 n 个独立答案(n ≥ k),统计其中通过测试的答案数量 c,再按下式计算:
为什么不能直接用“成功率”代替这个公式?假设一个模型在 n=10 次采样中有 8 次正确,那么它在 pass@1 上的真实表现更接近 0.8 还是某个调整值?答案是:如果直接取“在全部采样中的通过率”作为 pass@1,会因为样本方差而偏乐观。无偏估计公式在数学上消除了这种偏差。这就像用样本均值估计总体均值时需要修正自由度一样——统计的严谨性在此体现。
- 二项式分母从 n 个答案中抽取 k 个,总共有 \(\binom{n}{k}\) 种组合方式。
- 二项式分子从失败的 n-c 个答案中抽取 k 个(即抽到的全是错误答案)的组合数。
- 无偏概率全部抽到错误答案的概率为两者之比,用 1 减去该概率即为至少抽中一个正确答案的概率。
数据集构造特点
阿杰在考场上发现一个有趣的现象:这些题目的语言风格非常统一,像是出自同一位老师之手。HumanEval 的确如此——它的 164 道题目全部由 OpenAI 团队的研究人员手工编写,而非从 GitHub 等代码仓库中爬取。这个看似“费工夫”的决定,是为了规避一个大问题:数据污染。
如果题目是从公开仓库爬来的,模型的训练数据中很可能已经包含这些代码,那么考试就变成了“开卷抄答案”,分数字没有意义。手工编写意味着这些题在数据集发布之前从未出现在任何公开语料中,确保模型是“真会做”而非“背过答案”。
每一道题的构造也颇有讲究:题目包含函数签名、文档字符串描述、以及平均 7.7 个测试用例。这些测试用例不仅是简单的输入输出对,还覆盖了边界条件和特殊场景。文档字符串的风格模仿了 Python 标准库和各种开源项目的做法——用一句话描述函数功能,必要时补充参数说明和返回值说明。
这种构造方式的意义在于“防作弊”和“保公平”。就像高考出题组不会从学生人手一本的练习册里原封不动地选题一样,HumanEval 的出题人刻意避开了公开代码。同时,题目都足够“小而精”——一个函数、一个明确的功能描述、几个边界测试——不会因为题目歧义导致模型“读不懂题意”。
- 分母含义数据集总题目数为 164。
- 分子含义所有题目的测试用例数之和约为 1263 个。
- 比值含义平均每道题有 7.7 条断言,复杂度足以覆盖正常路径与边界条件。
典型性能表现
阿杰考完试,拿到了成绩单。他好奇数据:这份成绩单,正是 HumanEval 上各模型的“考分排名”。OpenAI 的论文《Evaluating Large Language Models Trained on Code》报告了 Codex 在 HumanEval 上的初始表现——pass@1 为 28.8%。这个数字很快成为业内标杆:一个在 54M 个 GitHub 公开仓库上预训练过的代码模型,仅能答对不到三分之一的题目。
这个“28.8%“的意义远不止一个分数。它像一把尺子,让后续所有模型有了明确的”进步刻度“:GPT-3.5 将 pass@1 提升到 48.1%,GPT-4 达到 67%,Claude 3.5 Sonnet 超过 92%。HumanEval 从”几乎不可能完成的任务“变成了”基本达标”的入门门槛。这种演变展示了 LLM 代码能力的快速进化。
另一个显著特征是题目难度分布的不均衡性。HumanEval 的 164 道题中,既有简单到“返回两个数的和”这样的送分题,也有涉及多重嵌套逻辑、动态规划的中高难度题。模型的“答题正确率”会随题目难度的上升而显著下降——这与人类考生的表现高度一致:难点该不会的还是不会。
- 初代成绩Codex 在 pass@1 下只能解决 28.8% 的题,说明当时模型离“会写代码”还有相当距离。
- 后续提升GPT-4 在 pass@1 达到 67%,意味着超过三分之二的题目能被一次生成就正确解出。
- 性能差异这种提升主要来自更大的模型规模、更充分的代码训练数据和更完善的对齐技术。
优化策略实践
阿杰发现同桌小李提交的答案通过率总是比自己高。一问才知道,小李的策略是:每道题不是只交一个答案,而是把几个不同思路的版本都交上去。而在 HumanEval 的世界里,“多写几个版本”正是最直接的涨分策略。
第一个关键参数是温度(temperature)。它控制着模型采样时的随机程度:温度越低,模型越倾向选择概率最高的 token,输出更保守稳定;温度越高,模型越敢于尝试低概率 token,输出更多样但也更容易出错。在 HumanEval 评测中,如果追求 pass@1,通常把温度设低(如 0.2)以获得最确定的输出;如果追求 pass@100,则把温度提高到 0.8,让模型生成更多样化的候选答案。
第二个策略是多次采样 + 投票。让模型对同一道题生成 k 个答案,然后用测试断言筛选出通过数量最多的答案(或随机选一个通过测试的)。这个策略的涨分效果令人惊讶:Codex 的 pass@100 达到了 71.2%,而 pass@1 只有 28.8%。多生成 99 次,正确率翻了一倍以上,因为不同的采样路径会探索到不同的解题思路,只要有一个想对了,就能得分。
第三个更高级的策略是自一致性(self-consistency)。它让模型在“自由发挥”模式下生成多个多样化答案,再通过“答案聚类”找到最一致的解题逻辑。在代码生成中,这意味着对同一道题生成多个候选,然后运行测试或用某种启发式方法找出“最可靠”的那个。这与人类解题时“多验算几遍”的直觉一致——共识往往更可信。
- 低温场景如果目标是“一次写对”,温度应设在 0.2 左右,模型输出更保守、更接近训练数据中的主流写法。
- 高温采样如果允许 k 次尝试,温度升到 0.8,模型会产出更多样化(但单次质量略低)的答案。
- 收益对比多样化的答案集合提高了“至少一个正确”的概率,因此 pass@k 随 k 增大而上升。
AI 应用场景
代码补全工具的评测基准
阶段:模型评测与选型
解决的问题:企业在挑选或开发 AI 编程助手时,如何客观衡量“这个模型写代码的能力到底行不行”。传统做法靠内测员工的“好不好用”这种模糊感受,难以量化。HumanEval 提供了一个标准化试卷——让候选模型在同样的题目上比拼正确率,用 pass@1 等指标做横评。
具体案例:某互联网公司的 AI 工具选型团队,用 HumanEval 对 CodeLlama、StarCoder、GPT-3.5 三款模型做对比,分别计算其 pass@1 和 pass@10,结合真实业务需求选择最适合内嵌到 IDE 中的模型。团队还结合内部代码库自建了“定制版 HumanEval”来评估垂直场景效果。
编程教学平台的自动判题系统
阶段:模型应用与反馈优化
解决的问题:在线编程教育平台需要大规模自动评估学员代码质量,人工判题耗时且主观。借鉴 HumanEval 的“函数级 + 断言验证”思路,平台可以构建一个自动判题系统:给定题意描述,让 AI 生成参考答案和测试用例,再对学员提交做断言级校验。
具体案例:某在线编程网站的“AI 模拟面试”功能中,系统使用 HumanEval 设计思路自动出题(函数签名 + 文档字符串),学员按要求完成函数后,由系统运行隐藏测试用例判定对错,并输出具体的错误断言信息指导学员修正。整个过程不需要真人老师介入。
Python 演示
import mathimport random def generate_candidates(model_prompt, n): """模拟从 LLM 采样 n 个候选函数体 (这里用随机猜测代替真实 LLM)。""" candidates = [] for _ in range(n): # 模拟:随机猜测两个数的和是否正确 guess_sum = random.randint(1, 50) candidates.append({"sum": guess_sum}) return candidates def run_test(candidate, a, b): """断言检查:候选函数是否正确实现了两数求和。""" return candidate["sum"] == a + b def compute_pass_at_k(n, c, k): """无偏估计 pass@k: 从 n 个样本中已知 c 个正确, 随机抽 k 个至少含一个正确的概率。""" if n - c < k: return 1.0 comb = math.comb return 1.0 - comb(n - c, k) / comb(n, k) def main(): # 题面: 实现两数求和函数 a, b = 3, 5 expected = a + b n = 100 # 模型采样 100 个候选答案 k = 10 # 允许 10 次尝试 (pass@10) temp = 0.8 # 采样温度 candidates = generate_candidates("def add(a, b): ...", n) correct_count = 0 correct_indices = [] for i, cand in enumerate(candidates): if run_test(cand, a, b): correct_count += 1 correct_indices.append(i) passk = compute_pass_at_k(n, correct_count, k) naive = correct_count / n print(f"题目: 实现 add({a}, {b}) -> {expected}") print(f"采样候选数: {n} | 温度: {temp}") print(f"正确的候选数: {correct_count} / {n}") print(f"朴素通过率: {naive:.4f}") print(f"无偏估计 pass@{k}: {passk:.4f}") if __name__ == "__main__": main()
def compute_pass_at_k(n, c, k):这是核心函数,实现了论文中的无偏估计公式,避免了直接用“通过率”代替“通过概率”的统计偏差。comb = math.combPython 内置的组合数计算函数,对应公式中的二项式系数 \(\binom{n}{k}\)。if n - c < k: return 1.0边界情况处理:如果失败样本数小于 k,那么随机抽 k 个必然至少包含一个正确样本,概率为 1。naive = correct_count / n这是朴素的“通过率”计算方法,注意它高估了 pass@k 中 k<n 时的真实通过概率,所以需要无偏估计修正。run_test(cand, a, b)模拟 HumanEval 的断言验证机制:每个候选答案必须通过测试用例才算正确。
常见误区
误区很多初学者认为 pass@10 就是“让模型生成 10 个答案,其中有至少一个正确的概率”,所以直接把 10 次采样中通过测试的比例当作 pass@10。他们这样算:生成 10 个答案,3 个正确,所以 pass@10 = 30%。
正解真正的 pass@k 是“随机抽取 k 个答案中至少有一个正确的概率”,如果 n=10 次采样中有 3 个正确,那么随机抽 1 个(即 pass@1)的期望通过概率并不是 30% 那么简单。无偏估计公式考虑的是“从 n 个中抽 k 个”的组合概率,而不是简单的成功率均值。这就好比从一盒弹珠(其中 3 红 7 蓝)里随机抓 1 颗,抓到红色的概率是 30%;但如果抓 5 颗,至少有一颗红色的概率远超 30%。两者的直觉相同,但数值关系要用组合数学算,不能简单相加。
为何常见:因为“成功率”和“至少一个成功的概率”在日常语言中常常被混用,加上“pass@k“这个符号本身带有”k 次尝试”的暗示,初学者很容易跳过组合数学直接取平均。
误区有人以为 HumanEval 中的“测试断言全部通过”就意味着模型生成的函数在真实场景中也是正确的。他们相信,既然边界用例都过了,代码质量一定没问题。
正解测试断言只能覆盖出题人预先设定的输入场景。HumanEval 每道题虽然平均有 7.7 条断言,但实际输入空间几乎是无限的。一个函数可能碰巧通过了所有断言,却在未见过的输入上出错——比如忽略了大数溢出、浮点精度、特殊字符等。这就像驾照考试通过了科目二并不代表你能应对所有路况一样,测试通过只是“在已考察的维度上合格”,不保证全面正确。
为何常见:因为测试驱动开发(TDD)的成功经验让开发者习惯于“测试通过 = 功能正确”的心智模型,但在 LLM 生成的代码中,模型可能通过“记忆相似模式”而非“真正理解算法”来通过测试。
误区不少人在看到某个模型在 HumanEval 上拿到了 90+ 的 pass@1 后,直接断定“这个模型写代码能力非常强,完全可以替代初级程序员”。
正解HumanEval 考的是“函数级补全”,每道题都是独立的小函数,不涉及跨文件依赖、系统设计、代码审查、性能调优、重构等真实工程要素。一个模型即使 HumanEval 满分,也未必能规规矩矩地完成一个大型项目的某个模块。这就像在学校里考满分的数学天才,不一定能独立完成一个建筑项目的结构设计——HumanEval 只是“基础题”,工程能力是“综合大题”。业界因此也逐渐引入了更具挑战性的基准,如 SWE-bench(涉及真实代码仓库的 issue 修复)。
为何常见:因为 HumanEval 是最早被广泛报道的代码基准,媒体和大模型厂商的宣传习惯性将其简化为“编程能力”的象征,忽略了它只是“函数级”这一事实。
误区初学 LLM 评测的同学常以为 HumanEval 是“手写出来的”就永远不会数据泄露了,模型不可能在其训练集中见过这些题。
正解HumanEval 的题目本身是手写的,但这个数据集后来被广泛用于各种模型的训练或微调公开数据中。许多模型在训练时直接或间接地接触过 HumanEval 的题目(如通过包含该数据集的指令微调集合)。一旦模型“见过”题目和标准答案,再在 HumanEval 上评测就变成了变相的记忆测验,而非能力评估。这也是为什么最新的评测基准(如 LiveCodeBench)强调“时间隔离”——不断加入新题,确保模型没有提前看到。
为何常见:人们低估了数据在开源生态中的传播速度,以为“发布时说手写”就永远干净,忽略了后续的二次传播和训练数据合并风险。
面试题
Q1. HumanEval 数据集包含多少道函数级编程题?
- A.100 道
- B.164 道← 正确答案
- C.254 道
- D.500 道
答案:B。HumanEval 由 OpenAI 在 2021 年发布,包含 164 道由人类专家手写的函数级编程题。100、254、500 都不是该数据集的题目数。
Q2. HumanEval 评测中,一个模型生成的函数必须满足什么条件才算该题通过?
- A.语法正确即可得分
- B.所有测试断言全部通过← 正确答案
- C.输出格式匹配就算对
- D.至少通过半数测试用例
答案:B。HumanEval 采用“全或无”计分:所有隐藏测试断言必须全部通过,该题才得 1 分。任何一条失败都算整题错误。
Q3. 为什么 HumanEval 的题目要由人类专家手工编写而非从 GitHub 爬取?
- A.手工编写速度更快
- B.避免与模型训练数据重叠导致数据污染← 正确答案
- C.爬取的代码通常无法运行
- D.手工写的代码必定没有 bug
答案:B。如果从公开代码仓库爬取题目,模型在训练中很可能已经见过这些代码,评测就成了“背答案”而非“考能力”。手工编写确保题目的新颖性,保障评测公平。
Q4. 在 HumanEval 评测中,温度参数的作用是什么?
- A.控制函数执行的运行速度
- B.调节解码时随机采样的自由度← 正确答案
- C.决定生成函数的代码长度上限
- D.影响测试用例的执行顺序
答案:B。温度 τ 控制模型采样时的随机性:τ 低则输出保守确定性高,τ 高则输出多样、探索性强。它直接影响的是 token 概率分布的形状与采样结果。
Q5. 关于 pass@k 评分算法,下列说法正确的是?
- A.pass@k 是指生成 k 个候选答案后至少有一个正确← 正确答案
- B.pass@k 直接等于“通过率”的均值
- C.pass@1 要求模型一次生成的答案就正确← 正确答案
- D.k 越大,pass@k 值越低
答案:AC。pass@k 表示从 k 个候选答案中至少有一个能通过全部测试的概率,pass@1 即必须一次生成就正确。它需要通过无偏估计公式计算,不能直接用通过率均值;k 越大覆盖正确解的概率越高,值通常越大而非越小。
Q6. 给定 n=100 次采样中有 c=60 个正确答案,用无偏估计公式计算 pass@1 约为多少?
- A.0.50
- B.0.60← 正确答案
- C.0.70
- D.1.00
答案:B。无偏估计公式 pass@1 = 1 - (n-c)/n = 1 - 40/100 = 0.60。当 k=1 时,公式退化为“正确样本数 / 总样本数”,即与朴素通过率一致。
Q7. 以下哪些是提升模型在 HumanEval 上表现的有效策略?
- A.调高采样温度增加多样性← 正确答案
- B.多次采样后投票选择最一致答案← 正确答案
- C.降低温度到接近 0 强制输出动态规划分解
- D.同一 prompt 立即重复采样无变化
答案:AB。提高温度增加输出多样性,配合多次采样投票可显著提升 pass@k;当温度接近 0 时输出固定,重复采样无意义。选项 C 的“强制输出动态规划分解”不是已证实的有效策略,D 完全无效。
Q8. 代码生成评测中“数据污染”问题的最有效防御手段是?
- A.加大测试集规模
- B.采用时间隔离的实时新题← 正确答案
- C.只评测模型在训练集上的表现
- D.人工审查每个模型的训练数据
答案:B。时间隔离(如 LiveCodeBench 的定期更新新题)能保证模型没有在训练阶段见过评测题,是从源头解决污染问题的方案。扩大集规模不解决污染,审查训练数据在深度学习时代不可行。
Q9. 在给定文档字符串和函数签名后,模型的函数体生成过程属于哪种任务类型?
- A.文本摘要式生成
- B.自回归式条件生成← 正确答案
- C.对话多轮补全
- D.非自回归编辑生成
答案:B。函数体生成是一个标准的自回归条件生成任务:模型逐 token 计算条件概率并在词表上采样,每次依赖已生成的前缀和给定的 prompt。
Q10. 关于 HumanEval 与真实工程代码能力的关系,表述正确的是?
- A.HumanEval 满分意味着模型具备完整工程能力
- B.HumanEval 仅覆盖函数级独立逻辑,不含跨文件依赖← 正确答案
- C.工程能力还需系统设计、代码审查等综合评测← 正确答案
- D.HumanEval 的 pass@1 可直接推断模型在实际项目中的成功率
答案:BC。HumanEval 是“基础题”级别的函数级评测,只考虑单函数逻辑正确性,不涉及工程体系能力。真实项目还要求系统设计、依赖协调、重构等综合能力,不能用 HumanEval 分数直接外推实际成功率。