在短短三周的时间里,两家顶尖的AI实验室就同一个工程难题给出了两个完全对立的诊断书。今年十月中旬,Anthropic宣布,他们发现自家单一大仓库已经无法支撑编程智能体的高频读写。于是他们召集7位工程师,用了5个月时间重新造了一个版本控制系统,叫Scion,把客户端命令行、底层存储和代码审查等周边工具全部替换了一遍。具体取舍可以参考上一篇Anthropic如何重造代码底座。
但就在三周后,OpenAI 也在11 月 2 日的工程博客中介绍了自己的解决方案Pando。他们也面临着类似的问题:在 Codex 智能体普及之后,一个工程师名下往往有多个改动在等待持续集成。与两年前的第一季度相比,今年第三季度的代码提交量增加了近十四倍,第四季度预计会增加约二十五倍。
高并发冲击之下,OpenAI 老旧的 Git 后端从去年底开始频繁卡顿,日常分支合并的可靠性一度跌破 90%。但 OpenAI 没有抛弃 Git,只让 1 名工程师设计核心架构,带着多支 Codex 智能体团队用 10 周时间重写了服务端。改造完成之后,程序员和智能体敲的日常 Git 命令保持原样、底层继续兼容标准 Git 协议,中位推送延迟却降了近八成,每天稳定分发近 1 PB 的代码数据。
为什么面对同样的智能体读写高峰,一家选择彻底抛弃 Git,另一家却只重做后端、原样保留客户端?OpenAI 的三位作者 Aaron Friel、Taylor Blau 和 Ted Nyman 在博客中提出一个关键问题:是 Git 本身有问题,还是传统的服务端托管单一大仓库的方式有问题?
Anthropic 认为问题是前者,Git 作为面向离线人类的工具,其设计已经不适合智能体时代。OpenAI 认为问题是后者,Git 的传输协议和数据格式没有问题,问题出在传统服务端给自己加了两个额外的限制。要想理解为什么会这样,我们首先得看 OpenAI 是如何放松服务端的读写限制的,然后再看这两支团队背后坐着什么样的人。
从写码抢跑说起:Codex 智能体普及后,OpenAI 一个大仓库里动辄同时有数百万分支。直觉上看,A 智能体改自己的功能分支,B 智能体改另一个完全不相关的分支,他们各自忙活,为啥 push 会卡在一起甚至超时?
这要从 Git 记录一个分支的基本操作说起。Git 仓库里的每个分支名称,本质上是一个指向最新提交的哈希值的轻量文本文件,我们下文统称为分支指针。客户端每次推送代码,先把新增的提交、目录和文件压缩成一个二进制数据包上传给服务器,这个数据包就是打包文件。服务器收到打包文件之后,再把对应的分支指针挪到新的哈希值位置,一次推送才算大功告成。
传统的 Git 服务端为了保证数据的安全性,会一直坚持一个保守的原则:不管什么时间读取 Git 仓库,仓库里所有的几百万个分支指针必须都是在同一个绝对时间点的仓库快照。为了实现这个全仓库的快照,以前一般有两种方法。
第一种方法是多个保存整个仓库副本的服务器之间进行通信,通过投票的方式决定如何更新分支指针,这在分布式系统中叫做三阶段提交。GitHub 长期使用的 Spokes 存储引擎就采用了这种方法。第二种方法则是写入节点将打包文件并行上传到云端的对象存储中,然后竞争对唯一的全局日志清单文件进行原子更新,这叫做预写日志和比较并交换机制。一个具体的例子可以参考 Cursor 最近公布的云端日志实现。
这两个途径虽然实现不同,但都面临相同的挑战。因为服务端要保证所有分支在同一时刻完全相同,所以即使有几百万个互不相关的分支更新,也必须串行进行。最终仓库能达到的写入极限取决于最慢的那个仓库副本,或者那把唯一的全局日志清单文件的锁。
OpenAI 团队在反思现实中的写代码和做测试的场景时,发现让数百万个分支在同一毫秒同步是一个不必要的限制。无论是测试机器还是智能体,在每次执行任务时只拉取与其正在开发的功能相关的特定分支以及主干分支。在现实中,没有一个任务要求同时查看两个毫不相关的边缘分支。
想清楚这点以后,Pando 干脆把整个仓库那把大锁给卸了,每个分支各管各的账。以前几百万个分支挤在一个全局清单里,谁更新都得抢同一个锁;现在每个分支的指针在数据库里都是独立的一行,代码包扔进云端对象存储,A 智能体推 A 分支只动 A 那一行,B 智能体同一毫秒推 B 分支也只动 B 那一行。几百万个分支从排队挤独木桥,变成各走各的车道,互不相关的代码推送再也不用排队等锁。
唯一需要处理的特例是 B 分支刚从 A 分支切出来。如果二者完全独立更新,别人来拉取 B 分支的时候找不到它基于的 A 分支代码怎么办?Pando 的做法是更新 B 分支时,顺手在自己的记录中记一下它切自 A 分支的哪个提交,别人来拉取 B 分支时就能顺藤摸瓜拿到完整的上游历史。这样,有上下游关系的分支就不会丢历史,没关系的分支就彻底各走各路。唯一的代价是如果有人同一时刻去读两个毫无关系的独立分支的话,可能看到相差几毫秒的状态。但这是日常开发中无关紧要的情况。有意思的是,今年十月下旬,GitHub 宣布的下一代架构改造也做了类似的取舍,把沿用十三年的全库锁拆细到了单个分支。
解决了写代码的排队锁,读代码的风暴其实更考验服务器。OpenAI 内部每次代码推送,都会触发大批测试机器、研究任务和其他智能体同时拉取最新提交,单小时峰值流量高达 57TB。既然成百上千台机器拉取的都是同一批代码,为什么传统 Git 服务器不能像普通内容分发网络那样直接发静态缓存文件,非要每次耗费大量算力现场重新打包?
正常情况下,你在主干分支上切出来一个新的分支,改了一些代码,然后把所有相对于主干的修改打包成一个包发送给服务器,这时候其他几百台已经装有主干代码的测试机来拉取这个新分支,服务器直接把刚才发的包发给他们不就好了。但传统的Git不是这样的,推送前它要做一些自作聪明的事情。传统Git服务器每次推送前,会把仓库里所有的几百万个分支的最新位置列一个巨长的清单发送给客户端,OpenAI光是发送这个清单就需要超过100MiB的流量。
客户端收到清单后,为了省下几千字节的上传流量,会做个精打细算的排除法:它发现你这次改的某几个文件,正好和隔壁组某个八竿子打不着的冷门分支里一段代码很像,于是只把相对那个冷门分支的差值打包上传。这一省流量,直接害惨了后面的拉取环节。你传上去的压缩包成了半截话,必须配合同事那个冷门分支才能还原。等一秒钟后几百台测试机器来拉取你的分支时,它们本地只有主干代码,没有同事那个冷门分支,拿到你刚传的包也解不开。服务器没办法,只好每次在后台重新比对历史,现场烧掉大量处理器算力,为每一台测试机单独定制打出一个新包。
看明白这个死结后,Pando 的改法就非常直观了:推送代码前,服务器默认对客户端隐瞒那几百万个功能分支,只告诉客户端主干分支在哪。客户端以为服务器上只有主干分支,就会老老实实把当前分支从主干切出来之后的全部改动完整打成一个包传上去。虽然往同一个分支连续推送时会重复上传偏离主干的那点改动,但功能分支寿命很短,平时改的又都是压缩率很高的纯文本代码,实测传上去的包体积中位数还不到 20 KiB,百分之九十九都小于 3 MiB。
多传几千字节这种微小的代价,换来了拉取环节彻底免去了现场计算。因为客户端传上去的这个包只依赖主干分支,后面几百台早已装好主干代码的测试机和智能体来拉取这个功能分支时,全都能直接解压它。服务器直接把刚才存进对象存储的打包文件原样交出去就行,连一秒钟的现场打包算力都不用花。
既然所有代码包都变成了可以直接复用的静态文件,Pando
干脆连服务器自己转发文件都省了。它启用了 Git 协议第二版里一个叫
packfile-uri 的现成机制:客户端来拉代码时,Pando
服务器只负责查一下元数据目录,把一串静态文件下载链接发回去。客户端拿到链接后,直接去离自己最近的
OpenAI 自建区域缓存服务 Express
节点并行下载静态包,就像平时从内容分发网络下载视频一样。在生产环境中,超过
99% 的数据字节都直接命中缓存节点的本地磁盘,用最普通的云主机就能跑满 10
Gbps 的网卡带宽。
唯一的小问题是,主干分支一天会收到成千上万次更新。如果始终只把每次推送后的零散小包存在网盘里,那一个几个月没更新的机器来拉取代码,岂不是要一口气下几万个碎片文件?为了解决这个问题,Pando 在后台有一个自动整理进程,像银行把零钱凑整一样,定期把连续多步的小包合并压缩成跨度更大的合集包,从小到大分成零到四层共五档。
传统的 Git 会在整理完成后删除旧的零散小包,但 Pando 把从零层零散小包到四层大合集包的所有层级的文件都保存在廉价的对象存储中。这样不管来拉取代码的客户端落后多少步,服务器都可以像凑整找零一样,用最少的文件拼出它需要的历史:刚落后两步的智能体只要两个几千字节的零层小包,落后几个月的新机器直接拿几个顶层大合集包,两边都不需要等服务器现场打包。
看到这里,一个自然的问题就会浮上来:既然保留日常 Git 命令和协议兼容就能在 10 周内把读写瓶颈解决得这么干净,为什么 Anthropic 当初要大动干戈抛弃 Git?两边走出截然相反的路线,是因为客观业务场景存在差异,还是因为操刀团队看问题的视角不同?顺着两篇技术博客背后的作者履历往下查,最核心的答案其实藏在人的知识基因里:熟悉什么工具,就会看到什么样的病根。
首先看看 Anthropic 的 Scion 团队。近期开源社区热烈讨论 Scion 为什么像 Meta 的内部系统,Scion 的核心作者在社区讨论中坦率地提到了他们的背景。Anthropic 内部有很多前 Meta 工程师,他们在 Meta 使用自家客户端 Sapling 和服务端 Mononoke 的体验非常好,所以从一开始,Mononoke 就是整个团队的北极星。这些熟悉 Meta 研发体系的工程师看到 Git 的高并发报警后,想到的自然就是以前在 Meta 替换旧工具的那套方案:不再像 Git 那样每次提交都记录整棵目录树,只记录本次改了哪几个文件,再把散装文件存上云端对象存储,最后借着 AI 编码的速度在 5 个月里把客户端命令行和外围六套研发工具全部重写一遍。
而 OpenAI Pando 的三位署名作者则来自另一个阵营。位列第二的 Taylor Blau 是 Git 社区的核心维护者、Git 领导委员会成员,进入 OpenAI 前担任 GitHub 的 Git 基础设施首席工程师。现代 Git 中实现大仓库加速的核心存储特性,包括几何重打包、多包索引和可达性位图,原本就是他的贡献。排在第三的是前 GitHub 基础设施工程总监 Ted Nyman。
请 Git
的核心存储维护者来诊断为什么一个大仓库越来越慢,他自然不会得出应该把 Git
协议扔进垃圾桶的结论。因为他一眼就能看到,Google 的工程师早在 2020
年就在 Git 协议中引入了 packfile-uri
机制,目的本来就是把打包文件卸载到 CDN 上。只是可惜这六年来没有多少 Git
服务端真正实现了这个特性。
既然 Google 六年前就把 packfile-uri 放进了
Git,为什么这么多年几乎没有大厂真的用?看 OpenAI
公开的补丁跟踪网站就知道,早年合进 Git
官方仓库的其实是个实验性的半成品,从来没在生产环境每天近 1 PB
的高压下跑过。一旦真像 Pando
这样把代码拆成现成的静态包直接发客户端、或者推送时藏起其他分支,原版 Git
客户端底层的老旧 C
语言代码就会犯各种糊涂:看到两个静态包有重复文件就误判报错,或者因为看不到其他分支在强制推送检查时直接罢工。
这正是团队基因决定成败的关键一步:一个普通工程组碰到 Git 那几十个遗留多年的古怪 C 语言特性,多半会觉得这老古董没法改了,然后自己另起炉灶写个新客户端;Taylor Blau 和 Aaron Friel 却是 Git 源码的天天维护者,别人眼中的黑盒在他们眼里就是自家客厅。他们动手做了 50 处微创修补,内部打包发给员工电脑和容器用,同时把补丁源源不断回馈给开源社区。Git 协议并非天生完美,只是懂它心脏构造的人知道怎么用微创手术帮它搭桥。
除了团队基因之外,两家在客观场景上也有两处分歧。一个地方是代码写入集中的地方不一样,Anthropic 每天有三万多次提交直接落到同一个主干分支,而 OpenAI 的并发压力主要分散在数百万个各自独立的功能分支上。另一个地方是客户端的运行环境和救火时间不同,Anthropic 的智能体大多运行在机房受控的在线容器里,工程团队有小半年时间从容替换全套工具;而 OpenAI 的 Codex 不仅跑在云端,还直接内嵌在用户电脑的跨平台 ChatGPT 桌面端里,去年底分支合并的可靠性已经跌破九成,团队必须在两个多月内紧急止血,客观条件不允许他们要求所有桌面端和周边工具放弃标准 Git 命令。
Pando 的研发过程本身,也是智能体时代工程演进的一个缩影。整个后端系统从立项到上线只花了两个多月,由单名工程师完成核心架构设计,多支 Codex 智能体团队用 Rust 语言从零写完实现代码。为了保证高并发读写不丢代码,除了常规的一致性测试和确定性仿真,OpenAI 还让 GPT-6 Astra 模型连续运行了数百小时。模型用 Lean 和 TLA+ 写出数学形式化规约,专门穷举排查多分支并发和包合并时的极端漏洞。对比我们此前关于 AI 辅助形式化验证的文章里介绍的单线程机器码验证,大模型已经开始协助工程师去攻克分布式并发状态机这类复杂难题了。
将 Anthropic Scion 与 OpenAI Pando 放在一起对比,可以为所有工程团队提供一个清晰的判断标准。在过去的一个月里,GitHub、Cursor、Anthropic 和 OpenAI 这四家公司几乎同时为编程智能体重构了版本控制系统的基础设施。AI 编码工具让写代码的速度提升了好几个数量级,拉起一支智能体队伍在几个月内重写几万行底层系统和周边工具,正在变得越来越便宜。
代码重写变得越来越便宜之后,一个常见的陷阱就是,一旦遇到性能问题,就倾向于把所有旧的东西都推倒重来。OpenAI Pando 的整个故事则告诉我们一种相反的、可能更为稀缺的能力:面对一个看起来已经到顶的旧系统,如何分辨哪些是牵一发而动全身的外部契约,哪些只是老旧实现中自己捆住手脚的内部假设。守住外部的命令行与生态契约,松绑内部的全库同步与动态打包假设,再用精准的底层补丁把路修通,这种分清契约与实现的工程眼光,在智能体时代反而变得比写代码本身更贵了。