AI 推理服务器倒逼 Linux 重审 fork()
50 年前,fork() 被设计用于复制全部内存仅几十 KB 的进程,随后的写时复制(CoW)优化让 fork()+exec() 保持了三十年的高效。上周,Linux 内核社区的热门讨论(LWN trending)正式将寻找 fork() 替代方案提上日程。造成这个局面的根本原因是,AI 推理服务器单进程持有 100GB+ 的模型权重,让 fork() 的页表复制开销变成了不可忽视的固定税。写时复制的优化对象是内存页本身。当 fork() 后立刻 exec() 替换程序镜像时,几乎不需要复制任何数据——子进程还没写任何东西就换掉了。这是 shell 执行外部命令的经典用法,效率没有问题。但进程的页表不在写时复制的覆盖范围之内。页表是描述"哪个虚拟地址映射到哪个物理页"的映射结构,fork() 必须完整独立复制。对持有 100GB 内存的 AI 推理进程,页表体积可达数百 MB。复制开销与进程内存大小线性相关,没有已有优化能绕开。在 NUMA 架构(多路服务器的标准配置)上,页表复制还涉及跨节点内存访问,延迟比单节点场景高一倍以上。加上文件描述符表、信号处理器配置、内存映射区间(VMAs)的复制量随进程复杂度增长,在高并发推理服务中 fork() 的成本已经可以测量到毫秒级甚至秒级。推理服务的 graceful reload 是直接触发点页表开销不是新发现,它长期存在但鲜少触发现实问题。转折点是推理服务的滚动更新场景。以常见的推理服务架构为例:当需要热更换模型权重(比如从 DS V4 Lite 切换到 DS V4 Pro),最直接的方案是 fork() 出子进程加载新权重,父进程继续处理请求,子进程准备好后切换流量,再杀死父进程。这是 Nginx、Gunicorn 的经典 graceful reload 模式。但在 100GB 进程上执行这个 fork(),仅页表复制就可能导致数秒的停顿——不是因为数据需要从磁盘加载,而是操作系统在纯粹地搬运元数据。实际部署中,不少推理服务已经在绕开这个问题:用 memfd_create 预分配模型内存、用 userfaultfd 懒加载权重、用共享内存加指针切换替代进程级切换。这些都是在规避 fork(),而非等待更好的内核原语。内核社区目前提出了三条替代路径,皆有妥协。posix_spawn 作为 POSIX 标准化的原子替代接口,将 fork 与 exec 合并为单次调用,跳过了子进程执行前短暂存在的中间态,但它受限于表达力,对预执行设置的支持弱于 fork。如果需要更精细控制,调用方可以使用 Linux 特有的 clone3() 精确指定资源共享策略,不过其过高的接口复杂度限制了通用性。更激进的方向是将进程创建阶段纳入 io_uring 的异步提交队列从而减少同步等待,这与内核侧执行能力契合,但尚未形成成熟实现。AI 推理进程的内存上限仍在增长。Mixture-of-Experts 架构在激活稀疏的同时要求驻留完整的专家参数,中等规模的 MoE 模型单机推理已需要数百 GB。如果未来的推理服务每次更新都需要 fork() 一个 TB 级别的进程,现有框架的压力只会更大。内核层面的系统性改进周期以年计,不会因为一次讨论迅速落地。更近期的影响来自应用层的变通方案逐渐标准化——这本身就是一个信号:进程创建原语与工作负载特征之间的错位,已经大到足以推动架构层面的绕行,而不只是等待操作系统跟上来。