状态:草稿(2026-11-14),实测。 本文中的数据由作者于 2026-11-13 直接实测。环境与请求同 第 1 篇一致。为 Kubernetes v1.34,单张 24GB 级 GPU 服务器与 8GB 级笔记本 GPU 服务器,节点间 1GbE,无 RDMA,Dynamo v1.6.2 的
vllm-runtime:1.6.2镜像(vLLM 0.31.0),模型为 Qwen3-0.6B。在此先说明,这是对分离式服务不利的条件。 编写规则:实测数据为基本,未测出的标为“未实测”,未确认的标为“未确认”。
在小型集群中重新测量分离式服务
目录
1. 分离式服务旨在做的事
处理单个请求的工作分为两部分:读取整个提示词并生成 KV 的工作(prefill),以及看着该 KV 逐个输出 Token 的工作(decode)。prefill 会一次性进行大型计算,而 decode 则会长时间重复小型计算。当两者在同一 GPU 上运行时,一个较长的 prefill 会暂停其他请求的 Token 生成。
分离式服务将这两项工作交由不同的 Worker 负责。prefill Worker 生成 KV,将其转移到 decode Worker 后,由 decode Worker 输出 Token。代价是 传输 KV 的时间。Dynamo 在这种传输中使用了名为 NIXL 的库,文档中是以 RDMA 为前提进行说明的。虽然也可以通过 TCP 运行,但据记载会慢得多。
本篇试图回答三个问题:
- 在 Kubernetes 中不使用 Grove 是否能启动分离式服务图。
- 在没有 RDMA 的环境中,同一服务器内进行拆分需要付出多少代价。
- 跨越 1GbE 进行拆分需要付出多少代价。
2. 架构
在 DGD 的组件中放置 type: prefill 和 type: decode ,并在 Worker 参数中写入角色。
# prefill 워커
- --disaggregation-mode
- prefill
- --kv-transfer-config
- '{"kv_connector":"NixlConnector","kv_role":"kv_producer","kv_buffer_device":"cpu"}'
# decode 워커
- --disaggregation-mode
- decode
- --kv-transfer-config
- '{"kv_connector":"NixlConnector","kv_role":"kv_consumer","kv_buffer_device":"cpu"}'
# 둘 다
env:
- {name: UCX_TLS, value: "tcp,self"}
- {name: UCX_RNDV_THRESH, value: "inf"}
由于没有 RDMA,传输被固定为 TCP。 kv_buffer_device 将 cpu 设为 主机内存是遵循了 Dynamo 示例中的注释。据记载,如果通过 GPU 缓冲区走 TCP,会在 UCX 内部崩溃。在此配置下,KV 经由主机内存传输。Worker 日志中也留下了“UCX CUDA support was not found”的警告。
部署有两种:
| 名称 | prefill Worker | decode Worker | KV 传输路径 |
|---|---|---|---|
| 同一服务器 | 24GB 级 GPU 服务器上的 2 台 | 同一服务器上的 2 台 | 单服务器内的 TCP |
| 服务器之间 | 8GB 级 GPU 服务器上的 1 台 | 24GB 级 GPU 服务器上的 2 台 | 1GbE |
前端采用与第 3 篇相同的 KV 路由模式。引擎参数和请求也相同。单个请求平均有 2,371 个 Token,单个 Token 的 KV 为 114,688 字节,因此 单个请求的 KV 约为 272MB 。
没有使用 Grove 就启动成功了。 应用 DGD 后,Operator 将前端、prefill 和 decode 分别创建为 Deployment,2 分钟内所有 5 个 Pod 全部变为 Ready。前端首先将请求发送到 prefill Worker,然后传递给 decode Worker。在图表默认设置中,Grove 是关闭的,对于这种规模的图表来说它并不是必需的。
3. 同一服务器内的拆分
包含前缀 2、7、12、24 种,并发 4、8,每个点测量 3 次的中位数。24 次测量中错误数全部为 0。对比值为第 3 篇的 KV 路由(6 个 Worker)。
| 前缀种类 | 并发 | TTFT p50:KV 路由 / 拆分 | TTFT p99:KV 路由 / 拆分 | 每秒请求数:KV 路由 / 拆分 |
|---|---|---|---|---|
| 2 | 4 | 34 / 286ms | 73 / 592ms | 10.9 / 6.5 |
| 7 | 4 | 40 / 375ms | 129 / 1,007ms | 8.9 / 5.6 |
| 12 | 4 | 43 / 465ms | 138 / 887ms | 8.7 / 5.0 |
| 24 | 4 | 64 / 518ms | 149 / 958ms | 8.1 / 4.8 |
| 2 | 8 | 49 / 462ms | 145 / 981ms | 14.7 / 9.0 |
| 7 | 8 | 60 / 650ms | 163 / 1,477ms | 12.2 / 7.2 |
| 12 | 8 | 69 / 794ms | 172 / 1,744ms | 12.1 / 6.4 |
| 24 | 8 | 106 / 896ms | 184 / 1,776ms | 11.5 / 6.0 |
这两种配置的资源并不完全相同。KV 路由方在相同的 24GB 级 GPU 上部署了 4 个 Worker,并额外使用了其他服务器上的 2 个 Worker。而拆分方仅在该张 GPU 上使用了 4 个 Worker(2 个 prefill,2 个 decode)。因此,吞吐量的差异中夹杂了这 2 个 Worker 的份额。另一方面, 首个 Token 时间的差异无法仅用这一点来解释。
从最有利的格子可以看出这一点。在前缀 2 种、并发 4 的情况下,缓存 Token 比例为 0.857,prefill Worker 几乎没有重新计算前缀。即便如此,到达首个 Token 仍花费了 286ms。而在 KV 路由中,相同的格子为 34ms。 即使命中 prefill Worker 的缓存,由于 decode Worker 没有该 KV,也必须完整接收 272MB。 增加的大约 250ms 就是这个代价。如果在 0.25 秒内传输了 272MB,则速率约为每秒 1GB,对于在同一服务器内经由主机内存进行 TCP 复制的速度而言,这是一个合理的数值。由于并未单独测量传输过程,因此这种分解属于推测。
随着并发度上升,传输重叠,耗时会进一步增加。在并发 8 时,中位数为 0.46~0.9 秒,p99 为 1~1.8 秒。
命中率也下降了。在前缀 12 种时缓存 Token 比例为 0.40~0.42,24 种时在 0.20 左右,不及 KV 路由(0.57~0.66,0.36~0.43)。这是因为掌握前缀缓存的只有两个 prefill Worker,其能够容纳的前缀总量仅为 6 个 Worker 时的三分之一。
综上所述,在此条件下,同一服务器内的拆分 首个 Token 延迟慢了 8~12 倍,吞吐量低了 37~48%。 由于只有一张 GPU,计算资源并未增加,且 prefill 在 100ms 以内即可完成的小模型本来就不存在“长 prefill 阻塞 decode”的问题。这是一个没有收获却徒增传输成本的架构。
4. 服务器之间的拆分:很慢,而且内存先满了
1GbE 实际传输速度为每秒 110~117MB。由此计算得出,传输 272MB 需要 2.3~2.5 秒。
这个部署方案没能完成全部测量, 因为 prefill Worker 在仅处理几个请求后就被 OOMKilled 了。
| 尝试 | 成功请求数 | 这些请求的 TTFT | 后续 |
|---|---|---|---|
| 并发 4 | 2 个 | 2,039ms, 2,040ms | 施加负载 9 秒后 prefill Worker 被 OOMKilled(退出码 137),剩余请求全部失败 |
| 并发 1 | 4 个 | 中位数 1,637ms,最大 2,653ms | 相同的 Worker 再次被 OOMKilled |
成功的 6 个请求的首个 Token 时间为 1.6~2.7 秒,与根据链路速度计算出的数值在同一量级。由于样本仅有 6 个,无法说明其分布情况。与 KV 路由的 34ms 相比,大约慢了 50 倍。
内存方面则是一个更有价值的发现。prefill Worker 的内存限制为 3,584Mi。这是使用相同镜像和相同引擎参数成功运行第 3 篇所有测量的限制值。 kv_buffer_device: cpu 由于需要传输的 KV 会加载到主机内存中,因此单个请求会额外消耗 272MB。在并发 4 的情况下,总量超过了 1GB。甚至在并发 1 时,仅凭 4 个请求就超出了限制,这表明传输完成的缓冲区并未立即释放。由于并未按时间测量 Worker 的内存使用量,因此原因未确认。
在同一服务器的部署中并未出现此问题。该部署中的 Worker 限制为 10Gi。我们并未调高服务器之间部署的限制重新进行测量,因为该服务器上还运行着其他工作负载,无法分配充足的限制。
这里值得记取的一点是: 如果迁移到分离式服务,必须重新计算 Worker 的内存限制。 在通过 GPU 缓冲区走 RDMA 的配置中情况可能有所不同(未实测),但在使用 TCP 和主机缓冲区的配置中,除了原有的限制外,还需要加上“并发请求数 × 单个请求的 KV 大小”以及一定的余量。
5. 那么什么时候划算
本文未能测出分离式服务占优的条件,测出的反而是处于劣势的两种条件。不过,测量结果揭示了决定盈亏的关键数量。
분리 서빙이 치르는 값 ≈ 요청 하나의 KV 바이트 ÷ 전송 처리량
분리 서빙이 얻는 값 ≈ 긴 prefill 이 같은 GPU 의 decode 를 막던 시간
- 付出的代价可以预先计算:用每个 Token 的 KV 字节数乘以输入长度,再除以链路吞吐量。根据本文的模型和请求,在同一服务器内约为 0.25 秒,在 1GbE 下约为 2 秒。模型的 KV 越大、输入越长,代价就越高。
- 获得的收益则在模型很大且输入很长、prefill 耗时达到秒级,并且能够将 prefill 和 decode 放在不同 GPU 上时产生。由于 0.6B 模型的 prefill 在 100ms 以内,因此毫无收益可言。
文档以 RDMA 为前提的原因也正在于此公式。只有当传输吞吐量达到数十 GB/s 时,付出的代价才会小于 prefill 时间。在存在 RDMA 或 NVLink 的环境中的盈亏目前未实测。
6. 总结
- 分离式服务图在没有 Grove 的情况下,仅凭单个 DGD 即可启动。只需配置组件的
type以及两个 Worker 参数即可。 - 如果在同一服务器内通过 TCP 进行拆分,首个 Token 时间从 34~106ms 增加到 286~896ms,吞吐量减少了 37~48%。这是因为即使是命中的请求也需要传输整个 KV。
- 跨越 1GbE 时,首个 Token 时间为 1.6~2.7 秒(成功的 6 个请求)。内存限制为 3,584Mi 的 prefill Worker 在并发 4 时 9 秒内、并发 1 时 4 个请求后即被 OOMKilled。
- 在使用 TCP 和主机缓冲区的分离式服务中,Worker 的内存限制必须加上“并发请求数 × 单个请求的 KV 大小”。
- 对于小模型、单张 GPU、没有 RDMA 的网络,完全没有理由开启分离式部署。如果是同一台设备, 第 3 篇中的 KV 路由方案在各个指标上都表现得更好。
如果将整个系列总结为一句话,那就是:Service 后面的 vLLM 如果前缀超过了一台机器的容量就会丢弃缓存(第 1 篇)。要想引入 Dynamo,首先得看 operator 的 webhook 范围和权限(第 2 篇)。KV 路由能让该区间的命中率提升两到三倍(第 3 篇)。分离式部署则是拥有高速网络和大模型时的方案(本篇)。
参考
- NVIDIA Dynamo v1.6.2 文档及示例中的分离式部署(disaggregated serving)相关内容。
- vLLM 文档,关于 Disaggregated Prefilling 与 NixlConnector。
- 清单(
d3-disagg.yaml)、网格记录以及 Worker 日志存放在仓库的docs/experiments/2026-11-13-dynamo-adoption/中。 - 在没有 Kubernetes 的情况下,单独测量两台服务器之间 KV 传输量与吞吐量的记录在 亲身体验 NVIDIA Dynamo一文中。