跳到正文
vLLM Blog· Sunita Nadampalli (AWS), Nicolò Lucchesi (Mistral)·· 19 小时前精选AI 评分62

vLLM 推出 push 模式 NIXL KV 传输连接器,降低分离式推理的 TTFT 与 TPOT

Push-Based KV Transfer to Improve TTFT and TPOT for Disaggregated Inference in vLLM

AI 导读

vLLM 新增基于 push 的 NIXL KV 传输连接器 NixlPushConnector,把传输协调与准备工作移出首 token 关键路径。

推荐理由

vLLM 新增 push 模式 KV 传输连接器,给出了与 pull 模式的时序差异和可复现的基准配置,便于评估分离式推理的延迟取舍。

正文 · AI 翻译

vLLM 是一个面向大语言模型(LLM)的开源推理与服务引擎。LLM 推理包含两个截然不同的阶段:prefill,处理输入提示并构建键值(KV)缓存;以及 decode,利用该缓存一次生成一个 token 的输出。这两个阶段具有不同的计算、内存和扩展特性。Prefill-decode(P-D)分离 将它们运行在独立的引擎实例上,使每个阶段能够独立扩展和优化。这可以在负载下提高资源利用率、吞吐量和延迟。然而,这也要求在开始生成 token 之前,将 KV 缓存从 prefill 实例传输到 decode 实例,因此高效的 KV 传输对首 token 时间(TTFT)至关重要。

vLLM 执行此传输的方式之一是通过 NVIDIA Inference Xfer Library(NIXL)连接器,它通过支持 RDMA 的互连(如 AWS Elastic Fabric Adapter(AWS EFA,AWS 服务器间使用的低延迟 RDMA 网络))移动 KV 块。在支持的实例类型上,AWS EFA 支持 GPUDirect RDMA 和 Neuron Direct RDMA,使 NIXL 能够直接在 GPU 或 Trainium 设备内存之间传输 KV 块,而无需通过主机内存中转。NIXL 连接器的默认模式是基于拉取(pull-based):在 prefill 实例完成提示 KV 缓存的计算后,路由器将源元数据(包括 prefill 块 ID)转发给 decode 实例。然后 decode 实例分配目标块,准备 NIXL 传输,并启动 NIXL READ 从 prefill 实例拉取 KV 块。由于这些协调和设置步骤在 prefill 之后、解码开始之前按顺序发生,它们延长了 TTFT 关键路径,如下图所示。

Pull-mode timeline: router relay, decode-side allocation, and NIXL READ run serially on the path to first token.
拉取模式时间线:路由器转发、解码侧分配和 NIXL READ 在首 token 路径上串行运行。

本文介绍了一种基于推送(push-based)的 NIXL 连接器,它将 prefill 后的传输协调和设置移出关键路径。对于使用 vLLM 运行分离式 prefill/decode 推理的客户,这可以通过降低 TTFT 和 TPOT 来提高应用响应速度,尤其是在高请求速率下。

在我们使用通过 EFA 连接的 Amazon EC2 P5en 实例进行的 1P1D 部署基准测试中,对于 Qwen3-32B 模型,推送模式相比拉取模式实现了高达 3 倍的 TTFT 提升和高达 30% 的每输出 token 时间(TPOT)提升(参见性能数据部分);结果取决于工作负载和配置。

我们在通过 EFA 连接的 AWS Trainium 实例集群上构建并验证了该功能,然后将其作为与加速器无关的功能贡献给了 vLLM。此处的结果来自使用 EFA 的 AWS p5en(GPU)集群,表明这些收益同样适用于 GPU。本文解释了基于推送的架构、传输流程、线程模型、性能结果以及如何复现基准测试。

基于推送的 KV 传输

架构概述

基于推送的传输改善 TTFT 和解码吞吐量有两个原因:

  1. 它消除了拉取模型的串行化,通过重叠生产者和消费者的工作。解码实例在生产者仍在计算 KV 时提前注册其目标块。然后,一旦生产者的块准备就绪,传输就开始,而不是在消费者侧标识符发现往返之后才开始,从而从首 token 路径中移除了该往返。
  2. 它将传输设置时间从解码实例上移走。该设置——准备 NIXL 描述符并提交传输——在预填充侧和后台线程上运行,因此解码实例不会在其延迟关键的 token 生成阶段花费这些周期。

