ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI基础设施为何集体转向Rust?从三个信号到落地指南

AI基础设施为何集体转向Rust?从三个信号到落地指南

最近在扒AI基建开源项目的代码时,我越来越强烈地感觉到一个变化:过去几年里,很多关键基础设施组件是用C++、Go甚至Java写的,但现在新起的最有生命力的项目,底层语言越来越集中于Rust。一开始我以为这只是个别团队的偏好,但看多了以后发现,这不是孤立的选型决定,而是AI基建这条技术路径上正在发生的系统性转向。

这个转向有三个非常明显的信号:第一,向量检索、数据管道这类基础设施开始用Rust重写;第二,推理服务运行时和调度层开始把Rust当作主力语言;第三,Python生态里越来越多的热点模块被Rust扩展替代,连跨语言调用的工具链都成熟到可以大规模落地。这三个信号放到一起,基本可以判断:AI基建从Python/C++主导,逐步走到“Python留应用层、Rust打地基”的格局,不是猜测,而是正在发生的事。

这篇文章我会把这3个信号逐个拆开,讲清楚每个信号背后的技术逻辑、落地场景和实操经验,最后再聊聊如果你所在团队也想跟进转向,该怎么选模块、怎么过渡、怎么避坑。无论你是在做AI平台、推荐系统、向量数据库还是大模型推理服务,这篇文章都值得你花几分钟看完。

1. 信号一:基础设施层的关键组件,进入Rust重写周期

1.1 AI基建的老痛点:性能、内存安全与并发三者难兼顾

先想一个问题:AI基建里的基础设施层到底在解决什么问题?简单说,大量数据的引入、预处理、索引、检索、调度,这些工作对吞吐和延迟的要求极高。拿向量数据库举例,海量向量要实时写入索引,线上要同时支撑几千上万个检索请求,每个请求要在一两毫秒内做完相似度计算。这种场景下,语言选型直接决定服务的上限。

过去大家通常有两个选择。一是C++,性能确实拉满,但开发节奏慢,内存安全问题让每个团队都得背着ASAN、Valgrind过日子,而且模块依赖管理基本靠手工。二是Go,并发模型好用,部署也简单,但GC是绕不过的坎,堆上对象一多,GC停顿就会在高峰期冒出来,导致p99延迟出现肉眼可见的尖刺。这个尖刺在普通业务里还能接受,在AI检索这种延迟敏感场景里,用户体感非常差。

Rust正好卡在这个矛盾点上。它没有GC,靠所有权和借用检查在编译期管理内存,所以运行时不存在GC停顿的问题;同时它提供的是零成本抽象,手写出来的代码性能跟C++平级,甚至因为编译器约束更容易做激进优化。加上Cargo这套统一的包管理和构建体系,工程效率比C++高一大截。我见过不少从C++迁过来的团队,普遍反馈是“性能没降,开发效率反而上来了,而且内存问题少了很多”。

1.2 率先浮现的Rust重写方向:向量索引与流式数据处理

在这波重写周期里,最先动起来的不是业务代码,而是离业务最远的底层。我观察到的核心重写方向主要落在两类组件上。

第一类就是向量索引。无论是暴力扫描还是图式索引,核心操作都离不开大批量浮点计算、内存紧凑布局和并发读写控制。Rust的强项在于能用很自然的代码表达这些需求,而且标准库加第三方库的组合,让开发者不需要像C++那样自己造轮子。举一个很实际的例子:一套百万级向量的检索服务,原本用带GC的语言写的时候,单实例QPS卡在两千左右,换Rust重写核心检索路径之后,同样的机器直接推到上万QPS,且p99波动远小于之前。这背后没有魔法,纯粹是GC和锁竞争消除带来的收益。

第二类是流式数据处理与管道。数据管道里需要处理背压、乱序、批量聚合这类问题,Rust的所有权模型让数据竞争的隐患在编译期就被堵死,运行时不需要锁,或者只需要极少量的细粒度同步。流式框架里最怕的就是数据竞争和状态共享失控,Rust的铁律虽然让写代码的人一开始觉得约束太多,但写完之后回头看,几乎不需要花时间调试并发问题,这种“编译过了就能跑”的体验,在AI基建这种高并发领域价值极其明显。

1.3 为什么取代的不是C++也不是Go,而是Rust

一定有人问:C++性能跟Rust差不多,为什么要换?我的看法是,不必把Rust定义成C++的完美替代,它更像是“带着现代工程化能力的C++”。如果你是底层系统开发者,你能感受到Rust保留了对内存布局、指令级细节的掌控力,这些在AI基础设施里很重要。但是Cargo让你享受依赖管理,测试工具链让单元测试、模糊测试在工程里跑起来更顺手。综合下来,在同样的项目周期里,Rust交付的质量和速度都比C++更有竞争力。

