ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

vLLM 分离式推理实战指南:把 prefill 和 decode 拆开跑

vLLM 分离式推理实战指南:把 prefill 和 decode 拆开跑 vLLM 分离式推理实战指南把 prefill 和 decode 拆开跑原文vLLM Blog - 《Taking vLLM Apart: A Practical Guide to Disaggregated Serving》https://vllm.ai/blog/2026-09-29-disaggregated-serving-guide一个 vllm serve 进程同时在干三件事处理提示词prefill、生成 tokendecode以及围绕这两件事的一大堆 CPU 工作。这三件事会互相干扰。vLLM 官方博客 9 月底发了一篇实用指南讲清楚分离式推理能在哪几个点上把工作拆开、什么时候拆划算、用 vLLM v0.30.0 及以上版本怎么跑起来以及还差什么。这篇把它整理成一条能照着做的路径先理解拆的维度和代价再动手跑三进程的示例最后用官方给的基准方法验证自己的机器。一、拆开解决的是什么问题单个进程里一次 8k token 的 prefill 会落在同一块正在 decode 的 GPU 上把它上面所有正在输出的流全部卡住直到这次 prefill 算完。指南里的实测很直观在 0.4 req/s 的负载下共置部署的 p99 ITL 跳到 169 ms而 P/D 分离保持在 29 ms且从未超过 52 ms在 0.4 到 0.6 req/s 之间两者的中位数接近共置的 p99 大约高出六倍。结论就一句话把长提示词的计算挪出 decode 那台机器前提是 KV 缓存搬得足够快。二、可以拆的维度指南列出几个可以拆、也可以组合的点Prefill 与 decode 拆成两个实例。它们之间传递的是 KV 缓存也就是 decode 在输出第一个 token 之前必须先拿到的、提示词每个 token 的注意力键值。它很大Llama-3.1-70B 用 BF16 时每 token 存 320 KiB一个 1 万 token 的提示词就要给 decode 大约 3 GB按 400 Gb/s 线速算光是这部分就约 65 ms而且全部落在 TTFT 上。搬运靠 KV connector通常走 RDMA。上游现在有十多个 connector包括 NIXL、LMCache、Mooncake、FlexKV 和 AMD 的 MoRI-IO还有一个能把它们串起来的 MultiConnector前端整体搬离 GPU 机器。/render把 OpenAI 请求变成 token ID引擎只做 token 进、token 出/derender再把输出 token ID 变回带 content、reasoning、tool_calls 的 OpenAI 响应。指南提到 derender 这一段是最近才补上的补上之后这个来回才闭环P/D 之外多模态有 encoder 拆分MoE 有把 attention 与 FFN 拆开的 AFD 插件思路上是同一件事换个位置做P/D 自身现在也覆盖了带 Mamba 状态传输的混合 SSM 模型。三、能换到什么代价是什么正面收益有三组官方数据同一台 8 卡 MI300X 节点上跑 Qwen3-235B-A22B-FP8、8 req/sAMD 的 MoRI-IO 单节点基准里 100 个请求有 73 个同时满足 1 s TTFT 与 50 ms ITL共置只有 30 个等于同样硬件上约 2.4 倍 goodput集群规模上llm-d 的 P/D 指南报告 gpt-oss-120b 在 16 张 H200 上相比同样 GPU 做成聚合副本平均端到端延迟降低约 59%P95 降低 67%CPU 那层很便宜同一台机器上给 Qwen2.5-7B 模板化并分词一个 9k token 的聊天提示词大约花 15 ms CPU单个 render 服务在默认设置下只用略超一个核心就到了 73 req/s在共置部署尾部崩掉的 0.4 req/s 这个量级上渲染开销不到单核的 1%。代价同样明确而且指南写得比收益更细每一个首 token 都要为这次传输买单。测试机用的两块 L40S 不支持点对点拷贝nvidia-smi topo -p2p r 报 NS一个 8k 提示词约 470 MB 的 KV 缓存Qwen2.5-7B 每 token 56 KiB要花约 1.3 s 才到 decode在 0.2 req/s 下 P/D 的中位 TTFT 是 2.2 s共置是 0.7 s。ITL 的尾部虽然平了但 TTFT 在所有速率上都没过 2 s 的目标指南提醒光看带宽解释不了这 1.3 s即使绕主机内存PCIe 4.0 应该只需几十毫秒多出来的是拷贝周边的开销其中包括 decode 只在自己的前向步骤之间轮询、才会发现传输已经完成运维复杂度上升从一个服务变成三到四个服务KV 传输本身成了新的失败模式。指南明确说很多部署场景里共置仍然是正确答案。你的情况建议生产负载下 ITL p99 打不中 SLO拆分这是主要用例长提示词加高并发拆分前提是 KV 传输够快prefill 干扰在这里最严重聊天或 Agent 循环、上下文持续增长拆分但要开双向传输并配合 KV offloading 或 LMCache、Mooncake 这类共享 KV 缓存GPU 节点的 CPU 画像里出现模板化、分词、解析把 render 层拆出去TTFT 是硬约束保持共置或者先量一次传输成本落在每一个首 token 上KV 传输很慢先修传输或保持共置它落在 TTFT 上也压住了吞吐顺便查网络配置错的网络通常也会拖慢集合通信流量低、突发或对延迟不敏感保持共置四、动手三个进程跑 P/D指南的所有示例都要求 vLLM v0.30.0 或更高版本。例子用 Qwen3-0.6B 只是为了加载快它太小显不出 P/D 的好处所以正式压测要换更大的模型。# Prefiller跑在 GPU 0CUDA_VISIBLE_DEVICES0UCX_NET_DEVICESallVLLM_NIXL_SIDE_CHANNEL_PORT5600\vllm serve Qwen/Qwen3-0.6B--port8100\--kv-transfer-config{kv_connector:NixlConnector,kv_role:kv_producer}# Decoder跑在 GPU 1CUDA_VISIBLE_DEVICES1UCX_NET_DEVICESallVLLM_NIXL_SIDE_CHANNEL_PORT5601\vllm serve Qwen/Qwen3-0.6B--port8200\--kv-transfer-config{kv_connector:NixlConnector,kv_role:kv_consumer}# Proxypython tests/v1/kv_connector/nixl_integration/toy_proxy_server.py--port8192\--prefiller-hosts localhost --prefiller-ports8100\--decoder-hosts localhost --decoder-ports8200把这三段连起来的是 proxy。对每个请求它先调 prefill带上一 token 的生成预算和标记远程解码的参数prefill 算好 KV 缓存、把 block 留在自己那里返回指向这些 block 的参数proxy 再把原始请求连同这些参数转发给 decodedecode 先通过 NIXL 把 block 拉过来然后才开始生成。客户端只要指向 8192 端口它看起来就是一个普通的 OpenAI 端点。指南提醒tests 目录里这个 proxy 是示例开发够用、生产不行生产环境应该看 llm-d 或 Dynamo。五、三个要早点知道的配置VLLM_NIXL_SIDE_CHANNEL_PORT 在同一台机器上必须每个 worker 各不相同示例里 prefill 用 5600、decode 用 5601 就是这个原因kv_lease_duration 在 kv_connector_extra_config 里设置默认 30 秒决定 prefiller 等 decoder 把 block 收走能等多久。高负载下这就是你要调的 timeoutkv_load_failure_policy 决定传输失败时的行为默认的 fail 让请求报错recompute 则让 decode 自己重算缺失的 KV更慢但请求能活下来。六、顺手省掉一次分词如果跑的是 chat completions 接口prefill 阶段其实已经把提示词分词过了decode 阶段没必要再来一遍。让 prefill 返回 token ID再通过传输参数把它们交给 decode 即可fromopenaiimportOpenAI MODELQwen/Qwen3-0.6Bmessages[{role:user,content:What is 17 * 23?}]prefill_clientOpenAI(base_urlhttp://localhost:8100/v1,api_keyEMPTY)decode_clientOpenAI(base_urlhttp://localhost:8200/v1,api_keyEMPTY)prefillprefill_client.chat.completions.create(modelMODEL,messagesmessages,max_tokens1,extra_body{return_token_ids:True,kv_transfer_params:{do_remote_decode:True}})# prefill 返回的参数把 decode 指向它的 block这里再补上 token IDdecodedecode_client.chat.completions.create(modelMODEL,messagesmessages,streamTrue,extra_body{kv_transfer_params:{**prefill.kv_transfer_params,prompt_token_ids:prefill.prompt_token_ids}})这段和 proxy 内部做的两步调用是同一件事唯一多出来的就是 token ID。七、多轮对话别重复计算整段上下文标准的 P/D 只把 KV 单向搬一次对聊天和 Agent 循环很浪费到第二轮时decode 手里还留着它刚生成的那段答案的 KV而 prefill 从没算过这段于是 prefill 从头重算。两个实例都打开双向 KV 传输之后prefill 改成从 decode 把这些 block 拉回来只计算新增的 token。proxy 需要按客户端传来的 conversation_id 跟踪哪些 block 属于哪段对话。指南同时给了一条推理模型上的警告decode 的 block 里包含它生成的思考轨迹如果下一轮的提示词把它们丢掉了Qwen3 自己的聊天模板就会丢prefill 的提示词就和 decode 的 block 对不上结果是输出错而不只是变慢。指南指出 vLLM 目前不检测这种不一致开启之前先确认自己的聊天模板。八、两个拆分合起来四层的形态只需要三台服务因为一个 render 服务同时提供 render 与 derender。指南提醒目前还没有上游组件替你驱动这四跳但组件是可组合的推理接口同样接受 KV 传输参数。# Render 与 derender不需要 GPUvllm launch render Qwen/Qwen3-0.6B--port8000\--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser hermes# PrefillGPU 0CUDA_VISIBLE_DEVICES0UCX_NET_DEVICESallVLLM_NIXL_SIDE_CHANNEL_PORT5600\vllm serve Qwen/Qwen3-0.6B --tokens-only--port8100\--kv-transfer-config{kv_connector:NixlConnector,kv_role:kv_producer}# DecodeGPU 1CUDA_VISIBLE_DEVICES1UCX_NET_DEVICESallVLLM_NIXL_SIDE_CHANNEL_PORT5601\vllm serve Qwen/Qwen3-0.6B --tokens-only--port8200\--kv-transfer-config{kv_connector:NixlConnector,kv_role:kv_consumer}注意 --tokens-only引擎只处理 token ID模板化和解析都在 render 层完成。此时 render 与 derender 之间的编排要你自己写proxy 那个示例只处理 completions 与 chat completions 两类接口。指南把 llm-d 和 Dynamo 列为 renderer 的预期调用方。九、怎么在自己的机器上验证指南给了一条务实的路径可以照着抄有两块 GPU 就用 7B 级模型跑上面的 NIXL 例子。Qwen3-0.6B 预填充太快几乎不打扰 decodeP/D 没什么可修的先量传输。单机上 nvidia-smi topo -p2p r 在 prefill 与 decode 两块 GPU 之间应该报 OK然后一次发几条长提示词读 decode 输出里的 KV Transfer metrics 那一行如果平均传输时间在几百毫秒先修它别急着测其它做对比基准。同一对 GPU 上把打到 8192 的 proxy 和单进程的 vllm serve --data-parallel-size 2 放在一起比 p99 ITL、TTFT 和 goodput把请求速率往上加直到共置那一侧的 p99 ITL 崩掉测试机上加 8k 提示词时这个点是 0.4 req/s用官方的基准命令并留意一个坑vllm bench serve--modelQwen/Qwen2.5-7B-Instruct--port8192\--dataset-name random --random-input-len8192--random-output-len256--ignore-eos\--request-rate0.4--num-prompts100\--percentile-metrics ttft,tpot,itl --metric-percentiles50,99\--goodputttft:2000 tpot:30这个坑是前缀缓存如果你在多个速率上复用同一个 --seed每个服务都要加 --no-enable-prefix-caching否则后面的速率会重放服务器已经缓存的提示词缓存命中正好把你想要测的 prefill 干扰掩盖掉。如果跑的是聊天或 Agent开双向 KV 传输并且测第五轮的 TTFT而不是第一轮如果已经在 Kubernetes 上从 llm-d 或 Dynamo 起步不要自己写 proxy如果给推理或工具调用模型开了 streaming derender先压测 render 层再定容量指南说这是最容易被低估的一块。小结分离式推理不是「拆了就快」它是一次明确的交易用更复杂的运维和一次 KV 传输换掉长提示词对 decode 的干扰。收益成立的条件写得很清楚ITL 的尾延迟在打 SLO、KV 传输够快、流量足够高条件不满足时共置依然是正确答案。指南里那句「先量传输」值得当成第一步平均传输时间已经是几百毫秒的话后面所有基准都是在量同一个瓶颈。对写 Agent 的人第七节最贴身。Agent 循环的上下文会一轮轮增长第二轮开始 prefill 重算整段历史的浪费会随轮数放大双向 KV 传输加共享 KV 缓存是官方点名的组合。这类优化不改变 Agent 的逻辑只改变它跑起来的成本和延迟。注文中版本号与配置项均来自原文vLLM v0.30.0 及以上本机未验证最新版本以官方文档为准。
返回列表