跳到正文
vLLM Blog· DaoCloud Team·· 2026-07-23精选AI 评分62

DaoCloud 如何在 24 张 NVIDIA B300 上用 vLLM 部署 GLM-5.2 并达成生产 SLA

From Day 0 to Production SLAs: Serving GLM-5.2 on 24 NVIDIA B300 GPUs with vLLM

AI 导读

DaoCloud 团队用 3 台 8 卡 B300 服务器(共 24 张 GPU)以 4 Prefill + 1 Decode 分离式拓扑部署 GLM-5.2-NVFP4,在平均 TTFT ≤ 2.5 s、平均 TPOT ≤ 20 ms 的 SLA 下把 TPOT 从约 40 ms 降到 17 ms。

推荐理由

完整记录了从 40ms 到 17ms 的调优路径与各步贡献,并给出可直接复用的 vllm serve 配置。

正文 · AI 翻译

TL;DR

我们使用分离式 4-Prefill + 1-Decode 拓扑,将 GLM-5.2-NVFP4 部署在三台 8-GPU B300 服务器上(共 24 块 GPU)。在生产 SLA 目标——平均 TTFT ≤ 2.5 s、平均 TPOT ≤ 20 ms——下,我们测得以下结果:

GLM-5.2-NVFP4 on 24x B300, disaggregated 4P1D serving: total throughput, output throughput, TTFT and TPOT across input lengths from 8K to 256K
GLM-5.2-NVFP4 在 24x B300 上,分离式 4P1D 服务:在 8K 至 256K 输入长度下的总吞吐量、输出吞吐量、TTFT 和 TPOT

在同一硬件上,我们的起点是 16K token 输入下平均 TPOT 接近 40 ms——大约是我们 SLA 上限的两倍。本文记录了从 40 ms 到 17 ms 的完整历程:我们在每一步改了什么、每项改动贡献了多少,以及为什么我们最终上线的并行策略并非原始吞吐量最高的那个。文末附有一套完整、可复现的 vllm serve 命令。

1. 为什么我们针对 SLA 合规性而非峰值吞吐量进行优化

在共置服务中,prefill 块被交错插入 decode 批次,而每个进入批次的长提示都会拉长所有已在解码请求的 token 间延迟。因此 TPOT 尾部延迟成为传入提示长度分布的函数——这是服务系统无法控制的。分离式架构将 prefill 工作完全从 decode 关键路径中移除,使 TPOT 仅取决于 decode 批次的组成。这正是紧凑的 TPOT SLA 得以实现的前提,也是本文其余部分将 P/D 视为起点、而非众多优化之一的原因。

生产服务不能仅凭峰值吞吐量来验收。真正的问题是:系统能在不违反延迟目标的前提下承载多少流量。我们的需求很明确:

  • 典型上下文长度:16K–256K token
  • 平均 TTFT ≤ 2.5 s——从用户操作到首个可见 token 之间可接受的最大延迟
  • 平均 TPOT ≤ 20 ms——约 50 token/s 的流式输出;低于此速率,阅读体验会明显下降
  • 在这两项硬性约束下,最大化吞吐量

关于批大小的说明:我们没有固定批大小。负载以请求速率施加,并发数就是该速率在 SLA 下产生的值——刻意做到延迟预算允许的那么大。由于 prefill 成本随上下文长度增长,并发数会随输入长度上升而降低:8K 时约 700 个并发请求,16K 时约 300 个,256K 时约 25 个,全部处于 --max-concurrency 1024 上限之下。

这一目标贯穿了本文的方法论:每次配置搜索都考虑约束。一个将吞吐量提升 30% 但把平均 TPOT 推过 SLA 的配置,对我们没有用。第 4 节有一个代表性示例。

GLM-5.2 是一个 744B 参数的 MoE 模型,激活参数为 40B。它使用 DSA 稀疏注意力,并原生支持 MTP 推测解码,而 vLLM 已为这三者提供了成熟支持。我们的工作是将它们与 P/D 分离式架构有效结合,然后围绕生产 SLA 目标调优参数、拓扑和调度。