至于Go,它的生态在云原生领域很强,但AI基建里越来越多的负载是高密集型计算和低延迟路径,Go的GC和内存模型在这种负载下会被放大劣势。更关键的是,Rust可以向下接管C++的存量库,而Go很难做到同等性能。所以这轮转向的逻辑很清楚:底层要性能选了Rust,上层要生态选了Python,中间那层交给谁就很自然了。

2. 信号二:推理服务运行时和调度层,把Rust当作主力语言

2.1 大模型推理的关键路径:低延迟与高并发的双重挑战

AI基建的另一块重头是推理服务。这里说的推理服务不只是“把模型加载起来跑一次前向”,而是包括请求接入、排队、动态批处理、采样、流式输出、审计等一系列环节。整个链路里,模型前向计算这一块通常还是C++或CUDA的天下,模型本身不会用Rust重写,但围绕模型的“服务骨架”,正在快速Rust化。

为什么?因为在线推理服务对两个指标非常敏感:延迟和并发。原始请求进来之后,服务端要把同一批请求动态组合成batch,塞给GPU跑,这中间涉及大量并发编排和排队策略。用动态语言写调度逻辑,说实话也能跑,但一旦请求量冲到几千QPS,解释器的开销和锁竞争就会变得不可控。更麻烦的是流式输出,模型一个token一个token往回流,服务端要同时维护成千上万个流式连接,这时候并发模型稍有波动,用户那边立刻能感知到“卡顿”和“中断”。

2.2 Rust在推理栈中拿下的核心位置:接入层与调度层

具体来说,我看到的Rust化主要发生在两个位置。

第一个是接入层,任务很简单:接收请求、校验参数、做鉴权、转发到推理进程或GPU节点。以往这块用Python或Go写也没问题,但接入层是流量最密集的地方,任何一个阻塞点都会被放大。Rust在这里的收益是线程模型和异步机制配合得非常好,可以用极少的线程支撑数万并发连接。实际调优中,一个Rust写的接入层实例,同时挂几万个WebSocket或gRPC流,CPU和内存占用都非常平稳,很难出现动态语言那种“线程一多就调度不过来了”的问题。

第二个是调度和动态批处理层。动态批处理的核心逻辑是:在一个时间窗口内收集请求,按模型和资源约束决定哪些请求可以拼在一起执行,再决定当前这批请求在GPU上跑完之后的令牌分发策略。这本质是一个带优先级的并发队列系统。Rust的异步运行时在这里提供了非常顺手的channel和超时控制原语,可以很干净地实现批处理窗口的轮询、控制批大小、处理超时任务。相比之下,Python的GIL让真正的并行受限制,Go的话则要小心GC对批处理时间线的影响。

2.3 从调度到模型加载:Rust正在接管模型服务的外围路径

还有一个容易被忽略但很关键的环节:模型加载与权重解析。一个大语言模型的权重文件动辄几十GB,加载过程中要解析大量metadata、处理张量存储格式、做合法性检查和数据重排。传统上用Python解析这类文件会比较慢,而且解析长度、偏移量这类字段时,稍不小心就有越界读取的风险。Rust在这个场景下的优势是双重的:一是解析性能高,同样一个权重格式,Rust的解析速度能比Python快几倍;二是Rust可以安全地处理不受信任的外部输入,避免在加载恶意构造的权重文件时出现内存破坏。随着新型安全权重格式逐步普及,加载路径上出现纯Rust实现的趋势已经越来越明显。

我个人做过的实验里,把模型元数据解析和加载逻辑重写成Rust模块之后,整个加载流程从原来的十几秒降到了几秒,而且边界检查让解析代码明显更容易验证正确性。对于追求服务快速扩容和弹性伸缩的团队来说,这个收益在流量突增时非常值得在意。

3. 信号三:Python外围出现“Rust后门”,工具链已成熟到可规模化落地

3.1 大多数团队的起点:不重写架构,只替换热点模块

对大部份团队来说,短期内不太可能把整个AI平台都换成Rust,最现实的路径是:Python还是那套Python,但把热点模块一个一个用Rust重写,通过扩展机制嵌回去。这就是第三个信号的核心。以前做这件事的门槛很高,因为用C或者C++写Python扩展很麻烦,需要考虑Python C API的引用计数、GIL、错误处理等一堆细节。一个扩展写下来,光把接口调对就要花不少时间。

但如今专门面向Rust的Python扩展工具链已经非常成熟,你可以用很接近Rust习惯的方式编写扩展函数,把数据结构从Python传到Rust,做完计算再传回来。配合现代的构建打包工具,一条命令就能把Rust源码编译成Python的wheel包,CI/CD里可以直接用。也就是说,现在一个团队在Rust里写一个高性能函数,包装成Python模块,整个过程的工程复杂度已经降到跟写纯Python差不多。

