vLLM 官方博客 · 2026/10/14 08:00:00

vLLM 引入基于推送的 KV 传输机制:优化分离式推理首字延迟与生成速度

出处作者 / 发布主体:Sunita Nadampalli (AWS), Nicolò Lucchesi (Mistral)原标题:Push-Based KV Transfer to Improve TTFT and TPOT for Disaggregated Inference in vLLM
68AI 研判分
核心综述

vLLM 在分离式预填充-解码架构中引入了基于推送(Push-Based)的 KV Cache 传输策略,旨在解决跨节点数据搬运带来的高延迟瓶颈。该方案通过 NIXL 连接器利用 RDMA 网络高效移动 KV 块,显著改善了时间到首字(TTFT)和每输出令牌时间(TPOT)。这一更新为大规模 LLM 服务提供了更精细的底层网络调度能力,提升了资源利用率与响应速度。

报道全文原始报道全文

本文目录18 个章节

vLLM 是一款面向大语言模型(LLM)的开源推理与服务引擎。LLM 推理包含两个截然不同的阶段:预填充(prefill),用于处理输入提示词并构建键值(KV)缓存;以及解码(decode),利用该缓存逐 token 生成输出。这两个阶段在计算、内存和扩展特性上存在显著差异。预填充-解码分离(P-D disaggregation)将这两个阶段运行在独立的引擎实例上,从而允许每个阶段独立进行扩展和优化。这有助于提升资源利用率、吞吐量以及在负载下的延迟表现。然而,这也要求在开始 token 生成之前,将 KV 缓存从预填充实例传输到解码实例,因此高效的 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):当预填充实例完成提示词 KV 缓存的计算后,路由器会将源元数据(包括预填充块 ID)中继给解码实例。随后,解码实例分配目标块,准备 NIXL 传输,并发起 NIXL READ 操作以从预填充实例拉取 KV 块。由于这些协调和设置步骤是在预填充完成后、解码开始前串行执行的,它们延长了 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 运行分离式预填充/解码推理的客户而言,这可以通过降低 TTFT(首 Token 延迟)和 TPOT(每输出 Token 时间)来提升应用响应速度,尤其是在高请求速率场景下效果显著。

在基于 Amazon EC2 P5en 实例并通过 EFA 互联的 1P1D(1 个预填充节点 + 1 个解码节点)部署基准测试中,相较于拉取模式(pull mode),推送模式在 Qwen3-32B 模型上实现了高达 3 倍的 TTFT 提升以及最高 30% 的 TPOT(每输出 Token 时间)优化(详见性能数据章节);具体结果取决于工作负载和配置。

我们在通过 EFA 互联的 AWS Trainium 实例集群上构建并验证了该功能,随后将其作为加速器无关的特性贡献给了 vLLM。本文展示的结果来自配备 EFA 的 AWS p5en(GPU)集群,表明这些性能增益同样适用于 GPU 环境。本文将详细解释推送式架构、传输流程、线程模型、性能结果以及如何复现基准测试。

推送式 KV 传输

架构概览

推送式传输通过以下两点提升了 TTFT 和解码吞吐量:

  1. 消除拉取模型的串行化瓶颈,实现生产者与消费者工作的重叠。解码实例在生成器仍在计算 KV 缓存时,提前注册其目标块。一旦生产者的块准备就绪,传输即刻启动,而无需等待消费端的标识符发现往返过程,从而将该往返开销从“首个 Token”的路径中移除。
  2. 将传输初始化时间移出解码实例。这一初始化过程——包括准备 NIXL 描述符并提交传输任务——在预填充端及后台线程上执行,因此解码实例不会在其对延迟敏感的 Token 生成阶段消耗这些周期。

最终效果是降低了 TTFT,并提高了解码实例的输出 Token 吞吐量。

推送连接器协调三个角色:预先注册目标块的解码实例、匹配每个请求并发出传输指令的预填充实例,以及一个专用后台线程,该线程负责将 KV 写入操作从引擎的前向传播循环中剥离出来。下图展示了这些组件如何协同工作。