最终效果是解码实例上更低的 TTFT 和更高的输出 token 吞吐量。

推送连接器协调三个角色:解码实例,它预先注册其目标块;预填充实例,它匹配每个请求并发出传输;以及一个专用的后台线程,它将 KV 写入从引擎的前向传递循环中移出。下图展示了这些部分如何组合在一起。

Push-based KV transfer architecture overview: the decode instance registers its blocks with the prefill instance, which pushes KV to the decode instance with a NIXL WRITE.
基于推送的 KV 传输架构概览:解码实例向预填充实例注册其块,预填充实例通过 NIXL WRITE 将 KV 推送到解码实例。

实现细节

该协议有三个逻辑步骤。

1. 注册(解码侧)。当解码实例为需要远程预填充的请求分配本地块时,它准备一个注册,描述预填充实例应写入 KV 的位置:它自己的身份以及它刚刚保留的块位置。在同一个引擎步骤中,工作器将此注册作为 NIXL 通知发送到预填充实例。由于注册在分配时发生,连接器在预填充实例完成预填充之前就知道目标块。

2. 会合(预填充侧)。来自解码器的注册和生产者上预填充的完成可以以任意顺序到达,连接器处理这两种情况。如果注册先到达,生产者会持有它直到自己的预填充完成。如果预填充先完成,生产者会持有请求的块直到注册到达。无论哪个事件第二个发生,都会触发传输。连接器通过标识符配对请求,匹配会考虑重新发出的请求,正确关联被抢占或重试的请求。

3. 传输(预填充侧)。一旦请求匹配,生产者建立到解码实例的连接(如果尚不存在),并发出一个 NIXL WRITE,将 KV 块直接复制到解码器注册的内存中,寻址每个目标张量并行等级。生产者和消费者可能使用不同的内部块布局。每一侧在传输时将块位置映射到自己的物理布局,允许两侧使用不同的块大小。每个 WRITE 携带一个完成通知,NIXL 在数据到达后将其传递给解码实例。解码器对这些通知进行计数,每个写入它的生产者等级一个,并且只有在所有预期通知到达后才将 KV 视为已接收,此时请求继续解码。

线程模型

推送连接器通过在每个 prefill worker 上使用专用的后台传输线程,将模型执行与 KV 传输准备分离。在请求开始时,decode 主循环分配其目标 KV 块,并将它们注册到 prefill 实例,同时 prefill 主循环计算 prompt KV 缓存。在目标注册和 prompt KV 都就绪后,prefill 主循环将 KV 块交给后台线程,并迅速返回模型执行。

后台线程建立或复用 NIXL 连接,将请求的 KV 块 ID 映射到内存描述符,将连续块合并为更大的传输区域,并向 decode 实例提交 NIXL WRITE。当 NIXL 完成传输后,它会向 decode 实例发送完成通知,后者随即开始 token 生成。将描述符准备和传输提交移出对延迟至关重要的主循环,可防止这种每请求的工作延迟模型执行。下图展示了单个请求的这些主循环与后台线程的交接。

Sequence diagram of one request through the push path, showing the main-loop and background-thread handoffs on the prefill and decode instances.
单个请求通过推送路径的序列图,展示了 prefill 和 decode 实例上的主循环与后台线程交接。

时序对比:拉取 vs 推送

下图总结了前几节描述的拉取和推送传输流程,并突出了它们对 TTFT 的影响。尽管两种模式使用相同的 NIXL 和 EFA/RDMA 数据路径,但拉取在 prefill 之后执行元数据中继和 decode 侧设置,而推送将 decode 侧设置与 prefill 重叠。因此,推送将这些操作从 post-prefill 关键路径中移除,使解码能够更早开始。

Timing comparison of pull and push modes on the path to first token.
拉取和推送模式在首 token 路径上的时序对比。

这两种模式还涉及路由权衡。拉取模式允许路由器在 prefill 完成后选择 decode 实例,这为负载感知路由和服务质量管理提供了更大的灵活性。推送模式更早选择 decode 实例,以便目标设置可以与 prefill 重叠。作为回报,在可比负载下,prefill 实例可以更早释放其 KV 块,因为一旦 prompt KV 和目标注册都就绪,传输就会开始。