3.2 热点替换的真实案例:文本预处理函数快30倍

我在一次数据处理管线的优化里做过一个很典型的尝试。当时线上有一个文本清洗函数,负责把原始语料里的噪声字符规则化、做子串匹配和统计。这个串在Python里被调用得非常频繁,因为要处理海量短文本,Python版本的实现跑一次大概要几百微秒,积少成多,CPU开销非常可观。Profile数据里,这个函数独占到了三分之一的CPU时间。

我当时做的事很简单:把这个函数的逻辑用Rust重写一遍,输入输出保持跟原函数一致,然后通过扩展机制暴露给Python调用。整个重写只花了一个下午。上线之后的结果非常直观,同一个函数在相同输入下耗时从几百微秒降到了几十微秒,速度提升接近30倍。那一刻你会真切感受到,Python在AI链路里的瓶颈很多时候不是算法本身的复杂度,而是解释器层面的固定开销。这个固定开销是可以被Rust模块精准拆解的。

3.3 渐进式迁移策略:按热点路径逐层替换

这带来一个很实用的渐进式迁移策略。不要试图一次性把Python平台换成Rust,而是把整个请求链路做一次针对性的性能剖析,找到消耗最高的那部分热点。热点通常集中在数据预处理、编解码、校验、排序去重、特征计算这类重复度高的逻辑上。先挑一个边界清晰、数据接口稳定、逻辑不怎么变动的函数,用Rust重写并接入。跑一段时间验证延迟、稳定性、部署流程都没问题之后,再去替换下一个热点。

这个策略的意义在于,它把Rust引入团队的风险降到了很低。你不需要立刻组建一个Rust团队,也不需要重写架构,只需要一两个熟悉Rust的工程师就能把收益先拿回来。等到热点模块覆盖得越来越多,团队对Rust的信心越来越足,再考虑把一个完整的新服务直接写成Rust,就顺理成章了。

4. 落地指南:AI基建转向Rust前,先想清楚这几件事

4.1 什么样的模块值得迁到Rust,什么样的不建议动

看多了成功案例,也见过不少迁移失败的。我的判断框架很简单,从四个维度评估:延迟敏感度、并发密度、变更频率和生命周期。

高延迟敏感度、高并发密度、低变更频率、长生命周期的模块,是最理想的Rust重写目标,比如索引构建、向量检索、接入网关、数据管道核心算子。这类模块一旦写好,长期稳定运行,投入产出比最高。

反之,如果某个模块业务逻辑变化极快,几乎每周都在改需求,那就不适合用Rust,因为Rust的静态类型约束和编译时间会让频繁迭代的成本变高。这个边界一定要想清楚,千万不要因为“Rust性能好”就把所有模块都硬塞进来,最后会被变更成本和编译时间拖垮。

我整理了一张简单的判断清单,供选型时参考:

评估维度适合Rust的场景不建议Rust的场景
延迟敏感度在线检索、推理调度、流式转发离线报表、后台管理接口
并发密度高并发连接、大量状态共享低频调用、简单CRUD
变更频率基础组件、长期稳定接口快速迭代业务逻辑
运行时长常驻服务、生命周期长短小一次性脚本
内存安全需求处理外部不可信数据纯内部可信计算,压力不大

4.2 团队过渡:怎么把学习成本控制在可接受范围

Rust的学习曲线是真实存在的,但也没有外界传得那么可怕。如果团队里已经有后端工程师,比如写Go或者写过C++的,过渡到Rust的时间会比完全零基础快很多。借用检查器是初期最大的拦路虎,但它的报错信息非常好用,基本是在“指导”你写出安全代码。我见过的绝大多数新人在前两周会疯狂跟编译器搏斗,到了第三四周就能比较顺畅地写出能跑的代码。

建议团队搞一个“缓冲项目”来做过渡:不要一上来就挑战核心组件,选一个内部工具、一个小型独立服务或一个测试用的子系统,用Rust完整写一遍,把整个CI/CD链路跑通。等这个项目做完了,团队对Rust的“手感”基本就建立起来了。

另外团队一定要建立Rust代码评审习惯。Rust虽然比C++安全,但unsafe块依然存在,如果代码里随处可见unsafe,那安全性就打折扣了。评审的重点就是“unsafe用得是否合理、是否可以避免”,以及错误处理是否用了错误类型把边界情况都覆盖了。只要把控住这两点,Rust项目的长期质量通常都不错。

4.3 架构分层建议:如果还没有思路,可以按这个顺序推进