基于推送的 KV 传输架构概览:解码实例将其块注册到预填充实例,后者通过 NIXL WRITE 将 KV 推送到解码实例。

基于推送的 KV 传输架构概览:解码实例将其块注册到预填充实例,后者通过 NIXL WRITE 将 KV 推送到解码实例。

实现细节

该协议包含三个逻辑步骤。

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

2. 会合(预填充端)。 来自解码器的注册信息和生产者上预填充的完成可能以任意顺序到达,连接器能处理这两种情况。如果注册信息先到达,生产者会将其保留直到自身的预填充完成。如果预填充先完成,生产者会保留该请求的块直到注册信息到达。后发生的事件将触发传输。连接器通过标识符配对请求,匹配过程考虑了重新发出的请求,从而正确关联被抢占或重试的请求。

3. 传输(预填充端)。 一旦请求匹配成功,生产者会与解码实例建立连接(如果尚未存在),并发出一个 NIXL WRITE,将 KV 块直接复制到解码器注册的内存中,寻址每个张量并行 rank。生产者和消费者可能使用不同的内部块布局。每一方在传输时将块位置映射到其自身的物理布局,允许双方使用不同的块大小。每个 WRITE 都携带一个完成通知,NIXL 会在数据到达后将该通知传递给解码实例。解码器统计这些通知,每个向其写入的生产者 rank 对应一个通知,只有在所有预期通知都到达后,才视为 KV 接收完成,此时请求进入解码阶段。

线程模型

推送连接器(push connector)通过在每个 prefill worker 上运行一个专用的后台传输线程,将模型执行与 KV 传输准备分离开来。在请求开始时,decode 主循环分配其目标 KV 块并向 prefill 实例注册这些块,与此同时,prefill 主循环计算 prompt 的 KV cache。当目标注册和 prompt KV 均就绪后,prefill 主循环将 KV 块交给后台线程,并迅速返回以继续模型执行。

后台线程负责建立或复用 NIXL 连接,将请求的 KV 块 ID 映射为内存描述符,合并连续的块以形成更大的传输区域,并向 decode 实例提交 NIXL WRITE 操作。当 NIXL 完成传输时,它会向 decode 实例发送完成通知,随后 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 实例上主循环与后台线程交接过程的时序图。

时序对比:Pull vs Push

Timing comparison of pull and push modes on the path to first token.

Pull 和 Push 模式在首 token 生成路径上的时序对比。

这两种模式还涉及路由策略的权衡。Pull 模式允许路由器在 prefill 完成后选择 decode 实例,这为基于负载感知的路由和服务质量管理提供了更大的灵活性。Push 模式则更早地选定 decode 实例,以便目标设置能与 prefill 重叠进行。作为回报,在同等负载下,prefill 实例可以更早释放其 KV 块,因为一旦 prompt KV 和目标注册均就绪,传输即可立即启动。

性能数据

在评估中,我们使用了 Qwen3-32B 模型,部署架构为 1P1D(1 个 Prefill 节点 + 1 个 Decode 节点),运行于通过 EFA 连接的 Amazon EC2 P5en 实例上。为了隔离 P→D 传输性能,我们将基准测试配置为禁用前缀缓存(--no-enable-prefix-caching)。此配置使得 Decode 节点必须从 Prefill 节点检索每个请求的完整 KV Cache。否则,重叠的提示词可能会复用 Decode 节点的本地缓存并绕过传输,从而产生误导性的对比结果。

下图比较了 Push 和 Pull 两种 KV 传输模式的 TTFT(首 Token 延迟)。我们扫描了 1k–16k 的输入长度以及 1–8 QPS 的请求速率。在低负载和短提示词情况下,两种模式的表现几乎相同。随着 QPS 和输入长度的增加,Pull 模式性能急剧下降,在 8 QPS 和 16k 输入长度时达到约 1,744 ms。相比之下,Push 模式保持在较低水平,约为 540 ms,实现了约 3 倍的降低。差距产生的原因在于:Pull 模式在 Decode worker 上同步内联发布 NIXL descriptors,这会阻塞其引擎循环中的 token 生成工作。而 Push 模式将该操作移至后台线程,使其脱离关键路径。

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.

