Files
tech/知识/CSDN博客/小徐炸酱面/2025-12-01_pd分离在vllm中用法.md
T
arno c0ba3fb853 文档(金鹏): 2026-08-06 章节 48 篇文章摘要归档
- 46 篇原文+摘要双文件归档(按 来源/作者 分层,复用本地归档 20 篇+新抓取 26 篇)
- 即梦生成 9 组主题配图(大图+列表缩略图)存入 知识/金鹏/20260806/
- 章节重组为 9 个主题分组并挂接摘要引用
2026-08-06 18:00:50 +08:00

225 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pd分离在vllm中用法
> **来源**:CSDN 博客(博主:小徐炸酱面)
> **作者**:小徐炸酱面(qq_44319972
> **发布日期**2025-12-012025-12-02 修改)
> **原文链接**https://blog.csdn.net/qq_44319972/article/details/155456852
---
### 1. 背景:为什么需要 PD 分离
大模型在线服务一般包含两种阶段:
- **Prefill 阶段**
- 输入:完整的 prompt
- 动作:一次性跑完所有输入 token 的前向,构建 KV Cache
- 特点:
- **计算密集**GEMM-heavy,吃算力)
- 对显存需求大(一次性占用大量 KV)
- 对带宽要求相对没那么极端(一次性大算子)
- **Decode 阶段**
- 输入:每轮只处理很少的新 token(一般 1~几 token
- 动作:基于已有 KV Cache 不断迭代生成新 token
- 特点:
- 单步计算量相对小
- 访问 KV Cache 频繁,**更吃显存带宽 / 内存带宽 / 通信带宽**
- 很多小 kernel,调度频率高
**PD 分离(Producer / Decoder Separation** 的目标就是:
- 把 prefill 和 decode 拆到**不同的进程 / 节点 / 设备**上
- 利用不同节点的算力/带宽特性,整体提升吞吐、降低延迟
### 2. PD 分离的基本概念与架构
#### 2.1 角色定义
- **P 节点(Producer**
- 负责:prefill 阶段
- 对应操作:
- 处理新请求的 prompt
- 运行大批量 GEMM
- 把得到的 KV Cache 写入某种共享介质(或通过 KV 通道发送给 D 节点)
- 资源特征:
- 更看重:**算力、GPU FLOPs**
- 对长尾小请求和频繁通信不是特别敏感
- **D 节点(customer**
- 负责:decode 阶段
- 对应操作:
- 不断从 KV 中取数据
- 迭代生成每个 request 的后续 token
- 资源特征:
- 更看重:**显存带宽 / 网络带宽 / 延迟**
- 关注 tail latencyTPOT、e2e
#### 2.2 KV Cache 的流转
PD 分离的**核心**就是:**prefill 生成的 KV,如何让 decode 能够用起来**。
一般数据流可以抽象成:
1. 请求进来,P 节点接收并执行 prefill
2. P 节点为该 request 分配 KV Block(或 Block 列表)
3. P 节点把 KV 的位置信息 + request 的 metadata 通过某种 **KV Connector / RPC 通道** 告诉 D 节点
4. D 节点接管该 request 的 decode
- 知道对应 KV 在哪里
- 在后续 decode 中,始终通过 KV Connector 访问这些 KV
典型注意事项:
- 如果有 DP/TP,P/D 侧的并行策略也要匹配(否则会出现 KV 维度不一致、block id 对不上等问题)
- 如果跨机器,需要额外考虑网络带宽、RDMA、压缩等问题
### 3. vLLM 中的 PD 分离思路(概念层面)
> 这里以 "vLLM + 自研 PD 功能" 的思路来整理,只写通用逻辑,不绑死具体实现细节。
#### 3.1 与调度器(scheduler)的关系
vLLM 的 serving 里,有一个统一的 scheduler 管理:
- 新请求的进入(prefill 阶段)
- 已有请求的继续 decode
- 每一轮 step,要算多少 token(`num_scheduled_tokens`
- 每个 request 一共已经算了多少 token`num_computed_tokens`
PD 分离后,逻辑可以拆解为:
1. **Producer 侧 scheduler**:只负责 prefill
2. **Decoder 侧 scheduler**:只负责 decode
3. 通过某种方式保证两边对 request 状态的约定一致,例如:
- request id
- 已生成 token 数
- KV block id 列表
- 当前是否已经结束(EOS / abort)
### 4. 在 vLLM 中使用 PD 分离的典型启动方式(示意)
> 下面用伪代码/伪命令展示结构,变量名你可以换成你们环境里的 `VLLM_USE_V1` / `vllm_PD_ROLE` / `kv_transfer_config` 等。
#### 4.1 不分离时(baseline
```
# 单机单进程示意
python3 -m vllm.entrypoints.openai.api_server \
--model /data/models/Qwen3-32B \
--tensor-parallel-size 2 \
--dtype bfloat16 \
--max-model-len 32768 \
--port 8000 \
--host 0.0.0.0
```
Benchmark 示意:
```
python3 benchmarks/benchmark_serving.py \
--backend openai-chat \
--base-url http://<host>:8000 \
--endpoint /v1/chat/completions \
--model Qwen3-32B \
--tokenizer /data/models/Qwen3-32B \
--dataset-name random \
--random-input-len 2000 \
--random-output-len 200 \
--request-rate 5 \
--num-prompts 100 \
--percentile-metrics ttft,tpot,e2el
```
#### 4.2 1P1D(单机或双机)
**4.2.1 Producer 节点**
```
export VLLM_USE_V1=1
export vllm_PD_ROLE=producer
python3 -m vllm.entrypoints.openai.api_server \
--model /data/models/Qwen3-32B \
--tensor-parallel-size 2 \
--max-model-len 32768 \
--max-num-batched-tokens 8192 \
--max-num-seqs 32 \
--port 8209 \
--host 0.0.0.0 \
--kv-transfer-config \
'{"kv_connector":"P2pNcclConnector",
"kv_role":"kv_producer",
"kv_rank":0,
"kv_parallel_size":2,
"kv_buffer_size":1,
"kv_port":9109,
"kv_connector_extra_config":{
"proxy_ip":"0.0.0.0",
"proxy_port":30001,
"http_port":8109
}}' \
&> Qwen3-32B-producer.log &
```
**4.2.2 Decoder 节点(Dkv_consumer**
> ⚠️ 原文此处的代码块与 Producer 节点完全相同(`vllm_PD_ROLE=producer`、`kv_role:"kv_producer"`),疑为作者复制粘贴遗漏修改。按原文忠实保留如下,实际部署时 Decoder 侧应改为 `vllm_PD_ROLE=decoder` / `kv_role:"kv_consumer"`、`kv_rank` 相应调整。
```
export VLLM_USE_V1=1
export vllm_PD_ROLE=producer
python3 -m vllm.entrypoints.openai.api_server \
--model /data/models/Qwen3-32B \
--tensor-parallel-size 2 \
--max-model-len 32768 \
--max-num-batched-tokens 8192 \
--max-num-seqs 32 \
--port 8209 \
--host 0.0.0.0 \
--kv-transfer-config \
'{"kv_connector":"P2pNcclConnector",
"kv_role":"kv_producer",
"kv_rank":0,
"kv_parallel_size":2,
"kv_buffer_size":1,
"kv_port":9109,
"kv_connector_extra_config":{
"proxy_ip":"0.0.0.0",
"proxy_port":30001,
"http_port":8109
}}' \
&> Qwen3-32B-producer.log &
```
**`kv-transfer-config` 参数解释:**
- **`kv_connector`**:用什么通道传 KV。现在用:`P2pNcclConnector`,表示走 NCCL P2P 通信。
- **`kv_role`**:这个进程是**发 KV 的**还是**收 KV 的**。
- P`"kv_producer"`
- D`"kv_consumer"`
- **`kv_rank`**:在这组通道里的编号,从 0 开始。多个并行通道时,用它来区分是谁↔谁。
- **`kv_parallel_size`**:一共有几路 KV 通道(也可以理解为这组里有多少 rank)。一般设成和 TP/world size 一致。P 和 D 必须一样。
- **`kv_buffer_size`**:通道里最多允许多少块 KV "在路上"。值越大,流水越深、占用显存可能越多;不确定就先用 `1`,稳一点。
- **`kv_port`**:KV 通道用的基础端口。P / D 两边要一致,别和其他服务冲突。
**`kv_connector_extra_config` 里的:**
- **`proxy_ip`**:KV 代理服务的 IP,或者要连的那台机的 IP。单机可以写 `0.0.0.0``127.0.0.1`,跨机就写对端/代理机的 IP。
- **`proxy_port`**:KV 代理服务监听的端口。P / D 配成同一个值,比如 30001。
- **`http_port`****Decoder vLLM HTTP 服务的端口**(就是 D 起 server 时的 `--port`)。用来让 KV 组件知道 D 的 HTTP 在哪里。
**关于 xPyD**
一般来说 xPyd,其中每一个 p 节点或者 d 节点都要能独立加载完整个模型。
总卡数量:总 GPU 数 = (P + D) × TP × DP
最后使用 vllm 自带的代理端口转发即可:
```
python3 vllm/examples/online_serving/disaggregated_serving_p2p_nccl_xpyd/disagg_proxy_p2p_nccl_xpyd.py
```
---
> **下载说明**:本文原文含大量 CSDN 页面噪声(侧边栏推荐文章、广告、评论 UI、VIP 弹窗等),已剔除非作者内容,仅保留正文与代码;CSDN 推荐栏中出现的其他博主的 PD 分离相关文章摘要未纳入。