分离式预填充#

概述#

分离式 Prefill (P/D) 在不同的 vLLM 实例上运行 Prefill 和解码:一个 Prefill 实例计算提示的 KV,将其通过 NIXL 交给一个 解码 实例,解码器生成令牌而无需重新计算提示。Prefill 和解码密集型工作可以独立扩展。

LMCache MP 在此基础上增加了跨请求的 KV 重用。 每个 vLLM 实例还将其 KV 卸载到一个共置的 LMCache 服务器,因此在后续请求中,或在从 GPU HBM 逐出后,重复的前缀将从 LMCache 加载,而不是重新计算。预取路径上的 KV 源顺序:本地 GPU 缓存 → LMCache → 重计算。

两者都是通过 vLLM 的 MultiConnector 组合而成,每个实例运行两个连接器:

  • NixlConnector — 当前请求的 Prefill→解码 KV 移交,通过 NIXL (UCX / RDMA)。

  • LMCacheMPConnector — 卸载/加载到实例的 LMCache 服务器以实现跨请求重用。

在 P/D 模式下,vLLM 路由器 将每个请求发送到一个 Prefill 实例,然后是一个解码实例,并在它们之间线程化 NIXL 握手。

它是如何工作的#

最小部署需要五个进程:

  • Prefill vLLM (生产者)MultiConnector[NixlConnector (kv_producer), LMCacheMPConnector]; 计算提示 KV,将其存储到 LMCache 服务器,并为解码器提供拉取功能。

  • 解码 vLLM (消费者)MultiConnector[NixlConnector (kv_consumer), LMCacheMPConnector]; 从生产者拉取提示 KV 并生成输出。

  • 两个 LMCache 服务器 — 每个 vLLM 实例一个 ``lmcache server``(它们不能共享一个)。

  • 路由器vllm-router --vllm-pd-disaggregation 路由 Prefill 然后解码。

为了在 Prefill 和解码池之间共享重用,在顶部逐层 P2P KV Cache 共享 <p2p>`(给两个服务器一个协调器和 `--p2p-advertise-url``)。

要求#

重要

vllm-project/vllm#46865 (MultiConnector 实际块修复)解锁了在 MultiConnector 下的 LMCache 卸载。没有它,卸载将静默地永远不会触发。

  • NIXL 在 vLLM 环境中可用(lmcache[nixl] 附加项,拉取 nixl>=1.3.0)。

配置#

每个 vLLM 实例都需要一个 --kv-transfer-config,选择 MultiConnector,并包含两个子连接器:

描述

kv_connector = MultiConnector

并行运行 kv_connector_extra_config.connectors

kv_role

kv_producer``(Prefill)或 ``kv_consumer``(解码),在顶层 ``MultiConnector 和嵌套的 NixlConnector 上均适用。

NixlConnector

Prefill→解码交接。将 kv_load_failure_policy 设置为 fail,以便在传输失败时显式显示,而不是静默重计算。

LMCacheMPConnector

卸载/加载到本地 LMCache 服务器;使用 kv_role = kv_both

lmcache.mp.host / lmcache.mp.port

实例的 LMCache 服务器的传输主机和 ZMQ ``--port``(每个实例不同)。

NIXL 传输通过 vLLM 实例上的环境变量进行调整:VLLM_NIXL_SIDE_CHANNEL_HOST``(用于握手的主机,由对等方可达),``VLLM_NIXL_SIDE_CHANNEL_PORT``(**在同一主机上的实例之间必须不同**;默认值为 ``5600),以及 UCX_NET_DEVICES=allNCCL_CUMEM_ENABLE=1。有关完整的 lmcache server 标志列表,请参见 lmcache server

运行部署#

下面的示例将 Prefill 池、解码池和路由器放置在不同的主机上。将 <PREFILL_IP> / <DECODE_IP> 替换为可路由地址,并将 <model> 替换为模型路径(在两个实例上相同)。

**步骤 1 — Prefill LMCache 服务器**(在 Prefill 主机上):

lmcache server \
    --port 6555 --http-port 8090 \
    --l1-size-gb 100 --eviction-policy LRU --chunk-size 256 \
    --instance-id prefiller

**步骤 2 — Prefill vLLM(生产者)**(在 Prefill 主机上):

VLLM_NIXL_SIDE_CHANNEL_HOST=<PREFILL_IP> VLLM_NIXL_SIDE_CHANNEL_PORT=5600 \
UCX_NET_DEVICES=all NCCL_CUMEM_ENABLE=1 \
vllm serve <model> \
    --port 8001 --tensor-parallel-size 1 \
    --kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_producer","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_producer","kv_load_failure_policy":"fail"},{"kv_connector":"LMCacheMPConnector","kv_role":"kv_both","kv_connector_extra_config":{"lmcache.mp.host":"tcp://localhost","lmcache.mp.port":6555}}]}}'

生产者在 <PREFILL_IP>:5600 上宣传其 NIXL 边通道,并将任务卸载到端口 6555 的 LMCache 服务器上。

**步骤 3 — 解码 LMCache 服务器**(在解码主机上):

lmcache server \
    --port 6556 --http-port 8091 \
    --l1-size-gb 100 --eviction-policy LRU --chunk-size 256 \
    --instance-id decoder