2. 起点:在 P/D 分离式架构下提升 Decode 性能

在初始配置下,Prefill 侧已经达标,并有充足的 TTFT 余量。瓶颈在 Decode:在 16K 输入 token 和 1K 输出 token 下,平均 TPOT 接近 40 ms,且 P99 抖动很大。

2.1 根本原因:P/D 交接处的混合批次

投机解码已成为大型 MoE 模型的标准推理时优化手段,生产环境中的 Decode 部署也越来越多地默认启用它。性能分析揭示了一个问题,它恰好位于 P/D 分离与投机解码的交互点上。

当一个请求通过 KVConnector 将其 prompt KV 缓存传输到 Decode 节点后,它的第一个 Decode 步骤只需计算一个 token。然而,当启用 MTP 时,该 Decode 节点上已有的请求每步会调度 1 + N 个 token。由于这两类请求的形状不同,该步骤就变成了混合批次。它无法再走 uniform-decode 的完整 CUDA Graph 快速路径,而是回退到开销更高的分段式或 eager 执行路径。

数据并行会放大这一影响。在 DP 下,CUDA Graph 模式和 padding 需要跨 rank 协调,因此只要任意一个 DP rank 收到新传输过来的请求,其余 rank 也会跟随它进入同一条执行路径。在稳态 P/D 运行中,新请求会持续到达 Decode 实例,因此慢路径会被不断触发。

2.2 优化:Decode 侧的投机 Padding

修复方案在概念上很简单。在请求到达后的第一个 Decode 步骤中,用虚拟投机 token 将其形状填充到 1 + N,与 Decode worker 中已有的其他请求保持一致。这样就能保持统一的 Decode 执行,使工作负载留在完整 CUDA Graph 快速路径上。

它不需要从 Prefill 节点传输已生成的 token 或 draft token。该优化已由 vLLM 社区在 PR #45237 中合并。

2.3 性能收益

随着混合批次导致的执行路径回退被消除,端到端平均 TPOT 从约 40 ms 降至约 22 ms——这是整个工作中单项最大的改进。

这一结果给同时采用 P/D 分离与投机解码的部署带来了更广泛的启示:最大的性能损失可能并不存在于任何单个 kernel 中。它可能出现在子系统之间的边界处,在那里,请求状态、调度形状和 CUDA Graph 执行模式上的微小不一致会被 DP 规模和持续流量放大。

3. 进一步的 Decode 侧优化

在 22 ms 时,我们已经接近 SLA,但没有安全余量,因此我们又进行了一轮配置搜索。

3.1 Model Runner V2:TPOT 降低 11%

vLLM Model Runner V2(MRV2)重构了运行时执行路径。自 v0.25.0 起,它已成为所有稠密模型的默认选项;GLM-5.2 是 MoE 模型,因此默认未启用,必须通过 VLLM_USE_V2_MODEL_RUNNER=1 显式激活。

在我们的 Decode 配置上,MRV2 相比 MRV1 将 TPOT 改善了约 11%。除了更短的执行路径外,MRV2 还带来了若干对可预测的生产延迟至关重要的能力:

  1. PR #47285 将 GLM-5.2 DSA indexer prefill-metadata kernel 加入启动预热,因此首个生产请求不再触发 Triton JIT 编译和延迟尖峰。这在基准测试中很容易被忽略,因为预热会将其吸收;但在生产环境中,它会在每次滚动部署后表现为冷启动尖峰。
  2. PR #46448 为多 GPU MTP 增加了本地 argmax 归约。启用 use_local_argmax_reduction 后,draft token 生成不再 AllGather 全词表 logits,将 TP 通信量从与词表大小成正比的规模降至约 2 × TP size。在 MRV2 下运行的 MTP、EAGLE、DFlash 及其他投机器都能从中受益。
  3. PR #45953 让动态推测长度可与完整的 CUDA Graphs 配合工作,减少了因草稿长度变化而导致的图未命中和回退到 eager 模式的情况。

