vLLM 分离式 Prefill/Decode 部署实战:XpYd 代理、多轮 KV 复用与 KV Cache 事件发布
2026/9/7 4:10:00 网站建设 项目流程

vLLM 分离式 Prefill/Decode 部署实战:XpYd 代理、多轮 KV 复用与 KV Cache 事件发布

【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm

本文围绕 disaggregated_serving 示例目录 展开,讲解 vLLM 的 Disaggregated Serving(Prefill/Decode 分离部署)能力:从最经典的 XpYd(X 个 Prefill 实例 + Y 个 Decode 实例)拉取模式代理,到双向 KV 传输的多轮对话代理、Push 模式代理,再到 KV cache 事件的 ZMQ 发布与订阅。读完本文,你可以直接复制仓库中的脚本搭建一套 P/D 分离的推理集群,并理解代理如何转发kv_transfer_params完成跨实例 KV cache 迁移。

示例目录概览

examples/disaggregated/disaggregated_serving/README.md 说明该目录包含演示 vLLM disaggregated serving 特性的脚本。README 中列出的核心文件如下:

文件作用
disagg_proxy_demo.py演示 XpYd(X 个 prefill 实例、Y 个 decode 实例)的拉取模式代理
kv_events.sh演示 KV cache 事件发布(KV cache event publishing)
mooncake_connectorMooncakeConnector 的代理演示(位于相邻的mooncake_connector目录)

此外,当前仓库中该目录还包含以下未在 README 中列出的补充脚本(可以推断是后续迭代加入的):

  • disagg_proxy_multiturn.py:支持双向 KV 传输的多轮对话代理,让每一轮复用上一轮 Decode 节点上的 KV 块;
  • disagg_proxy_pushconnector_demo.py:与拉取模式配套的 Push 模式代理演示;
  • moriio_toy_proxy_server.py:MoRI-IO P/D 分离的单节点协议参考代理。

XpYd 拉取模式代理(disagg_proxy_demo.py)

这是分离式部署最典型的形态:启动多个 vLLM 实例(示例中 2 个 prefill + 2 个 decode),再启动代理进行统一路由。文件头部 docstring 给出的启动方式为:

python3 examples/disaggregated/disaggregated_serving/disagg_proxy_demo.py \ --model $model_name \ --prefill localhost:8100 localhost:8101 \ --decode localhost:8200 localhost:8201 \ --port 8000

命令行参数

