跳到正文
原文
Cloudflare Blog· Ryan Skidmore·· 19 天前精选AI 评分71

Cloudflare 如何把 AI 代码审查单次成本从 2.43 美元降到 0.77 美元

Cutting our inference bill by 68% by being obsessive about optimization

AI 摘要

Cloudflare 通过优化提示词缓存前缀、减少 LLM 调用次数和调整模型与推理强度,把 AI 代码审查的平均成本从 2.43 美元降到 0.77 美元(降幅 68%),中位耗时从 4 分 09 秒降到 1 分 32 秒。

正文 · AI 翻译

我们之前在四月的博客中介绍过我们的 AI 代码审查器,当时平均每次审查的成本是可接受的 1.19 美元。到八月底,这个数字飙升到了约 2.46 美元。我们并没有做任何重大的架构调整:协调器仍然把工作交给同一组专业审查器,同样的风险等级也仍然决定我们进行多深入的审查。

九月中旬前后,我们让智能体们去弄清楚这些 token 都花到哪里去了,并大幅削减它们。在代码质量设为高投入的情况下,现在平均每次审查的成本仅为 0.77 美元,比我们开始之前最贵的两周平均 2.43 美元下降了 68%。我们的主要目标是降低成本,但作为这项工作的一部分,我们还顺带优化了审查器,使审查耗时的中位数现在仅为 1 分 32 秒,而之前是 4 分 09 秒。

约 40% 的节省来自对审查器代码的更改和优化,其余则来自试验我们使用哪些模型以及它们投入多少精力。

一次审查的成本如何翻倍

下图显示了自三月以来每个工作日平均每次审查的成本。它固定了审查等级的构成,因此某天有更多大型审查不会使数据产生偏差。

每次审查的成本稳步上升,从 1.22 美元(三月)→ 1.44 美元(六月)→ 1.74 美元(七月),最终升至 2.32 美元(八月)。其中一小部分原因是更多审查落入了最昂贵的等级(三月为 59%,八月为 65%)。我们认为原因很简单:模型正变得好得多,能力也强得多。自然地,工程师们正在产出更大、更复杂的变更,这意味着其中更多会落入我们的“完整审查”等级。不过我们发现,成本增加的大部分实际上来自这几个月间的模型更换。

八月初,我们把所有审查器都迁移到了 Anthropic 模型上,这使每个任务的成本从 1.38 美元提高到 2.03 美元。我们做出这一选择,是因为在评估中发现 Anthropic 模型在这个特定工作负载上表现优于其他模型。

我们的内部标准审查器 Codex 最终采用了 Claude Opus 4.7,这是出于性能和可靠性的综合考虑。这使其在八月的每次审查成本从约 0.25 美元升至 0.88 美元,比协调器智能体本身还要贵。

缓存命中率也有所提高,从四月的 85.7% 升至九月的 97%,而每次审查的 token 数量增长了两倍多,从 91 万增至 299 万。即使缓存读取成本很低,三倍 token 上的高命中率仍然要贵得多。

自上一篇博文以来还发生了什么变化?

最大的变化之一是审查器现在运行在 OpenCode v2 上,这是对 v1 的彻底重写。在将所有审查从 v1 切换到 v2 之前,我们在比较性影子模式下,对每个金丝雀仓库中的每次审查同时运行了 v1 和 v2。在我们测试的合并请求中,两者在批准决定上的一致率为 98%,因此我们在十月初一次性完成了全员迁移,并移除了 v1 代码。在相同模型下比较两天,v2 上的完整等级审查完成速度快约 13%。我们目前还在试验其他运行内部 AI 审查器的方式,包括仅使用 pi-durable 在 Durable Objects 上运行。希望很快能带来更多相关内容,敬请关注!

那么,token 都花到哪里去了?

就在我们开始专注于成本优化之前,我们的 AI 审查支出按以下方式划分:输出 token 约占 23%,缓存写入成本占 43%,缓存读取成本占 34%。这意味着大部分费用都花在了模型已经见过或即将再次见到的上下文上。