3.2 All-to-All 后端:TPOT 降低 4%

由于 GLM-5.2 是 MoE 模型,Decode 侧运行 DEP8,这会将专家分发与合并通信直接置于关键路径上。我们将默认基于 AllGather/ReduceScatter 的 EP 后端替换为 FlashInfer NVLink A2A 后端;在我们的测量中,flashinfer_nvlink_two_sided 进一步将 TPOT 降低了 4%。

vLLM 现在也提供了更新的 flashinfer_nvlink_one_sided 后端,预计性能会更好。本文保留双侧后端,因为这是我们实际测量的配置。在相同的 Decode 工作负载下评估单侧后端已列入我们的计划。

3.3 CUDA Graph 模式

Decode 实例同时运行 --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' 和 --max-num-batched-tokens 1024。Decode 侧不需要为 prefill 形状编译图,而 FULL_DECODE_ONLY 为 Decode 路径提供了完整的图覆盖,同时显著缩短了启动编译时间。

3.4 MTP 推测解码

我们在 Decode 侧使用 num_speculative_tokens=3,在 Prefill 侧使用 1。(MTP 只有在与 IndexerCache 结合时,才能在 GLM-5.2 上变得具有成本效益,详见第 5 节。)这种不对称是有意为之:Prefill 节点应尽快生成并移交 KV 缓存,因此更深的推测在那里收益甚微。Decode 节点位于延迟关键路径上,更深的推测可以分摊每个 token 的执行成本——前提是接受率保持较高。

4. Prefill 并行:为什么我们没有选择吞吐量最高的配置

在 Prefill 侧,我们在 8K 和 32K token 输入下比较了几种并行策略。由于这些配置使用不同数量的 GPU,TGS——每 GPU 吞吐量——才是有意义的指标。

Per-GPU prefill throughput for four parallelism strategies, at 8K and 32K input lengths.
四种并行策略在 8K 和 32K 输入长度下的每 GPU prefill 吞吐量。

从绝对值来看,TP1 DP4 EP 是最快的实例——在 8K 输入下达到 47,806 tok/s——但这只是因为它使用的 GPU 数量是其他配置的两倍;按每 GPU 计算,它排在第二位。有两个结论尤为突出:

  1. TP2 + EP 的表现比单纯的 TP2 更差。 在仅两块 GPU 的规模下,EP 引入的 all-to-all 开销超过了其收益。EP 需要足够多的专家分布在足够多的设备上,才能分摊其通信成本。
  2. TP1 DP2 EP 实现了最佳的 TGS,但我们最终交付的是 TP1 DP4 EP。

第二点正是生产工程与追逐基准测试分道扬镳之处。TP1 DP2 EP 提供了最佳的每 GPU 效率,但每个实例只有两块 GPU,留给 GLM-5.2 的 100 万 token 上下文能力的 KV 缓存容量太少。我们不愿为了 8% 的 TGS 优势而放弃该模型最重要的特性之一,因此选择了 TP1 DP4 EP——用大约 8% 的每 GPU 效率换取每个实例四块 GPU 的 KV 缓存容量。

5. MTP + IndexerCache:我们如何提高接受率

vLLM 已在常规配置下经过生产环境的广泛验证。本次部署同时启用了三项相对较新的能力——P/D 分离、MTP 和 MRV2——这使我们走上了一条较少被使用的组合路径,调优过程中也暴露出若干长尾问题。大多数修复在几天内就从报告走到了发布;使用 v0.26.0 的人已经拥有了所有这些修复。本节记录了这项工作,以展示 MTP 接受率是如何一步步稳定下来的。

5.1 IndexerCache:让 MTP 与 DSA 结合时具有成本效益

