08 启动加速:编译缓存挂载与 CUDA 图捕获裁剪,首启 8 分钟 → 3 分钟
- 系列:GLM-5.3-NVFP4 部署实录(8×A800 / sm80)。
- 上一篇:07 正确的部署脚本。
- GLM5.3 nvfp4 VLLM部署方案源码
文章目录
-
[08 启动加速:编译缓存挂载与 CUDA 图捕获裁剪,首启 8 分钟 → 3 分钟](#08 启动加速:编译缓存挂载与 CUDA 图捕获裁剪,首启 8 分钟 → 3 分钟)
-
- [1. 编译缓存挂载:一行挂载,六分钟到手](#1. 编译缓存挂载:一行挂载,六分钟到手)
- [2. CUDA 图捕获裁剪:389 秒 → 约 1 分钟](#2. CUDA 图捕获裁剪:389 秒 → 约 1 分钟)
-
- [2.1 先分清两种图模式:FULL 与 piecewise](#2.1 先分清两种图模式:FULL 与 piecewise)
- [2.2 CUDA 图无法落盘缓存的原因](#2.2 CUDA 图无法落盘缓存的原因)
- [2.3 默认值有多浪费和裁剪优化](#2.3 默认值有多浪费和裁剪优化)
- [3. 两条启动加速原则的适用边界](#3. 两条启动加速原则的适用边界)
-
模型部署调通之后,启动耗时就成了日常运维的痛点,尤其在你需要频繁重启调试、或在多机复刻部署时。
-
本文记录我们如何把"页缓存热"的启动从 ~8 分钟压到 ~3 分钟:一靠把 torch.compile 的编译缓存挂载到宿主机,二靠裁剪 CUDA 图的捕获尺寸列表。
先看启动耗时的构成(页缓存热,87 分片加载仅 ~9s):
| 阶段 | 首启(无缓存) | 缓存命中后 |
|---|---|---|
| Dynamo 字节码转换 | 31.5s | 1.8s |
| inductor 编译 | ~6min | 跳过 |
| piecewise CUDA 图捕获 | ~1min | ~1min(见下文,无法缓存) |
| init engine 全程 | ~8min | ~3min |
1. 编译缓存挂载:一行挂载,六分钟到手
- vLLM 的 torch.compile 缓存默认写在容器内
/root/.cache/vllm。容器重建即丢,下次启动重新编译 ~6 分钟。解法是把宿主机目录挂进去(deploy.sh已内置):
bash
mkdir -p .compile_cache
docker run ... -v $PWD/.compile_cache:/root/.cache/vllm ...
- 效果:Dynamo 字节码转换 31.5s → 1.8s,inductor 编译整体跳过 ,init engine 从 ~8min 降到 ~3min。缓存挂载后实测约 2.3G,内含三部分:
bash
.compile_cache/
├── torch_compile_cache/ # inductor 产物(TRITON 缓存 + 编译 artifact)
├── modelinfos/ # 模型/配置哈希信息(缓存有效性判断依据)
└── dummy_cache/
- 如何确认缓存命中 :看启动日志里 Dynamo 转换的耗时------命中时是
1.8s量级,未命中是31.5s量级,后接 inductor 编译长日志。这是最直接的判据,比翻缓存目录可靠。
两个细节:
- 缓存挂载要放在
docker run而不是docker commit里解决,缓存属于"运行时状态",镜像应该保持无状态,这样多机部署时只需要同步一个目录; - 失效是自动且精确的 :vLLM 按代码与配置的哈希管理缓存,改了补丁文件(改变编译产物)或换了编译配置,对应产物自动失效重编,不需要手动清理;只改
MAX_NUM_SEQS这类调度参数则不影响编译缓存。
2. CUDA 图捕获裁剪:389 秒 → 约 1 分钟
- 剩下的 ~1min 是 CUDA 图捕获。这一步无法靠缓存解决。
2.1 先分清两种图模式:FULL 与 piecewise
-
排查编译崩溃期间我们用过
mode=none + cudagraph_mode=FULL,最终配置是默认编译 + piecewise 图。两者值得辨析: -
FULL(整图):把整个 forward(含注意力、MoE)作为一张图捕获。不依赖 torch.compile,所以是编译崩溃期的天然避风港,把单流从 7.5 拉到 33 tok/s。
-
piecewise(分段,编译开启时的默认) :torch.compile 先把 forward 编译并切分,只在适合捕获的段(如 attention 之外的稳定段)上建图。它带来两个好处:图更精简、与编译优化叠加(最终 75.6 tok/s 里 CUDA 图与编译的贡献就是靠它同时拿到的)。
结论:FULL 是"保命用"的图,piecewise 是"得分用"的图。如果部署顺利,直接用默认 piecewise;只有编译路径有问题需要临时绕过时才退到 FULL。
2.2 CUDA 图无法落盘缓存的原因
- CUDA Graph 记录的是本进程内的显存地址------kernel 参数里写死的是捕获那一刻的指针。进程重启后地址布局变了,落盘的图就是一堆悬空指针。所以 vLLM 每次启动都要现场重新捕获,这份时间账躲不掉,只能少捕几张。
2.3 默认值有多浪费和裁剪优化
- vLLM 默认对一系列 batch size 逐个捕获,最多到 256 ------共 27 张图,实测捕获 389 秒 。而部署
--max-num-seqs 16:调度器根本不会排出超过 16 的 batch,batch 32/64/.../256 的图一张都不会被执行。把捕获列表限制在调度器实际会运行的尺寸以内即可:
bash
--compilation-config '{"cudagraph_capture_sizes":[1,2,4,8,12,16]}'
-
捕获耗时 389s → 约 1min ,图自身的显存占用约 1.17GiB (单张图也不小,少捕 20 张省的是真金白银的显存),日志证据:
Graph capturing finished in 12 secs, took 1.17 GiB。 -
裁剪的依据只有一条:捕获尺寸上界 = MAX_NUM_SEQS。如果你把并发调大(如 32、64),捕获列表要跟着改,否则高并发段会退回 eager 执行,吞吐掉档。给不同部署规模的参考起点:
| MAX_NUM_SEQS | 建议 CUDAGRAPH_CAPTURE_SIZES |
|---|---|
| 16(我们的场景) | 1,2,4,8,12,16 |
| 32 | 1,2,4,8,16,24,32 |
| 64 | 1,2,4,8,16,32,48,64 |
两点说明:低尺寸段要密(批量小的请求最常见,decode 大部分时间落在 1--8 的图上);高尺寸段可以稀(走到那里的 batch 少,每多捕一张是线性增加的启动时间)。空值恢复默认 ,CUDAGRAPH_CAPTURE_SIZES 留空时 vLLM 会捕到 256,如果业务真的会有 100+ 并发,那 389s 的捕获成本就得认。
3. 两条启动加速原则的适用边界
| 手段 | 适用 | 不适用 |
|---|---|---|
| 编译缓存挂载 | 所有开启 torch.compile 的 vLLM 部署;重启频繁的调试场景 | 补丁/模型变更后自动失效;换编译配置会重编 |
| CUDA 图捕获裁剪 | max_num_seqs 明确小于默认 256 的所有部署 |
需要支撑突发高并发的场景(列表上界要覆盖真实并发) |
- 合起来,最终配置下的启动时间线(页缓存热):
bash
87 分片加载 ~9s → Dynamo 转换 1.8s → (inductor 跳过) → 图捕获 ~1min → 就绪
init engine 全程 ~3min;完整冷启动(含权重加载)约 5 分钟