SGLang 权重加载提速 785 倍,1T 模型重启仍要 32 秒
1. 权重加载提速 785 倍,引擎恢复为何仍是 32 秒?
蚂蚁 Ling 团队与 SGLang 团队在 2026 年 8 月公布了一组让推理系统工程师很难忽略的数字。在一台 8 张 H20-3e 的机器上加载 Ling-2.6-1T FP8,权重放在 3.5 TB NVMe 冷存储上,采用 TP=8、161 个 shard、每卡约 120 GB 的部署形态,从拉起到能接请求要约 527 秒,也就是 8.8 分钟。其中权重加载一项吃掉约 495 秒,占整个启动的 93.9%。换成权重缓存守护进程(Weight Cache Daemon,下文简称守护进程)之后,权重加载被压到约 0.63 秒,约 785 倍提速;整个引擎恢复却只降到约 32 秒。加载提速了 785 倍,总恢复只改善了约 16 倍,这中间的落差,就是本专题要拆开的东西。LMSYS
先看那 495 秒是怎么构成的。下面是博客给出的完整启动时间分解:
| 阶段 | 耗时 | 占比 |
|---|---|---|
| Pre-init & ServerArgs | 约 1 s | 0.2 % |
| Tokenizer 初始化 | 约 13 s | 2.4 % |
| Init torch distributed | 约 5 s | 0.9 % |
| Load weight(磁盘) | 约 495 s | 93.9 % |
| Cache allocation(KV + Mamba) | 约 1 s | 0.2 % |
| Capture CUDA graph | 约 7.7 s | 1.5 % |
| Server ready | 约 4 s | 0.8 % |
| Total | 约 527 s | 约 8.8 min |
「启动慢就是磁盘慢」在直觉上成立:磁盘加载占了 93.9%,动它一定最快见效。但如果真的只有磁盘慢,抹平它之后总启动也应该被抹平,现实却是 8.8 分钟后面还站着一个 32 秒。反常的地方在于:最慢的路径被干掉之后,剩下的每一条单项都不算慢,加起来却仍是不可忽略的常数项。
另一条证据线说明这件事有多不寻常。MLSys 2026 上发表的那篇系统拆解 vLLM 启动时间的论文,把启动分为六个阶段:框架引导、tokenizer 初始化、模型加载、torch.compile、KV cache profiling、CUDA graph 捕获,并在 22 个模型上做了测量。在 Llama3.2-3B 这类小模型上,总启动约 20.3 秒,权重加载只占 7-10%,大头全在 compile 这类 CPU 侧步骤上。论文还实测了存储介质的影响:从 SSD 换到 DRAM 后,权重加载改善约 0.5 倍,总启动却仅改善 1.04 倍。存储介质快慢对整体启动几乎不起作用,这正说明加载在小模型里不是主瓶颈。vLLM-ColdStart

图注:vLLM V1 在 Llama3.2-3B(H100)上的启动时间分解,总长约 20.3 秒。六段水平堆叠条里,Model Loading(权重搬运)只是一小段,框架引导与 torch.compile 才是大头,小模型场景里权重加载根本不是瓶颈。vLLM-ColdStart
小模型里权重加载只占个位数百分比,所以对小模型做的加载优化,收益天花板最多几个百分点;「权重加载是否值得做」与模型规模强相关,守护进程是 1T 级 MoE 场景的针对性优化,不是普适方案。理解了这条对照,再来看为什么把最大的一项抹平,整体却推不动。这就是 Amdahl 定律的固定成本版本,经典形式是:
$$S_{total} = \frac{1}{1 - F + F / S_{partial}}$$
$F$ 是可被优化的部分占总时间的比例,$S_{partial}$ 是这部分获得的加速比。当 $S_{partial}$ 趋于无穷,也就是被优化部分「免费」时,整体加速上限收敛到:
$$\lim_{S_{partial} \to \infty} S_{total} = \frac{1}{1 - F}$$
剩下的全看没被优化的那部分。Malekar 与 Zand 在 TMLR 2025 的论文 Amdahl's Law for LLMs 里把这条定律形式化用于 LLM,并画出了大模型场景下的曲线:只改进一部分时,收益很快被残余项卡死。Amdahl