性能数据

在评估中,我们在使用通过 EFA 连接的 Amazon EC2 P5en 实例的 1P1D 部署上使用了 Qwen3-32B 模型。为了隔离 P→D 传输性能,我们将基准配置为使用 --no-enable-prefix-caching 禁用前缀缓存。此配置使 decode 节点为每个请求从 prefill 节点检索完整的 KV 缓存。否则,重叠的 prompt 可能会复用 decode 节点的本地缓存并绕过传输,从而产生误导性的比较结果。

下图比较了推送和拉取 KV 传输的 TTFT。我们扫描了输入长度 1k–16k 和请求速率 1–8 QPS。在低负载和短 prompt 下,两种模式的表现几乎相同。随着 QPS 和输入长度的增长,拉取模式急剧下降,在 8 QPS 和 16k 时达到约 1,744 ms。推送模式保持在约 540 ms 的低水平,减少约 3 倍。差距的出现是因为拉取模式在 decode worker 上内联提交 NIXL 描述符,在其引擎循环中同步执行,因此这项工作会停滞 token 生成。推送模式将其移至后台线程,脱离关键路径。

Four-panel TTFT comparison of push and pull KV transfer for Qwen3-32B on Amazon EC2 P5en instances with EFA. Test configuration: 1P1D topology, 128 output tokens, and prefix caching disabled on the Decode node.
四面板 TTFT 对比图,展示在配备 EFA 的 Amazon EC2 P5en 实例上,Qwen3-32B 的 push 与 pull KV 传输方式。测试配置:1P1D 拓扑,128 个输出 token,Decode 节点上禁用前缀缓存。

此图展示了相同条件下的 TPOT。其趋势与 TTFT 一致。在轻负载下,push 与 pull 接近,约为 5.6–5.8 ms。随着负载和输入长度增加,pull 在 4–8 QPS 和 16k 时攀升至约 10.4 ms,而 push 保持在 7 ms 附近。原因是 push 将描述符准备和传输提交从 decode worker 中卸载。Token 生成永远不会被饿死,因此即使在高并发和长上下文下,每 token 延迟也保持平稳。

Four-panel TPOT comparison of push and pull KV transfer for Qwen3-32B on Amazon EC2 P5en instances with EFA. Test configuration: 1P1D topology, 128 output tokens, and prefix caching disabled on the Decode node.
四面板 TPOT 对比图,展示在配备 EFA 的 Amazon EC2 P5en 实例上,Qwen3-32B 的 push 与 pull KV 传输方式。测试配置:1P1D 拓扑,128 个输出 token,Decode 节点上禁用前缀缓存。

如何运行基准测试

要复现我们的 1P1D 基准测试配置,即一个 prefill 节点和一个 decode 节点,使用通过 EFA 连接的 Amazon EC2 P5en 实例,请参阅 复现我们的结果 指南中的详细说明。该指南涵盖环境设置、启动 vLLM prefill 和 decode 服务器、运行基准测试以及收集结果。该功能可在其他受支持的配置上运行,但性能结果会因硬件、模型、工作负载和网络配置而异。

结论

在本文中,我们展示了基于 push 的 KV 连接器如何将 prefill 计算与 decode 侧的块分配和注册重叠,从而将 post-prefill 路由器往返和 decode 侧传输设置从关键路径中移除。这降低了 TTFT 和 TPOT,尤其是在高请求速率下。在我们使用通过 EFA 连接的 Amazon EC2 P5en 实例进行 1P1D 部署的 Qwen3-32B 基准测试中,与 pull 模式相比,push 模式将 TTFT 提升了最高 3 倍,TPOT 提升了 30%。结果因工作负载和配置而异。要使用 push 模式,请将 prefill 和 decode 服务器都配置为使用 NixlPushConnector 而不是 NixlConnector。

当可以接受提前选择 decode 节点且优先考虑降低 TTFT 时,请选择基于 push 的连接器。基于 pull 的连接器仍然更适合需要双向 KV 传输或推迟到 prefill 之后再进行 decode 实例选择以实现负载感知路由的工作负载。vLLM 同时支持基于 push 和 pull 的 KV 连接器,允许用户选择最适合其部署的方法。

