PD 分离推理 — Request Routing 内部实现

调研日期 2026-07-15 · 覆盖:vLLM (NixlConnector + Push + Lease), NVIDIA Dynamo, SGLang (Mooncake/NIXL + router), LMCache MP (NixlConnector + ZMQ + P2P)

⚡ TL;DR (30s 速览)
系统Preferred Prefill Worker 选择Decode Worker 选择KV 路由方向Lease/失效机制
vLLM NixlConnectorRouter 任选可用 P;Proxy 轮询 PP 完成 prefill 后由 Router/Proxy 以 kv_transfer_params 选定 Dpull (默认 D READ) / push (P WRITE)lease + heartbeat,D 在 queueing/in-flight 期续租
NVIDIA DynamoPlanner 按 SLA 输出 P 池大小;Router KV-aware scoringRouter 用 workload + cache-overlap 打分pull (READ via NIXL/NVLink)ModelExpress weight streaming;Canary liveness
SGLang PD客户端直送 P 端;sgl_router --pd-disaggregation 轮询/负载均衡Bootstrap server 协调:P 向 D 主动推送 transfer_info 选择某 Dpush:P-side sender 主动 WRITE/transfer KV indices to D's prealloc 地址;receiver 等待 pollbootstrap_timeout (默认 300s);HiCache restore 失败回退本地重 prefill
LMCache MP + NixlConnector与 vLLM 相同(共享 NixlConnector);LMCacheMPConnector 叠加跨请求 KV 复用Router --vllm-pd-disaggregation 模式pull:D READ from P over NIXL;MP server (CPU DRAM) 作为第二源(本地 GPU → LMCache → recompute)L1 TTL;P2P 跨节点 RDMA lock-and-locate;NIXL lease(复用 vLLM lease)

1. vLLM — NixlConnector (Pull mode) + NixlPushConnector + Lease Renewal

核心思想:Router/Proxy 先将请求只发 1 个 token 做 prefill leg(P 端,do_remote_decode=True),拿到 kv_transfer_params 后再发 decode leg(D 端,do_remote_prefill=True)。KV 在 P→D 之间通过 NIXL 传输,由 NixlConnectorScheduler 协调 scheduler/worker 两侧抽象传输。

1.1 Pull 模式生命周期 设计文档

① Request arrives → Router 选 P:Router/Proxy 挑一个可用 prefill instance(策略由上层决定,vLLM 自身仅暴露 do_remote_decode/do_remote_prefill 两个标志位)。

② P 执行 prefill:跑 full prefill,得到 KV cache blocks,提交 NIXL READ-ready 状态。

③ P 返回 kv_transfer_params:通过 HTTP response 将 P 的 NIXL 侧信道 (host/port/tp_size/engine_id/block 元信息) 给 Proxy。

④ Proxy 选 D 并签发 decode leg:D 端 worker pre-allocates 等量 KV blocks,对 P 发起 RDMA READ。

⑤ D 读取完成后通知 P:P 释放 blocks(lease 到期或 transfer 完成即释放)。

出处:NIXL KV Cache Lease Renewal 设计文档 docs/design/nixl_kv_cache_lease.md

1.2 Push 模式(NixlPushConnector,更晚的内部实现) 设计文档

动机:默认 pull 模式下 D 必须主动拉,两侧握手顺序和 watchdog 逻辑复杂;push 让 P 直接 WRITE 到 D 预注册内存,"发送即完成"。

新增 nixl-push-writer 线程:每个 TP rank 起一个 background writer 线程,仅负责 nixl_wrapper.get_new_notifs / make_prepped_xfer / transfer(WRITE)。

Writer-local matching tables 实现两侧顺序无关的 request_id 匹配:_pending_d_registrations(D 侧发来的 PUSH_REG)与 _push_finished_blocks(P 侧 scheduler stage 的 blocks)。时间对齐用 get_base_request_id() 剥离 cmpl-uuid-index 后的 8-hex 随机 suffix。关键:这是 Proxy 给两腿的同一 X-Request-Id 被 input_processor 随机化后的解同步手段

Wire format:D→P 的 PUSH_REG 通过 NIXL notification 发 PUSH_REG:<msgpack>,字段:request_id, decode_engine_id, decode_host, decode_port, decode_tp_size, local_block_ids (per-group), remote_engine_id。

Transfer 流程:

D allocate → stash → worker build_connector_meta → meta.push_registrations
→ writer enqueue PUSH_REG → NIXL send_notif → P writer 收到 → _handle_push_reg_notif
P prefill finish → meta.push_finished_blocks → writer 匹配 → NIXL WRITE → D
→ D 收到 completion notif → update_connector_output(finished_recving) → stop watchdog

Wake model:三个事件源 + 1ms self-poll:① start_load_kv 握手产生新材料;② get_finished 始终唤醒(让 writer drain HB / completion / late PUSH_REG);③ handshake 完成回调。

1.3 Lease / Heartbeat 机制 设计文档

设计动机:单一大超时 480s 遇 D crash 保活浪费显存;过短则易误杀排队的 D。解法:lease renewal via heartbeat。

Lease 默认参数:initial lease 30s;heartbeat interval ≈5s(lease_duration//6);每次续租延长 ≈20s(2/3 lease);P 校验用 max(old_expiry, now + lease_extension)(never 缩短)。

关键设计:D 端 on_new_request() 从 request 进入 scheduler waiting queue 就开始发 HB,不等被 schedule 执行。所以 prefill 完成时 P blocks 立即进入 "leased" 状态。

HB 基于 request:不是 per-instance,而是 per-request。这样 P 无需知道 "哪个 D 持有我的 blocks",路由解耦。

Transport:复用 NIXL send_notif/get_new_notifs,后端 IB/RoCE/TCP 自动 fallback。HB 消息格式 HB:<payload>,每个 HB 消息可批量续租同一 P 的所有 in-flight requests。

Proactive handshake:D 发 HB 时若尚未与 P 握手(请求还在排队),就触发 background thread 主动完成握手,加速后续 KV READ。

1.4 vLLM 的 PD 路由抽象层 设计文档

vLLM 自身不实现 Router,而是提供 Connector / LookupBuffer / Pipe 三层抽象:

每个进程一个 connector:Scheduler connector(调度 KV transfer ops)+ Worker connectors(执行 KV transfer ops)。

Connector 实现:NixlConnector、LMCacheConnectorV1、MooncakeConnector、MoRIIOConnector、MultiConnector、OffloadingConnector、FlexKVConnectorV1。

出处:docs/features/disagg_prefill.md

2. NVIDIA Dynamo — KV-Aware Router + Planner + KVBM

定位:Dynamo 是编排层,不替代 SGLang/TRT-LLM/vLLM,而是把多 GPU 集群变成协调推理系统。Router 是 Dynamo 的内置组件,不是外部 proxy。

2.1 路由拓扑 GitHub 官方文档

两种入口:

Router 决策基于 worker load + KV cache overlap(KV-aware routing),消除冗余 prefill 计算。

2.2 核心能力矩阵 README

能力作用与 PD 路由的关系
Disaggregated Prefill/DecodeP/D 独立可扩展 GPU 池Router 把 prefill leg 和 decode leg 分别路由到 P 池和 D 池
KV-Aware Routing基于 worker load + KV cache overlap 路由避免重复 prefill,2x TTFT 加速
KVBM (KV Block Manager)GPU → CPU → SSD → remote 多级 KV 卸载Router 可感知 KV 在远端存储中的位置
ModelExpressNIXL/NVLink GPU-to-GPU 权重流式加载7x 冷启动加速,新 replica 快速上线
PlannerSLA-driven autoscaler,profile + right-size pools决定 P/D 池大小,间接影响路由选择空间
Grove (K8s operator)拓扑感知 gang scheduling (NVL72)把 workload 放到最优 rack/host/NUMA
Fault ToleranceCanary health check + in-flight request migrationWorker 失败时请求不丢

2.3 关键设计决策 README

3. SGLang — PD Disaggregation (Mooncake/NIXL + sgl_router)

核心思想:SGLang 的 PD 分离是 push 模式 — P 端 sender 主动把 KV 推到 D 端 receiver 预分配的地址。Router 是独立的 sgl_router 进程,--pd-disaggregation 模式把请求先送 P 再送 D。

3.1 整体流程 用户文档

启动命令:

# P 端
python -m sglang.launch_server --model-path ... --disaggregation-mode prefill --port 30000
# D 端
python -m sglang.launch_server --model-path ... --disaggregation-mode decode --port 30001
# Router
python -m sglang_router.launch_router --pd-disaggregation \
  --prefill http://127.0.0.1:30000 --decode http://127.0.0.1:30001 --host 0.0.0.0 --port 8000

Router 把请求先送 P,P 完成 prefill 后把 transfer_info 给 Router,Router 再送 D。

3.2 P 端生命周期(prefill.py) 源码

四个阶段:

1. Bootstrap Queue
   a. 为每个 request 初始化 sender
   b. 用 queue 存储 bootstrap 未完成的 requests
   c. poll senders 检查 bootstrap 状态
   d. bootstrap 完成后移到 Waiting Queue

2. Waiting Queue
   a. PrefillAdder pop requests
   b. 跑 forward
   c. 移到 Inflight Queue

3. Inflight Queue
   a. 非阻塞 poll sender
   b. transfer 完成后 return request

Sender 通过 bootstrap_room 与 D 端 receiver 握手,bootstrap_addr 是 P 端的 HTTP bootstrap server 地址。

3.3 D 端生命周期(decode.py) 源码

四个阶段:

1. PreallocQueue
   a. 为每个 request 初始化 receiver
   b. request 先 handshake,有可用 KV 时 pre-allocate
   c. 移到 TransferQueue

2. TransferQueue
   a. poll receiver 检查 transfer 状态
   b. transfer 完成后移到 waiting queue

3. WaitingQueue
   a. 用 queue 中的 requests 构造 PrebuiltExtendBatch
   b. 跳过 prefill forward,只填充 metadata

4. RunningBatch
   a. 把 PrebuiltExtendBatch 合并进 running batch 跑 decoding

关键:D 端 跳过 prefill forward,直接用 P 端传来的 KV 构造 PrebuiltExtendBatch,省掉 prefill 计算。

3.4 Bootstrap Server 与 KV transfer 协调 源码

CommonKVBootstrapServer:只在 P 端启动,HTTP 服务,D 端 receiver 通过它发现 P 端 sender。

CommonKVManager:每个 TP rank 一个,bind ZMQ PULL socket,管理 request_status / socket_cache / failure_records。

CommonKVSender.init(num_kv_indices, aux_index):设置本地 request 的 index metadata。

CommonKVSender.send(kv_indices, state_indices):把指定 indices 的 KV cache 推到 D 端。

CommonKVSender.poll():检查 transfer 状态,返回 KVPoll (Failed/Bootstrapping/WaitingForInput/Transferring/Success)。

DP attention 下的 prefill dp_rank 注册:_register_prefill_dp_rank() 把 P 端的 dp_rank 注册到 bootstrap server,D 端据此选择正确的 P rank。follow_bootstrap_room 模式用 bootstrap_room % dp_size 决定。

3.5 NIXL 后端(nixl/conn.py) 源码

2717 行,核心:

3.6 Mooncake 后端(mooncake/conn.py) 源码

TransferInfo 结构:

@dataclasses.dataclass
class TransferInfo:
    room: int
    endpoint: str
    dst_port: int
    mooncake_session_id: str
    dst_kv_indices: npt.NDArray[np.int32]
    dst_aux_index: int
    dst_state_indices: List[List[int]]
    required_dst_info_num: int
    is_dummy: bool
    decode_prefix_len: Optional[int] = None
    staging: Optional[StagingTransferInfo] = None

Mooncake 用 mooncake_session_id 标识 session,支持 SGLANG_MOONCAKE_CUSTOM_MEM_POOL 环境变量选择 NVLINK / INTRA_NODE_NVLINK / BAREX 模式。

3.7 HiCache — KV Cache 恢复优化 源码

DecodeHiCachePreallocMixin / DecodeHiCacheTransferMixin:D 端 mixin,支持从 P 端恢复 KV cache 时做 prefix match。

HiCacheRestoreGatedKVReceiver:带门控的 KV receiver,restore 失败时回退到本地重 prefill。

DecodePrefixMatch:在 D 端 radix cache 中匹配已有 prefix,减少需要传输的 KV 量。

3.8 SGLang Router 的 PD 模式 用户文档

sgl_router --pd-disaggregation --prefill <urls> --decode <urls>

Router 把请求先送 P,P 完成 prefill 后把 transfer_info 给 Router,Router 再送 D。

环境变量:

4. LMCache MP 模式 — NixlConnector + ZMQ + P2P

核心思想:LMCache 作为独立 daemon 进程(每节点一个),通过 ZMQ 与 vLLM 实例通信。PD 分离时,每个 vLLM 实例同时挂两个 connector:NixlConnector(当前请求 P→D)+ LMCacheMPConnector(跨请求 KV 复用)。

4.1 架构 设计文档

vLLM Instance(s)
     |
     | ZMQ (tcp)
     v
MessageQueueServer (mq.py)
     |
     | dispatch by RequestType
     v
MPCacheServer (server.py)
     |
     |--- TokenHasher / SessionManager
     |
     v
StorageManager (distributed/storage_manager.py)
     |
     |--- L1Manager (l1_manager.py)
     |       |--- L1MemoryManager (CPU DRAM) or
     |       |    GDSL1MemoryManager (NVMe slab via cuFile / hipFile)
     |       |--- TTLLock per object (read/write)
     |
     |--- StoreController  -----> L2 Adapter(s) (async L1->L2 push)
     |--- PrefetchController ---> L2 Adapter(s) (async L2->L1 load)
     |--- EvictionController ----> L1Manager (watermark-triggered eviction)
     |
     v
EventBus + OTel providers (observability)

Server 入口:lmcache server(推荐,ZMQ + FastAPI HTTP frontend)或 python3 -m lmcache.v1.multiprocess.server(legacy)。

4.2 Disaggregated Prefill(MP 模式) 设计文档

最小部署 5 个进程:

KV source order on prefill path:本地 GPU cache → LMCache → recompute。

配置:

--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both",
  "kv_connector_extra_config":{"connectors":[
    {"kv_connector":"NixlConnector","kv_role":"kv_both"},
    {"kv_connector":"LMCacheMPConnector","kv_role":"kv_both"}]}}'