**步骤 4 — 解码 vLLM(消费者)**(在解码主机上):

VLLM_NIXL_SIDE_CHANNEL_HOST=<DECODE_IP> VLLM_NIXL_SIDE_CHANNEL_PORT=5558 \
UCX_NET_DEVICES=all NCCL_CUMEM_ENABLE=1 \
vllm serve <model> \
    --port 8002 --tensor-parallel-size 1 \
    --kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_consumer","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_consumer","kv_load_failure_policy":"fail"},{"kv_connector":"LMCacheMPConnector","kv_role":"kv_both","kv_connector_extra_config":{"lmcache.mp.host":"tcp://localhost","lmcache.mp.port":6556}}]}}'

消费者将任务卸载到端口 6556 的 LMCache 服务器;其侧信道端口 (5558) 与生产者的不同,以便两者可以共享一个主机。

**步骤 5 — 路由器**(在任何可以访问两个 vLLM 实例的主机上):

vllm-router \
    --policy round_robin \
    --vllm-pd-disaggregation \
    --prefill http://<PREFILL_IP>:8001 \
    --decode http://<DECODE_IP>:8002 \
    --host 0.0.0.0 --port 30000

将请求发送到路由器(http://<ROUTER_IP>:30000/v1/...);它透明地处理 Prefill→解码的分离。

在单个节点上运行(测试和调试)#

localhost 上运行相同的五个进程,GPU 6 上进行 Prefill,GPU 7 上进行解码。实例必须在 每个 共享主机端口上有所不同:LMCache 的 --port--http-port,vLLM 的 --port,以及 VLLM_NIXL_SIDE_CHANNEL_PORT

lmcache server --port 6555 --http-port 8090 \
    --l1-size-gb 100 --eviction-policy LRU --chunk-size 256 --instance-id prefiller

CUDA_VISIBLE_DEVICES=6 VLLM_NIXL_SIDE_CHANNEL_HOST=127.0.0.1 VLLM_NIXL_SIDE_CHANNEL_PORT=5600 \
UCX_NET_DEVICES=all NCCL_CUMEM_ENABLE=1 \
vllm serve <model> --port 8001 --enforce-eager --max-model-len 16384 --gpu-memory-utilization 0.4 \
    --kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_producer","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_producer","kv_load_failure_policy":"fail"},{"kv_connector":"LMCacheMPConnector","kv_role":"kv_both","kv_connector_extra_config":{"lmcache.mp.host":"tcp://localhost","lmcache.mp.port":6555}}]}}'

lmcache server --port 6556 --http-port 8091 \
    --l1-size-gb 100 --eviction-policy LRU --chunk-size 256 --instance-id decoder

CUDA_VISIBLE_DEVICES=7 VLLM_NIXL_SIDE_CHANNEL_HOST=127.0.0.1 VLLM_NIXL_SIDE_CHANNEL_PORT=5558 \
UCX_NET_DEVICES=all NCCL_CUMEM_ENABLE=1 \
vllm serve <model> --port 8002 --enforce-eager --max-model-len 16384 --gpu-memory-utilization 0.4 \
    --kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_consumer","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_consumer","kv_load_failure_policy":"fail"},{"kv_connector":"LMCacheMPConnector","kv_role":"kv_both","kv_connector_extra_config":{"lmcache.mp.host":"tcp://localhost","lmcache.mp.port":6556}}]}}'

vllm-router --policy round_robin --vllm-pd-disaggregation \
    --prefill http://localhost:8001 --decode http://localhost:8002 --host 0.0.0.0 --port 30000

--enable-tracing --otlp-endpoint http://localhost:4317 添加到每个 lmcache server 以将跟踪/指标导出到本地 OpenTelemetry 收集器。

备注

在单个主机上,NIXL 通过 localhost 使用回环/TCP,而不是 RDMA,因此延迟并不具有代表性——单节点模式仅用于 功能 测试。

验证其是否正常工作#

curl -s http://localhost:30000/v1/completions -H "Content-Type: application/json" \
    -d '{"model":"<model>","prompt":"The capital of France is","max_tokens":16}'
  • P/D 路由 — 响应 id 被标记为工作节点,例如 cmpl-___prefill_addr_localhost:8001___decode_addr_localhost:8002_...

  • NIXL 传输 — 解码 vLLM 日志 KV Transfer metrics: NixlConnector={'Num successful transfers': N, ...}.

  • LMCache 卸载storage_manager.l1_manager.total_object_countcurl -s http://localhost:8090/status 中增长(提示必须至少为 --chunk-size 个标记才能存储任何内容)。

  • LMCache 命中率 — 阅读 vLLM 的 “外部前缀缓存命中率” 日志行(从 LMCache 提供的 Prefill 令牌的比例),而不是 LMCache 服务器 /status。当工作集适合显存时,它保持为 0,一旦重用溢出显存缓存,它就会上升。

限制与调优#

  • 在高并发下保持冷 KV 传输的边界。 vLLM 的原生 NIXL P/D 路径对同时进行的许多大型 传输(完整提示,未缓存)非常敏感。保持解码实例的 GPU KV Cache 足够大,以容纳工作集,从而使传输保持较小并由缓存服务,或者在每个请求的传输较大时减少并发。