图注:Amdahl 定律的 LLM 版(Cloud TPU 模拟)。横轴是改进部分的加速比,纵轴是整体加速比上限;虚线族表示只改进 projection 层,实线族表示只改进 attention 层。模型越大,虚线在大加速比处越逼近 1/(1-F) 这道硬上限,改进一部分的收益最终被剩余部分卡死。这张图讲的不是权重加载,而是同一条物理规律:不动的部分决定上限。Amdahl
回到 Ling-2.6-1T 这组数字:权重加载就是那个被优化到近乎「免费」的 $F$,剩下来的 tokenizer 初始化、分布式初始化、CUDA graph 捕获、服务就绪,就是决定新上限的 $(1-F)$。527 秒里 93.9% 是权重加载,把这一项抹平后总恢复同样降了约 93.9%,剩下的约 32 秒,就是那个 $(1-F)$ 项换到现实里的样子。
这组数字值得先确认一次。中文媒体 CocoLoop 的独立报道把同样的口径复核了一遍:整体 527 秒、权重加载 495 秒占 93.9%、守护进程后加载 0.63 秒、总恢复约 31.7 秒,与博客一致。两条独立编辑流程看到同一组事实,后面讨论机制与代价时可以放心站在这些数字上。结论先行:权重加载这条最慢路径被抹平后,瓶颈没有消失,只是转移。「启动慢」的真面目从来不只是磁盘慢。CocoLoop
2. 权重是怎么做到不随引擎一起死的?
把权重从引擎进程里剥离出来、让另一个进程持有,这套心智在服务端早有先例。无状态服务会把会话状态放进共享存储(Redis 之类),应用实例崩溃、重启、再拉起,用户会话还在,因为状态的生命周期不属于执行进程,进程只是「借用」状态。Ling 和 SGLang 把同一招用在了权重上:让一个独立于引擎的守护进程常驻显存,持有已经加载并量化好的权重;引擎重建时不读盘,而是重新「连接」这份驻留状态。对应关系可以逐条点出来:共享存储对应守护进程,都是持有一份跨进程存活的状态;无状态应用实例对应推理引擎,都是随时可以死的执行进程;实例重启后从存储取回会话,对应引擎重启后映射权重,都是重连而非重算。这套设计把模型状态与执行引擎变成了两个独立生命周期,分开管理、分开死亡。LMSYS
顺便说清楚这个类比的边界:会话数据是宽松的业务中间态,权重却是只读的、身份严格绑定版本与量化格式,所以「能不能命中缓存」必须显式校验,第 4 章会看到这份校验清单。
具体是怎么「外置」的?每张 GPU 上跑一个守护进程,对应一个 TP rank。它冷启动时完整走一遍磁盘路径:从 NVMe 读 TP 分片、做 FP8 量化、重排,然后把 model.state_dict() 里每个参数和 buffer 都导出成 CUDA IPC handle,通过 Unix socket(/tmp/sglang_weight_cache_rank{i}.sock)等引擎来连。引擎重启时先在 meta device(PyTorch 里只建结构、不占显存的设备)上搭好模型骨架,再把每个 param.data 直接指向守护进程导出的 IPC tensor,0 字节拷贝。连 weight_scale 这类后量化参数也由守护进程一并缓存,引擎端不用重新量化。物理显存只有一份,由 daemon 与引擎共享,CUDA IPC 的引用计数决定它何时释放:两方都退出才真正归还。这套部署形态在一个中文独立报道里也有描述:161 个分片、每卡约 120 GB、权重放在 3.5 TB NVMe 上。CocoLoop