关键 PR:vllm-project/vllm#46865(MultiConnector real-blocks fix)是 LMCache offload 的前置条件。

4.3 P2P KV Cache Sharing 设计文档

问题:多节点部署时,每个节点独立缓存,相同 prefix 在不同节点需重复 prefill。

解法:P2P 把 per-node caches 变成逻辑上的一个 cache。

三组件:

流程:cache miss → 向拥有 prefix 的 peer 请求 lock-and-locate → 拿到远端地址 → RDMA read 到本地 L1 → 服务请求。

特点:one-sided RDMA read,拥有数据的 node 不被中断;peer 自动发现/断开。

4.4 ZMQ Protocol 设计文档

vLLM ↔ LMCache 用 ZMQ DEALER/ROUTER 模式通信。

RequestType 枚举:

Request TypeHandler描述
REGISTER_KV_CACHESYNC注册 GPU KV cache tensors
UNREGISTER_KV_CACHESYNC反注册
STOREBLOCKINGGPU → L1 (CPU),LMCache-driven,CUDA IPC
RETRIEVEBLOCKINGL1 (CPU) → GPU,LMCache-driven,CUDA IPC
PREPARE_STORE / COMMIT_STOREBLOCKINGEngine-driven path,CPU/accelerator workers
PREPARE_RETRIEVE / COMMIT_RETRIEVEBLOCKINGEngine-driven path