如果团队认可Rust方向但不知道从哪里入手,我给一个循序渐进的落地顺序。

第一步,选一个处理大量数据且逻辑稳定的热点函数,做成Python扩展,把最直接的性能收益拿到手。第二步,把某个独立服务的接入层或调度层用Rust重写,积累服务端Rust的运维经验。第三步,在验证充分后,再考虑把核心检索或索引层整体迁到Rust,到这一步才是真正的“AI基建Rust化”。

这样做的好处是每一层都有上一层的经验做支撑,风险被摊薄,团队能力也能跟着项目逐步提升。反过来如果一开始就直扑最核心的索引层,一旦出了问题,你连排查的基础经验都不够,很容易造成项目反复和团队信心受挫。

5. 常见问题与避坑实录

5.1 编译时间过长,怎么优化

Rust项目做大了之后,编译时间确实是个痛点。我第一次写一个中等规模的服务时,一次全量编译要三分钟,迭代起来很难受。后来做了三件事:合理拆分成多个crate,让增量编译只影响改动的那一部分;调整链接器,比如用更快的替代链接器;把开发环境的优化级别调低,发布时才开全量优化。这三点做完,日常迭代编译能压到几十秒级别,基本不影响开发心情。

5.2 借检查器劝退新人,怎么办

借用检查器确实会劝退一部分新人,这是正常的。我通常的建议是:不要一开始就追求写出非常优雅零拷贝的代码。先用最简单的方式写,该clone就clone,该用引用计数就用引用计数,代码能跑起来自己先舒服。等对所有权模型理解深入了,再回头把明显多余的clone优化掉。如果你刚写Rust就满脑子“性能最优、零拷贝”,很容易被编译器绕昏头。

5.3 异步线程模型里,线程和异步运行时怎么选

AI基建里经常需要判断:到底是每个请求开一个线程,还是统一走异步?我的经验是,如果请求之间没有太多CPU密集型计算,以IO为主且数量很庞大,那用Rust生态里的异步运行时模型收益明显,线程开销和上下文切换都能降下来。如果任务本来就是CPU密集型,或者每个任务的生命周期很长,那普通的多线程模型反而更简洁,也不容易踩异步生命周期和任务调度的坑。实际项目里两种模型混用也很常见,重点是每个服务根据自己的负载特征做选择,不要一概而论。

5.4 与Python交互时的性能陷阱

用Rust写Python扩展的时候,最大的性能陷阱不是计算本身,而是数据来回拷贝和对象转换。如果你在Rust里处理一个很长的列表,频繁把数据转换成Python对象,那开销会非常大,甚至抵消Rust带来的性能优势。正确的做法是在Rust侧尽可能以小批量的方式读取原始内存数据,整个计算过程都在Rust内存空间里完成,最后只返回一个最终结果。还有一个细节是,如果扩展的某些分支不涉及Python对象,记得在有长耗时计算时主动释放GIL,否则核心线程还是会被卡住。

5.5 依赖管理要克制,别把Rust项目变成全家桶

Rust的Cargo让引入依赖变得非常容易,但依赖并不是越多越好。AI基建场景里,你引用的每一个依赖都意味着更多的编译时间、更大的体积和潜在的安全风险。我见过一些项目,为了省事把一个大型工具库拖进来,结果整个编译链变重,出了问题还不好定位。合理的做法是:能用标准库解决的,不引依赖;必须引用的,选维护活跃、API稳定的库;像序列化、异步运行时、命令行解析这类基础库选型之后就不要轻易换,因为改动成本会波及到整个项目。

5.6 线上出问题,Rust服务反而更难排查吗

很多团队担心Rust代码panic之后服务不好排查。实际上Rust的panic信息比大多数动态语言更容易定位。如果你的编译关闭了debug信息优化,panic栈会直接指到源码行号。更大的坑往往是panic之后连接残留、状态未清理这类问题。我的建议是:边界处要收敛好错误,不该让下层panic一路冒到对端;所有外部请求入口都要做错误转换,统一返回业务码,而不是直接暴露panic。这套规则在Rust项目里一旦建立起来,线上排查的效率跟其他语言没有太大区别,甚至因为类型安全,很多问题根本不会在运行时出现。


这波AI基建转向Rust的趋势,我自己最大的体会是:技术选型永远要跟着负载特征走。Rust并不是要替代Python,而是补上Python和C++之间那块空白——既要高性能、又要内存安全、还要现代工程化能力。如果你所在团队也正在做AI平台或推理服务,我的建议很简单:不要急着搞全局重写,找一个稳定的热点模块,用Rust做一次扩展,亲自测一下延迟和吞吐的变化,你会对这个趋势有更真实的体感。数据会告诉你下一步该往哪个方向走。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进