Prompt Bisect:通过二分查找定位破坏 Eval 的修改
评测榜单的分数一夜之间掉了两分。在绿色运行(通过)和红色运行(失败)之间,唯一发布的内容就是上周的提示词 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 是一个便利层,而不是权威机构。这比让产品人员直接将新词句粘贴到文本框中要重一些,但它使二分查找成为可能,因为它让“到底改变了什么”变得清晰可答。原本需要两天的事故复盘变成了二十分钟的二分查找。