Transfer mode:--supported-transfer-mode 可选 lmcache_driven / engine_driven / auto(默认)。

5. Mooncake — Transfer Engine(外部观察)

⚠️ 注意:github.com/MoonshotAI/Mooncake 仓库已 404(可能已删除或改名)。本报告基于 SGLang/vLLM 集成文档和 stmatengss/SuSG2025-MooncakeTransferEngine 等间接信息。

5.1 已知信息

6. 横向对比 — 关键设计决策

维度vLLMNVIDIA DynamoSGLangLMCache MP
KV 路由方向pull (默认) / push (NixlPushConnector)pullpush (sender → receiver)pull (NixlConnector) + MP server
Preferred P 选择Router/Proxy 任选Planner + Router KV-awaresgl_router 轮询/负载均衡Router 任选
Decode Worker 选择Proxy 拿到 kv_transfer_params 后选 DRouter 用 workload + cache-overlap 打分Bootstrap server 协调,P 主动推Router --vllm-pd-disaggregation
Lease 机制lease + heartbeat,D 从 queueing 开始续租Canary health checkbootstrap_timeout (300s)L1 TTL;NIXL lease
KV Pool 感知LookupBuffer (SQL 语义)KVBM (GPU→CPU→SSD→remote)radix cache prefix matchL1 (CPU DRAM) + L2 (NIXL/GDS)
跨请求 KV 复用Automatic Prefix CachingKV-aware routing 避免重复 prefillHiCache restoreLMCacheMPConnector + P2P
传输后端NIXL (UCX/GDS)NIXL/NVLinkMooncake / NIXL / AscendNIXL + ZMQ
Router 位置外部 (Proxy)内置 (Dynamo Frontend + Router)内置 (sgl_router)外部 (vllm-router)

