跳到主要内容

Prompt Bisect:通过二分查找定位破坏 Eval 的修改

· 阅读需 12 分钟
Tian Pan
Software Engineer

评测榜单的分数一夜之间掉了两分。在绿色运行(通过)和红色运行(失败)之间,唯一发布的内容就是上周的提示词 PR —— 那个包含了 17 处修改的 PR。两个章节调整了顺序。三个新的 few-shots。一个更严厉的拒绝条款。一个更换过的角色描述。还有一些人称之为“润色”的单词级改动。当复盘开始时,有人说了句显而易见的话:“肯定就是其中之一。”然后他们花接下来的两天时间去搞清楚到底是哪一个。

这两天时间是寻找单一回归最昂贵的方式。而另一种只需几分钟的方法则完全借用了一个有着 40 年历史的内核调试技巧:对补丁进行二分查找 (bisect)。将提示词视为一系列可回滚的变动块 (hunks),将评测套件作为断言条件,让二分查找隔离出导致分数波动的代码行。其中的数学原理与 git bisect 在提交记录上运行的原理一致,而且它强制要求的提示词管理规范,其带来的收益甚至超过了二分查找本身。

问题在于,只有当提示词确实是可二分查找的时,Prompt Bisect 才奏效。如果在配置 UI 中粘贴了一个 4000 token 的大块,由三个人在一周内完成编辑,且保存时没有记录每个变动块的归属,那么无论周围的测试框架多么聪明,它都是不可二分的。二分规范始于二分操作的上游:即团队决定什么才算作对提示词的一次“改动”那一刻。

17 处修改的问题本质上是提交粒度的问题

之所以“肯定就是那 17 个之一”在当下无法回答,是因为这 17 处修改并不是独立的 17 个点。它们是一个未分化的差异 (diff)。版本控制层记录的是“提示词 v34 → v35”,在这个单一的版本中,存在着所有的顺序调整、所有的表述修改和所有的附加示例。你无法只回滚“关于退款的 few-shot”而不回滚其他的润色,因为从来没有人将那个关于退款的 few-shot 作为一个独立的改动提交。

这种失败模式与在代码仓库中每个人都将一周的工作压缩 (squash) 成一个合并提交,从而导致 git bisect 失效的情况如出一辙。二分查找可以找到错误的提交,但如果错误的提交包含一千行不相关的改动,那么知道是哪个提交也几乎无法缩小搜索范围。代码审查中的解决方案已经是十年来的常识了 —— 小而集中的提交,每个提交只包含一个逻辑改动 —— 而在任何二分工具发挥作用之前,提示词也必须落地同样的解决方案。

对于提示词,改动的单位是变动块 (hunk):对某个部分的连续编辑。一个新的 few-shot 是一个变动块。一个更严厉的拒绝条款是另一个。令人懊恼的是,顺序调整实际上是两个变动块(一个删除和一个插入),如果你把它们提交在一起,二分查找会将它们视为一个。想要让 Prompt Bisect 奏效的团队会分别提交这些改动,并附带评测分数。正是这个分数让提交历史在日后变得可检索:你拥有的不再是一连串的“修改了提示词”,而是一连串的“修改了提示词,评测分数从 0.81 变动到 0.815”。当分数出现回归时,你只需在分数实际发生变动的小区间内进行二分查找,而不是在整个季度中寻找。

二分查找如何实际运行

在机械操作上,Prompt Bisect 步步遵循 git bisect 的流程。你标记一个已知的良好版本(评测分数为 0.83 的那个)和一个已知的错误版本(评测分数为 0.81 的那个),测试框架会检出中点。这里的中点是指“应用第 1 到 N/2 个变动块;回滚其余部分”。运行评测,获得分数,决定回归发生在中间点的哪一侧,然后递归进行。如果有 17 个变动块,你大约在 5 轮内就能收敛。每一轮成本是一次评测运行 —— 如果你的套件很小且供应商速度很快,大约需要 20 分钟;如果不是,则需要更长时间。

断言条件是评测套件,使用方式与 git bisect run 使用脚本的方式相同:在每个中点给出“好”或“坏”的确定性结论。代码二分查找中不存在但提示词二分查找中存在的陷阱是,断言条件是有噪声的。单元测试要么通过要么失败。而 LLM 评测今天可能得 0.81 分,明天对同样的提示词可能得 0.83 分,因为温度为 0 并非真正确定,评测模型 (judge model) 也有其自身的方差,且 100 个案例的套件只是一个小样本。

最近的测量工作将其分解为预测噪声(模型在多次运行中对同一问题给出不同答案)和数据噪声(问题的不同采样改变了分数),其中预测噪声通常是两者中较大的一个 —— 在某些数学基准测试中,它大约是数据噪声的两倍。如果你正在追查的回归是 2 分,而评测的运行间标准差是 1 分,那么每个二分步骤都有很大几率落在错误的一侧。二分查找会因为错误的数据而崩溃。

缓解措施是那种乏味的方法。多次运行每个二分步骤并取平均值。使用针对已知良好基准的配对比较 (paired comparison) 而不是绝对分数,这样两轮运行共有的噪声就会抵消。如果评测套件太小,导致 50 个案例产生的置信区间比回归值还宽,那么在修复提示词之前先修复套件 —— 你无法二分查找一个低于噪声底 (noise floor) 的信号,当事实是“评测无法区分”时,“我做了二分查找,发现是 few-shot 的问题”就是一个虚假的自信答案。研究如何在不完美的评测模型下进行鲁棒 LLM 评估的研究人员一直得出同样的结论:校准集和方差校正阈值是将评测分数视为决策依据而非凭直觉行事的前提代价。

