跳到正文
vLLM Blog· Sunita Nadampalli (AWS)·· 21 小时前精选AI 评分72

vLLM 推出双向 KV 传输,多轮智能体负载 TTFT 最高降 3.3 倍

Improve TTFT for Multi-Turn Agentic Workloads with Bi-Directional KV Transfer in vLLM

AI 导读

vLLM 博客介绍双向 KV 传输机制,让 decode 节点在后续轮次把已计算的 KV 块回传给 prefill 节点,避免重复计算对话历史。在 Amazon EC2 P5en 加 AWS EFA 的 1P1D 部署上,最长提示下 Qwen3-32B 的 TTFT 降低约 1.6 倍,zai-org/GLM-5-FP8 降低约 3.3 倍。

推荐理由

vLLM 官方给出双向 KV 传输的架构、阈值参数与复现脚本,可据此评估多轮智能体场景下的部署改造。

正文 · AI 翻译

多轮智能体工作负载建立在智能体推理、调用工具并整合先前结果时不断累积的上下文之上。每新增一轮,客户端都会重新发送对话历史,包括系统指令、先前的提示、工具结果和模型响应,导致不断增长的上下文被反复处理。在 Prefill-decode(P-D)分离中,每个请求被拆分到专用节点上:prefill 节点处理提示并构建其 KV 缓存,然后 decode 节点获取这些 KV 块并生成响应。

然而,传统的分离式服务只在一个方向上传输 KV 缓存:从 prefill 节点到 decode 节点。这种单向流动给多轮工作负载带来了两个低效之处。首先,生成响应后,decode 节点同时持有提示和新生成 token 的 KV 缓存条目。在处理下一轮时,prefill 节点无法获取这些条目,因此必须重新计算先前响应 token 的 KV 投影。其次,decode 节点维护会话状态,而 prefill 节点不维护,这增加了 prefill 节点上活跃会话的缓存被逐出的概率。在完全缓存未命中的情况下,prefill 节点必须重新计算整个对话历史的 KV 投影,包括系统指令、先前的用户请求、工具结果和模型生成的响应。随着对话增长,这种冗余工作会消耗更多加速器容量并增加首 token 时间(TTFT)。

对于构建对话助手和智能体应用的客户而言,更长的交互历史会因重复的 prefill 计算而增加延迟并消耗加速器容量。在本文中,我们展示双向 KV 缓存传输如何使 prefill 和 decode 节点能够跨轮次复用先前计算出的上下文,从而提高加速器利用率,并帮助应用在对话增长时保持响应迅速的 TTFT。

在我们使用通过 AWS Elastic Fabric Adapter(AWS EFA,AWS 中跨服务器使用的低延迟 RDMA 网络)连接的 Amazon EC2 P5en 实例、采用 1P1D 部署的多轮基准测试中,双向 KV 传输将 Qwen3-32B 的 TTFT 最多降低 1.6 倍,将 zai-org/GLM-5-FP8 的 TTFT 最多降低 3.3 倍(参见性能数据部分);结果取决于工作负载和配置。

我们最初在通过 EFA 连接的 AWS Trainium 实例集群上构建并验证了该功能,并将其作为与加速器无关的能力贡献给了 vLLM。此处展示的结果来自带有 EFA 的 Amazon EC2 P5en GPU 集群,表明这些优势同样适用于 GPU。本文介绍了双向传输架构、路由器元数据流、KV 块保留与重算控制、性能结果,以及如何复现该基准测试。

双向 KV 传输

双向 KV 传输是一种机制,允许 decode 节点在对话的后续轮次中将先前计算出的 KV 返回给 prefill 节点,从而避免在 prefill 节点上重新计算共享上下文,并由可配置的 KV 重算阈值控制。这缓解了前文描述的两个计算冗余场景,而实现这一点需要 KV 在其产生的轮次之后仍然存活,并以反方向传输,由具备 KV 缓存感知能力的路由器进行协调。

面向双向传输的 NIXL KV 连接器增强

我们扩展了 NVIDIA Inference Xfer Library (NIXL) KV connector,使 prefill 节点能够消费远程 KV 块,类似于 decode 节点已经消费 prefill 节点提供的 KV 块的方式。这不是一个简单的改动,因为这两种节点类型在架构上不同:prefill 节点为新提示 token 计算 KV 表示,并能提供完整的提示 KV 块。decode 节点缺乏生成完整 KV 表示的能力(或效率),只能提供先前缓存的 KV 块。因此,prefill 和 decode 节点仍然各自承担自己的角色和逻辑,用于计算远程和本地 KV 块的元数据。然而,实际的 KV 传输机制(NIXL READ)对于 P->D 和 D->P 两个方向都是通用的。