如果你深入查看单个会话,这就完全说得通了。代码质量审查器每次审查平均发起 16.6 次大语言模型(LLM)调用,在这些调用中,它向缓存写入了 72k token,并读回了 1.08m,因为每次调用都会把整个对话作为上下文发送。一个会话的成本会随着调用次数和上下文大小的增长而增长,而在过去几个月里,我们慢慢地把这两者都提高了。

值得花几分钟了解一下 Anthropic 的提示缓存 是如何工作的。它的原理是缓存提示中已经由大语言模型(LLM)计算过的部分。这些部分被称为前缀,因为它们只对对话开头的消息有效,对中间或结尾的消息无效。这意味着未来的请求可以复用早先的前缀,直到对话中与已缓存内容不同的那部分为止。在我们的场景中,harness 工具、系统提示以及 AI 审查器会话开头的消息很少变化,因此它们非常适合做前缀缓存。前缀只有在缓存断点结束它的位置才会被存储。写入缓存的成本是正常输入价格的 1.25 倍,读取成本是 0.1 倍,并且条目会在五分钟后过期,除非有东西读取它。

这意味着我们有两件事要做:让每个会话的第一次请求从缓存读取而不是写入缓存,并让每个会话使用更少的调用。

在每个仓库之间共享缓存前缀

从 9 月 14 日开始,审查器记录了每个审查器第一次 LLM 调用的 token 使用量,以及第一个系统块按发送给提供商时的原样哈希值。

数据显示,会话的第一次请求只有 14% 到 26% 的情况下能找到缓存前缀。每个会话都以相同的工具定义和相同的 agent 提示开头,在我们审查的每个仓库中,这 11k 到 21k token 都是完全相同的,所以几乎所有这些都本应是缓存命中。

问题最终出在 OpenCode v1 构建系统提示的方式上:它构建了一个包含我们 agent 提示的块,后面跟着一个包含工作目录和日期的环境部分,并把唯一的缓存断点放在该块的末尾。共享部分从未被单独存储,因此在一个仓库中的审查永远无法复用另一个仓库中审查器运行时所写入的内容。

9 月 16 日,我们在每个 agent 提示的末尾添加了一个标记,并添加了一个插件钩子,在该标记处将块一分为二。前半部分现在以每个任务都共享的字节结尾,并拥有了自己的断点。为了保持这一点,我们不得不移除它前面任何与仓库相关的内容,因此可用技能列表被禁用,工具描述也不能在任务之间变化。

同一天,我们还将内部标准(Codex)审查器的参考索引(一张内部标准表、一个技能文件和一个参考文件列表)移入了它的提示中。在此之前,每个会话都会在前三轮通过工具调用读取这些文件。它每次运行的 LLM 调用次数从 15.9 次降至 9.6 次,每次运行的成本从 1.12 美元降至 0.72 美元。

这些改动合在一起,把每次评审的成本从 2.47 美元降到了 2.02 美元,而且我们一启动发布就能明显感觉到下降。

更少的调用,更少的钱

下一批改动于 9 月 21 日和 23 日上线。单看每一项都不算特别亮眼,但合在一起,每次评审省下了约 0.20 美元。

改动

当时的情况

改动效果

移除评审者用不了的工具

子评审者能看到发布最终评审评论的那个工具。在 57% 到 76% 的会话中,它们尝试调用该工具,被服务器上的检查拒绝,然后又花了一次调用去恢复。

少了一次浪费的调用,并且每个子评审者请求中的工具定义少了约 2.9k 个 token

并行读取文件

在代码质量评审者 64% 的轮次中,模型只发起了一次工具调用,通常只是读取单个文件。

评审者在第一次响应中就读取共享上下文和所有补丁,最多 150 KB

在重新评审时跳过评审者

我们大约三分之二的作业是对同一个合并请求的第二次或更晚的评审,而且通常有三个评审者没有新内容可看。

在完整层级的重新评审中,AGENTS.md 评审有 56% 的时间被跳过,性能评审跳过 23%,文档评审跳过 13%。

在评审发布后停止

OpenCode 把结果传回模型再跑一轮,模型重新读取整个对话来写一份没人看的摘要。

每个作业节省约 0.07 美元

区别对待慢评审者和故障提供商

达到时间上限的评审者被当作提供商故障处理,并以相同的时间上限重新开始。

结合更长的时间上限和更低的 effort,安全评审者的超时率从 1.8% 的运行降至几乎为零

