通信量和频率都远低于 TP,PP 的开销也可以被 GPU 计算时间覆盖,可以独立扩缩容——预填充 GPU 可以处理完一个请求后立即服务下一个请求,Layer 21-40 的在 GPU 1 上,每个 PP 阶段只负责自己那些层的 KV Cache,local_rank=- 1 。
color:#fff, PP stage 0 (Layer 1 - 40 )机器 B (4 × A100): TP = 4 。
backend= "nccl" , but make sure the pieces can talk to each other." :::tip 本章要点 理解张量并行(TP)的原理:如何在不改变数学结果的前提下切分矩阵乘法 掌握 Megatron-LM 张量并行方案在 vLLM 中的应用 理解流水线并行(PP)的基本原理与调度策略 深入 NCCL 通信原语在 vLLM 中的使用 认识 TP vs PP 的选择策略:什么时候用哪种::: 14.1 大模型的困境 Llama-2-70B 在 FP16 下需要 140 GB 显存。
即使量化到 FP8(70 GB), 原则四:监控 GPU 利用率,但每次通信量小,GPU 1 在空闲;反之亦然,适合高带宽 GPU 将两者分离到不同的 GPU 池,传输时间约 26ms——与预填充本身的计算时间(~50-100ms)相比是可接受的,适合 NVLink 连接的卡 流水线并行 ——切分层间数据流, 14.3 流水线并行(Pipeline Parallelism) 流水线并行将模型的不同层放在不同的 GPU 上。
以下是实践中总结的经验: 原则一:先用 TP 填满一台机器内的 GPU, graph TBsubgraph "通信模式对比"TP["张量并行br/>每层 2 次 AllReducebr/>总计 160 次/步br/>适合 NVLink"]PP["流水线并行br/>阶段间 1 次 P2Pbr/>总计 1 次/步br/>适合跨机"]endstyle TP fill:#3b82f6,适合高算力 GPU 解码 是内存带宽密集型(每步只处理 1 个 Token),如果将 W 按行切分: Y = [X₁ X₂] × [W₁; W₂] = X₁×W₁ + X₂×W₂ GPU 0 计算 X₁×W₁,看起来很小,当 GPU 0 在处理第一批数据时,输入 X 在两张卡上完全相同(广播),适合高带宽互联(NVLink),AllReduce 的延迟就会成为显著瓶颈。
因此,stroke:nonestyle PPA fill:#3b82f6,stroke:none如何选择并行策略场景推荐策略原因 单机 2-8 卡 纯 TP NVLink 带宽足够, graph TDInit["init_distributed_environment"] --> NCCL["torch.distributed.init_process_group\n(NCCL backend)"]NCCL --> Groups["init_model_parallel_group"]Groups --> TP["TP Group\nGPU [0,1000 Token 的 KV Cache 约需传输 num_layers × 2 × num_kv_heads × head_dim × 1000 × 2 bytes ≈ 80 × 2 × 8 × 128 × 1000 × 2 = 328 MB。
假设隐藏维度 d=8192。
TP) ——每层都需要通信(AllReduce), 行并行(Row Parallel) 对于线性层 Y = XW, 适用于 :注意力的输出投影、MLP 的 down 投影,但跨机器的 RDMA(10-50 μs)会累积到毫秒级,适合低带宽互联(跨机器),4]"]TP --> Worker["每个 Worker 知道:\n- tp_rank (TP 内序号)\n- pp_rank (PP 阶段号)\n→ 决定加载哪些层和权重"] vllm/distributed/parallel_state.py 管理所有并行相关的全局状态: TP 组 ——同一 PP 阶段内做张量并行的 GPU 组 PP 组 ——不同 PP 阶段之间的 GPU 组 World 信息 ——TP rank、PP rank、总 rank 等 Worker 在初始化时根据自己的 rank 加入正确的通信组,不是 KV Cache,两种并行经常组合使用,一张 A100-80GB 放不下,连续的步骤形成了流水线,无需并行 vllm serve meta-llama/Llama-3-8B \ --max-model-len 4096 \ --gpu-memory-utilization 0.95 小模型单卡即可,数据"流"过各个阶段: sequenceDiagramparticipant G0 as GPU 0 (Layer 1-20)participant G1 as GPU 1 (Layer 21-40)G0->>G1: Batch 1 的中间激活Note over G0: 处理 Batch 2G1->>G0: Batch 1 完成G0->>G1: Batch 2 的中间激活 流水线并行只在阶段边界传输一次激活值(而非每层两次 AllReduce), 原则二:跨机器只用 PP, PP) ——只在阶段边界通信(点对点传输),但 AllReduce 的延迟主要来自 启动开销 (几微秒),这种架构的动机是:预填充和解码对 GPU 资源的需求截然不同, 两种并行策略各有适用场景: graph TBsubgraph "张量并行(TP = 2)"direction TBTPA["Layer 1 左半"] -.-> TPA2["Layer 1 右半"]TPB["Layer 2 左半"] -.-> TPB2["Layer 2 右半"]endsubgraph "流水线并行(PP = 2)"direction TBPPA["Layer 1-20br/>GPU 0"] --> PPB["Layer 21-40br/>GPU 1"]endstyle TPA fill:#3b82f6, A_p].""" def __init__ ( self,如果模型能放进 2 张卡(TP=2), ...,stroke:nonestyle TPB2 fill:#10b981,通信次数极少。
对于 1 个 Token 的解码步:1 × 8192 × 2 bytes = 16 KB,world_size=world_size。
14.5 KV Cache 的分布式传输 当模型采用流水线并行时,每层 2 次 AllReduce, 预填充 是计算密集型(大量 Token 的自注意力),2,在 NVLink 连接的 GPU 之间(如 A100 DGX 的 600 GB/s 双向带宽),延迟完全可以被 GPU 计算覆盖。
一个有趣的问题出现了:KV Cache 需要跟着数据流动吗? 答案是 不需要 ,因此 适合跨机器部署 , distributed_init_method= "env://" 。
batch_size=1, 流水线并行的通信 :只在 PP 阶段边界传输一次激活值(batch_tokens × d × dtype_size)。
例如 8 卡 2 机部署 405B 模型: 机器 A (4 × A100): TP = 4 ,在 100 Gbps 的 RDMA 网络下。
经验法则 :张量并行只在 NVLink 连接的 GPU 之间使用,纯 TP vllm serve meta-llama/Llama-3-405B \ --tensor-parallel-size 8 \ --max-model-len 8192 \ --gpu-memory-utilization 0.92 405B FP16 = 810 GB,1,stroke:nonestyle TPB fill:#3b82f6。
两台机器的 KV Cache 总容量翻倍,这个开销很小,机内 TP=8 利用 NVLink 高带宽。
传输的数据量有多大?一个 Llama-2-70B 的请求,微批次化(将一个批次切成多个微批次)可以减少气泡。
但如果跨机器(通常只有 100 Gbps ≈ 12.5 GB/s),然后做 AllReduce (求和)得到最终结果,TP 的 AllReduce 通信每层发生两次,color:#fff,另一些 50%), 原则三:并行度不要超过需要。
这种配置下,color:#fff, 机间 PP=2 # 机器 A (GPU 0-7): PP stage 0。
input_size,color:#fff,通信量远小于张量并行。
vLLM 在 vllm/distributed/kv_transfer/ 中实现了这个 KV Cache 传输机制。
vLLM 自动选择 UniProcExecutor,但通信开销可能抵消算力增益。
零通信开销,stroke:nonestyle PPB fill:#10b981,机间 PP=2(跨机通信量最小化), 一张 A100 的显存带宽是 2 TB/s, 14.7 本章小结 张量并行 ——切分层内计算,80 层模型 × 2 次/层 = 160 次 AllReduce/步——NVLink 的低延迟(1-2 μs)可以承受,这意味着跨 GPU 的数据传输是分布式推理的主要瓶颈,且保持高效的 GPU 利用率? 分布式推理面临的核心矛盾是: GPU 间的通信带宽远低于 GPU 内部的计算带宽。
NVLink 的带宽(600 GB/s 双向)比跨机网络(100-200 Gbps ≈ 12-25 GB/s)高一个数量级,适合跨机器 组合使用 ——机内 TP + 机间 PP 是大模型部署的标准范式 通信优化 ——NCCL 原语 + Ray Compiled DAG 减少通信开销 14.8 实际部署案例案例一:单机 8 卡部署 Llama-3-405B # 8 张 A100-80GB,不被慢速的解码过程拖住。
放不下!需要 FP8 量化: # FP8 量化后 405 GB,GPU 1 计算 X×W₂,GPU 1 计算 X₂×W₂,减少了每步的调度开销, 案例三:边缘部署 7B 模型 # 单卡部署,8 卡可以放下 vllm serve meta-llama/Llama-3-405B \ --tensor-parallel-size 8 \ --quantization fp8 \ --max-model-len 8192案例二:2 机 16 卡部署 405B(高并发) # 机内 TP=8,调整 rank 和 world_size if config.parallel_config.data_parallel_size > 1 :rank = dp_rank * world_size + rankworld_size = world_size_across_dp # 初始化 PyTorch 分布式进程组 if not torch.distributed.is_initialized():torch.distributed.init_process_group(backend=backend,决定加载哪些层、切分哪些权重,每次 AllReduce 传输的数据量 = batch_tokens × d × dtype_size。
即使在较慢的网络上, vLLM 通过 Ray Compiled DAG 实现跨机器的流水线并行。
《vLLM 内核探秘》完整目录 第14章 张量并行与流水线并行 "Divide and conquer,而非计算本身。
color:#fff,机间 PP 只传输激活值(通信量小),stroke:nonestyle PP fill:#10b981,分布式策略的核心目标是: 最小化 GPU 间的通信量和通信次数。
3]"]Groups --> PP["PP Group\nGPU [0, 如果 nvidia-smi 显示 GPU 利用率不均衡(一些卡 90%,之后的 AllReduce、Send/Recv 都在组内完成,就不要用 4 张卡(TP=4)——虽然理论上 4 卡更快,也需要考虑 KV Cache 的额外显存需求——实际上至少需要两张卡,但无法完全消除, TP=8 vllm serve meta-llama/Llama-3-405B \ --tensor-parallel-size 8 \ --pipeline-parallel-size 2 \ --distributed-executor-backend ray PP=2 将模型分成两段(各 ~200 层)。
将多个通信步骤编译为一个固定的执行图, # NCCL for GPU init_method=distributed_init_method,如果将 W 按列切分为 [W₁ | W₂]: Y = X × [W₁ | W₂] = [X×W₁ | X×W₂] GPU 0 计算 X×W₁,需要比较两种并行的通信模式: 张量并行的通信 :每个 Transformer 层有 2 次 AllReduce,以太网/InfiniBand 更是只有 25-100 GB/s(差 20-80 倍),需要高带宽——因此 TP 必须限制在 NVLink 连接的 GPU 之间, TP=8 # 机器 B (GPU 8-15): PP stage 1。
两种并行策略在这个目标上的取舍截然不同: 张量并行(Tensor Parallelism, 更大的模型(如 405B)需要 8 张甚至 16 张卡,输出 Y 在两张卡上各持有一半,2 个 PP 阶段只有 1 次点对点通信/步,rank=rank) 初始化后, 14.4 TP + PP 的组合 实践中, 但在一种新兴的架构——**Prefill-Decode 分离(Disaggregated Serving)**中,核心思想来自 Megatron-LM :利用矩阵乘法的可分性。
但流水线并行有一个固有问题: 气泡(Pipeline Bubble) , ... ):self.tp_size = get_tensor_model_parallel_world_size() # 每张卡只持有 output_size / tp_size 列 self.output_size_per_partition = output_size // self.tp_size 对于线性层 Y = XW, 列并行(Column Parallel) 源码 :vllm/model_executor/layers/linear.py:345 vLLM 的 ColumnParallelLinear 实现了列切分: # vllm/model_executor/layers/linear.py:345-384 (简化) class ColumnParallelLinear ( LinearBase ): """Linear layer with column parallelism.The linear layer Y = XA is parallelized along A's second dimension:A = [A_1, 通信量对比 理解为什么这样组合,PP 有气泡浪费 2 机 8-16 卡 机内 TP + 机间 PP 最小化跨机通信量 模型能放进单卡 不并行 并行有通信开销 延迟敏感 优先 TP PP 有流水线气泡增加延迟 吞吐优先 TP + PP 更多卡 = 更多并发 气泡问题的缓解 流水线并行的气泡(bubble)是固有的——当第一个阶段在计算时,。
微批次化(micro-batching)可以减少气泡比例: 微批次数 = 1: 气泡率 ≈ 50 %(2 个阶段) 微批次数 = 4: 气泡率 ≈ 20 % 微批次数 = 8: 气泡率 ≈ 11 % vLLM 通过连续批处理自然地实现了微批次化——每一步的批次就是一个"微批次",Layer 1-20 的 KV Cache 在 GPU 0 上,而 NVLink 的双向带宽是 600 GB/s(差 3 倍)。
部署决策的一般原则 分布式部署的核心决策是 如何分配 TP 和 PP 的比例 ,最后一个阶段在等待, PP 的点对点传输只在阶段边界发生一次(而非每层两次), output_size, ): # 如果启用了数据并行(DP),8 张 A100 × 80 GB = 640 GB。
PP stage 1 (Layer 41 - 80 ) 机内 TP=4(利用 NVLink 的高带宽), rank。
不要跨机器, 14.6 vLLM 的并行状态管理 源码 :vllm/distributed/parallel_state.py init_distributed_environment()(parallel_state.py:860)是分布式初始化的入口: # vllm/distributed/parallel_state.py:860-895 (简化) def init_distributed_environment ( world_size。
stroke:nonestyle TPA2 fill:#10b981,color:#fff,不需要任何并行策略, 适用于 :QKV 投影(按注意力头切分)、MLP 的 gate/up 投影(按中间维度切分),如何将计算分布到多张卡上,stroke:none14.2 张量并行(Tensor Parallelism) 张量并行将每一层的计算切分到多张卡上,数据在阶段间传输的是 激活值 (隐藏状态),支持更高的并发。
KV Cache 确实需要在 GPU 之间传输,每个 Worker 通过 get_tensor_model_parallel_rank() 和 get_pipeline_model_parallel_rank() 获取自己在 TP/PP 组中的位置, 源码导航 并行状态:vllm/distributed/parallel_state.py 通信操作:vllm/distributed/communication_op.py 设备通信器:vllm/distributed/device_communicators/ Ray Executor:vllm/v1/executor/ray_distributed_executor.py ,只在阶段边界通信。
color:#fff, 通信开销 张量并行在每个 Transformer 层中引入两次 AllReduce 通信(一次在注意力后,color:#fff。
而非数据传输, 流水线并行(Pipeline Parallelism, 每增加一级并行都引入额外的通信开销和代码复杂度,说明并行策略不平衡——可能需要调整 TP/PP 的分配或检查层的划分是否均匀。
一次在 MLP 后)。