作为未来的改进,我们正在探索将后台 NIXL 线程模型引入 pull 模式连接器,将 NIXL 设置移出关键路径,以便 pull 模式工作负载也能受益于相同的优化;我们已提交一个 pull request(#45211)来探索这一点。

致谢

我们感谢 vLLM 社区对维护此功能的支持。

附录:复现我们的结果

拓扑为两个 p5en GPU 节点(prefill 在 :8100,decode 在 :8200)并启用 EFA,外加一个运行代理和基准测试客户端的第三个较小节点。本文附带三个脚本:serve_pd.sh 以任一模式启动任一侧,run_sweep.sh 驱动扫描,cleanup.sh 拆除所有内容。plot_push_vs_pull.py 从结果 JSON 重绘上述两张图。

测试环境

两个 GPU 节点的配置完全相同:

实例类型p5en.48xlarge(8 块 NVIDIA H200),一个用于 prefill,一个用于 decode
区域 / 可用区ap-south-1(孟买),ap-south-1b(可用区 ID aps1-az3),两个节点位于同一可用区
放置无集群置放群组;两个实例均启动到定向的按需容量预留中,默认租期
AMI适用于 Ubuntu 24.04 x86_64 的公共 AWS ParallelCluster 3.14.2 镜像,aws-parallelcluster-3.14.2-ubuntu-2404-lts-hvm-x86_64-202602121713(ami-0133f195033a4baae 在 ap-south-1 中;AMI ID 因区域而异)。它自带内核 6.14.0-1018-aws、NVIDIA 驱动 570 和 CUDA 12.8,因此请将驱动、CUDA 工具包和 EFA 安装程序升级到以下版本。
操作系统 / 内核Ubuntu 24.04.4 LTS,6.14.0-1018-aws
NVIDIA 驱动 / CUDA驱动 580.126.16(CUDA 13.0 驱动 API);CUDA 工具包 13.0
EFA 接口每个实例 16 个,每张网卡一个(网卡 0-15,所有接口类型均为 efa),每个 200 Gb/s,总计 3.2 Tb/s。每个 NUMA 节点上有 8 个接口,每块 GPU 与其中 2 个共享一个 PCIe 交换机。
EFA 软件EFA 安装程序 1.50.0,EFA 内核模块 3.3.0g,libfabric 2.6.0amzn1.0,rdma-core 64.0amzn0

NIXL 的 EFA 设备选择。 LIBFABRIC 后端使用 efa provider 将所有 16 个 EFA 设备作为 rail 打开。然后它仅在该 GPU 共享 PCIe 交换机的 2 个 rail 上注册每块 GPU 的 KV 缓存,因此每次传输都保持在 PCIe 本地。未设置任何 NIXL 或 libfabric 设备覆盖;这是默认选择。要在您自己的节点上确认,请使用 NIXL_LOG_LEVEL=DEBUG 启动服务器并检查注册行:

grep -E "Created rail [0-9]+ \(device=|mapped to rail" server.log

1. 配置节点

在两个 GPU 节点上安装 EFA 用户态栈并确认 provider 存在。我们使用了安装程序 1.50.0:

curl -O https://efa-installer.amazonaws.com/aws-efa-installer-1.50.0.tar.gz
tar -xf aws-efa-installer-1.50.0.tar.gz && cd aws-efa-installer
sudo ./efa_installer.sh -y
fi_info --version         # libfabric 2.6.0amzn1.0
fi_info -p efa            # verify the EFA provider is present

两个 GPU 节点必须能在 8100/8200 以及 NIXL 侧通道(5600)上互相访问。

2. 安装 vLLM 和 NIXL

在两个 GPU 节点上:

python3.12 -m venv ~/vllm_env && source ~/vllm_env/bin/activate
pip install --upgrade pip
pip install vllm==0.29.0      # any build that includes the NIXL push connector
pip install nixl==1.4.1       # NIXL with the libfabric/EFA backend

在代理节点上(无需 GPU 运行时):

pip install vllm aiohttp fastapi uvicorn pandas datasets

3. 下载模型

export HF_HOME=/shared/huggingface     # a large shared volume
hf download Qwen/Qwen3-32B

4. 启动 P/D 对

在每个 GPU 节点上运行一次 serve_pd.sh。两种模式之间唯一的区别是 kv_connector:pull 为 NixlConnector,push 为 NixlPushConnector。端口、引擎 ID、角色和 rank 保持不变。

# on the prefill node
PREFILL_IP=<PREFILL_IP> DECODE_IP=<DECODE_IP> ROLE=prefill MODE=pull ./serve_pd.sh
# on the decode node
PREFILL_IP=<PREFILL_IP> DECODE_IP=<DECODE_IP> ROLE=decode  MODE=pull ./serve_pd.sh

在 prefill 侧展开为:

vllm serve Qwen/Qwen3-32B --served-model-name Qwen --dtype bfloat16 \
  --tensor-parallel-size 8 --max-model-len 32768 --max-num-seqs 8 --port 8100 \
  --kv-transfer-config '{"kv_connector":"NixlConnector","kv_buffer_device":"cuda",
    "kv_role":"kv_producer","engine_id":"prefill-engine-001","kv_rank":0,
    "kv_parallel_size":2,"kv_connector_extra_config":{"backends":["LIBFABRIC"]}}'