下图首先以蓝色展示了现有流程:路由器将请求发送到 prefill 节点,prefill 节点计算提示 KV 并返回其块元数据,然后路由器将请求和元数据转发到 decode 节点,以进行标准的 P→D NIXL READ。编号的红色路径展示了双向 KV 传输的新增部分:(1) decode 节点在生成后保留符合条件的 KV 块,(2) 在流式响应中返回其元数据,(3) 路由器缓存该元数据以供下一次 prefill 请求使用。然后 prefill 节点 (4) 在应用本地缓存匹配后检查可远程复用的 KV,(5) 当可复用 token 数量达到阈值时排队一个异步接收,以及 (6) 在计算剩余后缀之前执行 D→P NIXL READ。

Architecture diagram of bidirectional KV transfer. Existing behavior is shown in blue: the client sends a request to the KV-cache-aware router, the router sends it to the prefill node, receives prefill metadata, and forwards the request and metadata to the decode node, which reads the newly computed prompt KV with the existing P-to-D NIXL READ. New behavior is shown as six numbered red steps: (1) the decode node retains completed prompt and response KV until decoder_kv_blocks_ttl, (2) returns decode metadata in the terminal response chunk, (3) the router caches it by conversation_id and attaches it to the next prefill request, (4) the prefill node checks remote reuse after its local prefix-cache lookup, (5) queues an asynchronous remote receive when the reusable remainder meets kv_recompute_threshold, and (6) loads the retained KV from the decode node with a new D-to-P NIXL READ and computes the remaining suffix locally. The router carries metadata only; KV tensors move peer-to-peer.
双向 KV 传输的架构图。现有行为以蓝色显示:客户端向 KV-cache-aware 路由器发送请求,路由器将其发送到 prefill 节点,接收 prefill 元数据,并将请求和元数据转发到 decode 节点,decode 节点使用现有的 P-to-D NIXL READ 读取新计算的提示 KV。新行为以六个编号的红色步骤显示:(1) decode 节点保留已完成的提示和响应 KV,直到 decoder_kv_blocks_ttl,(2) 在终止响应块中返回 decode 元数据,(3) 路由器按 conversation_id 缓存它,并将其附加到下一个 prefill 请求,(4) prefill 节点在其本地前缀缓存查找后检查远程复用,(5) 当可复用剩余部分满足 kv_recompute_threshold 时排队一个异步远程接收,以及 (6) 使用新的 D-to-P NIXL READ 从 decode 节点加载保留的 KV,并在本地计算剩余后缀。路由器仅携带元数据;KV 张量在对等节点之间移动。

KV-cache-aware 路由器

vLLM 包含连接器端的双向 KV 传输支持,而跨对话轮次协调 KV 复用则需要一个 KV-cache-aware 路由器。vLLM GitHub 提供了一个示例代理,它实现了本文中使用的流程,可以根据你的环境进行调整。

KV-cache-aware 路由器是客户端、prefill 和 decode 节点之间的一个轻量级、有状态的控制平面组件。它维护一个从客户端提供的 conversation_id 到 decode 的 kv_transfer_params 的内存映射。路由器仅存储块位置元数据,而不存储 KV 张量,并且从响应流中获取该元数据,而不是单独查询 decode 节点。

下图展示了 router 如何在多轮对话中管理 KV 元数据。在第一轮时,缓存查找未命中,请求走标准的 P→D 流程;随后 router 从响应流中保存 decode 的元数据。在后续轮次中,router 使用 conversation_id 检索该元数据,使 prefill 能够复用来自 decode 的 KV,并在生成完成后刷新元数据以供下一轮使用。

Two stacked sequence diagrams focused on the KV-cache-aware router. On turn one, the router records a cache miss, sends the request to Prefill, receives Prefill metadata, sends Decode the request and Prefill metadata, saves Decode metadata from the stream, and returns the response. On a later turn, the router pops saved Decode metadata, sends it to Prefill, receives updated Prefill metadata, sends Decode the request and Prefill metadata, refreshes the Decode metadata entry, and returns the response. Purple arrows show the Decode-to-Prefill and Prefill-to-Decode KV reads; KV tensors do not pass through the router.
两张堆叠的序列图,聚焦于具备 KV 缓存感知能力的 router。在第一轮中,router 记录一次缓存未命中,将请求发送给 Prefill,接收 Prefill 元数据,将请求和 Prefill 元数据发送给 Decode,从流中保存 Decode 元数据,并返回响应。在后续某一轮中,router 取出已保存的 Decode 元数据,将其发送给 Prefill,接收更新后的 Prefill 元数据,将请求和 Prefill 元数据发送给 Decode,刷新 Decode 元数据条目,并返回响应。紫色箭头表示 Decode 到 Prefill 以及 Prefill 到 Decode 的 KV 读取;KV 张量不经过 router。