Qwen3-32B 在配备 EFA 的 Amazon EC2 P5en 实例上,Push 与 Pull KV 传输模式的四面板 TTFT 对比。测试配置:1P1D 拓扑结构,128 个输出 token,且在 Decode 节点上禁用前缀缓存。

该图展示了相同条件下的 TPOT(每输出 Token 延迟)。其趋势与 TTFT 类似。在轻负载下,Push 和 Pull 表现接近,约为 5.6–5.8 ms。随着负载和输入长度的增加,Pull 模式在 4–8 QPS 和 16k 输入长度时攀升至约 10.4 ms,而 Push 模式则稳定在 7 ms 左右。原因是 Push 模式将 descriptor 准备和传输提交任务从 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.

Qwen3-32B 在配备 EFA 的 Amazon EC2 P5en 实例上,采用 push 和 pull KV 传输方式的 TPOT 对比四联图。测试配置:1P1D 拓扑结构、128 个输出 token,并在 Decode 节点上禁用前缀缓存。

如何运行基准测试

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

结论

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

当可以接受提前选择 decode 节点且优先降低 TTFT 时,请选择基于 push 的 connector。对于需要双向 KV 传输或希望在 prefill 之后才根据负载感知路由选择 decode 实例的工作负载,基于 pull 的 connector 仍然是更合适的选择。vLLM 同时支持基于 push 和 pull 的 KV connector,允许用户选择最适合其部署的方案。

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

致谢

感谢 vLLM 社区在维护此功能方面提供的支持。

附录:复现我们的结果

拓扑结构包括两个 p5en GPU 节点(预填充运行在 :8100,解码运行在 :8200),并启用了 EFA,外加一个较小的第三个节点,用于运行代理和基准测试客户端。本文附带三个脚本:serve_pd.sh 可在任一模式下启动任一侧服务,run_sweep.sh 驱动参数扫描,cleanup.sh 负责清理所有资源。plot_push_vs_pull.py 可根据结果 JSON 文件重新绘制上述两张图表。

测试环境

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

实例类型p5en.48xlarge(8x NVIDIA H200),分别用于预填充和解码
区域 / 可用区ap-south-1(孟买),ap-south-1b(AZ 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(在 ap-south-1 中为 ami-0133f195033a4baae;AMI ID 因区域而异)。该镜像自带内核 6.14.0-1018-aws、NVIDIA 驱动 570 和 CUDA 12.8,因此需将驱动、CUDA 工具包和 EFA 安装程序升级到以下版本。
OS / 内核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 设备作为 rails(通道)打开。随后,它仅在每个 GPU 所连接的 PCIe switch 对应的 2 条 rails 上注册该 GPU 的 KV cache,从而确保所有传输均保持在 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            # 验证 EFA provider 是否存在

两个 GPU 节点之间必须能够在端口 8100/8200 以及 NIXL side channel(侧信道,端口 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      # 包含 NIXL push connector 的任何构建版本
pip install nixl==1.4.1       # 带有 libfabric/EFA 后端的 NIXL

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

文本
pip install vllm aiohttp fastapi uvicorn pandas datasets

3. 下载模型

文本
export HF_HOME=/shared/huggingface     # 一个较大的共享存储卷
hf download Qwen/Qwen3-32B

4. 启动 P/D 配对

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

文本
# 在 prefill 节点上
PREFILL_IP=<PREFILL_IP> DECODE_IP=<DECODE_IP> ROLE=prefill MODE=pull ./serve_pd.sh
# 在 decode 节点上
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 模式需要在 P 和 D 两端均使用 NixlPushConnector。
  • 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),确保 P 和 D 之间的侧信道端口(5600)已开放,并检查 PREFILL_TP_SIZE 是否与 prefill 服务器的张量并行大小匹配。

清理

文本
./cleanup.sh proxy      # 在代理 / 基准测试驱动节点上执行
./cleanup.sh server     # 在每个 GPU 节点上执行

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