3. Tensor Parallelism(TP)基础

好,咱们进入正题。Tensor Parallelism,简称TP,说白了就是把一个巨大的张量切成几块,分给不同的GPU去算。我刚开始接触这个概念时,觉得这不就是「分而治之」嘛,但真正落地时才发现,里面的门道比想象中多得多。

3.1 TP的核心思想

你想想看,一个大模型,比如LLaMA-70B,光一个权重矩阵就可能占几十GB。单张GPU根本塞不下。怎么办?TP的思路很简单:把矩阵切成几块,每块放一张GPU上。

举个例子。假设我们有一个线性层:Y = XW。W是一个大矩阵。用TP的话,我们把W按列切成两块:W = [W1, W2]。然后GPU0算XW1,GPU1算XW2。最后把结果拼起来。这就是最基础的列并行。

我在项目中遇到过一个问题:切分策略选错了,导致通信开销比计算还大。嗯,这里要特别注意,切分不是随便切的,得看你的模型结构和硬件拓扑。

3.2 张量切分策略

常见的切分方式有三种。我列个表,方便你对比:

切分方式 切分维度 适用场景 通信模式
行并行 按行切分权重 注意力层、FFN层 all-reduce
列并行 按列切分权重 线性层、Embedding all-gather
2D并行 行列同时切分 超大模型(>100B) 混合通信

我个人习惯用列并行做线性层,行并行做注意力层。为什么?因为注意力层里Q、K、V的计算,行并行可以减少一次all-reduce。这个经验是我调了十几个模型后总结出来的。

核心要点:切分策略的选择直接影响通信量。列并行只需要一次all-gather,而行并行需要一次all-reduce。all-reduce比all-gather贵得多,因为要同时做求和和广播。

3.3 all-reduce通信

说到通信,这是TP里最头疼的部分。all-reduce,说白了就是所有GPU先把各自的结果算出来,然后互相交换数据,最后每个GPU都拿到完整的结果。

举个例子。假设4张GPU做TP,每张算出一部分梯度。all-reduce的过程是这样的:

  1. 每张GPU把自己的梯度发出去
  2. 每张GPU收到其他GPU的梯度
  3. 每张GPU把所有梯度加起来
  4. 每张GPU都拿到完整的梯度

我曾经踩过一个坑:用默认的NCCL配置跑TP,结果通信时间占了总时间的60%。后来发现是ring算法在跨节点时效率太低。换成tree算法后,通信时间降到了30%。

注意:all-reduce的带宽瓶颈非常致命。特别是跨节点时,NVLink带宽是600GB/s,但跨节点只有100GbE。如果你的TP组跨了节点,通信开销会急剧上升。

3.4 TP配置参数

在vLLM里,TP的配置主要通过这几个参数控制:

# vLLM启动示例
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-2-70b-hf \
    --tensor-parallel-size 4 \    # TP组大小,这里是4张GPU
    --pipeline-parallel-size 1 \  # PP大小,这里不启用
    --dtype bfloat16 \
    --max-model-len 4096

关键参数说明:

  • tensor-parallel-size:TP组内的GPU数量。必须是2的幂次方,比如2、4、8。我一般推荐4或8,太小了没效果,太大了通信开销受不了。
  • pipeline-parallel-size:PP大小。可以和TP混用,但要注意总GPU数 = TP × PP。
  • --gpu-memory-utilization:GPU显存利用率。默认0.9,但TP场景下建议调到0.95,因为TP会多占一些显存用于通信缓冲区。

我的经验:TP大小不要超过单节点内的GPU数。比如A100节点有8张卡,TP=8是最优的。跨节点做TP,通信延迟会让你怀疑人生。我曾经试过TP=16跨两个节点,结果吞吐量还不如TP=8。

3.5 TP的通信拓扑

为了让你更直观地理解TP的通信模式,我画了一张图:

TP通信拓扑示意图(4 GPU) GPU 0 W1, 部分结果 GPU 1 W2, 部分结果 GPU 2 W3, 部分结果 GPU 3 W4, 部分结果 通信模式 实线:相邻GPU直接通信(NVLink) 虚线:环形all-reduce路径 最终:每张GPU都拿到完整结果

这张图展示的是4张GPU做TP时的通信拓扑。实线是NVLink直连,虚线是环形all-reduce的路径。你看,GPU0和GPU1之间是直连的,但GPU0和GPU3之间就需要绕一圈。这就是为什么TP组内GPU数量最好是2的幂次方——环形通信的效率最高。

3.6 实战建议

最后,我总结几条实战经验:

  • TP大小 = 单节点GPU数:别跨节点,别问为什么,问就是带宽不够。
  • 先用小模型验证:我习惯先用7B模型跑TP=2、4、8,看通信开销占比。如果超过40%,说明配置有问题。
  • 监控NCCL日志:设置NCCL_DEBUG=INFO,看有没有超时或重试。我曾经遇到过网卡降速导致TP性能暴跌,就是靠日志发现的。
  • 显存预留:TP会多占约5-10%的显存用于通信缓冲区。别把显存塞满,留点余量。

一句话总结:TP是把双刃剑。用好了,吞吐量翻倍;用不好,通信开销吃掉所有收益。我的建议是:先小规模验证,再逐步扩大。别一上来就TP=8,先TP=2跑通再说。


公众号:蓝海资料掘金营,微信deep3321