块保留 TTL

通常,decode 实例会在生成完成后立即释放请求的块。在双向模式下,它会将这些块保留一段可配置的时长(decoder_kv_blocks_ttl,默认 480 秒),并发布它们的位置和过期时间,因此对话的 KV(包括提示前缀和生成的响应)在下一轮仍可用。对于在 TTL 到期前到达的轮次,prefill 节点会复用该 KV;对于更晚到达的轮次,则回退到常规 prefill。由于该 TTL 是固定的且不会续期,应将其调整为轮次之间的预期时间,以在 KV 复用与加速器内存使用之间取得平衡。一个自然的下一步改进是将心跳续期的租约扩展到 decode 侧,这样 decode 实例可以在对话空闲时立即回收块,而不是在固定计时器结束时才回收。

KV 重算阈值

KV 复用并不总是比本地重算更快。对于较短的可复用前缀,互连和协调开销可能超过重算这些 token 所需的时间。kv_recompute_threshold 设置控制这一决策,其默认值为 64 个 token。当需要计算的 token 数低于阈值时,prefill 节点进行重算;当达到或超过阈值时,则从 decode 节点拉取 KV。部署时可以根据模型计算时间、KV 大小和互连性能来调整该值。

性能数据

在评估中,我们在使用 Amazon EC2 P5en 实例并通过 EFA 连接的 1P1D 部署上使用了 Qwen3-32B 和 GLM-5-FP8 模型。为了模拟 prefill 节点已逐出其本地缓存,或 router 选择了另一个没有该对话 KV 块的 prefill 节点的场景,我们通过 --no-enable-prefix-caching 禁用了 prefill 节点上的前缀缓存。

在两个模型上,开启双向 KV 传输都减少了多轮对话中的 prefill 计算量,而且收益通常随提示长度增长。关闭该功能时,prefill 实例每轮都会重算整个增长的上下文,因此 TTFT 急剧上升:Qwen3-32B 从 2k 时的 158 ms 攀升到 24k 时的 778 ms,GLM-5-FP8 从 2k 时的 500 ms 攀升到 20k 时的 2,334 ms。开启该功能后,decode 实例保留的 KV 被复用而非重算,使 TTFT 平缓得多:Qwen3-32B 在同一范围内仅从 122 ms 上升到 460 ms(在最长提示下约减少 1.6 倍),而 GLM-5-FP8 从 358 ms 上升到 702 ms,在长提示下比重算路径低至多 3.3 倍。prefill 耗时分解解释了原因。

双向传输会增加少量 KV 传输时间(Qwen3-32B 约为 30 ms 至 130 ms;GLM-5-FP8 约为 29 ms 至 110 ms),但它所省去的 prefill 计算远超这段 KV 传输时间。因此,开启传输后的总 prefill 时长(Qwen3-32B:128 ms 至 414 ms;GLM-5-FP8:316 ms 至 541 ms)远低于仅重算路径(Qwen3-32B:153 ms 至 744 ms;GLM-5-FP8:500 ms 至 2,331 ms)。更大的 GLM-5-FP8 模型表现出更大的绝对和相对差距,因为其更高的每 token 重算时间使得 KV 复用随着模型和上下文规模扩大而收益更显著。

Qwen3-32B

下图绘制了 Qwen3-32B 的 TTFT 与提示长度的关系,对比了双向 KV 传输开启与关闭的情况。

TTFT versus input prompt length for Qwen3-32B with bi-directional KV transfer ON versus OFF.
Qwen3-32B 在双向 KV 传输开启与关闭时的 TTFT 与输入提示长度关系。

下一张图将相同运行的 prefill 时长拆分为重算和 KV 传输两部分。

Prefill duration for Qwen3-32B split into prefill compute and KV transfer, ON versus OFF at each prompt length.
Qwen3-32B 的 prefill 时长拆分为 prefill 计算和 KV 传输,在每个提示长度下对比开启与关闭。