IndexerCache(PR #44420)不是传统的 KV cache——它复用 DSA indexer 产生的 Top-K 稀疏索引。朴素的实现会在每个 MTP 草稿步骤重新运行 indexer,而由于稀疏检索成本随上下文长度增长,这会消耗掉投机解码预期收益的很大一部分。PR #44420 引入了 index_share_for_mtp_iteration,让第一个草稿步骤计算 Top-K 索引,后续草稿步骤复用它们。这是 MTP 在 GLM-5.2 上能够有价值的前提条件。

随后社区围绕这一机制完成了三项改进,共同在高并发下稳定了接受率:

  • PR #45895 改进了跳过 Top-K 层时的 indexer 初始化,并修复了 GLM-5.2 MTP 归一化循环。该 PR 报告称,在 TP=8 的 GLM-5.2-FP8 上,平均接受长度从约 3 提升到约 4,平均接受率约 60%,同时 IFBench 保持在 74.62。
  • PR #47238 针对批量请求优化了共享索引缓冲区的布局:在第一个草稿步骤之后,仅保留每个请求最终查询 token 对应的 Top-K 索引。这是将索引共享从单请求执行扩展到高并发批处理的关键一步。
  • PR #47448 确保 MTP 循环复用最终归一化后的隐藏状态。

综合来看,IndexerCache 不仅仅是一项节省计算的优化。它也是在​​高并发下维持 MTP 接受率的关键机制。

5.2 组合配置的两项额外修复

MRV2 调度分类。切换到 MRV2 后,我们在特定基准测试模式下观察到过大的 TPOT 方差,并在 Issue #47239 中报告了该问题。社区很快将其定位到 uniform-decode 排序:投机解码步骤被归类为 prefill,因而走了较慢的执行路径。PR #47381 修复了该问题。

P/D 部署中异步 KV 加载的预读处理。PR #46694 改进了 GLM-5.2 + NIXL P/D + MTP 组合配置的槽位分配时机。Decoder 现在会等到远程 KV 传输完成后再分配投机 token 槽位,正确处理了最后一个不完整 KV 块的边界情况。这种不完整块交接是 P/D 分离与投机解码交互所特有的问题,修复它是让该路径走向生产可用的又一步。

这两项修复都包含在 v0.25.0 及后续版本中。

5.3 精度验证

我们使用最终配置运行了一组公开基准测试,以验证 NVFP4 量化、MTP 和 P/D 分离的组合没有降低输出质量:

测试项得分
AIME 202586.67
GPQA92.89
LongBench V264.01
MMLU-Pro86.3
SWE-bench Verified(Agentic)85.2

结果与社区报告的 GLM-5.2 数据一致。LongBench V2 对我们最重要,因为它直接检验了长上下文下的 DSA 稀疏注意力和 IndexerCache 索引共享。64.01 的得分表明索引共享在长上下文工作负载中依然有效,并且投机解码带来的加速没有以输出质量为代价。

6. 上游进展

本次部署中使用的各项能力,建立在 vLLM 社区在 GLM-5.2 优化跟踪 issue(Issue #46654)下协调的更广泛优化集合之上。除上文提到的 PR 外,还有两项进展尤为相关。

P/D 分离的二级缓存层。 PR #42285 引入了统一的 CPU KV-cache 布局作为中间层。TieringManager 协调主缓存层与 P/D 连接器,降低了传输后端与模型执行路径之间的耦合。已在 v0.25.0 中合并。

面向长上下文 Prefill 的 PCP 虚拟批处理。 PR #46570 将请求拆分为多个虚拟批次行,在上下文并行 rank 间并行处理,仅聚合 MLA latent cache 和 DSA indexer cache。在最初的 4-GPU GLM-5.2-NVFP4 / 32K Prefill 测试中,TP=2 配合 PCP=2 将 prompt 吞吐量从约 20.1K tok/s 提升至约 27.3K tok/s。该 PR 已于 2026 年 7 月 19 日合并入 main,并将在下个版本中发布。由于 KV-cache 容量正是我们在第 4 节中拒绝 TGS 最优配置的原因,PCP 可能会改变这一权衡,它是我们下一阶段验证的重点。

7. 可观测性:在生产上线后验证 SLA

通过基准测试并不意味着系统已具备生产就绪能力。对于分离式 P/D 部署,可观测性比单实例部署更难,因为请求延迟被拆分到两个资源池:TTFT 主要由 Prefill 池决定,TPOT 由 Decode 池决定,中间还隔着一次 KV 传输。当任一阶段出现退化时,用户只会看到一种症状——服务变慢了。

我们使用 Prometheus 和 Grafana 为 P/D 拓扑构建了监控。在生产环境中,我们关注以下几组指标:

  • 各池的 TTFT 和 TPOT 百分位数,而不仅仅是端到端聚合值。这是判断问题属于 Prefill 还是 Decode 时首先要看的地方。
  • MTP 接受率和平均接受长度。 容易被忽视,却是最早出现的预警信号之一。接受率下降不会产生任何错误;TPOT 只是逐渐退化。在实践中,这是我们对任何 Decode 侧异常首先打开的仪表盘,我们建议将 MTP 接受率视为一等告警指标。
  • KV-cache 利用率与 GPU 利用率,在 Prefill 和 Decode 两侧都要关注。P/D 分离的核心优势在于两类资源可以独立扩展;这些曲线的相对水平就是扩展信号。
  • KV 传输延迟和队列深度,用于判断节点间网络是否已成为瓶颈。

7.1 仅在长时间稳定性测试中才会显现的问题

短时基准测试验证的是性能,而非长期稳定性。我们的生产验收流程包含多天连续运行,正是这一步揭示了持续的主机内存增长:vLLM 进程 RSS 在数十小时内线性增长,始终未达到平台期。

Grafana Memory Usage (WSS) panel: vLLM container memory grew linearly from approximately 721 GiB to approximately 800 GiB over tens of hours.
Grafana 内存使用量(WSS)面板:vLLM 容器内存在数十小时内从约 721 GiB 线性增长至约 800 GiB。

以下几个特征解释了为什么这个问题需要生产监控而非基准测试才能发现:

  • 增长速度缓慢,只有经过数小时才能显现。一次持续数秒或数分钟的 vllm bench serve 运行无法揭示它。
  • 增长影响的是主机内存,而不是 GPU 内存。所有 GPU 侧的指标看起来都正常。
  • 常规的内存分析工具无法发现它。EngineCore 在启动期间调用 gc.freeze(),因此泄漏的对象从未出现在 gc.get_objects() 或 tracemalloc 中;症状看起来像是分配器碎片化。

我们在 PR #47723 中向社区报告了该行为及我们的初步诊断,随后一位维护者将其纳入了 PR #44490。

根本原因是生产者与消费者之间的门控不一致。PR #35219 引入了 SingleTypeKVCacheManager.new_block_ids 用于清除 Mamba SSM 缓存状态。条目根据 KV 缓存规范类型记录——FullAttentionSpec、MLAAttentionSpec 等——但仅在模型包含 Mamba 层时才清除。对于不含 Mamba 层的模型(包括大多数标准注意力模型以及使用 MLA 的 GLM-5.2),每次块分配都会被记录,而该列表从未被清空,因此随着请求量增加,它会无限制地增长。修复方法是在每个调度步骤无条件清空 take_new_block_ids(),并仅在确实需要清除时使用其结果。Mamba 的行为保持不变。

8. 完整部署方案

8.1 环境

部署配置
硬件3 × 8 B300(24 块 GPU)
模型GLM-5.2-NVFP4
拓扑4 个 Prefill(TP1 DP4 EP,每个 4 块 GPU = 16)+ 1 个 Decode(TP1 DP8 EP = 8)
KV 传输NIXL

8.2 Prefill 节点

export VLLM_USE_V2_MODEL_RUNNER=1
 
vllm serve /mnt/model/glm/GLM-5.2-NVFP4 \
    --trust-remote-code \
    --kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_producer"}' \
    --chat-template-content-format=string \
    -ep \
    -tp 1 \
    -dp 4 \
    --tool-call-parser glm47 \
    --enable-auto-tool-choice \
    --reasoning-parser glm45 \
    --gpu-memory-utilization 0.92 \
    --enable-prompt-tokens-details \
    --speculative-config='{"method":"mtp","num_speculative_tokens":1}' \
    --shutdown-timeout 300 \
    --fingerprint-mode=none

8.3 Decode 节点

export VLLM_USE_V2_MODEL_RUNNER=1
 
vllm serve /mnt/model/glm/GLM-5.2-NVFP4 \
    --trust-remote-code \
    --chat-template-content-format=string \
    --kv-transfer-config '{"kv_connector":"NixlConnector","kv_role":"kv_consumer"}' \
    --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \
    --max-num-batched-tokens 1024 \
    -ep \
    -tp 1 \
    -dp 8 \
    --tool-call-parser glm47 \
    --enable-auto-tool-choice \
    --reasoning-parser glm45 \
    --gpu-memory-utilization 0.90 \
    --enable-prompt-tokens-details \
    --all2all-backend=flashinfer_nvlink_two_sided \
    --speculative-config='{"method":"mtp","num_speculative_tokens":3}' \
    --shutdown-timeout 300 \
    --fingerprint-mode=none

8.4 基准测试方法

所有性能数据均使用 vllm bench serve 中的随机数据集收集。没有前缀缓存命中,因此 TTFT 数值代表最坏情况下的纯计算延迟。请求以固定的 --request-rate 注入,该值由目标 TPS 除以 (input_len + output_len) 再乘以一个调优系数计算得出。

vllm bench serve \
  --backend openai-chat \
  --model /mnt/model/glm/GLM-5.2-NVFP4 \
  --endpoint /v1/chat/completions \
  --dataset-name random \
  --random-input-len 16384 \
  --random-output-len 1000 \
  --request-rate <target TPS / (input + output) × tuning factor> \
  --percentile-metrics ttft,tpot,itl,e2el \
  --metric-percentiles 50,90 \
  --save-result

9. 下一步

对于 GLM-5.2 以及后续更大的 MoE 模型,我们看到了 Decode 优化的两个主要方向。以下是前瞻性评估,而非本文中实测得出的结论。

第一,降低每次目标前向传播的成本。除了 GEMM、Attention 和 Indexer 等核心内核之外,这还包括探索 PDL、持久内核、局部化巨型内核,以及跨 MoE Dispatch、Expert GEMM 和 Combine 的计算与通信协同。随着部署跨节点扩展,WideEP、分层 all2all 以及节点间通信与计算的重叠变得越来越重要。目标是缩短完整 Decode 步骤的端到端关键路径。

第二,推进推测解码的模型–运行时协同优化。在模型侧,可以在更广泛的数据集上训练更强的草稿模型(如 DSpark),以提高连续候选 token 的准确率。在运行时侧,动态推测解码、按请求的候选长度以及紧凑验证可以在不同工作负载下控制验证成本。草稿模型决定系统能预测多远;服务运行时决定预测在多大程度上真正经济。

此外,第 6 节中描述的 PCP 虚拟批处理工作已在 main 中落地。我们将评估它是否能消除第 4 节中讨论的 KV 缓存容量与每 GPU 效率之间的权衡。

关于我们

本工作由 DaoCloud 团队完成。我们提供面向企业 LLM 训练与推理的全栈平台,包括异构加速器调度、推理服务编排和可观测性。在整个调优过程中,我们与 vLLM 社区分享了观察结果和验证结果,相关改进已合并到上游。本文中的配置可直接用于 v0.26.0。

特别感谢来自 Mistral AI 的 Nicolò Lucchesi(@NickLucche),他最初建议撰写本文,详细审阅了内容,并持续推动它从一组粗略笔记变成您刚刚读到的文章。

我们还要感谢 vLLM 社区在 Issue #46654 下的高效协作。在大多数情况下,修复从最初报告到发布版本仅用了一周时间。

来源:vLLM Blog · vllm.ai