取消已取消的作业

GitLab 的 Kubernetes executor 在作业被取消时不会向作业脚本发送信号。在 446 个已取消作业的样本中,有 358 个继续运行直到评审完成。

作业每 15 秒检查一次自身状态,若已取消则停止

最复杂的一项改动是在三个评审者之间共享同一个前缀。代码质量、安全和性能评审者现在发送完全相同的首次请求,使用相同的工具、共享的规则文件,然后是包含所有补丁的合并请求。每个评审者自己的指令随后跟上,我们确保其中一个写入缓存,另外两个从缓存读取。

缓存条目在写入它的响应开始流式传输之前无法被读取,而三个评审者是同时启动的。因此第二个和第三个评审者要等待第一个评审者的响应开始,最多等 20 秒,然后才发送自己的首次请求。这会让它们稍微变慢,但每个评审者节省约九美分。

更换模型和 effort

下一张图展示了从 9 月初开始的同一条曲线,并标出了每一次改动。

当我们按每次发布和每次配置变更来切分时间段时,代码改动只占节省额中较小的一部分。

代码改动占 1.67 美元中的 0.65 美元,模型和 effort 改动占 1.06 美元。每一步都比较了改动前的工作日时段和改动后的时段,所以这并非统计上严谨的对照实验。

自8月以来,协调器一直在运行三种模型与投入力度的组合。在同一模型上,中等投入力度的完整层级任务提前20%完成,因此我们放弃了另外两种组合。在随后的一次随机分组中,中等投入力度的 Opus 5.5 在审查结果或关键发现上与 Opus 5 相比没有可测量的变化,因此我们进行了切换。高投入力度的 Opus 5.5 成本高于中等投入力度的 Opus 5.5,但并未带来发现数量的显著增加。

Opus 5.5 每 token 比 Opus 5 更便宜,但价格只贡献了约 0.20 美元的降幅。其余部分来自发送和生成更少的 token。自9月初以来,每次审查的缓存读取下降了 82%,缓存写入下降了 54%,输出下降了 67%。

最近的一次变更是换用新模型。10月3日,我们将每个 Sonnet 审查器按 50/50 的比例分配到 Sonnet 5 和 Sonnet 5.5 上。两天半后,Sonnet 5.5 在代码质量上每次运行便宜 62%,性能上便宜 58%,安全上便宜 47%。两个模型每 token 成本相同,因此全部节省都来自使用更少的 token。在完整层级审查中,5.5 审查器发布的关键和警告发现数量与 Sonnet 5 审查器大致相同,因此我们在10月5日将所有审查器都切换到了 5.5。

在中等投入力度的 Sonnet 5.5 上,代码质量审查器每次运行进行 1.7 次 LLM 调用,而不是 6.2 次,并且超过一半的运行在单次调用中完成,这意味着它们不会打开仓库中的任何其他文件。在高投入力度下,它每次运行进行 3.4 次调用。我们担心这会使审查变得肤浅,这一点我们在下面的质量部分中讨论。

为了观察更高的投入力度是否能在更大的层级上找回发现,自10月5日起,代码质量审查器在 5.5 上按 50/50 的比例分配到中等和高投入力度。一天后,高投入力度每次运行成本高出 61%,并发布多出 58% 的关键和警告发现。这大约是每次审查两美分,而下面的质量部分表明这些额外的发现是值得拥有的,因此我们正在将代码质量切换到高投入力度。本文中的成本数据基于这一假设。

延迟

我们并未以加快审查为目标,但更少的调用也意味着更少的等待,这意味着更快的审查和更满意的工程师。

最慢的审查改善最大。在9月14日之前的四周内,37.9% 的审查耗时超过五分钟,而现在只有 0.7%。今天的第 99 百分位比四月份四分之三的审查还要快。每个层级都一路变快,完整层级审查的中位数从 5 分 16 秒降至 1 分 54 秒,lite 从 2 分 58 秒降至 1 分 14 秒,trivial 从 77 秒降至 30 秒。

到8月时,审查中位数比我们在四月份报告的慢了 14%,第 99 百分位慢了 48%,因此这项工作的很大一部分只是让我们回到了原来的水平。

审查质量

