动态分块流水线并行 (DeepSeek-V3.1)¶
快速开始¶
vLLM-Ascend 支持动态分块流水线并行(CPP),用于优化流水线并行场景下的预填充性能。此功能自版本 v0.19.1rc1 起支持。
Important
CPP 专为预填充-解码(PD)分离部署中的 P(预填充)节点设计。 D(解码)节点无需配置 CPP。通过基于性能分析数据动态计算最优分块大小,CPP 可显著降低 P 节点上长序列的首令牌时间(TTFT)。
本指南演示了在 3 台 Atlas 800T A3 服务器(64GB × 16)上使用 DeepSeek-V3.1 进行 PD 分离部署:一台服务器作为预填充节点(P 节点,启用 CPP),两台服务器作为解码节点(D 节点,不启用 CPP,采用 DP32 标准配置)。
配置详情请参阅特性指南。
设计详情请参阅设计文档。
有关完整的 PD 分离设置说明(环境验证、Mooncake 安装、代理部署),请参阅:
环境准备¶
模型权重¶
DeepSeek-V3.1-W8A8(量化版本):1 台 Atlas 800T A3(64GB × 16)节点
下载到共享目录,例如 /mnt/weight/
使用 Docker 运行¶
在每个节点上启动 Docker 容器。
export IMAGE=m.daocloud.io/quay.io/ascend/vllm-ascend:v0.23.0
export NAME=vllm-ascend
docker run --rm \
--name $NAME \
--net=host \
--shm-size=1g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci4 \
--device /dev/davinci5 \
--device /dev/davinci6 \
--device /dev/davinci7 \
--device /dev/davinci8 \
--device /dev/davinci9 \
--device /dev/davinci10 \
--device /dev/davinci11 \
--device /dev/davinci12 \
--device /dev/davinci13 \
--device /dev/davinci14 \
--device /dev/davinci15 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /etc/hccn.conf:/etc/hccn.conf \
-v /mnt/weight:/mnt/weight \
-it $IMAGE bash
安装 Mooncake¶
PD 分离的 KV 缓存传输需要 Mooncake。有关安装和编译步骤,请参阅 PD 分离多节点 - 安装 Mooncake。
部署¶
Important
在 PD 分离部署中,仅在 P(预填充)节点上启用 CPP。D(解码)节点不启用流水线并行,专注于低延迟的逐令牌解码。
- 建议使用
MooncakeConnectorV1作为kv_connector,因为它对 PP 提供了更全面的支持。 - 建议不要在 PP 的 P 节点上启用
async-scheduling,因为这可能导致预填充阶段性能下降。
假设 P 服务器 IP 为 192.0.0.1,D 服务器 IP 为 192.0.0.3(解码器 1)和 192.0.0.4(解码器 2)。
#!/bin/sh
unset https_proxy
unset http_proxy
# For nic_name, run the `ifconfig` command to check the network adapter whose IP address is the same as that of the local host.
nic_name="eth0"
local_ip="192.0.0.1"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export OMP_PROC_BIND=false
export PYTORCH_NPU_ALLOC_CONF="expandable_segments:True"
export OMP_NUM_THREADS=1
export HCCL_BUFFSIZE=2048
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD
export HCCL_OP_EXPANSION_MODE="AIV"
export VLLM_USE_V1=1
export TASK_QUEUE_ENABLE=1
export ASCEND_LAUNCH_BLOCKING=0
export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
export VLLM_ASCEND_ENABLE_FLASHCOMM1=1
export VLLM_RPC_TIMEOUT=3600000
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000
export HCCL_EXEC_TIMEOUT=204
export HCCL_CONNECT_TIMEOUT=120
vllm serve /mnt/weight/DeepSeek-V3.1-w8a8 \
--host 0.0.0.0 \
--port 8003 \
--served-model-name model \
--tensor-parallel-size 8 \
--pipeline-parallel-size 2 \
--enable-expert-parallel \
--max-num-seqs 32 \
--max-model-len 131072 \
--max-num-batched-tokens 32768 \
--gpu-memory-utilization 0.9 \
--enable-chunked-prefill \
--enable-prefix-caching \
--no-async-scheduling \
--trust-remote-code \
--quantization ascend \
--additional-config '{
"scheduler_config": {
"profiling_chunk_config": {"enabled":true, "smooth_factor":1.0, "min_chunk":4096}
}
}' \
--kv-transfer-config \
'{
"kv_connector": "MooncakeConnectorV1",
"kv_role": "kv_producer",
"kv_port": "36000",
"engine_id": "0",
"kv_connector_extra_config": {
"prefill": {
"pp_size": 2,
"dp_size": 1,
"tp_size": 8
},
"decode": {
"dp_size": 32,
"tp_size": 1
}
}
}'
如果日志输出 vLLM API server started on 0.0.0.0:8003,则表示服务器已成功启动。
D 节点使用 launch_online_dp.py 在每个节点上启动多个 DP 实例。从仓库获取启动器和模板脚本:
在每个 D 节点上修改 run_dp_template.sh。启动器向模板传递七个位置参数($1–$7)。有关每个参数的完整说明,请参阅 PD 分离多节点。
#!/bin/sh
unset https_proxy
unset http_proxy
nic_name="eth0" # network card name
local_ip="192.0.0.3"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export HCCL_BUFFSIZE=600
export TASK_QUEUE_ENABLE=1
export HCCL_OP_EXPANSION_MODE="AIV"
export VLLM_USE_V1=1
export ASCEND_RT_VISIBLE_DEVICES=$1
vllm serve /mnt/weight/DeepSeek-V3.1-w8a8 \
--host 0.0.0.0 \
--port $2 \
--data-parallel-size $3 \
--data-parallel-rank $4 \
--data-parallel-address $5 \
--data-parallel-rpc-port $6 \
--tensor-parallel-size $7 \
--enable-expert-parallel \
--seed 1024 \
--served-model-name model \
--max-model-len 131072 \
--max-num-batched-tokens 256 \
--max-num-seqs 40 \
--trust-remote-code \
--gpu-memory-utilization 0.94 \
--quantization ascend \
--no-enable-prefix-caching \
--compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \
--additional-config '{
"scheduler_config": {"recompute_scheduler_enable": true},
"multistream_overlap_shared_expert": true,
"finegrained_tp_config": {"lmhead_tensor_parallel_size": 16}
}' \
--kv-transfer-config \
'{
"kv_connector": "MooncakeConnectorV1",
"kv_role": "kv_consumer",
"kv_port": "36000",
"engine_id": "0",
"kv_connector_extra_config": {
"prefill": {
"pp_size": 2,
"dp_size": 1,
"tp_size": 8
},
"decode": {
"dp_size": 32,
"tp_size": 1
}
}
}'
#!/bin/sh
unset https_proxy
unset http_proxy
nic_name="eth0" # network card name
local_ip="192.0.0.4"
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=10
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export HCCL_BUFFSIZE=600
export TASK_QUEUE_ENABLE=1
export HCCL_OP_EXPANSION_MODE="AIV"
export VLLM_USE_V1=1
export ASCEND_RT_VISIBLE_DEVICES=$1
vllm serve /mnt/weight/DeepSeek-V3.1-w8a8 \
--host 0.0.0.0 \
--port $2 \
--data-parallel-size $3 \
--data-parallel-rank $4 \
--data-parallel-address $5 \
--data-parallel-rpc-port $6 \
--tensor-parallel-size $7 \
--enable-expert-parallel \
--seed 1024 \
--served-model-name model \
--max-model-len 131072 \
--max-num-batched-tokens 256 \
--max-num-seqs 40 \
--trust-remote-code \
--gpu-memory-utilization 0.94 \
--quantization ascend \
--no-enable-prefix-caching \
--compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \
--additional-config '{
"scheduler_config": {"recompute_scheduler_enable": true},
"multistream_overlap_shared_expert": true,
"finegrained_tp_config": {"lmhead_tensor_parallel_size": 16}
}' \
--kv-transfer-config \
'{
"kv_connector": "MooncakeConnectorV1",
"kv_role": "kv_consumer",
"kv_port": "36000",
"engine_id": "0",
"kv_connector_extra_config": {
"prefill": {
"pp_size": 2,
"dp_size": 1,
"tp_size": 8
},
"decode": {
"dp_size": 32,
"tp_size": 1
}
}
}'
在每个 D 节点上启动服务:
# on 192.0.0.3 (decoder 1, DP rank 0–15)
python launch_online_dp.py --dp-size 32 --tp-size 1 --dp-size-local 16 --dp-rank-start 0 --dp-address 192.0.0.3 --dp-rpc-port 12321 --vllm-start-port 7100
# on 192.0.0.4 (decoder 2, DP rank 16–31)
python launch_online_dp.py --dp-size 32 --tp-size 1 --dp-size-local 16 --dp-rank-start 16 --dp-address 192.0.0.3 --dp-rpc-port 12321 --vllm-start-port 7100
在与预填充服务实例相同的节点上运行代理服务器。您可以在仓库的示例中获取代理程序:load_balance_proxy_server_example.py
python load_balance_proxy_server_example.py \
--host 192.0.0.1 \
--port 8080 \
--prefiller-hosts 192.0.0.1 \
--prefiller-port 8003 \
--decoder-hosts \
192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 \
192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 192.0.0.3 \
192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 \
192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 192.0.0.4 \
--decoder-ports \
7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 \
7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115
| 参数 | 含义 |
|---|---|
| --port | 代理端口 |
| --prefiller-port | 预填充的所有端口 |
| --decoder-ports | 解码器的所有端口 |
使用代理服务器端点检查服务健康状态。
curl http://192.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "model",
"messages": [
{
"role": "system",
"content": "You are a useful AI assistant."
},
{
"role": "user",
"content": "Question: Janet'\''s ducks lay 16 eggs per day. She eats three for breakfast and bakes muffins with four. She sells the remainder for $2 each. How much does she make?\nAnswer:"
}
],
"max_completion_tokens": 100,
"temperature": 0
}'
要验证 P 节点上的 CPP 是否已激活,请检查启动日志中与性能分析相关的消息,例如 profiling_chunk_config 已启用以及分块大小计算输出。
关键参数
--pipeline-parallel-size 2:启用流水线并行(必需,仅P节点)--enable-chunked-prefill:启用分块预填充(必需,仅P节点)--max-num-batched-tokens 32768:初始分块大小(推荐用于128k序列)profiling_chunk_config.enabled:启用动态分块流水线并行profiling_chunk_config.smooth_factor:平滑因子(0 < x ≤ 1.0)。值越高越信任动态预测profiling_chunk_config.min_chunk:动态计算的最小分块大小。应小于max-num-batched-tokensprofiling_chunk_config.need_timing:启用/禁用在线校准profiling_chunk_config.max_fit_chunk:在线校准的分块时间数据数量。当性能分析失败时应增加使用CPP进行PD分离的关键要点:
配置详情请参阅特性指南。
在线校准¶
为获得最佳性能,请在投产前使用真实数据进行在线校准:
您可以使用 aisbench 生成固定长度的随机数据集。详情请参阅使用 AISBench 进行性能评估。
-
修改
<YOUR_AISBENCH_PATH>/benchmark/ais_bench/datasets/synthetic/synthetic_config.py:synthetic_config = { "Type": "string", "RequestCount": 5, "TrustRemoteCode": False, "StringConfig": { "Input": { "Method": "uniform", "Params": {"MinValue": 131072, "MaxValue": 131072} # Your max sequence length, max-model-len }, "Output": { "Method": "uniform", "Params": {"MinValue": 1, "MaxValue": 1} } }, } -
运行以进行在线校准:
将在线校准数据长度配置为与您的 max-model-len 匹配。使用 batch_size=1 并确保数据不同,以避免在启用前缀缓存时命中缓存。
精度评估¶
详情请参阅使用 AISBench。
| 数据集 | 准确率 |
|---|---|
| gsm8k | 95.83 |
性能基准测试¶
详情请参阅使用 AISBench 进行性能评估。
为了评估动态分块流水线并行在 PD 分离长序列 LLM 推理场景中的有效性,我们使用 DeepSeek-V3.1-W8A8 和 Qwen3-235B,在 Ascend Atlas 800T A3 服务器(64GB × 16)上部署预填充实例,配置和性能数据如下。
固定长度请求,并发数=1:
-
DeepSeek-V3.1-W8A8:
配置 CPP
(动态分块,
块大小=32k)PP
(静态分块,
块大小=32k)输入长度 128k TTFT:22.5s TTFT:27.0s -
Qwen3-235B:
配置 CPP
(动态分块,
块大小=32k)PP
(静态分块,
块大小=32k)输入长度256k TTFT: 53.5s TTFT: 61.4s
可变长度请求,并发数=4:
-
DeepSeek-V3.1-W8A8:
配置 4k~64k 输入, 均值=32k, 标准差=32k
前缀命中率=99%CPP2TP8 输入吞吐量: 22424 tps/卡 DP2TP8 输入吞吐量: 16150 tps/卡 TP16 输入吞吐量: 18875 tps/卡