跳到正文
Cloudflare Blog · AI· Ryan Skidmore·· 2 小时前精选AI 评分66

Cloudflare 通过优化将 AI 代码审查成本降低 68%

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

AI 导读

Cloudflare 把内部 AI 代码审查的平均单次成本从 2.43 美元降到 0.77 美元,降幅 68%,中位耗时从 4 分 09 秒缩短到 1 分 32 秒。成本上涨主要来自模型更换:8 月初全部审查器切换到 Anthropic 模型后,单任务成本从 1.38 美元升至 2.03 美元,内部标准审查器 Codex 改用 Claude Opus 4.7 后单次成本从约 0.25 美元涨到 0.88 美元。

推荐理由

Cloudflare 公开了自家 AI 代码审查器降本 68% 的具体改动清单,包括缓存前缀拆分与模型档位实验,可迁移到其他 Agent 部署。

正文 · AI 翻译

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

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

大约 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 个 token,因为每次调用都会把整个对话作为上下文发送。一个会话的成本会随着调用次数和上下文大小的增加而增长,而在过去几个月里,我们逐渐把这两者都提高了。

值得花几分钟了解一下 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。每一步都比较了改动前的工作日时段与改动后的时段,因此这并非统计上严谨的对照实验。

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

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

最近的一次变更是换用新模型。10 月 3 日,我们将每个 Sonnet 审查器在 Sonnet 5 和 Sonnet 5.5 之间按 50/50 拆分。两天半后,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 秒。

到八月,中位数审查比我们在四月份报告的慢 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 · AI · blog.cloudflare.com