parse_args(disagg_proxy_demo.py#L419-L446)定义了四个参数:

参数缩写说明
--model/-m必填模型名,用于启动时校验各实例服务的模型是否一致
--prefill/-p多个Prefill 节点 URL 列表(host:port
--decode/-d多个Decode 节点 URL 列表(host:port
--port默认 8000代理自身监听端口

启动时ProxyServer.validate_parsed_serve_args会强制要求 prefill、decode 节点各至少一个,并逐一校验实例地址格式(host 为 IP 或localhost,端口在 1~65535 之间),随后调用verify_model_config请求每个实例的/v1/models,比对模型名后缀是否一致,不一致则直接抛错——这避免了 P/D 节点模型错位导致的隐性故障。

两阶段请求转发

代理对/v1/completions/v1/chat/completions均执行同样的两段式转发(以create_completion为例,见 disagg_proxy_demo.py#L250-L278):

  1. Prefill 阶段:复制一份请求并将max_tokens改写为 1(chat 接口同时改写max_completion_tokens),转发给轮询选中的 prefill 实例。该实例只完成提示词的 KV 计算,产出 token 被丢弃;
  2. Decode 阶段:将原始请求转发给轮询选中的 decode 实例,以StreamingResponse流式返回给客户端。KV 数据的实际迁移由 P/D 两侧引擎通过 KV connector 完成,代理只负责编排两次 HTTP 调用。

调度策略由SchedulingPolicy抽象类定义,默认实现RoundRobinSchedulingPolicy基于itertools.cycle做轮询;上游连接失败时,forward_request抛出HTTPException,代理会调用remove_instance_endpoint将故障实例摘除并重建 cycler,实现简单的故障剔除。

代理的 HTTP 接口

setup_routes(disagg_proxy_demo.py#L67-L85)注册了四个路由:

  • POST /v1/completionsPOST /v1/chat/completions:客户端入口,均要求Content-Type: application/json(否则返回 415);
  • GET /status:返回 prefill/decode 节点数量与地址列表,可用于运维监控;
  • POST /instances/add:运行时动态扩容节点,需要X-API-Key请求头且服务端设置了ADMIN_API_KEY环境变量;请求体形如{"type": "prefill", "instance": "host:port"},代理会先通过/v1/models校验模型一致后再加入实例池。

需要注意 docstring 中的一段说明:该 demo 计划在内置 PDController 支持 XpYd 之后移除,因此它定位是协议演示,而非生产级路由组件。

多轮对话的双向 KV 传输(disagg_proxy_multiturn.py)

多轮聊天场景下,第一轮结束后 KV 块留在 Decode 节点上;如果第二轮重新走 Prefill 重算,会浪费大量计算。disagg_proxy_multiturn.py 头部 docstring 给出了其架构与请求流:

Client ──► Proxy ──► Prefill (P) ──► Decode (D)

每个请求的处理流程为:

  1. 客户端向代理发送 chat/completions 请求;
  2. 代理按conversation_id查询上一轮缓存的 D 节点块信息;
  3. 命中缓存时,代理把 D 的块信息附到请求上,让 P 直接读取 D 上的 KV 块而不是重算;
  4. 代理将请求发给 P(max_tokens=1,非流式);
  5. P 返回携带自身块信息的kv_transfer_params
  6. 代理将请求和 P 的块信息转发给 D(流式);
  7. D 流式返回响应,最后一个 chunk 中包含 D 的kv_transfer_params,代理将其缓存供下一轮使用。

其中conversation_id是请求 JSON 顶层的非标准扩展字段,用于跨轮次关联同一个会话;缺少该字段时代理无法串联轮次,会退化为无缓存行为。docstring 同时提醒:严格的 OpenAI 兼容前端会拒绝未知字段,因此该字段只被代理消费、不会转发给 vLLM 引擎。

Push 模式代理(disagg_proxy_pushconnector_demo.py)

disagg_proxy_pushconnector_demo.py 是拉取模式的“孪生”演示,客户端侧 API 完全相同,区别在于 P/D 之间 KV 传输的协调方向(见其头部 docstring):

  • Pull 模式disagg_proxy_demo.py):代理把 P 的kv_transfer_params(含remote_block_ids)转发给 D,由 D 通过 NIXL READ 主动从 P 拉取 KV;
  • Push 模式:代理只把 P 的坐标(remote_engine_idremote_hostremote_porttp_sizepp_size)和共享的remote_request_id交给 D;D 先通过 NIXL 通知向 P 注册自己本地分配的块,随后由 P 通过 NIXL WRITE 把 KV 推给 D。

Push 模式下 vLLM 实例需配置NixlPushConnector以及匹配的engine_id/side_channel_port,代理的启动命令比 Pull 模式多了 P 侧坐标参数:

python3 examples/disaggregated/disaggregated_serving/\ disagg_proxy_pushconnector_demo.py \ --model $model_name \ --prefill localhost:8100 \ --decode localhost:8200 \ --prefill-engine-id prefill-engine-001 \ --prefill-kv-host 10.0.0.1 \ --prefill-side-channel-port 5600 \ --prefill-tp-size 1 \ --prefill-pp-size 1 \ --port 8000

KV Cache 事件发布(kv_events.sh)

kv_events.sh 演示引擎对外发布 KV cache 事件的完整链路。脚本开头即声明该功能为实验特性("The usage of KV cache events is experimental and subject to change")。

服务端配置

脚本通过环境变量HF_MODEL_NAME(默认meta-llama/Meta-Llama-3.1-8B-Instruct)确定模型,并设置VLLM_HOST_IP为机器第一个 IP。核心是带--kv-events-config的启动命令(kv_events.sh#L37-L44):

vllm serve "$MODEL_NAME" \ --port 8100 \ --max-model-len 100 \ --enforce-eager \ --gpu-memory-utilization 0.8 \ --trust-remote-code \ --kv-events-config \ '{"enable_kv_cache_events": true, "publisher": "zmq", "topic": "kv-events"}'

其中--kv-events-config是一个 JSON 配置:enable_kv_cache_events开启事件发布,publisher指定发布通道为 ZMQ,topic为订阅主题名kv-events。脚本随后用wait_for_server轮询/v1/completions直到服务就绪(超时 1200 秒),再启动订阅者,最后发送两条示例请求触发事件,并打印两次 completions 的响应。

需要注意一个路径细节:脚本按$SCRIPT_DIR/kv_events_subscriber.py定位订阅者,即期望它与kv_events.sh位于同一目录;而当前仓库中该文件实际位于 examples/features/kv_events/kv_events_subscriber.py,运行前需将其放到kv_events.sh所在目录(或将路径改为指向 features 目录),否则订阅进程无法启动。

订阅者实现要点

kv_events_subscriber.py 展示了事件消费端的标准写法:

  • 消息类型:使用msgspec定义KVEventBatch(含时间戳ts与事件列表),事件包括BlockStored(新增块,携带block_hashesparent_block_hashtoken_idsblock_size等字段,块哈希类型来自 vllm/v1/core/kv_cache_utils.py 的ExternalBlockHash)、BlockRemoved(块被逐出)与AllBlocksCleared(缓存清空);
  • 双通道 ZMQ:主通道为 SUB socket,连接tcp://localhost:5557并订阅kv-events主题;另外用 REQ socket 连接tcp://localhost:5558作为重放(replay)通道;
  • 断流补齐:每条消息携带 8 字节大端序列号,若发现seq > last_seq + 1说明有消息丢失,订阅者会向 replay 通道请求从last_seq + 1开始补齐,直到序列号追平(重放结束以空 payload 帧标记)——这让外部系统可以基于事件流重建 KV 块状态的完整视图。

这类事件对上层调度系统很有价值:路由/缓存感知组件可以据此掌握各实例上 KV 块的分布,为跨实例前缀复用提供依据。

MoRI-IO 单节点参考代理(moriio_toy_proxy_server.py)

moriio_toy_proxy_server.py 是 MoRI-IO 分离式部署的最小单节点参考代理。docstring 明确其定位与边界:

  • 它演示 connector 期望的DP rank 固定(pinning)契约:每个请求选定一个 prefill DP rank(flat_interleaved_dp_route),并通过X-data-parallel-rank请求头加上kv_transfer_params中的remote_dp_rank/remote_dp_size/remote_tp_size把请求的两段(P 与 D)都固定到该 rank 上;
  • 它有意不提供跨 Pod 的 Wide-EP(如 2P2D)对等节点信息(remote_hostsmulti_pod_hostsdata_parallel_size_localis_request_leader等)。生产级多 Pod 部署中,这些信息通常由路由 sidecar(如 llm-d-router 或 NIXL 形态的路由器)生成;
  • 因此该文件应作为协议参考使用,而不是生产路由器。

小结与适用建议

  • 理解 P/D 分离的代理层协议、kv_transfer_params的流转方式,从disagg_proxy_demo.py入手最直接;
  • 需要多轮会话复用 Decode 侧 KV 时,参考disagg_proxy_multiturn.py的双向传输与conversation_id设计;
  • 评估 Push 模式(P 侧主动 NIXL WRITE)时,使用disagg_proxy_pushconnector_demo.py并配置NixlPushConnector
  • 构建缓存感知的外部调度/路由系统时,kv_events.sh+ 订阅者是完整的 ZMQ 事件链路范例(注意其实验状态);
  • 更完整的分离式部署形态(如 LMCache 集成、NIXL connector 的 prefill/decode 独立脚本、encoder 分离等)可继续参考 examples/disaggregated/lmcache/、examples/disaggregated/example_connector/ 与 examples/disaggregated/disaggregated_encoder/。

以上脚本均为示例级实现:轮询调度、单进程状态、内存中的会话缓存等设计决定了它们适合验证协议与拓扑,生产环境应结合仓库中正式的 KV connector 实现与外部路由组件评估使用。

【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询