SGLang 推出 Weight Cache Daemon,实现亚秒级引擎重启
Blog Fast Engine Recovery: Sub-Second Engine Restart for SGLang via Weight Cache Daemon Nowadays, State-of-the-Art (SOTA) models are getting much bigger and reloading the model service after a crash is very expensive. Therefore, we are introducing the Weight Cache Daemon, a persistent GP... Ant Ling Infra Team (Ant Group), Alibaba, SGLang Team
SGLang 团队推出 Weight Cache Daemon,通过 CUDA IPC 零拷贝映射将模型权重加载从约 495 秒降至约 0.63 秒(约 785 倍加速),端到端启动时间减少 93.9%。该守护进程在 GPU 内存中持久化后量化权重,支持多实例共享和亚秒级主备切换,是 Fast Engine Recovery Framework 的第一阶段。
摘要
如今,最先进的(SOTA)模型规模越来越大,崩溃后重新加载模型服务的成本极其高昂。因此,我们推出了 Weight Cache Daemon(权重缓存守护进程),这是一个常驻 GPU 进程,将量化后的模型权重保存在 GPU 内存中,并通过 CUDA IPC 零拷贝映射将其提供给新的 SGLang 引擎实例。这使权重加载时间从数分钟缩短到数秒。
Weight Cache Daemon 是我们快速引擎恢复框架的第一阶段,该框架的目标是为生产环境中的 LLM 服务实现冷启动小于 10 秒、热备切换小于 1 秒。
关键成果:
- 权重加载:约 495 秒 → 约 0.63 秒——基于 Ling-2.6-1T FP8 模型,实现了约 785 倍的加速。
- 总启动时间:8.8 分钟 → 0.528 分钟——端到端引擎启动时间减少了 93.9%。
- 多实例权重共享——同一 GPU 上的多个引擎实例映射到相同的 IPC 句柄,消除了重复的磁盘 I/O 和量化后变换。
- 主备故障切换小于 1 秒——备用引擎通过零拷贝共享权重,无需将整块 GPU 专用于空闲副本,即可实现近乎零停机的故障切换。
- 多节点实例权重共享——支持大模型的多节点模式。
背景
随着 LLM 模型规模不断增大——Qwen3-235B、Ling-2.6-1T,以及新发布的 2.8T Kimi K3——服务引擎的冷启动时间已成为生产效率的关键瓶颈。一个部署在 8×H20-3e GPU 上的 Ling-2.6-1T FP8 实例仅准备就绪就需要约 8.52 分钟,权重存储在 3.5T NVME SSD 上。在生产环境中,这意味着:
- 重启期间 P99 尾延迟飙升——所有进行中的请求要么失败,要么无限期排队。
- 可用性降低——数分钟的恢复窗口违反了 SLA 目标。
- 运维摩擦——滚动更新、配置变更和故障恢复都受制于重启周期。
- GPU 资源浪费——传统的主备部署会将整组 GPU 专用于空闲副本,使故障切换的硬件成本翻倍。
时间都花在哪里了?我们对 Ling-2.6-1T FP8 的完整 SGLang 引擎启动过程进行了剖析:
| 阶段 | 时间(秒) | 占比 | 说明 |
|---|---|---|---|
| 预初始化与 ServerArgs | 约 1 | 0.2% | 预初始化与 ServerArgs 解析 |
| Tokenizer 初始化 | 约 13 | 2.4% | 加载并初始化 tokenizer |
| 初始化 torch 分布式 | 约 5 | 0.9% | NCCL 2.28.9,8 卡 H20,NVLink mesh 370.8 GB/s,P2P/IPC;最慢 rank TP1=5.19s |
| 加载权重(磁盘) | 约 495 | 93.9% | 161 个分片,W8A8 FP8(CompressedTensorsW8A8Fp8MoE),最慢 rank=495.3s,每卡 120GB;磁盘 I/O 瓶颈 |
| 缓存分配(KV+Mamba) | 约 1 | 0.2% | KV:553,599 个 token/5.94GB bf16;Mamba SSM 状态:5.33GB,max_mamba_cache_size=155 |
| 捕获 CUDA 图 | 约 7.7 | 1.5% | 仅 3 个解码 BS [1,2,4] |
| 服务器就绪 | 约 4 | 0.8% | Unified RadixTree 初始化,HTTP/uvicorn 启动,预热请求 |
| 总计 | 约 527 | 约 8.8 分钟 |
瓶颈很明显:从磁盘加载权重占启动时间的 93.2%。对于 Ling-2.6-1T FP8 模型,每个 TP rank 从磁盘读取约 120GB 的 safetensors,进行反序列化、应用 TP 分片,并执行量化后变换(FP8 量化、权重重打包)。每次重启都会重复完全相同的工作,尽管最终得到的 GPU 张量是确定性的,而且往往已经存在于 GPU 内存中。
我们能否避免每次从磁盘重新加载?答案是肯定的——方法是在引擎重启期间将权重保留在 GPU 内存中。
设计
核心思路:通过 CUDA IPC 实现持久化权重缓存
权重缓存守护进程是一个持久的 GPU 进程,将量化后、TP 分片后的权重保存在 GPU 内存中。引擎重启时,新的引擎进程通过 CUDA IPC 零拷贝从守护进程映射权重——无需磁盘 I/O、无需反序列化、无需量化。
每个 GPU 为其 TP rank 运行一个守护进程。该守护进程:
- 从磁盘加载模型权重(完整流水线:磁盘 → TP 分片 → 量化 → 重打包)。
- 将 model.state_dict() 中的每个参数和缓冲区导出为 CUDA IPC 句柄。
- 记录 CacheConfig 指纹(模型路径、TP/DP 大小、量化配置哈希、dtype)。
- 通过 Unix socket 向请求的引擎进程提供 IPC 句柄。
引擎连接到守护进程,验证配置兼容性,并将权重直接映射到其地址空间中——引擎和守护进程通过 CUDA IPC 共享同一块物理 GPU 内存。
通过 Meta 设备实现零拷贝加载
亚秒级加载的关键在于零拷贝:引擎的 param.data 指针直接指向 IPC 映射的 GPU 张量。不复制任何数据。
为实现这一目标,引擎在 meta 设备上初始化模型(不分配 GPU/CPU 内存),然后将每个参数的数据指针替换为 IPC 映射的张量。
由 process_weights_after_loading() 创建的后量化参数(例如 FP8 量化产生的 weight_scale)也会被守护进程缓存并直接映射——无需重新量化。
配置校验:安全第一
引擎配置与守护进程缓存配置之间的任何不匹配都会触发完整的磁盘重新加载,以确保正确性:
| 字段 | 不匹配示例 | 后果 |
|---|---|---|
| model_path + model_arch + revision | 不同的模型或版本 | 权重完全错误 |
| tp_size + tp_rank | 不同的 TP 分片方式 | 该 rank 拿到错误的分片 |
| pp_size + pp_rank | 不同的 PP 划分方式 | 该流水线阶段拿到错误的层 |
| dp_size + ep_size | 不同的 DP/EP 策略 | 权重分布不正确 |
| quant_method + quant_config_hash | 不同的量化方式 | 未量化与 FP8 不匹配 |
| dtype | float16 与 bfloat16 | 类型不匹配 |
| device_capability + torch_version | 不同的 GPU 架构或 torch 版本 | 权重映射正常但数值结果错误 |
最后两个字段构成一个环境戳记:如果守护进程和客户端运行了不同的后处理分支(不同的计算能力或 torch/内核版本),它们产出的权重虽然能通过 IPC 正常映射,却可能输出垃圾结果——将环境信息戳记写入 CacheConfig,就能把这种情况变成一个清晰的不匹配。
这对生产环境的安全性至关重要:如果运维人员更改了模型或量化配置,引擎会检测到不匹配并回退到磁盘加载,而不是映射不兼容的权重。
在配置校验之上,量化方法还受 IPC 白名单约束。CUDA IPC 零拷贝只导出原始张量数据,因此只有当 process_weights_after_loading() 的全部效果都被该数据完整覆盖时,这种方式才是正确的。那些会在 Python 侧写入元数据或重新打包/转置权重的方法(per-tensor FP8、Marlin、AWQ/GPTQ)会静默地产生错误的数值结果——它们会直接抛出硬错误。目前已验证通过:未量化和 block-wise FP8(已设置 weight_block_size);更多方法将在端到端验证后陆续加入。
三种模式:daemon、client 和 off
| 模式 | 流程 | 权重加载时间 | GPU 显存 | 使用场景 |
|---|---|---|---|---|
| 守护进程 | 引擎启动守护进程 → 守护进程从磁盘加载 → 引擎映射 IPC | < 1 秒(守护进程就绪后) | 1×(共享) | 首次启动;引擎管理守护进程生命周期 |
| 客户端 | 连接至已运行的守护进程 → 映射 IPC | < 1 秒 | 1×(共享) | 引擎重启;守护进程已在运行 |
| 关闭 | 正常磁盘加载 | 405–411 秒(Ling-2.6-1T FP8) | 1× | 默认;无缓存 |
在守护进程模式下,引擎在启动时生成守护进程,并等待它们从磁盘加载权重。首次启动仍然较慢(守护进程必须从磁盘加载),但后续重启是即时的。
在客户端模式下,引擎连接到已在运行的守护进程。这是快速重启路径——守护进程提前启动,且已在 GPU 显存中持有权重。
安全性与健壮性
权重缓存守护进程的设计目标是非侵入性和安全性:
- 最小侵入性:该功能自包含在 python/sglang/srt/weight_cache/ 中,对核心引擎的改动极小(仅 load_model() 分发和一个 CLI 标志)。
- 崩溃安全:如果守护进程崩溃,现有引擎实例继续运行——它们已通过 CUDA 引用计数持有 IPC 映射张量的引用。只有当守护进程和引擎都退出时,GPU 显存才会被释放。
- 守护进程恢复:如果守护进程重启,它会从磁盘重新加载权重并重新导出 IPC 句柄。新的引擎实例随后可以连接到重启后的守护进程。
- 配置不匹配时的回退:配置不匹配会自动回退到磁盘加载(客户端模式),或抛出错误(守护进程模式,因为两个进程共享同一 GPU,回退会导致 OOM)。
超越重启:生产场景
权重缓存守护进程解锁了传统基于磁盘的加载方式难以实现的生产模式:
多实例权重共享
每 GPU 一个守护进程在内存中持有权重;多个引擎实例(例如独立服务)通过零拷贝映射到相同的 IPC 句柄。无论有多少实例消费权重,权重都仅从磁盘加载并量化一次。
优先级协同服务
在同一块 GPU 上运行高优先级在线服务和低优先级批处理任务,两者由同一个权重缓存守护进程提供支持。低优先级实例可以在亚秒级时间内被驱逐并重新启动,无需从磁盘重新加载权重——从而实现灵活的 GPU 分时共享,且没有通常的启动开销。
主备故障切换
在主引擎旁边部署一个备用引擎,两者由同一个权重缓存守护进程提供支持。备用引擎通过零拷贝映射权重并保持热备状态。当主引擎发生故障时,备用引擎在 < 1 秒内接管——无需加载权重,无需磁盘 I/O。
这实现了近乎零宕机的故障切换,而无需将整组 GPU 专门用于空闲副本,避免了传统热备部署中昂贵的 GPU 资源浪费。
性能
权重加载:磁盘 vs IPC 零拷贝
单节点
| 模型 | 权重大小 | 磁盘加载(秒) | IPC 零拷贝(秒) | 加速比 |
|---|---|---|---|---|
| Qwen3-235B FP8 | ~235 GB | ~306–327 | <1 | ~500× |
| Ling-2.6-1T | ~1 TB | ~405–411 | <1 | ~780× |
性能图表
使用方法
启动权重缓存守护进程 - 单节点
一条命令即可启动所有 TP 秩守护进程:
python -m sglang.srt.weight_cache.daemon \
--model-path /path/to/model --tp-size 4 \
--load-format auto --dtype auto --quantization fp8
等待守护进程就绪(它们会为每个秩写入一个 .ready 文件):
ls /tmp/sglang_weight_cache_rank*.ready
使用权重缓存启动引擎
python -m sglang.launch_server \
--model-path /path/to/model --tp-size 4 \
--weight-cache-mode client
启动权重缓存守护进程 - 多节点
在多节点部署中,每个节点为其本地 TP 秩运行自己的守护进程。所有守护进程加入同一个分布式组,因此 --nnodes、--node-rank 和 --dist-init-method 必须在各节点间保持一致,$MASTER_ADDR 指向节点 0:
python -m sglang.srt.weight_cache.daemon \
--model-path /path/to/model --tp-size 2 \
--load-format auto --dtype auto --quantization fp8 \
--nnodes 2 --node-rank 0 \
--dist-init-method tcp://$MASTER_ADDR:29500
python -m sglang.srt.weight_cache.daemon \
--model-path /path/to/model --tp-size 2 \
--load-format auto --dtype auto --quantization fp8 \
--nnodes 2 --node-rank 1 \
--dist-init-method tcp://$MASTER_ADDR:29500
当每个节点都报告其守护进程就绪后,启动引擎客户端。它们使用与守护进程(29500)不同的独立 rendezvous 端口(29600):
python -m sglang.launch_server \
--model-path /path/to/model --tp-size 2 \
--weight-cache-mode client \
--nnodes 2 --node-rank 0 \
--dist-init-addr $MASTER_ADDR:29600 --port 34000
python -m sglang.launch_server \
--model-path /path/to/model --tp-size 2 \
--weight-cache-mode client \
--nnodes 2 --node-rank 1 \
--dist-init-addr $MASTER_ADDR:29600
快速引擎恢复框架:路线图
权重缓存守护进程是更广泛的快速恢复框架的第一阶段,该框架的目标是 < 10 秒冷重启和 < 1 秒热备切换:
| 阶段 | 当前(秒) | 目标(秒) | 方案 | 状态 |
|---|---|---|---|---|
| 加载权重 | ~306–327 | < 1 | 权重缓存守护进程(CUDA IPC) | 已完成(本 PR) |
| 捕获 CUDA 图 | ~34.9 | < 3 | CUDA 图序列化 + 重放 | 计划中 |
| DeepGEMM JIT 预热 | ~23.1 | < 2 | 内核缓存持久化,并行预热 | 计划中 |
| 服务器初始化与分词器 | ~17.3 | < 3 | 延迟分词器初始化,配置缓存 | 计划中 |
| 初始化 torch 分布式 | ~4.7 | < 2 | NCCL 会话复用,持久化进程组 | 计划中 |
| KV 缓存分配 | ~0.5 | < 0.5 | kvcache 复用 | 计划中 |
| 服务器就绪 | ~3.4 | < 1 | 重启时跳过预热请求 | 计划中 |
| 总计(单节点) | 约 390 | < 10 |
对更多模型的支持也即将推出。
公开路线图
权重缓存守护进程只是第一步——还有很多工作要做,我们对未来的路线充满期待。目前的第一阶段涵盖 TP + PP、单节点和多节点启动、每 GPU 零拷贝 CUDA IPC,以及非量化加块级 FP8。除此之外,还有许多高影响力的方向仍然开放:
- 更多模型与量化:将 IPC 允许列表扩展到块级 FP8 之外(逐张量 FP8、INT8、MXFP8、NVFP4、AWQ/GPTQ 等),并覆盖更多架构,包括多模态和 LoRA 基础权重。
- DP/EP 与多节点:DP/EP 分片键控和跨节点守护进程协调、生命周期管理和故障转移。
- 无需重新加载的权重更新:用于 RL / 在线更新的就地权重刷新,以守护进程作为交付代理。
- 跨 GPU 与集群共享:对等复制和集群填充,使集群冷启动时每个分片组大约只需一次磁盘读取。
- KV 缓存恢复:在重启 / 故障转移期间保留并重新映射 KV 缓存(KV 复用、交接给备用节点),使进行中的上下文在恢复后得以保留,而不是从头重新计算。
- 启动路径的其余部分:CUDA 图序列化、内核预热持久化,以及更快的服务器 / 分布式初始化,以实现 < 10 秒的冷重启目标。
- 其他硬件后端:将此功能扩展到提供类似功能的其他加速器(AMD 和 Intel 都有可比的 IPC 机制)。
- 运维与可靠性:指标、状态工具、安全加固和 CI 覆盖。
这很大程度上是一项社区工作。完整计划在 sgl-project/sglang#33522 中公开跟踪——非常欢迎贡献和反馈,而且有很多有影响力的工作可以接手。
致谢
蚂蚁集团 Ant Ling 基础设施团队:Michael Qiu qiudayu.qdy@antgroup.com
阿里巴巴:Siyu Liu liusy58@smail.nju.edu.cn
SGLang 团队:Alex Nails
来源:LMSYS Blog · lmsys.org