图注:守护进程架构(单张 GPU 内)。左侧 daemon 常驻持有 TP 分片权重、weight_scale 与全部后量化参数,并把每个 state_dict 张量导出为 IPC handle;右侧新引擎在 meta device 上搭骨架后,把 param.data 直接指向 IPC tensor;中间红色实线是 cudaIpcMemHandle 零拷贝映射,灰色虚线是 Unix socket 握手与配置校验;最下方是两进程共享的同一份物理显存,只有双方都退出才释放。LMSYS
这张图值得记住的是最下面那行:共享的物理显存只有 daemon 与引擎都退出才释放。这就是「引擎死一百次、权重不必重读」的物理基础,也是下一节代价讨论的起点,因为零拷贝不等于零开销。
开销体现在两个层面。第一层是显存:权重常驻意味着每张卡约 120 GB 被钉死,这部分容量不能挪作他用。第二层是映射本身的耗时:引擎端仍要初始化 meta 骨架、反序列化 handle、materialize 非持久 buffer。SGLang PR #27139 的实测 profile 显示,一次 IPC 加载总共约 5.05 秒,其中 handle 反序列化与参数映射占 4.7 秒;把循环里每次都要做的全模型遍历(O(n) 查找)改成预构建的 param/buffer dicts(O(1) 查找),这部分降到 0.95 秒,总计约 1.30 秒。这套数字来自 H20-3e 实测,其他平台尚未验证。SGLang-PR
守护进程还有第三种模式值得说清,因为它直接决定「首次启动」和「快速恢复」是两码事。CLI 提供三个取值:
| 模式 | 流程 | 权重加载时间 | 适用场景 |
|---|---|---|---|
| daemon | 引擎拉起守护进程,守护进程从磁盘加载,引擎映射 IPC | < 1 s(守护进程就绪后) | 首次启动;引擎管理守护进程生命周期 |
| client | 连接已在运行的守护进程,映射 IPC | < 1 s | 引擎重启;守护进程已常驻 |
| off | 默认,正常磁盘加载 | 约 405-411 s | 无缓存 |
注意 daemon 模式本身不会带来更快的重启:它是引擎的子进程,首次启动照样要读盘,只是把加载工作挪给了守护进程。真正快的是「守护进程早已常驻 + client 模式重连」,这也是博客宣传的路径。另外预告一层边界:CUDA IPC 只导出原始张量数据,量化方法必须能整个走 IPC 而不依赖 Python 侧元数据,这个白名单的细节第 4 章展开。SGLang-PR
到此机制已经完整:权重与引擎是两个独立生命周期,引擎可以随便死,权重不用重读。但「权重加载消失」与「恢复完成」是两回事:加载只用 0.63 秒,服务却花了 32 秒才恢复。下一章就拆这 32 秒。
3. 权重就绪之后,32 秒到底花在哪里?
回到第 1 章那张分解表,把 Load weight 一行从约 495 秒换成 0.63 秒,剩下的每一项都还在:
| 阶段 | 恢复时耗时 |
|---|---|
| Pre-init & ServerArgs | 约 1 s |
| Tokenizer 初始化 | 约 13 s |
| Init torch distributed | 约 5 s |
| Load weight(IPC 映射) | 约 0.63 s |
| Cache allocation | 约 1 s |
| Capture CUDA graph | 约 7.7 s |
| Server ready | 约 4 s |
| Total | 约 32 s |
权重就绪只是把那个约 495 秒的大项抽走了;tokenizer 初始化约 13 秒、CUDA graph 捕获约 7.7 秒、分布式初始化约 5 秒、服务就绪约 4 秒,这些固定成本一个都没有消失。于是 0.63 秒作为「权重加载时间」是成立的,作为「恢复完成时间」是假的:用户能看到服务可用,是 32 秒之后的事。恢复时间不能被加载时间冒充:0.63 秒不是用户可用的语义时间,32 秒才是更接近 SLO 的数字。这一章的任务就是承认这个 32 秒的构成与来源。LMSYS
这些残余项里最值得单独拎出来的是分布式初始化(Init torch distributed)。它是隐藏的「第二慢路径」,只是被 495 秒的权重加载完全盖住了。rack-scale 的量化旁证把它暴露得很清楚:FlashBoot 论文在 GB300 NVL72 整机柜环境里实测,NCCL 通信组建立要花 10 到 110 秒,这中间包括 rendezvous、ncclCommInit、拓扑搜索、barrier 一串动作,而第一个权重字节还没开始移动,光是 20 个 rank 的 init_process_group 就要 11.9 秒。要注意这是跨节点机柜量级的测量,与 SGLang 单机 TP=8 内部的约 5 秒不是同一量级;但方向一致:权重搬运本身已经被压到毫秒级(FlashBoot 的远程映射约 10 ms),卡在通信组建立上的时间反而成为主导。FlashBoot
既然权重传输本身已经快到毫秒级,剩下的瓶颈就完全是「初始化」而不是「传输」。顺着这个思路看 FlashBoot 的另一个设计:FabricArena。它与守护进程是同一成本项的两条不同解法。守护进程走的是零拷贝映射,FabricArena 走的是连续内存一次搬完:它把整棵模型里几万块离散参数压成一块连续的 device memory arena,每个参数只是 arena 里的一个 view:

图注:FabricArena 的扁平化布局。顶部的离散参数对象(L0 w13、L0 w2、attn q、norm/embed 等,L0/L1 是 MoE 专家层编号)不再各自持有独立存储,而是通过 stride/offset 的 view 指向底部一块连续 arena 的不同字节区间,专家与非专家参数分两段;权重只存在一份,host→GPU 与 GPU→GPU 都只填充这块扁平 buffer。FlashBoot
守护进程是「引擎重建时映射驻留权重」,FabricArena 是「把离散参数压成连续内存、一次动作搬完」,共性是:把几万个小对象变成一份可整体操作的数据。而 NCCL 通信组建立这种「初始化税」两边都还没解决。SGLang 自己的路线图把 NCCL session reuse / persistent process group 列为 Phase 4 的目标,FlashBoot 则干脆用 fabric handle 绕开 NCCL。所以 32 秒不是什么烂摊子,而是七八项 1 到 13 秒的小固定成本的总和:每一项单独看都不起眼,加起来就是语义就绪的门槛。SGLang-PR
4. 守护进程的快速恢复,在什么条件下会突然失效?
到这里,守护进程的价值看起来很清楚:把 495 秒变成 0.63 秒。但「0.63 秒」前面永远藏着一个前提:命中。缓存不命中,一切回到冷启动。这一章看三样能让快速恢复突然失效的东西:版本指纹、量化白名单、硬件故障。
4.1 第一道门槛:CacheConfig 版本指纹
守护进程加载时会把一整套 CacheConfig 指纹落盘,引擎连上来先比对,任一字段不一致就判定失效:
| 指纹字段(分组) | 失配含义 |
|---|---|
| model_path + model_arch + revision | 模型、架构或版本不同,可能拿到错误权重 |
| tp_size + tp_rank | TP 切分不同,该 rank 拿到错误 shard |
| pp_size + pp_rank | PP 切分不同,stage 层错位 |
| dp_size + ep_size | DP/EP 策略不同,权重分布错配 |
| quant_method + quant_config_hash | 量化方式或配置不同 |
| dtype | 精度不同(fp16 vs bf16) |
| device_capability + torch_version | 环境 stamp:IPC 干净但数值可能错乱 |
失配后的处理因模式而异:client 模式退回 LoadFormat.AUTO,安静地走磁盘加载,启动时间回到分钟量级;daemon 模式与引擎共享同一张 GPU,回退等于在已经占满的显存里再塞一份权重,会直接 OOM,所以选择直接报错而不是回退。最后两行尤其关键。device_capability 和 torch_version 即使权重本身在 IPC 层面是干净的,也可能因为 daemon 与 client 走了不同的后处理分支而数值错乱;把它们放进指纹,是为了把「沉默的错乱」变成「显式的不匹配」。SGLang-PR
4.2 第二道门槛:量化方法白名单
为什么需要白名单?CUDA IPC 只能导出原始 tensor 数据,带不走 Python 侧的后处理元数据。如果某个量化方法在 process_weights_after_loading() 里做了转置、重排或者写入 Python 侧标记,meta 初始化的 client 无法复现这些副作用,映射过来的权重就会「看似可用、数值已错」。所以白名单有硬边界:
| 量化方法 | 能否走 IPC zero-copy | 原因 |
|---|---|---|
| unquantized | 可以 | 无后处理副作用 |
| FP8(block-wise,设置了 weight_block_size) | 可以 | 已验证可整体 round-trip |
| FP8(per-tensor) | 拒绝 | 后处理会转置 layer.weight,client 复现不了形状 |
| Marlin / AWQ / GPTQ | 拒绝 | repack/transpose 成 client 无法复现的形状,抛 UnsupportedQuantForIPCError |
白名单之外的量化方法会抛 UnsupportedQuantForIPCError,报错信息提示改用 --weight-cache-mode off。这带来一个很现实的失效场景:模型升级换了个量化方案(比如从 block-wise FP8 换成 per-tensor FP8),或者换上 Marlin/AWQ/GPTQ 的检查点,快速恢复当场失效。失效之后的去向要按模式分开看,别把两条路径写成同一种结果:client 模式自动退回磁盘加载,引擎照常服务,只是启动回到分钟量级;daemon 模式则直接报错,因为回退意味着在已被守护进程占满的显存里再塞一份权重,会直接 OOM,所以它宁可报错也不回退。真正人为的冷启动只有一种走法:主动把 --weight-cache-mode 改成 off,彻底关掉缓存。SGLang-PR
4.3 显存常驻的红利,与第三道门槛
第三道门槛是硬件本身。权重驻留在显存里,节点断电、驱动故障、GPU 失联,驻留状态跟着物理显存一起消失,快速恢复退化成真实冷启动。守护进程能救的是进程级的事故,救不了物理层的事故。这一点先记下,第 5 章会把它变成判断适用场景的分界线。
不过显存常驻也有收益,而且这两份收益正好解释为什么「占住显存」值得。第一份:同一张 GPU 上多个引擎实例可以零拷贝共享同一份权重,磁盘只读一次、量化只做一次:

图注:多实例权重共享(单张 GPU 内)。左侧 daemon 只从磁盘加载并量化一次权重,右侧三个引擎实例 A/B/C 通过 CUDA IPC 零拷贝共享同一份;底部对照是传统方式:每个实例各自读盘、各自量化,N 份磁盘 I/O、N 份显存。LMSYS
第二份:主备接管不必再买一套备用 GPU:

图注:主备接管(单张 GPU 内)。primary 与 standby 都在同一个 daemon 上,standby 通过零拷贝保持 warm、不读盘,primary 故障后约 1 秒内接管;底部对照:传统热备要整套备用 GPU(2× 成本),权重缓存方案在同一张卡上完成(1× 成本)。LMSYS
注意「约 1 秒接管」是博客的经验口径,不是可以写进长期 SLO 的承诺。把三道门槛和两份收益放在一起看,守护进程的全貌是:用常驻显存换亚秒级恢复,把方案的价值押在缓存命中率上。缓存失效之后的走向要按模式分开说:版本、切分、量化等指纹字段失配,或者量化方法落进白名单之外,这些缓存不可用的情况,client 模式都会自动退回磁盘加载,引擎照常服务,只是回到分钟量级;daemon 模式则直接报错,因为同 GPU 回退会 OOM。真要走冷启动,只有人为把 --weight-cache-mode 改成 off 这一条路。硬件故障是另一类失效:驻留状态随物理显存一起消失,没有回退可言,只剩真实冷启动。昂贵,且脆弱。LMSYS
5. 这项快速恢复该用在哪些场景,又救不了哪些场景?
最后的问题变成工程判断:我该在什么场景信任这个「快恢复」?判断标准只有一个:故障落在进程内还是进程外:

