当大模型参数量突破单卡显存上限时,工程师首先想到的是张量并行(Tensor Parallelism, TP)——把每一层的权重切分到多张卡上,所有卡同时计算同一层的不同部分。但 TP 有一个致命瓶颈:每层都需要 All-Reduce 通信,卡数越多通信开销越大,通常超过 8 卡后扩展效率急剧下降。这时候,流水线并行(Pipeline Parallelism, PP)就成了关键武器——它把模型的不同层分配到不同卡上,卡之间只在层边界传递激活值,通信量与卡数无关。在百亿、千亿参数模型的推理部署中,PP 与 TP 的组合使用是工业界标准方案。
本文将从流水线并行的底层原理出发,深入解析 PP 的切分策略、气泡(Bubble)问题及优化方案,并通过 vLLM 的实际配置演示如何在多卡环境下部署流水线并行推理服务。
一、流水线并行的核心原理:把模型当作流水线
流水线并行的思想非常直观:想象一条工厂流水线,产品依次经过工位 1、工位 2、工位 3……每个工位只负责一道工序。在 LLM 推理中,我们把 Transformer 模型的 L 层按顺序切分到 N 张 GPU 上。例如一个 80 层的模型部署在 4 张卡上,GPU 0 负责第 0-19 层,GPU 1 负责第 20-39 层,GPU 2 负责第 40-59 层,GPU 3 负责第 60-79 层。
前向传播时,输入 token 经过 GPU 0 的 20 层计算后,将中间激活值(activation)通过点对点通信发送给 GPU 1,GPU 1 继续计算 20 层后传给 GPU 2,以此类推。与 TP 不同,PP 的通信只在相邻卡之间发生,且通信内容仅为激活值(而非梯度和权重碎片),通信量与卡数无关。
1.1 PP 与 TP 的本质区别
| 维度 | 张量并行 (TP) | 流水线并行 (PP) |
|---|---|---|
| 切分对象 | 每层的权重矩阵 | 不同的层(Layer 级别) |
| 通信模式 | 每层 All-Reduce | 层边界点对点 Send/Recv |
| 通信量 | 随卡数线性增长 | 与卡数无关(固定为激活值大小) |
| 显存节省 | 每层权重 ÷ N | 总层数 ÷ N,每卡只存部分层 |
| 扩展上限 | 通常 ≤ 8 卡(受通信限制) | 可达数十卡 |
| 延迟特性 | 低延迟(并行计算) | 较高延迟(串行流水线) |
| 带宽要求 | 高(需要 NVLink 级带宽) | 低(PCIe 即可满足) |
关键洞察:TP 适合在同一个节点的 NVLink 互联域内使用(通常 4-8 卡),而 PP 可以跨节点部署,因为 PP 的点对点通信只需要传输激活值,对带宽要求远低于 TP 的 All-Reduce。这就是为什么千亿参数模型的典型部署方案是 TP=8(节点内)× PP=N(跨节点)。
二、气泡问题:流水线并行的最大痛点
流水线并行的最大问题是流水线气泡(Pipeline Bubble)——在流水线填充和排空阶段,部分 GPU 处于空闲状态。理解气泡的成因是优化 PP 性能的前提。
2.1 简单流水线(GPipe 方式)的气泡分析
假设模型被切分到 4 张卡(PP=4),一个 batch 的前向传播需要依次经过 GPU 0→1→2→3。在朴素流水线中,必须等整个 batch 的前向传播完成后才能开始下一个 batch,这会产生大量空闲时间:
1
2
3
4
5
6
7
8 时间 →
GPU 0: [====前向====][ 空闲 ][====下一个batch====]
GPU 1: [空闲][====前向====][ 空闲 ]
GPU 2: [空闲][====前向====][ 空闲 ]
GPU 3: [空闲][====前向====][ 空闲 ]
气泡比例 = (PP - 1) / (PP - 1 + num_micro_batches)
当 PP=4, num_micro_batches=1 时,气泡比例 = 3/4 = 75%
气泡比例公式为
1 | (PP - 1) / (PP - 1 + M) |
,其中 PP 是流水线深度,M 是 micro-batch 数量。当 M 远大于 PP 时,气泡比例趋近于 0。这就是为什么流水线并行需要微批次(Micro-batching)技术——把一个大 batch 切分成多个小 micro-batch,让它们像流水线上的产品一样依次注入,填满流水线。
2.2 微批次填充:用并发填满流水线
1
2
3
4
5
6
7
8 时间 → t0 t1 t2 t3 t4 t5 t6 t7
GPU 0: [m1] [m2] [m3] [m4] [m1] [m2] [m3] [m4] ← 前向+后向
GPU 1: [m1] [m2] [m3] [m4] [m1] [m2] [m3] [m4]
GPU 2: [m1] [m2] [m3] [m4] [m1] [m2] [m3] [m4]
GPU 3: [m1] [m2] [m3] [m4] [m1] [m2] [m3] [m4]
↑ 填充阶段 ↑ ↑ 稳态阶段 ↑ ↑ 排空阶段 ↑
气泡比例 = (PP - 1) / (PP - 1 + M) = 3 / (3 + 4) ≈ 43%
当 M=4、PP=4 时,气泡从 75% 降到 43%。继续增大 M 到 16 时,气泡降到 3/(3+16) ≈ 16%。但增大 M 也意味着更大的显存占用(需要缓存更多 micro-batch 的激活值),需要在气泡和显存之间做权衡。
三、1F1B 调度:推理场景下的气泡优化
在训练场景中,1F1B(One Forward One Backward)调度是主流方案,它通过交错前向和后向来减少激活值缓存需求。但在推理场景中,没有后向传播,气泡优化的策略有所不同。

