TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

亲自试用 NVIDIA Dynamo:KV感知路由几乎全对,而分离式推理在单张显卡上慢了5倍,在两台服务器之间慢了20多倍

状态:草稿 (2026-10-07)。 本文的所有数值均由作者亲自实测。环境为:1张8GB级GPU、0.6B模型、Dynamo 1.6.0.dev20261006(开发版)。第6篇阅读的文档是v1.5.0,因此标志和默认值可能会有所不同。两台不同GPU服务器之间(1GbE级网络)的测试在4.5节进行,而NVLink和RDMA连接是 未实测 。 编写规则:以实测数据为基础,未能测出的标记为“未实测”。

目录

第6篇是通过阅读文档整理Dynamo的文章。为了验证文档所说的是否属实,我将其直接安装在了单GPU环境中。本文将回答以下四个问题。

  1. 在前面放置Dynamo前端后,每个请求会增加多少开销。
  2. KV感知路由将重复发送相同前缀的请求发送到同一工作进程(worker)的概率有多大,与轮询(Round-Robin)相比有何区别。
  3. 如果路由器收不到KV事件会发生什么。
  4. 如果将prefill和decode分为两个进程,单GPU上的首字延迟(TTFT)会有什么变化。
  5. 如果将其拆分到配备不同GPU的两台服务器上(1GbE级网络),会有什么变化。

简短的回答。 在服务器空闲时,前端的开销约为13 ms,而在繁忙时则不明显。在重复发送相同前缀的60次请求中,KV感知路由有53到60次被发送到同一工作进程,而轮询为35到43次。当开启路由器的事件模式但工作进程未发出事件时,即使命中率降至与随机选择相同的水平,也不会报错,日志中仅留下一条警告。在单GPU上进行分离推理时,在所有条件下首字延迟都慢了4.7到7.2倍。这是因为两个工作进程共享同一GPU,并且叠加了传输KV的开销,这并不代表分离推理带来的收益。如果将其拆分到不同GPU的两台服务器上,1,500个单词输入的首字延迟为1.7秒,3,000个单词为3.4到3.5秒,比独立推理(44到172 ms)增加了20多倍,增加的时间与KV字节数除以链路吞吐量(104 MB/s)的结果相符。

1. 环境与方法

2. 穿过前端的开销

对比了仅运行vLLM的模型(solo)和放在Dynamo前端背后的模型(dyn),输入512个Token,输出128个Token,各进行三次测试。数值为首字延迟(TTFT)的中位数。

并发请求 vLLM独立运行 Dynamo背后
1 29~39 ms 42~43 ms
8 129~130 ms 129~141 ms
32 272~274 ms 272~274 ms

在并发度为1时,中位数从30 ms增加到43 ms,增加了约13 ms。在并发度为8和32时,没有明显区别,吞吐量也相同(并发度32时分别为每秒1,507和1,509个Token)。前端(HTTP、Token化、路由决策)的开销仅在服务器空闲时是可见的固定开销,繁忙时会被掩盖。由于该模型生成Token的速度非常快,因此13 ms是可见的,预计对于更大的模型,其相对占比会更小,但此项属于 未实测 。

3. KV感知路由

3.1 配置

将两个工作进程以相同模型启动,仅更改了前端的 --router-mode 。

名称 前端选项 工作进程端
轮询 --router-mode round-robin 关闭KV事件
随机 --router-mode random 关闭KV事件
KV感知,近似 --router-mode kv --no-router-kv-events 关闭KV事件
KV感知,事件模式但无事件 --router-mode kv 关闭KV事件
KV感知,事件模式 --router-mode kv 开启KV事件(通过ZMQ发布)

负载制作了12种前缀(约1,500个单词,合成数据),每种混合发送6次,每次更改尾部的24个单词。请求总数为72个,作为第二次及以后发送的重复请求有60个。如果重复请求的首字延迟小于首次发送请求(约80 ms)的60%,则计为缓存命中。命中时约为15 ms,未命中时约为80 ms。

3.2 结果

路由 60次重复请求中的命中次数(三次) 重复请求首字延迟平均值
轮询 35, 40, 43 34~42 ms
随机 44, 31, 45 32~46 ms
KV感知,近似 60, 53, 60 15~26 ms
KV感知,事件模式但无事件 39, 44, 41 32~38 ms
KV感知,事件模式 60, 60, 60 15 ms

先前进行过一次相同配置的测试结果(轮询35、40、43,随机37、37、41,近似60、60、60,无事件41、44、37,事件模式60、60、60)趋势也是一致的。近似方法在第一次运行中三次都是60次,但在重新运行中出现了一次53次,因此我们认为自我记忆方式可能比事件方式略微不稳定,但无法下定论。事件方式的六次测试全部都是60次。

3.3 静默失败

表中第四行是本次实验中最重要的结果。如果使用 --router-mode kv 开启了事件模式,但工作进程没有发出KV事件,路由器的索引将永远为空,所有请求的重叠度(overlap)变为0,系统仅通过负载均衡来选择工作进程。命中率降至随机水平(39、44、41),但服务器响应正常且没有报错。前端日志中仅有一行下方的警告,而在切换为正常发出事件的工作进程运行中则没有该警告。

WARN ... KV-event publishing is disabled for a role that requires KV events
diagnostic_code="kv_event_publisher_disabled" ... requirement="cache_aware_routing"

要想察觉命中率与随机无异,要么查看日志中的警告,要么在重新发送相同前缀时单独测量首字延迟是否减少。如果只开启路由器而不测量命中率,系统可能会在不知情的情况下以这种状态运行。

3.4 模拟器与实测的差异

如果用纯Python编写该路由器的规则并运行相同的负载(选择从头开始连续重叠块最多的工作进程,若为空则选择工作量较少的工作进程),KV感知可以达到60次全中,而在不通知事件的情况下则会得出与轮询相同的值。趋势与实测结果一致。不过,模拟器中的轮询命中率为60分之49,高于实测值(35~43)。模拟器中的工作进程会永远记住处理过的前缀,但实际工作进程的KV槽位只有14,816个Token,无法容纳全部12种前缀(约18,000个Token),较早的会被挤出。KV感知路由将前缀分发给各个工作进程,使得每个工作进程只需容纳6种(约9,000个Token),因此不会被挤出。这意味着前缀路由的收益除了“发送到相同位置”之外,还叠加了“每个工作进程的工作集变小了”。

3.5 两台服务器前的路由器

将工作进程分别放在拥有不同GPU的两台服务器上(KV槽位分别为14,816个Token和27,088个Token),并通过服务器之间的网络发送相同的负载。60次重复请求中的命中次数分别为:轮询40、47、50,随机47、40、52,KV感知近似60、60、60,KV感知事件模式43、60、60。即使在两个GPU环境不同的情况下,KV感知路由也朝着相同的方向命中了。事件模式第一次迭代只有43次的原因并未深入解析(可能是因为在事件订阅建立之前的请求,但未能证实)。由于每台服务器的首次请求速度不同(44ms和80ms),这也带来了一定的局限性,即“首次请求的60%以下”这一命中判定标准在该测试中显得不够干净利落。

4. 单GPU上的prefill/decode分离

4.1 配置

将Prefill专用工作进程和Decode专用工作进程作为两个进程部署在同一个GPU上(分别占用38%的GPU内存,KV传输使用NIXL),并与单个进程完成两项任务的配置在相同负载下进行对比。输出为64个Token,数值为三次重复测试中TTFT的中位数。

4.2 结果

输入(单词) 并发请求 单进程 (ms) 分离 (ms) 倍率
1,500 1 78 406 5.2
1,500 4 190 1,359 7.2
3,000 1 171 812 4.7
3,000 4 398 2,442 6.1

在所有格子中,分离后的速度都更慢。从工作进程的KV传输指标来看,在并发度为1、输入1,500个单词的情况下,每个请求平均耗时196 ms(每次传输119.3 MB,吞吐量608 MB/s);在并发度为4、输入3,000个单词的情况下,平均耗时为711~1,723 ms。在首字延迟比单进程慢约330 ms的并发度为1的情况下,196 ms的传输时间占了很大一部分。其余约130 ms是什么并未进行分解测量(未实测).

)。4.3 如何解读这些结果

这并非“分离服务很慢”。由于两个 Worker 共用同一张 GPU,计算资源并未增加,且该配置中只有一个装置用来解决分离旨在解决的问题(长 prefill 干扰其他请求的 decode)。其中只凭空增加了迁移 KV 的成本。分为不同 GPU 的两台服务器的情况已在 4.5 节中进行过测量。本文未能测出在 NVLink 或 RDMA 连接的环境中是否存在收益(未实测).

由于每个 Token 的 KV 为 114,688 字节,因此 1,500 个 Token 约为 164 MiB。这与第 6 篇引用的文档中的警告(通过慢速路径迁移 KV 会将 TTFT 延长至约 98 秒)方向一致。不过,由于那 98 秒对应的是其他模型、其他输入长度以及其他连接的数值,因此无法与本实验的规模进行直接对比。以 608 MB/s 的速度迁移 1,500 个 Token 大约需要 280 ms,而如果是类似服务器间 TCP 的 0.1 GB/s 级别,则需要超过 1.5 秒。

4.4 为什么 KV 传输会很慢

未能确定原因。我们尝试更改了传输方式,结果如下。

UCX 配置 结果
默认(无限制) 传输吞吐量 608 MB/s,首个 Token 379~384 ms
开启调试日志的默认配置 615 MB/s,首个 Token 387~389 ms
限制为 TCP 系列(UCX_TLS=tcp,cuda_copy,sm,self) 621 MB/s,首个 Token 381~384 ms
同时允许 TCP 与设备内 IPC 首个 Token 383~384 ms(吞吐量下降未记录在日志中)
仅允许设备内 IPC(UCX_TLS=cuda_ipc,cuda_copy,sm,self) 引擎未能启动

仅允许 IPC 时的错误为 no active messages transport ... cuda_ipc/cuda - no am bcopy 和 Destination is unreachable 。据推测,NIXL 的 UCX 后端在初始化时要求能够发送主动消息(active message)的传输方式,而 IPC 传输无法提供该功能。因此,如果希望使用设备内传输,则将 UCX_TLS 仅限缩小为 IPC 的配置在此组合中无法使用。默认配置下由 UCX 创建的接口是 tcp/eth0, tcp/lo, cuda_ipc/cuda 。

即使更改传输方式,速度也没有发生变化,因此瓶颈可能并非 UCX 的传输选择。我们并未拆解测量具体瓶颈所在(请求级串行化、28 个块级传输描述符、复制路径等)(未实测).

4.5 拆分为两台服务器的分离服务(不同 GPU,1GbE 级别网络)

其中一台服务器是上述 8GB 级 GPU,另一台是 24GB 级消费级 GPU(与其它服务分时复用),Pod 间的 TCP 吞吐量为 113 MB/s。Worker 发现通过同一个 Pod 内的 etcd 进行,KV 传输使用 NIXL 默认(UCX)。采用相同的负载(输出 64,每个栏重复 3 次的首个 Token 中位数)。

配置 1,500 词并发 1 1,500 并发 4 3,000 并发 1 3,000 并发 4
8GB 级服务器单独运行 78ms 190ms 172ms 421ms
24GB 级服务器单独运行 44ms 92ms 83ms 181ms
8GB 级 prefill,24GB 级 decode 1,752ms 5,818ms 3,533ms 12,476ms
24GB 级 prefill,8GB 级 decode 1,721ms 5,637ms 3,407ms 12,222ms

5. 遇到的问题

6. 局限性与未实测项

7. 运维检查清单

  1. 若开启了感知 KV 的路由,请首先确认 Worker 是否在发出 KV 事件 。检查 Frontend 日志中是否存在 kv_event_publisher_disabled 警告。
  2. 更改路由后,直接测量“再次发送相同前缀时首个 Token 是否减少”。即使没有错误,它也可能会降至随机水平。
  3. 如果前缀的变体多于单个 Worker 的 KV 容量,则会产生路由在 Worker 之间分散前缀的效果。请对比 Worker 的 KV 容量(服务器日志中的 GPU KV cache size)与前缀总和。
  4. 分离服务应在单独测量 KV 传输路径(传输吞吐量)后进行评估。在本实验中,如果链路为 1GbE 级别,则迁移时间与计算结果几乎完全一致(4.5 节)。将每个 Token 的 KV 大小(层数 × KV 头数 × 头大小 × 2 × 字节数)乘以输入长度即可得出单个请求要迁移的字节数,将其除以实际传输吞吐量即为需要叠加到首个 Token 上的时间。
  5. 每次运行前,请确认没有残留的旧进程(引擎核心)以及未清除的模型下载锁。
  6. 如果各服务器的 NVIDIA 驱动版本不同,请先确认该驱动程序是否支持所安装的 vLLM 和 torch 的 CUDA 构建版。如果不支持,则不会在服务器日志中报错,而是在引擎启动阶段因 CUDA 错误直接崩溃。

参考资料

登录后即可点赞

评论

还没有评论。

登录后即可发表评论