两张图共同表明,开启该功能时 TTFT 几乎保持平稳,而关闭路径随着上下文增长急剧攀升,因为少量 KV 传输时间远小于它所省去的重算时间。

zai-org/GLM-5-FP8

GLM-5-FP8 也呈现相同的两个视图。第一张图绘制了该功能开启与关闭时的 TTFT 与提示长度关系。

Line plot for zai-org/GLM-5-FP8 on Amazon EC2 P5en instances with AWS EFA, comparing time to first token with bi-directional KV transfer enabled and disabled across input prompt lengths.
zai-org/GLM-5-FP8 在配备 AWS EFA 的 Amazon EC2 P5en 实例上的折线图,对比了在不同输入提示长度下启用和禁用双向 KV 传输时的首 token 时间。

第二张图将 prefill 时长拆分为重算和 KV 传输两部分。

Grouped bar plot for zai-org/GLM-5-FP8 on Amazon EC2 P5en instances with AWS EFA, comparing prefill duration with bi-directional KV transfer enabled and disabled and showing the KV-transfer component.
zai-org/GLM-5-FP8 在配备 AWS EFA 的 Amazon EC2 P5en 实例上的分组柱状图,对比了启用和禁用双向 KV 传输时的 prefill 时长,并展示了 KV 传输部分。

GLM-5-FP8 表现出比 Qwen3-32B 大得多的差距,在长提示下 TTFT 最多降低约 3.3 倍,因为其更高的每 token 重算时间使得 KV 复用随着上下文长度增长而收益更显著。

如何运行基准测试

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

结论

在本文中,我们展示了双向 KV 缓存传输如何在智能体的对话历史增长时降低 TTFT。在 P-D 分离部署中,该功能允许 prefill 节点跨轮次复用 decode 节点保留的 KV 块,从而减少重复计算。可配置的块保留和重算阈值决定何时发生复用。在使用通过 EFA 连接的 Amazon EC2 P5en 实例的 1P1D 部署中,在测试的最长提示长度下,我们观察到 Qwen3-32B 的 TTFT 降低了 1.6 倍,zai-org/GLM-5-FP8 降低了 3.3 倍;结果取决于工作负载和部署配置。要在您的环境中实现这一点,请使用 vLLM v0.21.0 或更高版本并配合 NixlConnector,在 prefill 和 decode 服务器上均启用 bidirectional_kv_xfer,使用 KV 缓存感知路由器,并遵循 复现指南。

致谢

我们感谢 Nicolò Lucchesi(Mistral)、Mark McLoughlin(Red Hat)以及 vLLM 社区在维护此功能方面给予的支持。

附录:复现我们的结果

拓扑结构为两台 AWS p5en 实例(prefill 在 :8100,decode 在 :8200)并启用 EFA,另有一台小型节点在 :8000 上运行代理和基准测试客户端。

本文附带三个脚本:serve_pd_multiturn.sh 在开启或关闭该功能的情况下启动任一侧,run_multiturn_sweep.sh 驱动提示长度扫描,cleanup.sh 拆除部署。

测试环境

两台 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 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 -t FI_EP_RDM            # verify the EFA provider is present on both GPU nodes

2. 在两台 GPU 节点上安装 vLLM 和 NIXL

python3 -m venv ~/venv-pd && source ~/venv-pd/bin/activate
pip install --upgrade pip && pip install vllm==0.29.0 nixl==1.4.1

在两台节点上各应用一次 vLLM PR #40186。它将 kv_transfer_params 附加到 streaming 响应上,这正是多轮代理关联各轮所需:代理始终使用 stream=True 查询 decode 节点,因此如果没有此补丁,decode 节点永远不会返回块元数据,每一轮都会变成缓存未命中。

SITE=$(python -c 'import site; print(site.getsitepackages()[0])')
curl -sL https://github.com/vllm-project/vllm/pull/40186.diff -o /tmp/40186.diff
patch -p1 -d "$SITE" --fuzz=3 < /tmp/40186.diff

3. 下载模型

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

4. 启动 P/D 对

在每个 GPU 节点上运行一次 serve_pd_multiturn.sh。BIDIR 是 A/B 两侧之间唯一变化的东西,且必须在两个节点上保持一致:

# on the prefill node
P_IP=<P_IP> D_IP=<D_IP> ROLE=prefill BIDIR=false ./serve_pd_multiturn.sh
# on the decode node
P_IP=<P_IP> D_IP=<D_IP> ROLE=decode  BIDIR=false ./serve_pd_multiturn.sh