3.1 推理场景的连续流水线填充
在推理服务中,请求是持续到达的。关键优化是让请求以 micro-batch 形式持续注入流水线,保持所有 GPU 尽可能忙碌。vLLM 的 PP 实现采用了以下策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 # vLLM 流水线并行的核心调度逻辑(简化版)
class PipelineParallelScheduler:
def __init__(self, num_stages, num_micro_batches):
self.num_stages = num_stages # PP 深度
self.num_micro_batches = num_micro_batches
self.stage_id = None # 当前 GPU 的 stage 编号
def schedule_forward(self, requests):
"""将请求切分为 micro-batch 并注入流水线"""
micro_batches = self.split_into_micro_batches(requests)
for i, mb in enumerate(micro_batches):
# 非最后一个 stage:计算后发送激活值给下一个 stage
if self.stage_id < self.num_stages - 1:
output = self.forward_layers(mb)
self.send_activation(output, dest=self.stage_id + 1)
# 非第一个 stage:先接收前一个 stage 的激活值
if self.stage_id > 0:
mb.input = self.recv_activation(src=self.stage_id - 1)
output = self.forward_layers(mb)
# 最后一个 stage:输出结果
if self.stage_id == self.num_stages - 1:
yield output
3.2 交错流水线(Interleaved PP)
Megatron-LM 提出了交错流水线(Interleaved Pipeline)方案,将模型的 L 层分成 V 个 chunk,每个 GPU 负责不连续的多个 chunk。例如 16 层模型、4 卡、V=2:GPU 0 负责 [0-1, 8-9],GPU 1 负责 [2-3, 10-11],GPU 2 负责 [4-5, 12-13],GPU 3 负责 [6-7, 14-15]。
1
2
3
4
5
6
7
8
9 交错 PP 调度(V=2, PP=4):
GPU 0: [c0-m1][c0-m2][c4-m1][c4-m2] ← chunk 0 和 chunk 4 交替
GPU 1: [c1-m1][c1-m2][c5-m1][c5-m2]
GPU 2: [c2-m1][c2-m2][c6-m1][c6-m2]
GPU 3: [c3-m1][c3-m2][c7-m1][c7-m2]
气泡比例 = (PP - 1) / (V × (PP - 1) + M)
当 V=2, PP=4, M=4 时: 气泡 = 3 / (2×3 + 4) = 3/10 = 30%
交错 PP 将气泡从 (PP-1)/(PP-1+M) 降低到 (PP-1)/(V×(PP-1)+M),代价是通信次数增加 V 倍。在推理场景中,由于没有后向传播,交错 PP 的收益相对训练场景较小,但对于长序列推理(Prefill 阶段计算量大)仍有价值。
四、vLLM 流水线并行部署实战
vLLM 从 0.4 版本开始支持流水线并行,通过
1 | --pipeline-parallel-size |
参数配置。下面是一个完整的多卡 PP 部署示例。
4.1 基本部署:纯 PP 模式
1
2
3
4
5
6
7
8 # 部署 Llama-3-70B 模型,使用 4 卡流水线并行
# 每卡负责约 20 层(70B 模型共 80 层)
vllm serve meta-llama/Meta-Llama-3-70B \
--pipeline-parallel-size 4 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--port 8000
这个配置将 70B 模型均匀切分到 4 张 GPU 上,每卡约 17.5GB 权重(70B × 2 bytes / 4 ≈ 35GB ÷ 2 = 17.5GB per FP16),加上 KV Cache 和激活值,单卡显存占用约 30-40GB,适合 A100-40G 或 A100-80G。
4.2 TP + PP 混合并行:千亿参数部署
对于 405B 级别的模型,单靠 TP 或 PP 都不够。标准方案是节点内 TP=8、跨节点 PP=N:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 # 部署 Llama-3-405B-FP8,2 个节点,每节点 8×H100
# 节点内 TP=8(NVLink 互联),跨节点 PP=2
# 总 GPU = 8 × 2 = 16
# 节点 0(PP rank 0)
vllm serve meta-llama/Meta-Llama-3-405B-FP8 \
--tensor-parallel-size 8 \
--pipeline-parallel-size 2 \
--pipeline-parallel-rank 0 \
--pipeline-parallel-size-total 2 \
--gpu-memory-utilization 0.92 \
--max-model-len 4096 \
--quantization fp8 \
--port 8000 \
--distributed-executor-backend ray
# 节点 1(PP rank 1)
vllm serve meta-llama/Meta-Llama-3-405B-FP8 \
--tensor-parallel-size 8 \
--pipeline-parallel-size 2 \
--pipeline-parallel-rank 1 \
--pipeline-parallel-size-total 2 \
--gpu-memory-utilization 0.92 \
--max-model-len 4096 \
--quantization fp8 \
--port 8000 \
--distributed-executor-backend ray
4.3 层切分策略:均匀 vs 非均匀
vLLM 默认采用均匀切分,但不同层的计算量并不相同(例如 Embedding 层和 LM Head 层的计算量远小于 Transformer 层)。对于非均匀模型,可以手动指定切分点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 # 自定义层切分策略
from vllm.distributed.pipeline_parallel import get_pipeline_parallel_layer_split
# 假设模型有 80 层,切分到 4 卡
# 方案 A:均匀切分(默认)
# GPU 0: layers 0-19, GPU 1: layers 20-39,
# GPU 2: layers 40-59, GPU 3: layers 60-79
# 方案 B:非均匀切分(考虑 Embedding 和 LM Head)
# GPU 0: layers 0-22 (含 Embedding)
# GPU 1: layers 23-41
# GPU 2: layers 42-60
# GPU 3: layers 61-79 (含 LM Head)
split_indices = [23, 42, 61] # 3 个切分点 → 4 段
# 在 vLLM 启动参数中通过环境变量指定
# export VLLM_PP_LAYER_SPLIT="23,42,61"
非均匀切分的目标是让每张卡的前向计算时间尽可能相等,因为流水线的吞吐量受限于最慢的 stage(木桶效应)。实践中,可以通过 profiling 每层的 FLOPs 和显存占用来确定最优切分点。
五、PP 推理性能调优与常见问题
5.1 通信优化:NCCL 点对点操作
PP 的核心通信是相邻 stage 之间的 Send/Recv。vLLM 使用 NCCL 的
1 | ncclSend |
和
1 | ncclRecv |
原语实现点对点通信:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 # vLLM PP 通信核心代码(简化)
import torch.distributed as dist
class PipelineParallelCommunicator:
def send_activation(self, tensor, dest_rank):
"""发送激活值到下一个 stage"""
# 使用 NCCL Send,非阻塞
dist.send(tensor, dst=dest_rank)
def recv_activation(self, src_rank, shape, dtype):
"""从前一个 stage 接收激活值"""
tensor = torch.empty(shape, dtype=dtype, device='cuda')
dist.recv(tensor, src=src_rank)
return tensor
def send_recv_overlap(self, send_tensor, recv_shape, recv_dtype,
dest_rank, src_rank):
"""通信与计算重叠:用 CUDA Stream 实现"""
send_stream = torch.cuda.Stream()
recv_stream = torch.cuda.Stream()
with torch.cuda.stream(send_stream):
dist.send(send_tensor, dst=dest_rank)
with torch.cuda.stream(recv_stream):
recv_tensor = torch.empty(recv_shape, dtype=recv_dtype,
device='cuda')
dist.recv(recv_tensor, src=src_rank)
# 等待通信完成
send_stream.synchronize()
recv_stream.synchronize()
return recv_tensor
通信与计算重叠(Overlap)是 PP 性能调优的关键。通过将 Send/Recv 操作放在独立的 CUDA Stream 上,可以让通信和前向计算并行执行,隐藏通信延迟。在跨节点 PP 部署中,这一点尤为重要——InfiniBand 的延迟约 1-2μs,如果通信和计算不重叠,每个 micro-batch 都要付出额外的通信等待。
5.2 KV Cache 在 PP 中的分布
在 PP 模式下,KV Cache 也分布在不同的 GPU 上。每个 stage 只存储自己负责层的 KV Cache。这意味着:
1
2
3
4
5
6
7
8
9 模型: 80 层, PP=4, 每卡 20 层
序列长度: 4096, KV Cache per layer ≈ 32MB (FP16)
GPU 0: KV Cache = 20 × 32MB = 640MB
GPU 1: KV Cache = 20 × 32MB = 640MB
GPU 2: KV Cache = 20 × 32MB = 640MB
GPU 3: KV Cache = 20 × 32MB = 640MB
总 KV Cache = 2560MB(与单卡部署相同,只是分散存储)
PP 的一个优势是 KV Cache 的增长不会集中在单卡上。但需要注意的是,最后一个 stage(GPU 3)通常还需要存储 LM Head 的权重和输出 logits,显存压力可能比其他 stage 更大。在调整
1 | gpu_memory_utilization |
时要考虑这一点。
5.3 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| PP stage 间通信超时 | NCCL 跨节点初始化失败 | 检查 IB 驱动、设置 NCCL_SOCKET_IFNAME |
| 某些 GPU 利用率明显偏低 | 层切分不均匀导致木桶效应 | 用 Nsight Systems profiling 各 stage 耗时,调整切分点 |
| 吞吐量不如纯 TP | 气泡比例过高(M 太小) | 增大 max_num_seqs 以提高 micro-batch 数量 |
| 首 token 延迟(TTFT)过高 | PP 串行特性导致 Prefill 延迟叠加 | 对延迟敏感场景优先用 TP,PP 适合吞吐优先 |
| OOM 在最后一个 stage | LM Head + logits 占用额外显存 | 非均匀切分,给最后 stage 分配更少层 |
六、PP vs TP vs DP:推理场景选型决策
在实际部署中,如何选择并行策略?以下是基于实际经验的决策框架:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 ┌─────────────────────────────────────────────────────────────┐
│ 并行策略选择决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 模型能放进单卡? │
│ ├─ 是 → 不需要并行,单卡部署 │
│ └─ 否 → 模型能放进单节点(8卡 NVLink)? │
│ ├─ 是 → TP=8,节点内张量并行 │
│ └─ 否 → 需要跨节点 │
│ ├─ 延迟敏感(< 500ms TTFT)? │
│ │ └─ TP=8 × PP=2,尽量减少 PP 深度 │
│ └─ 吞吐优先? │
│ └─ TP=8 × PP=N,N 根据模型大小确定 │
│ └─ 请求量大?叠加 DP(数据并行)多副本 │
│ │
└─────────────────────────────────────────────────────────────┘
核心原则:TP 优先于 PP,因为 TP 的延迟更低(并行计算 vs 串行流水线)。只有当 TP 达到扩展上限(通常 8 卡)仍无法容纳模型时,才引入 PP。对于推理服务,PP 更适合吞吐优先的场景(如批量处理、离线推理),对延迟优先的场景(如实时对话)要谨慎使用 PP,因为流水线的串行特性会叠加每卡的延迟。
6.1 三种并行策略的适用场景对比
| 策略 | 适用模型规模 | 延迟 | 吞吐 | 扩展性 | 典型配置 |
|---|---|---|---|---|---|
| TP only | ≤ 70B (FP16) / ≤ 180B (FP8) | 低 | 中 | ≤ 8 卡 | TP=4 或 TP=8 |
| PP only | 70B-400B | 高 | 高 | 数十卡 | PP=4 到 PP=16 |
| TP + PP | 400B+ | 中高 | 高 | 数百卡 | TP=8 × PP=2-16 |
| TP + PP + DP | 任意规模 | 中高 | 极高 | 无上限 | 多副本 TP+PP |
总结
流水线并行是大模型推理部署中不可或缺的并行策略。与张量并行互补,PP 通过层间切分突破了 TP 的扩展上限,使得千亿参数模型的推理成为可能。关键要点回顾:
- PP 的核心优势是通信量与卡数无关,适合跨节点扩展;核心劣势是流水线气泡和串行延迟。
- 气泡优化的关键是增大 micro-batch 数量 M,使
1(PP-1)/(PP-1+M)
趋近于 0;交错 PP 可进一步降低气泡。
- vLLM PP 部署通过
1--pipeline-parallel-size
参数配置,支持 TP+PP 混合并行,是当前最易用的 PP 推理框架。
- 选型原则:TP 优先于 PP,PP 用于突破 TP 扩展上限;延迟敏感场景慎用 PP,吞吐优先场景适合 PP。
- 通信优化:利用 CUDA Stream 实现 Send/Recv 与计算的 Overlap,隐藏跨节点通信延迟。
在百亿到千亿参数模型的推理部署中,TP+PP 的混合并行是工业界标准方案。理解 PP 的原理和调优方法,是 AI Infra 工程师必备的核心能力。随着 vLLM、SGLang 等框架对 PP 支持的不断完善,流水线并行推理部署的门槛正在降低,但背后的原理和调优经验仍然决定着生产环境的最终性能表现。
汤不热吧