大模型推理并行策略
70B 的模型塞不进一张 80GB 的 H100——这是所有推理工程师都会遇到的第一道墙。解法不是换更大的 GPU,而是把模型拆开、把数据拆开、把计算拆开,让多张 GPU 协作完成推理。
「拆」的方式有五种:DP(数据并行)、TP(张量并行)、PP(流水线并行)、EP(专家并行)、SP(序列并行)。它们的名字听起来相似,但切的东西完全不同——有的切权重,有的切数据,有的切 KV Cache。本文从「切的到底是什么」出发,用统一的图示把五种策略讲清楚。
一、总览:五种策略,切三种东西
| DP | TP | PP | EP | SP | |
|---|---|---|---|---|---|
| 切的维度 | batch | hidden | layers | experts | seq_len |
| 每张 GPU 有 | 完整模型 | 1/N 权重 | 1/N 层 | 1/N 专家 | 完整模型 |
| KV Cache | 独立 | 分片 | 独立 | 独立 | 分片 |
| 通信量 | 无¹ / 高² | 极高 | 中 | 低 | 中 |
| 典型场景 | 训练 | 大模型推理 | 超大模型 | MoE 推理 | 长上下文 |
¹ 多实例推理 DP:每个实例是独立进程,无通信。
² 训练 DP / vLLM 引擎内 DP:rank 间有梯度同步或元数据通信。
二、数据并行(DP)
DP 在不同语境下含义不同,先说清楚这三种场景,再聚焦本文的核心——多实例推理 DP。
| 语境 | 模型副本 | GPU 间通信 | 调度方式 | 典型用法 |
|---|---|---|---|---|
| 训练 DP | 完全相同 | 每步 AllReduce 梯度 | 框架(DDP/FSDP) | 所有分布式训练 |
| 多实例推理 DP | 完全相同 | 无 | 外部负载均衡器 | LLaMA/Qwen 单卡部署 |
| vLLM 引擎内 DP | 可不同(MoE 下各 rank 专家不同) | 元数据同步 | vLLM scheduler 显式分配 | --data-parallel-size N,多与 EP 联用 |
本文聚焦多实例推理 DP——这是推理部署中最常见的 DP 形式。vLLM 引擎内 DP(--data-parallel-size)将在下文中单独说明。
多实例推理 DP
场景:你有 4 张 H100,要跑 4 个 LLaMA-70B 的推理实例,每个实例独立服务不同的用户请求。所有 4 个实例用同一份模型权重,不需要互相通信——这是最简单的并行方式,也是推理部署的默认策略。
问题:单 GPU 能装下整个模型,但一个实例的吞吐有限——同一时间只能服务有限数量的请求。你需要把请求分发到多个实例上,但每个实例的 KV Cache 是隔离的:实例 A 算过的 prompt,实例 B 不知道,如果 B 后来遇到同样的 prompt 还得重算。
1 | GPU 0: [模型副本 0] ← 请求 1,2 GPU 1: [模型副本 1] ← 请求 3,4 |
方案:起多个独立的 vLLM 或 SGLang 服务进程,每个绑定不同的 GPU(如 CUDA_VISIBLE_DEVICES=0),前面挂一个负载均衡器(如 Nginx、Envoy 或 Ray Serve)。每个实例是完全自治的——独立加载权重、独立管理 KV Cache、独立处理请求。不需要任何 GPU 间通信,也不需要框架层面的 DP 感知。
权衡:KV Cache 不共享是多实例 DP 最大的代价。实例 A 算过的 prompt,实例 B 不知道——如果 B 后来遇到同样的 prompt,Prefill 还得重做。跨实例的 Cache 复用需要额外的基础设施——LMCache 用磁盘做共享存储,Mooncake 用 RDMA 传输,HiCache 用 Mooncake 后端。这些方案的复杂度远高于单实例部署。
vLLM 引擎内 DP(--data-parallel-size)
另一种 DP 形态:引擎内原生 DP。与多实例推理 DP 不同,多个 DP rank 共享同一个 scheduler 进程,请求被显式分配到不同 rank,rank 间通过 _synchronize_dp_ranks() 同步 batch 元数据(ubatch 大小、cudagraph 模式)。多节点时通过 --data-parallel-backend ray 用 Ray placement group 编排。SGLang 目前没有引擎内 DP。
1 | Dense 模型(LLaMA/Qwen): |
引擎内 DP 的 DP rank 间存在元数据通信——_synchronize_dp_ranks() 同步 ubatch 大小、cudagraph 模式等。这与多实例推理 DP 的”零通信”不同。当前引擎内 DP 在 Dense 模型上主要用于 testing/development,生产级 Dense 模型部署仍以多实例推理 DP 为主;在 MoE 模型上则是与 EP 联用的生产级方案。
三、张量并行(TP)
场景:70B 的 LLaMA-2 在 FP16 下需要 ~140GB 显存,一张 80GB 的 H100 装不下。DP 不能解决这个问题——DP 只复制模型,每张卡还是需要装完整的 140GB。
问题:你需要把单层权重矩阵切成小块,让每张 GPU 只持有 1/N,从而突破单卡显存上限。但这引入了新的问题——每张 GPU 只有部分权重,如何拼出完整的前向计算结果?
1 | GPU 0: [W_Q 第0-7头] GPU 1: [W_Q 第8-15头] GPU 2: [W_Q 第16-23头] GPU 3: [W_Q 第24-31头] |
方案:Megatron-style TP 是业界标准——每层 Attention 和 FFN 被拆成一对 column-parallel + row-parallel 矩阵。列切(column-parallel)的矩阵各 GPU 输入相同(X 被复制到所有 rank),各自算完产生分片输出,不需要通信;紧接着的行切(row-parallel)矩阵接收上一层分片后的输入,各自算完本地结果后 AllReduce 求和合并,产生完整输出传给下一层。vLLM 的 --tensor-parallel-size 4 和 SGLang 的 --tp-size 4 都遵循此实现。
KV Cache 也随权重一起按 head 切分:每个 TP rank 只存自己负责的那部分 head 的 K 和 V。总量不变,但分散后单卡显存压力降为 1/N。
权衡:TP 是突破单卡显存上限最直接的方式,代价是通信量极高——每层 Attention 和 FFN 之后都需要 AllReduce。在 NVLink 互联的同一节点内这可以接受(~450 GB/s,延迟 ~5μs),但跨节点时延迟上升 5-10 倍,性能不可接受。因此 TP 的硬约束是:TP size ≤ 单节点 GPU 数,TP 组内所有 GPU 必须在同一 NVSwitch 域内。
另一个权衡是故障域:TP 组内任何一张 GPU 挂掉,整个组的 KV Cache 都不可用。生产环境通常用多组较小的 TP(如 2×TP=4)而非一组大 TP(1×TP=8),牺牲单组吞吐换取更小的故障域。
KV Cache offloading 在 TP 下的行为(LMCache 与 SGLang HiCache 源码逻辑一致):
| 模型架构 | 存储行为 | 物理文件 |
|---|---|---|
| 标准 MHA/GQA(LLaMA、Qwen) | N 个 rank 各自独立写自己的 shard | rank_0/block_N.bin … rank_N/block_N.bin |
| MLA(DeepSeek V2/V3) | 只有 rank 0 写,其余 rank 被动 broadcast | block_N.bin,1 份完整文件 |
MLA 将 KV 压缩后体积仅为标准 MHA 的 1/4–1/8,一份完整文件不是瓶颈。LMCache 通过 save_only_first_rank(cache_engine.py L113)、SGLang 通过 is_mla_model(hicache_storage.py L335)控制此差异。
四、流水线并行(PP)
场景:模型有 64 层,单节点 8 张 GPU 做了 TP=4 已经装下了权重,但还有 4 张 GPU 闲着。或者模型大到 TP=8 也不够——需要跨节点了。
问题:跨节点的 TP 不可行(NVLink 只在节点内)。你需要一种通信量更低的并行方式,能在跨节点时将模型拆开,而不需要每层都 AllReduce。
1 | GPU 0: Layer 0-15 ──→ GPU 1: Layer 16-31 ──→ GPU 2: Layer 32-47 ──→ GPU 3: Layer 48-63 |
方案:PP 按层切分——每张 GPU 负责一段连续的层,每段内的权重完整。通信只在层边界发生:当前 GPU 算完最后层的激活值,传给下一张 GPU 的第一层。通信量远低于 TP(只传激活值,不传权重梯度)。vLLM 通过 --pipeline-parallel-size 2、SGLang 通过 --pp-size 2 启用。
权衡:PP 的主要代价是流水线气泡和延迟放大。气泡来自串行依赖——GPU 0 在处理 token t+1 时,GPU 1 还在处理 token t,越靠后的 GPU 等待越久。推理中延迟随 stage 数线性增长——每个 token 必须串行经过所有 PP stage。因此纯 PP 在在线推理中不常用,更常见的是 PP+TP 混合:节点内 TP=4 突破单卡显存,节点间 PP=2 突破单节点显存。
KV Cache 按层分段——PP stage 0 的 GPU 不存 stage 1 的 KV。读取历史 KV 时可能触发跨 stage 查询,需要框架的 block table 支持跨 stage 寻址。
五、专家并行(EP)
场景:DeepSeek-V3 有 671B 总参数、256 个路由专家。如果所有专家都装在一张 GPU 上,显存会瞬间爆炸。但它的 MLA 架构让单 token 的计算量很小(每次只激活 37B),问题不在计算而在存储。
问题:你需要把 256 个专家分散到多张 GPU 上,每个 token 的前向计算只经过少数被路由到的专家所在的 GPU。但这些专家的权重怎么分布?token 到专家的路由怎么跨 GPU 通信?
1 | GPU 0: Expert 0-63 GPU 1: Expert 64-127 GPU 2: Expert 128-191 GPU 3: Expert 192-255 |
方案:每个 GPU 固定持有 1/N 的专家权重。路由层计算 token 到专家的匹配分数后,通过 All-to-All 通信将 token 的隐藏状态发送到对应专家所在的 GPU,专家计算完成后 All-to-All 返回。DeepSeek-V3 在 vLLM 和 SGLang 中均默认 EP=8(256 专家 ÷ 8 GPU = 每卡 32 专家)。
权衡:EP 的最大优势是通信量天然低——每个 token 只路由到 Top-K 专家(K=8),大部分 GPU 之间不需要通信。代价是负载均衡:如果某几个专家特别热门(token 大量涌向 Expert 5),对应的 GPU 会过载。SGLang 的 EPLB(Expert Parallel Load Balancer)动态监控各专家负载,将热门专家副本迁移到空闲 GPU 来解决。vLLM 通过 `KVCacheGroup