在 prefill 侧展开为:

export FI_PROVIDER=efa
export VLLM_NIXL_SIDE_CHANNEL_HOST=<P_IP> VLLM_NIXL_SIDE_CHANNEL_PORT=5600
vllm serve Qwen/Qwen3-32B --served-model-name Qwen --dtype bfloat16 \
  --tensor-parallel-size 8 --max-model-len 32768 --max-num-seqs 8 \
  --no-enable-prefix-caching --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"],
    "kv_recompute_threshold":64,"decoder_kv_blocks_ttl":480,
    "bidirectional_kv_xfer":false}}'

--no-enable-prefix-caching 仅在 prefill 节点上设置——这就是基准测试模拟的缓存被逐出的场景。在 decode 节点上保持前缀缓存启用。等待两个 /health 端点返回 200。

5. 启动多轮代理

python3 -m venv ~/venv-proxy && source ~/venv-proxy/bin/activate
pip install fastapi uvicorn httpx pandas numpy aiohttp transformers tqdm
sudo dnf install -y jq || sudo apt-get install -y jq
git clone --depth 1 --branch v0.29.0 https://github.com/vllm-project/vllm.git ~/vllm_src
export VLLM_SRC=~/vllm_src
 
python3 $VLLM_SRC/examples/disaggregated/disaggregated_serving/disagg_proxy_multiturn.py \
  --port 8000 --prefiller-host <P_IP> --prefiller-ports 8100 \
  --decoder-hosts <D_IP> --decoder-ports 8200 \
  2>&1 | tee ~/proxy_$(date +%Y%m%d_%H%M%S).log

代理接收主机和端口,而不是 URL。复用由客户端发送 conversation_id 驱动,因此这里没有可启用的标志。

6. 运行 OFF 侧,然后运行 ON 侧

VLLM_SRC=~/vllm_src LEG=off ./run_multiturn_sweep.sh

对于 ON 侧,停止两个服务器和代理,在每个节点上使用 BIDIR=true 重新启动步骤 4,重启代理(其对话缓存是进程内的),然后:

VLLM_SRC=~/vllm_src LEG=on ./run_multiturn_sweep.sh

对 GLM-5-FP8 使用 MODEL=zai-org/GLM-5-FP8 SERVED_NAME=GLM 重复两侧。

7. 收集数据

三个量,每个来源一个:

  • TTFT,来自基准测试由 --stats-json-output 写入的每请求统计 JSON。每条记录都带有 ttft_ms 以及 input_num_tokens 和 conversation_id。这是 TTFT 图表的 y 轴。
  • Prefill 时长,来自代理日志,该日志将每个请求的 prefill 阶段报告为 Prefill done in <N>ms。这是 prefill 时长分解中每个柱状条的总高度。
  • KV 传输时间和传输量,来自 prefill 节点的日志,该日志报告 KV Transfer metrics: 行,包含平均和 P90 传输时间、每次传输的字节数以及吞吐量。这是每个 ON 柱状条中的 KV-xfer 组件。

NIXL 遥测由发起传输的一方记录,因此 D->P 回读出现在 prefill 节点的日志中,而普通的 P->D 拉取出现在 decode 节点的日志中。

按提示长度对每个量求平均,然后绘图:将 TTFT 对提示长度绘制为两条线(ON 和 OFF),并将 prefill 时长绘制为按提示长度成对的堆叠柱状条,其中 ON 柱状条拆分为 prefill 计算和 KV 传输。两种视图之间的差异正是关键所在——柱状条所揭示的传输开销,正是 TTFT 曲线所转化出的节省。

故障排查

  • 代理在 ON 运行中始终记录 cache MISS——缺少 --send-conversation-id、PR #40186 未在两个节点上应用,或 BIDIR=false 遗留在 decode 节点上,此时它完全不返回 kv_transfer_params。
  • prefill 节点上出现 Declining expired remote read——decode 节点的块在下一轮到达之前已失效。提高 decoder_kv_blocks_ttl(默认 480 秒),并保持代理的缓存 TTL 低于该值。
  • fi_info -p efa 为空或回退到套接字——缺少 EFA provider。重新安装 aws-efa-installer 并确认实例启动时启用了 EFA 接口;否则 NIXL 要么初始化失败,要么静默地通过 TCP 运行,这会使传输数据失去意义。

清理

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

然后终止实例并确认没有实例仍在运行。

来源:vLLM Blog · vllm.ai