大多数团队尚未弥补的工具鸿沟

当团队第一次尝试对提示词进行二分查找(bisect)时,令人沮丧的发现是,该方法论所假设的工具在标准的提示词管理技术栈中大多并不存在。Langfuse、PromptLayer、Maxim、Humanloop,或是内部基于 Notion 页面构建的提示词 CMS——几乎所有这些工具都将提示词作为一个整体(blob)进行版本管理。它们会为你提供 v34 和 v35 之间的差异(diff),但不会给你“v34 + v35 差异中的第 1、2、3 个变更块(hunks),但不包括第 4 到 17 个”,它们也不允许你对这种合成的中间状态进行评分。这些平台拥有修订追踪功能,但它们没有“逐一应用变更并评估”的功能,而这正是二分查找真正需要的。

一些团队通过将提示词维护在真实的 git 仓库中,并将每个变更块视为一次真实的提交来绕过这个问题,然后运行以评估脚本作为断言的 git bisect run。当提示词是由工程部门拥有的扁平文件时,这种方法是行之有效的。但它无法扩展到这样的组织:产品团队在 UI 中编写提示词,评估团队拥有框架,而平台团队拥有注册中心——三个群体,三种工具,没有共享的原子变更单位。二分查找需要一个单一事实来源,其中的变更块可以独立迁出(check out),而实现这一目标主要是一个流程问题:谁被允许编辑提示词?在什么工具中编辑?以什么为单位编辑?

在资深的 LLM 团队中出现的一种工作模式是“以代码形式存在的提示词,且前端配有 UI”。权威存储是一个 git 仓库。UI 中的编辑会转化为类似 PR 的变更请求,每个请求都带有逐块的差异,并附带在合并前计算的评估增量。UI 是一个便利层,而不是权威机构。这比让产品人员直接将新词句粘贴到文本框中要重一些,但它使二分查找成为可能,因为它让“到底改变了什么”变得清晰可答。原本需要两天的事故复盘变成了二十分钟的二分查找。

必须首先落地的组织产出物

在内部推行这种纪律时,最有效的视角是:提示词二分查找是症状检查,而不是目标。目标是让每一次提示词变更都成为一个足够小的单位,从而使“我改了一些东西,然后效果变好了”不再是团队中唯一的变更记录。目前,在大多数发布 LLM 功能的团队中,提示词的历史记录读起来就像 2014 年左右设计师的 Sketch 文件:当前状态可见,之前的状态在某个地方,而任何特定决策的解释都在某人的脑子里,并且正在腐烂。

在二分查找发挥作用之前,必须落地的产出物反而是那些枯燥的东西。一个提示词注册中心,其中最小的变更单位是变更块(hunk),而不是版本。一个评估流水线,能够为每次提交生成评分,将其附加到提交上,并支持向后查询。一种规范——这是文化层面的——即提示词以 PR 的单位进行审查,每个单位仅包含一个逻辑变更,就像函数一样。如今那些推送“v35:清理 + 新示例 + 重新排序”的作者,需要像初级工程师第一次尝试提交 4000 行重构 PR 时受到的代码审查提醒一样:拆分它。

做到这一点的团队报告了二分查找之外的二阶收益。一旦提示词的历史记录变成了由小的、单独评分的变更组成的序列,变更日志本身就成了学习产出物。新团队成员可以像新工程师阅读函数的 git log 一样阅读提示词:“添加这个子句是因为评估在这个切片(slice)上发生了翻转;删除这个示例是因为它导致了过度拒绝;这个重新排序经过了测试,虽然没有带来任何变化,但为了可读性而保留了。”如果没有这些记录,提示词就成了民间传说——只有当前的作者维护它,而更换作者则会引发知识转移危机。

何时进行二分查找,何时直接回滚

当回归是细微的、真实的,并且存在于已知的一组近期变更区间内时,二分查找是正确的工具。在一些值得指出的常见案例中,它是错误的工具,因为反射性地使用它会浪费它本应节省的时间。

如果测试套件尚未稳定——如果同一提示词的连续运行产生剧烈波动的评分——二分查找将会在追逐噪声中耗尽评估预算。先修复测试套件。

如果回归幅度巨大而差异(diff)很小,直接盯着差异看就行了。二分查找的全部价值在于差异大到肉眼难以处理时;对于三行的变更,人工检查速度更快。

如果“回归”结果证明是由于模型提供商方面的更新引起的,那么对提示词历史进行二分查找将一无所获,因为提示词本身没有任何改变。这种二分查找应该在提示词版本和模型版本的笛卡尔积上运行,这是一种不同的方法论,有其自身的设置成本。

而且如果团队能够可靠地回滚整个 PR,并在调查期间发布之前的版本,这几乎总是正确的当务之急。二分查找是为了寻找原因;回滚是为了止血。两者并不冲突——先回滚再进行二分查找的团队,在查明是哪个 few-shot 导致问题时,无需向用户道歉。

更深层的启发是内核维护者在 20 世纪 90 年代后期就已经领悟到的,而 LLM 团队正在慢慢重新学习:回归的成本主要取决于回归出现时变更历史的粒度。提示词二分查找的工具正在跟上。使工具发挥作用的纪律——小量提交、附加评分、原子变更块、单一事实来源——必须是一种刻意的选择,在最终迫使你进行讨论的回归发生之前做出。现在就做,下一次隔夜出现的两分下降将是二十分钟的二分查找,而不是两天的紧急会议。

References:Let's stay in touch and Follow me for more thoughts and updates