图注:进程内故障走快恢复,节点级故障只能冷启动
进程级故障(引擎崩溃、滚动升级、短暂调度扰动)正好落在守护进程能覆盖的范围内:引擎死了,驻留状态还在,重建引擎重连权重即可,它替代的是冷启动,不是跨节点容灾。节点级故障一发生,驻留状态随显存消失,快速恢复直接失效,正确的预案仍然是灾难恢复那一套,不能指望守护进程。LMSYS
用工程账把这句话钉死。滚动升级是守护进程最典型的用武之地:按每次重启约 8.8 分钟、8 张卡核算,每个实例每轮升级空转约 1.2 GPU·小时,实例上百的集群一轮就是三位数 GPU·小时;守护进程把它压到分钟级以下。active-standby 的账更直接:回到第 4 章那张主备图,传统热备要为一组 8 卡实例再押 8 张 H20 在旁边空转(2× 成本),共享权重的 standby 在同一张卡上保持 warm(1× 成本),省下的就是这一整组 GPU。多节点侧的 daemon 协调(Phase 5 的 primary-secondary 状态机)仍是 roadmap 项,单机是今天能用的边界。CocoLoop
场景判断落到运营上,指标要跟着变。过去衡量启动优化用「平均加载时间」,而守护进程场景下这个数字毫无区分度:命中的实例快得离谱,miss 的实例慢得像冷启动,平均一个数把两种状态混在一起。更接近 SLO 的口径是三个:
| 指标 | 度量什么 | 为什么替代平均加载时间 |
|---|---|---|
| 缓存命中率(cache hit rate) | 多少次恢复真正走了 IPC 零拷贝 | miss 就是冷启动,命中率决定方案真实价值 |
| 恢复可用时间(recovery SLO) | 从故障到语义就绪 | 0.63 s 不是可用时间,32 s 才是 |
| 失效尾延迟(failure tail latency) | 缓存失效时的最差恢复耗时 | 最坏情形才是这份显存租金要付的代价 |
这个口径不是 SGLang 一家在收敛。vLLM 的 Q3 cold-start roadmap 自己写了份 Ship contract:只在语义就绪(semantic readiness)之后接流量,而不是进程或端口就绪;禁止把不同起点状态的优化数字相加。vLLM-Roadmap
绕了一圈,回到开头那组数字。权重加载从 495 秒到 0.63 秒,约 785 倍的提速是真的;但它买到的东西不是「冷启动消失」,而是一个可以独立于引擎存活、崩溃后不必重读的权重生命周期。瓶颈没有消失,只是从磁盘加载转移到了显存驻留与缓存命中管理上,一个更贵、更脆弱的住所。带着这份判断去评估部署:命中率是多少,失效时最差多久,故障到底落在进程内还是进程外。把这几个问题回答清楚,比记住「785 倍」这个数字有用得多。
参考文献
本专题引用的核心资料如下,建议深入学习时阅读原文:
- Fast Engine Recovery: Sub-Second Engine Restart for SGLang via Weight Cache Daemon(LMSYS Blog,Ant Ling / Ant Group + Alibaba + SGLang Team)https://www.lmsys.org/blog/2026-08-21-sglang-fast-recovery/
- 万亿模型重启从 8.8 分钟压到 32 秒(CocoLoop 中文报道)https://news.cocoloop.cn/2026/08/sglang-weight-cache-daemon/
- Breaking the Ice: Analyzing Cold Start Latency in vLLM(MLSys 2026)https://arxiv.org/abs/2606.07362
- FlashBoot: Sub-Second Weight Loading for Large Models at Rack Scale https://arxiv.org/abs/2608.08482
- [FEAT] Support fast engine recovery through weight cache(SGLang PR #27139)https://github.com/sgl-project/sglang/pull/27139
- Amdahl's Law for LLMs: A Throughput-Centric Analysis of Extreme LLM Quantization(Malekar & Zand,TMLR 2025)https://arxiv.org/abs/2408.11939
- FlashBoot arXiv abstract(NCCL setup 成本切片) https://arxiv.org/abs/2608.08482v1
- [Roadmap] Cold Start Q3 2026 #48193(vLLM GitHub issue)https://github.com/vllm-project/vllm/issues/48193