文档(金鹏): 2026-08-06 章节 48 篇文章摘要归档
- 46 篇原文+摘要双文件归档(按 来源/作者 分层,复用本地归档 20 篇+新抓取 26 篇) - 即梦生成 9 组主题配图(大图+列表缩略图)存入 知识/金鹏/20260806/ - 章节重组为 9 个主题分组并挂接摘要引用
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
# vLLM PD 分离
|
||||
|
||||
> **来源**:微信公众平台
|
||||
> **作者**:marcus
|
||||
> **发布日期**:2026-08-05(原文未标注日期,按下载日占位)
|
||||
> **原文链接**:https://mp.weixin.qq.com/s/p6gh4hK25PujQ1UJzFTbLg
|
||||
|
||||
> 本文是 vLLM 基于 Mooncake Connector 实现 PD 分离的源码级机制剖析,覆盖 Proxy 层、请求传递、Scheduler 调度(P producer / D consumer)、Worker 层 RDMA KV Cache 传输、传输后恢复执行、完整时序与关键设计要点。
|
||||
|
||||
---
|
||||
|
||||
## 一、Proxy 层:同一份 Prompt 同时发给 P 和 D
|
||||
|
||||
**是的,Proxy 会把原始 prompt 同时发给 Prefill 和 Decode 节点**,但携带不同的 `kv_transfer_params` 标志:
|
||||
|
||||
```
|
||||
Client Prompt "Hello, how are you?"
|
||||
│
|
||||
▼
|
||||
mooncake_connector_proxy.py
|
||||
│
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
Prefill Decode
|
||||
节点 节点
|
||||
```
|
||||
|
||||
**发给 Prefill 节点**(`send_request_to_service`, 第 250–280 行):
|
||||
|
||||
```
|
||||
req_data["kv_transfer_params"] = {
|
||||
"do_remote_decode": True, # ← "我是 P,算完把 KV 传出去"
|
||||
"do_remote_prefill": False,
|
||||
"transfer_id": f"xfer-{request_id}",
|
||||
}
|
||||
req_data["max_tokens"] = 1 # ← 只算 1 个 token 就停
|
||||
req_data["stream"] = False # ← 不等流式返回
|
||||
```
|
||||
|
||||
以 `asyncio.create_task` fire-and-forget 发送,不等结果。
|
||||
|
||||
**发给 Decode 节点**(`stream_service_response`, 第 283–312 行):
|
||||
|
||||
```
|
||||
req_data["kv_transfer_params"] = {
|
||||
"do_remote_decode": False,
|
||||
"do_remote_prefill": True, # ← "我是 D,从远端的 P 拉 KV Cache"
|
||||
"remote_bootstrap_addr": ..., # ← P 的 bootstrap 地址
|
||||
"remote_engine_id": ..., # ← P 的 Mooncake engine ID(含 DP rank)
|
||||
"transfer_id": f"xfer-{request_id}",
|
||||
}
|
||||
```
|
||||
|
||||
以流式方式等待返回,逐 chunk 回传客户端。
|
||||
|
||||
**每个请求都有唯一的 `transfer_id`(UUID)**——这是 P 和 D 两端关联同一次 KV Cache 传输的关键标识。
|
||||
|
||||
---
|
||||
|
||||
## 二、请求进入引擎:`kv_transfer_params` 的传递路径
|
||||
|
||||
```
|
||||
Proxy → API Server (SamplingParams.extra_args)
|
||||
→ EngineCoreRequest
|
||||
→ Request.__init__() 提取 kv_transfer_params
|
||||
→ Scheduler 调度
|
||||
→ MooncakeConnector 处理
|
||||
```
|
||||
|
||||
在 `vllm/v1/request.py` 第 116–119 行:
|
||||
|
||||
```
|
||||
if sampling_params.extra_args is not None:
|
||||
self.kv_transfer_params = sampling_params.extra_args.get(
|
||||
"kv_transfer_params"
|
||||
)
|
||||
```
|
||||
|
||||
`kv_transfer_params` 作为 Request 对象的属性一直保留,供 Scheduler 和 KVConnector 使用。
|
||||
|
||||
---
|
||||
|
||||
## 三、Scheduler 调度层:区分本地计算和远程 KV 加载
|
||||
|
||||
这是 PD 分离最核心的逻辑,在 `vllm/v1/core/sched/scheduler.py` 的 `schedule()` 方法中。
|
||||
|
||||
### 3.1 Prefill 节点 (is_kv_producer) 的调度
|
||||
|
||||
当 `do_remote_decode=True` 时:
|
||||
|
||||
1. **`get_num_new_matched_tokens`**(MooncakeConnectorScheduler 第 712–755 行):返回 `(0, False)`——P 节点不需要加载任何外部 KV,正常计算 prompt。
|
||||
2. **`update_state_after_alloc`**(第 757–805 行):记录该请求需要将 KV Cache 发送出去:
|
||||
```
|
||||
elif params.get("do_remote_decode"):
|
||||
self._reqs_need_send[request.request_id] = (request, [])
|
||||
```
|
||||
3. **`request_finished`**(第 838–892 行):当请求完成时(只生成了 1 个 token),不立即释放 block,而是标记为需要异步发送:
|
||||
```
|
||||
if delay_free_blocks:
|
||||
self._reqs_need_send[request.request_id] = (
|
||||
request, self.get_sw_clipped_blocks(block_ids),
|
||||
)
|
||||
return delay_free_blocks, None # ← True = 延迟释放
|
||||
```
|
||||
|
||||
### 3.2 Decode 节点 (is_kv_consumer) 的调度
|
||||
|
||||
当 `do_remote_prefill=True` 时:
|
||||
|
||||
1. **`get_num_new_matched_tokens`**:返回 `(num_prompt_tokens, True)`——告诉 Scheduler:"这 N 个 token 的 KV 已经在远端算好了,异步去拉"。
|
||||
```
|
||||
if params.get("do_remote_prefill"):
|
||||
token_ids = request.prompt_token_ids or []
|
||||
count = self._get_remote_prefill_token_count(len(token_ids)) - (
|
||||
num_computed_tokens
|
||||
)
|
||||
if count > 0:
|
||||
return count, True # ← count 是 prompt 长度,True = 异步加载
|
||||
```
|
||||
2. **Scheduler 分配 KV block**(第 953–971 行):`allocate_slots` 中传入 `num_external_computed_tokens`,并为远程 token 预留 block。关键参数:
|
||||
- `delay_cache_blocks=True` — block 分配了但不立即写入 prefix cache(因为数据还在远端)
|
||||
- `reserved_blocks` — 确保有足够空间完成整个异步传输,防止死锁
|
||||
3. **请求进入 `WAITING_FOR_REMOTE_KVS` 状态**(第 1010–1040 行):
|
||||
```
|
||||
if load_kv_async:
|
||||
request.status = RequestStatus.WAITING_FOR_REMOTE_KVS
|
||||
step_skipped_waiting.prepend_request(request)
|
||||
request.num_computed_tokens = num_computed_tokens
|
||||
self._inflight_prefills.add(request)
|
||||
# 跳过 zeroing:异步写入可能与 zeroing 竞态
|
||||
```
|
||||
此时 request 被放入 `skipped_waiting` 队列,不会进入 `running` 列表,本轮不参与 GPU 计算。
|
||||
4. **`build_connector_meta`**:将需要 recv 的请求打包成 `MooncakeConnectorMetadata`(含 `remote_engine_id`、`remote_bootstrap_addr`、`transfer_id`、`local_block_ids`),放入 `SchedulerOutput.kv_connector_metadata` 传递给 Worker。
|
||||
|
||||
---
|
||||
|
||||
## 四、Worker 层:Mooncake 的 KV Cache RDMA 传输
|
||||
|
||||
### 4.1 D 节点 Worker:拉取 KV Cache
|
||||
|
||||
**`start_load_kv`**(`MooncakeConnectorWorker`, 第 2028–2039 行):
|
||||
|
||||
```
|
||||
def start_load_kv(self, metadata: MooncakeConnectorMetadata):
|
||||
if not self.is_kv_producer and metadata.reqs_to_recv:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self._start_load_kv(metadata.reqs_to_recv), self.receiver_loop
|
||||
)
|
||||
```
|
||||
|
||||
**`_start_load_kv` → `handle_new_engine_id` → `receive_kv` → `receive_kv_from_single_worker`**:
|
||||
|
||||
1. 首先通过 `_connect_to_prefiller_bootstrap(remote_bootstrap_addr)` 查询 P 节点的 bootstrap server,获取 P 侧所有 TP/PP Worker 的 ZMQ 地址
|
||||
2. 构建 `MooncakeXferMetadata`(第 1825–1840 行),包含:
|
||||
- `remote_hostname`/`remote_port`:D 侧的地址,P 侧用来做 RDMA 写入
|
||||
- `remote_tp_size`/`remote_tp_rank`:TP 拓扑信息
|
||||
- `req_blocks`:`{req_id: (transfer_id, local_block_ids)}`——D 侧已分配的 block ID
|
||||
- `kv_caches_base_addr`/`block_lens`/`kv_block_lens`:D 侧 KV Cache 的物理内存地址和布局
|
||||
3. 通过 ZMQ DEALER socket 向 P 侧对应 TP rank 的 Worker 发送此元数据
|
||||
4. P 侧收到后,通过 RDMA (`batch_transfer_sync_write`) 将 KV Cache 数据直接写入 D 侧的 GPU 内存地址
|
||||
5. 传输完成后,D 侧标记 `finished_recving_reqs`
|
||||
|
||||
### 4.2 P 节点 Worker:推送 KV Cache
|
||||
|
||||
**`record_send_reqs`**(第 2000–2026 行):P 侧的 Worker 将 Scheduler 传递来的 `reqs_to_send`(含 `transfer_id` 和 `local_block_ids`)记录到 `self.reqs_need_send` 字典。
|
||||
|
||||
**`send_kv_to_decode`**(第 1193–1382 行):当 D 侧的 ZMQ 请求到达 P 侧时触发,核心逻辑:
|
||||
|
||||
1. **等待 P 端 block 就绪**:`send_meta.ready.wait()`——P 侧的 forward pass 可能尚未完成
|
||||
2. **地址计算**(`_build_transfer_params` 第 1424–1614 行):
|
||||
- 逐层、逐 block 计算 P 侧源地址和 D 侧目标地址
|
||||
- 处理 TP 异构(不同 TP 大小之间映射)
|
||||
- 处理 HMA(Hybrid Memory Allocator)多 KV group
|
||||
- 处理 Mamba/GDN 状态块
|
||||
3. **RDMA 传输**(`_send_blocks` 第 1622–1651 行):
|
||||
```
|
||||
ret_value = self.engine.batch_transfer_sync_write(
|
||||
remote_session, src_ptrs, dst_ptrs, lengths
|
||||
)
|
||||
```
|
||||
P 侧直接通过 RDMA/NVLink 将 KV Cache 字节写入 D 侧的 GPU 内存。
|
||||
4. 传输完成后,标记 `finished_sending_reqs`,释放 P 侧的 block。
|
||||
|
||||
---
|
||||
|
||||
## 五、KV Cache 传输完成后:请求恢复执行
|
||||
|
||||
### 5.1 Scheduler 收到传输完成信号
|
||||
|
||||
`update_from_output` → `_update_from_kv_xfer_finished`(第 2713–2740 行):
|
||||
|
||||
```
|
||||
for req_id in kv_connector_output.finished_recving or ():
|
||||
req = self.requests[req_id]
|
||||
if req.status == RequestStatus.WAITING_FOR_REMOTE_KVS:
|
||||
self.finished_recving_kv_req_ids.add(req_id)
|
||||
```
|
||||
|
||||
### 5.2 下一轮调度时提升请求状态
|
||||
|
||||
`_try_promote_blocked_waiting_request`(第 2677–2711 行):
|
||||
|
||||
```
|
||||
if request.status == RequestStatus.WAITING_FOR_REMOTE_KVS:
|
||||
if request.request_id not in self.finished_recving_kv_req_ids:
|
||||
return False
|
||||
self._update_waiting_for_remote_kv(request)
|
||||
request.status = RequestStatus.WAITING # 回到正常调度队列
|
||||
```
|
||||
|
||||
`_update_waiting_for_remote_kv`(第 2634–2675 行):
|
||||
|
||||
- 将接收到的 KV block 写入 prefix cache(`kv_cache_manager.cache_blocks`)
|
||||
- 如果全部 prompt token 都命中,减少 1 个 computed token 以确保最后 1 个 token 被本地重算(用于生成第一个 output token)
|
||||
|
||||
### 5.3 正常调度执行
|
||||
|
||||
此时 request 回到 `WAITING` 状态,在下一轮 `schedule()` 中被正常调度:
|
||||
|
||||
- `num_computed_tokens` 已经包含了外部加载的 token 数
|
||||
- Scheduler 从 `num_computed_tokens` 之后开始分配新的 compute
|
||||
- 新分配的 token(通常只有 1 个 decode token)在本地 GPU 上执行
|
||||
|
||||
---
|
||||
|
||||
## 六、完整时序总结
|
||||
|
||||
```
|
||||
┌─ Proxy ──────────────────────────────────────────────────────────────┐
|
||||
│ ① POST /v1/chat/completions {"messages": [...], "max_tokens": 100} │
|
||||
│ ② asyncio.create_task() ③ Stream Decode Response │
|
||||
│ → P Node (fire & forget) → D Node (等待返回) │
|
||||
│ kv_transfer_params: kv_transfer_params: │
|
||||
│ do_remote_decode=True do_remote_prefill=True │
|
||||
│ max_tokens=1 remote_bootstrap_addr=<P地址> │
|
||||
│ stream=False remote_engine_id=<P engine id> │
|
||||
│ transfer_id=xfer-UUID transfer_id=xfer-UUID │
|
||||
└───────┬─────────────────────────────────┬───────────────────────────┘
|
||||
┌───────▼── P Node ───────────┐ ┌───────▼── D Node ────────────────────┐
|
||||
│ ④ Request 进入 Scheduler │ │ ⑤ Request 进入 Scheduler │
|
||||
│ is_kv_producer=True │ │ is_kv_consumer=True │
|
||||
│ ⑥ get_num_new_matched_tokens │ │ ⑦ get_num_new_matched_tokens │
|
||||
│ → (0, False) 正常计算 │ │ → (N_prompt, True) 远端加载 │
|
||||
│ ⑧ Scheduler 分配 block │ │ ⑨ Scheduler 分配 block │
|
||||
│ → RUNNING 状态 │ │ → WAITING_FOR_REMOTE_KVS 状态 │
|
||||
│ ⑩ GPU forward pass │ │ ⑪ Worker.start_load_kv() │
|
||||
│ KV Cache 写入本地显存 │ │ → ZMQ 发送 MooncakeXferMetadata │
|
||||
│ ⑫ send_kv_to_decode() │ │ (含 D 侧 block 物理地址) │
|
||||
│ ← ZMQ 收到 D 的请求 │◄───── ZMQ ────────────────────────────│
|
||||
│ → batch_transfer_sync_write│ │ │
|
||||
│ → RDMA 写入 D 侧 GPU 显存 ═══════ RDMA/NVLink ═══════════════════► │
|
||||
│ ⑬ finished_sending_reqs │ │ ⑭ finished_recving_reqs │
|
||||
│ 释放 P 侧 block │ │ ⑮ WAITING_FOR_REMOTE_KVS → WAITING │
|
||||
│ │ │ ⑯ 下一轮 schedule() 正常调度 decode │
|
||||
│ │ │ ⑰ GPU forward pass 自回归生成 output │
|
||||
│ │ │ ⑱ 流式返回 tokens → Proxy → Client │
|
||||
└──────────────────────────────┘ └──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、关键设计要点
|
||||
|
||||
1. **P 和 D 收到的 prompt 完全一致**,但通过 `kv_transfer_params` 标志区分行为。P 正常计算整个 prompt,D 跳过本地计算而从 P 拉取 KV Cache。
|
||||
2. **`transfer_id` 是连接 P/D 的关键**:Proxy 生成 UUID,同时发给 P 和 D。P 侧通过 `reqs_need_send[transfer_id]` 等待 D 侧通过 ZMQ 发来的拉取请求(携带同样的 `transfer_id`),从而匹配同一请求。
|
||||
3. **D 侧 block 物理地址是传输的基础**:D 侧在 `receive_kv_from_single_worker` 中将自己的 `kv_caches_base_addr` + `block_lens` + `block_ids` 发给 P,P 侧据此计算出 RDMA 的目标地址,直接写入 D 的 GPU 显存。
|
||||
4. **异步非阻塞设计**:D 侧在等待 KV 传输时处于 `WAITING_FOR_REMOTE_KVS` 状态,不占用 GPU 计算资源。同时 P 侧的发送也通过独立的后台线程池异步执行,不阻塞 P 的 forward pass。
|
||||
5. **`remote_engine_id` 区分不同 DP rank 的 KV Cache**:在 DP(数据并行)场景下,每个 DP rank 有独立的 KV Cache 分区。`remote_engine_id` 包含 `engine_id_dp{N}` 后缀,精确定位到哪个 DP rank 的 KV Cache。
|
||||
@@ -0,0 +1,80 @@
|
||||
# 📊 文章摘要:vLLM PD 分离
|
||||
|
||||
> **原文**:[2026-08-05_vLLM_PD分离_源码级机制_marcus.md](./2026-08-05_vLLM_PD分离_源码级机制_marcus.md)
|
||||
> **原文链接**:https://mp.weixin.qq.com/s/p6gh4hK25PujQ1UJzFTbLg
|
||||
> **来源**:微信公众平台(marcus)
|
||||
> **作者**:marcus
|
||||
> **发布日期**:2026-08-05(原文未标注日期,按下载日占位)
|
||||
> **摘要日期**:2026-08-06
|
||||
> **价值评级**:⭐⭐⭐ 高
|
||||
|
||||
---
|
||||
|
||||
## 核心命题
|
||||
|
||||
> **源码级解构** — 把 PD 分离从概念论述落到 vLLM + Mooncake Connector 的代码行号级机制,是本批 PD 分离文章中唯一触及实现细节的第一手资料。
|
||||
|
||||
---
|
||||
|
||||
## 文章概要
|
||||
|
||||
本文逐层拆解 vLLM 基于 Mooncake Connector 实现 PD 分离的完整代码路径:Proxy 将同一 prompt 双发至 P/D 节点并携带不同标志、Scheduler 通过 `WAITING_FOR_REMOTE_KVS` 状态机暂停等待远端 KV、Worker 层经 ZMQ 握手后以 RDMA 将 KV Cache 直写 D 侧 GPU 显存、传输完成后请求恢复调度。其价值在于回答了其他文章不回答的"怎么实现"问题——`transfer_id` 如何关联 P/D、D 侧 block 物理地址如何成为传输基础、为何要"少算 1 个 token 本地重算"。局限在于只覆盖 Mooncake Connector 一种实现,无性能数据,不讨论 P:D 配比与传输延迟等工程参数。
|
||||
|
||||
---
|
||||
|
||||
## 关键要点
|
||||
|
||||
1. **Proxy 双发请求** — 同一 prompt 同时发给 P 和 D,靠 `kv_transfer_params` 标志区分角色:P 侧 `do_remote_decode=True` 且 `max_tokens=1` fire-and-forget,D 侧 `do_remote_prefill=True` 流式等待返回。 `[分类: 共识]`
|
||||
2. **`transfer_id` 关联机制** — Proxy 生成 UUID 同时发给两端,P 侧以 `reqs_need_send[transfer_id]` 等待 D 侧经 ZMQ 发来的拉取请求,是 P/D 匹配同一请求的唯一纽带。 `[分类: 共识]`
|
||||
3. **D 侧调度暂停态** — `WAITING_FOR_REMOTE_KVS` 状态下请求不进入 running 队列、不占 GPU 计算资源,并跳过 zeroing 以避免与异步写入竞态;`reserved_blocks` 预留空间防死锁。 `[分类: 共识]`
|
||||
4. **RDMA 直写远端显存** — D 侧将自己的 `kv_caches_base_addr`/`block_lens`/`block_ids` 经 ZMQ 发给 P,P 用 `batch_transfer_sync_write` 直接写入 D 的 GPU 内存,D 侧零计算加载;`remote_engine_id` 的 DP rank 后缀用于定位不同数据并行分区的 KV。 `[分类: 共识]`
|
||||
5. **传输后恢复执行** — 完成后 KV 写入 prefix cache,并故意减少 1 个 computed token 让最后 1 个 prompt token 本地重算以生成首个输出 token;P 侧发送走后台线程池,不阻塞 forward pass。 `[分类: 共识]`
|
||||
6. **机制层面未触及的性能问题** — 全文只讲"如何实现"不讲"收益几何",P:D 比例、传输延迟、SLO 达成等维度完全缺席,需与性能类文章(如 DistServe 解读)互补阅读。 `[分类: 未探索]`
|
||||
|
||||
---
|
||||
|
||||
## 批判性分析
|
||||
|
||||
### 假设前提
|
||||
|
||||
作者立论建立在三个前提上:读者熟悉 vLLM v1 架构与 Mooncake 生态;该实现路径是 PD 分离的"典型范式"——但 SGLang、TensorRT-LLM 等引擎的实现机制并不相同;RDMA/NVLink 高速互联为默认可用资源。
|
||||
|
||||
### 论据与逻辑
|
||||
|
||||
论据为具体源码行号(`scheduler.py` 712-755 行、`MooncakeConnectorWorker` 2028-2039 行等),证据直接、链条完整,从请求进入、调度、传输到恢复执行全程无逻辑跳跃,可信度高。但全文无任何运行数据或基准测试,无法证明"这套机制实际有效、开销几何"——机制正确性与性能有效性是两回事。
|
||||
|
||||
### 边界与局限
|
||||
|
||||
结论仅适用于 vLLM + Mooncake Connector 这一特定组合;文中机制(如延迟释放 block、传输后减少 1 个 computed token)属于工程实现细节而非普适原理;不适用于评估 PD 分离的收益/代价,也不覆盖 D 节点等待期间的整体调度吞吐影响。对只想了解 PD 分离概念与价值的读者,本文过于深入。
|
||||
|
||||
---
|
||||
|
||||
## 可引用金句
|
||||
|
||||
> "每个请求都有唯一的 `transfer_id`(UUID)——这是 P 和 D 两端关联同一次 KV Cache 传输的关键标识。"
|
||||
|
||||
> "P 和 D 收到的 prompt 完全一致,但通过 `kv_transfer_params` 标志区分行为。"
|
||||
|
||||
---
|
||||
|
||||
## 总体评价
|
||||
|
||||
**亮点**:
|
||||
- 全链路(Proxy→Scheduler→Worker→恢复执行)代码级剖析,行号精确,是稀缺的一手工程资料
|
||||
- 清晰回答了"KV 如何从 P 到 D、请求如何暂停与恢复"这一概念文章普遍回避的实现问题
|
||||
- 时序图(完整时序总结)直观完整,可作为机制速查图
|
||||
|
||||
**不足**:
|
||||
- 无任何性能数据,机制正确性与性能收益之间留白
|
||||
- 未讨论 P:D 配置、传输延迟、失败处理等工程参数
|
||||
- 依赖 Mooncake Connector 特定实现,通用性未说明
|
||||
|
||||
**适用场景**:推理系统工程师、vLLM 二次开发者、想从"知道 PD 分离"进阶到"读懂 PD 分离代码"的读者;面试中需要讲解 PD 分离实现细节时的高价值素材。
|
||||
|
||||
**关联建议**:搭配本文同批的 DistServe 解读(原理与收益)与丁师兄文章(落地代价)构成"原理—机制—代价"完整链条;可进一步阅读 vLLM `v1/core/sched/scheduler.py` 中 `MooncakeConnectorScheduler` 与 `MooncakeConnectorWorker` 的完整源码,以及 Mooncake 项目关于 KVCache 传输的官方文档。
|
||||
|
||||
---
|
||||
|
||||
## 配图
|
||||
|
||||

|
||||
Reference in New Issue
Block a user