decode 服务器添加了 --no-enable-prefix-caching,这会强制每个请求从 prefill 获取完整 KV——这正是我们要测量的。开启缓存后,重叠的提示可能命中 decode 节点的本地缓存并跳过 P->D 传输,从而给出误导性的数字。在基准测试之前,等待两个 /health 端点都返回 200。

5. 运行扫描

基准测试脚本位于 #57567;它会选择与模式匹配的代理(pull 为 disagg_proxy_demo.py,push 为 disagg_proxy_pushconnector_demo.py),并用 vllm bench serve 驱动它。

git clone https://github.com/vllm-project/vllm.git ~/vllm && cd ~/vllm
git fetch origin pull/57567/head:pd-bench && git checkout pd-bench
export VLLM_SRC=~/vllm
 
VLLM_SRC=~/vllm PREFILL_IP=<PREFILL_IP> DECODE_IP=<DECODE_IP> \
  MODE=pull ./run_sweep.sh

然后停止两个服务器,在每个节点上使用 MODE=push 重新启动步骤 4,并使用 MODE=push 重新运行扫描。Push 模式还需要 prefill 实例的 NIXL 坐标(PREFILL_ENGINE_ID、PREFILL_KV_HOST、PREFILL_SIDE_CHANNEL_PORT、PREFILL_TP_SIZE、PREFILL_PP_SIZE),以便代理告诉 decode 节点在哪里注册;run_sweep.sh 会从上面使用的默认值中填入这些。保持两次运行中的 QPS_LIST、INPUT_LENS 和 OUTPUT_LENS 相同,并确保 PREFILL_TP_SIZE 与 prefill 服务器的实际张量并行大小匹配。

results/ 保存 <mode>_qps<q>_in<in>_out<out>_iter<i>.json(加上 .log),包含每次运行的 TTFT、ITL、吞吐量和端到端延迟。要重绘图表:

python plot_push_vs_pull.py --results-dir ./results --metric ttft
python plot_push_vs_pull.py --results-dir ./results --metric tpot

故障排除

  • 服务器在第一个请求后立即崩溃 —— 模式与连接器不匹配。Pull 需要 NixlConnector;push 需要 NixlPushConnector,在 P 和 D 上都是如此。
  • Backend 404: model ... does not exist —— SERVED_MODEL_NAME 与服务器的 --served-model-name(Qwen)不匹配。
  • 代理就绪超时 —— 确认所选模式的代理存在于 $VLLM_SRC/examples/disaggregated/disaggregated_serving/ 下,并且 P 和 D 的 /health 都返回 200。
  • NIXL 握手或传输错误 —— 验证 EFA(fi_info -p efa),确认侧通道端口(5600)在 P 和 D 之间开放,并且 PREFILL_TP_SIZE 与 prefill 服务器的张量并行大小匹配。

清理

./cleanup.sh proxy      # on the proxy / benchmark driver node
./cleanup.sh server     # on each GPU node

然后按照 AWS EC2 用户指南终止实例。

来源:vLLM Blog · vllm.ai