如果更便宜的审查也发现更少的问题,那它就没有多大价值,而这里的第一个图表看起来正是这种情况。

在高投入力度的 Sonnet 5.5 上运行代码质量审查时,它发布的关键和警告发现比八月下旬少了 40%。保持在中等投入力度的安全和性能审查,分别少了 56% 和 71%。严重发现的减少本身并不能说明是漏掉了真实问题还是削减了噪音,因此我们从两个方向进行了考察。

首先,我们检查了作者是否针对审查者标记的问题采取了行动。对于严重和警告发现的样本,我们将被审查的提交与作者的后续推送进行比较,如果发现所指出的行发生了变化,就将其计为已采取行动。在每个时期,距离每个发现30到40行的行大约有15%的时间发生变化,这为偶然发生的编辑提供了一个基线。

在变更之前,作者对代码质量审查者严重发现的34%采取了行动。现在是59%。审查者标记的内容更少,但被标记的内容更有可能导致变更,因此每1,000次审查中导致变更的发现数量大致相同。我们现在看到132个,而之前是145个,这在误差范围内。在所有四个审查者中,导致变更的严重发现从每1,000次审查229个增加到321个。

其次,我们让来自不同提供商的兩個模型对来自三个Sonnet审查者的657个发现进行盲审,附上每个发现所指的差异,并且不知道它来自哪个时期。分歧交由第三个模型处理。每个发现被标记为重要、次要、重复、错误或无法验证,其严重性被标记为合理、过高或过低。

消失的大部分是次要发现,还有一些重复项以及评审者无法从差异中验证的发现。每次审查的重要发现从之前的0.28增加到现在的0.30,且区间重叠。更大的变化在严重性方面。之前,评审者将每次审查0.46个严重或警告发现评为比其应得的更严重。现在是0.14,下降了70%,这是研究中最清晰的结果。严重发现下降的很大一部分原因是审查者不再将次要问题称为严重。

所以答案取决于审查者。对于高投入的代码质量,消失的主要是噪音和夸大的严重性,它仍然发现与之前大致相同数量的真实问题。中等投入确实失去了真实发现。在随机拆分中,中等投入的代码质量导致采取行动的发现大约是高投入的四分之一,这就是我们将其移至高的原因。性能也失去了真实发现:评审者发现每次审查的重要性能问题减少了约64%,在实践中大约是每24次完整层级审查少一个重要发现。安全性下降了约四分之一,尽管这在统计上不显著。两者仍处于中等投入,接下来将在高投入下测试它们。

两种方法都不完美。作者更改被标记的行表明他们做了些什么,而不是发现是正确的。评审者也是模型,每组只有35到66个发现。“现在”窗口也只有一天长。即便如此,两种方法相互一致,且都不表明代码质量发现的真实问题更少。

可靠性也保持住了。没有结论就结束的审查从0.79%降至0.27%,安全审查者重试从3.5%降至0.04%。

这让我们处于什么位置

现在平均每次审查的成本是 0.77 美元,而不是 2.43 美元;中位数审查在 1 分 32 秒内完成,而不是 4 分 9 秒。最大的单项节省来自检查每次会话的首次请求实际到达提供商的内容,并找到一种方式,使这些消息在提交、合并请求和仓库之间尽可能保持一致地排序。其余的成本节省来自不断尝试不同的模型和努力组合,同时确保我们抛开对特定模型的任何先入为主的偏见。

成本更低的审查并不自动意味着质量更差,但也不自动意味着没问题。高努力下的代码质量发现的实际问题与之前大致相同,噪音少得多,虚高的严重程度也少得多。性能在中等努力下丢失了真实发现,安全可能也是如此,因此接下来要在高努力下测试这两者。如果你以任何规模运行代理,我们首先要检查的两件事是:每次会话的首次请求是否真的从缓存读取,以及你的审查者在你付费的努力水平下是否仍能发现真实问题。

如果你在旧金山的 Cloudflare Connect,我会在我的会议 It’s time to build, not buy 中谈论我们如何构建审查器而不是购买一个,时间是 10 月 20 日星期二太平洋夏令时下午 12:30。下午 1:10 我也会在 Speaker Connection Lounge,如果你想询问本文中的任何数字。

来源:Cloudflare Blog · blog.cloudflare.com

00