7. 深度洞察

1. Push vs Pull 的本质是 "谁拥有调度权"

Pull 模式下 D 主动拉,D 端 scheduler 决定何时传输,与本地 batch 调度天然对齐;但 D 端需要知道 P 的地址和 block 布局,耦合度高。Push 模式下 P 主动推,P 端完成即可发送,D 端只需等待 poll;但 D 端需要预分配内存,且需要处理 "P 推完了但 D 还没 prealloc" 的竞态。vLLM 的 NixlPushConnector 用 writer-local matching tables 解决这个竞态。

2. Lease 机制是 PD 分离的 "垃圾回收"

vLLM 的 lease + heartbeat 设计精妙之处在于:lease 是 per-request 不是 per-instance。P 无需知道哪个 D 持有它的 blocks,D 也无需知道 P 的 block 布局。这实现了 P/D 的完全解耦,Router 可以自由选择 D 而不影响 P 的 block 管理。

3. Bootstrap Server 是 SGLang PD 的 "控制平面"

SGLang 的 bootstrap server 是 P 端的一个 HTTP 服务,D 端 receiver 通过它发现 P 端 sender。这个设计把 "P 在哪里" 的发现逻辑从 Router 解耦出来,Router 只需要知道 P 的 bootstrap server 地址即可。bootstrap_room 是 request 级别的会话标识。

4. HiCache — KV Cache 的 "增量传输"

SGLang 的 HiCache 在 D 端 radix cache 中匹配已有 prefix,只传输 "缺失的 suffix"。这大幅减少 KV 传输量,尤其在多轮对话场景。DecodePrefixMatch + HiCacheRestoreGatedKVReceiver 的组合是工程上的精巧设计。

5. LMCache MP — "KV Cache 即服务"

LMCache 把 KV cache 从 vLLM 进程中完全剥离,变成独立 daemon。这带来:① 进程隔离(cache 问题不 crash 推理引擎);② 跨 pod 共享 L1 缓存;③ 多级存储 (L1 CPU DRAM + L2 NVMe/remote);④ P2P 跨节点 RDMA 共享。这是 "KV Cache as a Service" 的范式。

6. 大厂公开信息的局限性

Mooncake 仓库已 404,NVIDIA Dynamo 的 Router 内部实现(scoring 函数、KVBM 细节)未完全开源。实际生产系统的路由策略可能远超公开水平。

8. 访问的 URL 和状态

#URL状态备注
1raw.githubusercontent.com/vllm-project/vllm/main/docs/design/nixl_kv_push_connector.md✅ OK8500+ chars 本地保存
2raw.githubusercontent.com/vllm-project/vllm/main/docs/design/nixl_kv_cache_lease.md✅ OK完整设计文档
3docs.vllm.ai/en/latest/features/disagg_prefill.html❌ 404Rerouted
3braw.githubusercontent.com/vllm-project/vllm/main/docs/features/disagg_prefill.md✅ OK (curl)Markdown 成功获取
4github.com/ai-dynamo/dynamo✅ OKREADME 7.5K stars
5docs.sglang.ai/references/disagg.html❌ 404Not found
5braw.githubusercontent.com/sgl-project/sglang/main/docs_new/docs/advanced_features/pd_disaggregation.mdx✅ OK用户文档
5cgithub.com/sgl-project/sglang/blob/main/python/sglang/srt/disaggregation/{prefill,decode,nixl/conn,mooncake/conn,decode_hicache_mixin,common/conn}.py✅ OK源码级 PD routing 内部实现
6github.com/MoonshotAI/Mooncake❌ 404仓库已删除/改名
7docs.lmcache.ai/✅ OK主站
7braw.githubusercontent.com/LMCache/LMCache/dev/docs/source/mp/{index,disaggregated_prefill,p2p}.rst✅ OKMP 模式内部实现
7craw.githubusercontent.com/LMCache/LMCache/dev/docs/source/disaggregated_prefill/nixl/1p1d.rst✅ OK1P1D 示例

9. 本地研究目录

所有原始资料保存在 /Users/backyes/work/claude_workspace/pd_routing_research/: