
1. 先搞清楚 Tensor Parallel 到底在并行什么1.1 模型为什么放不进一张卡 —— 显存墙做推理部署的人迟早会撞上显存这堵墙。以 70B 参数量的大模型为例哪怕用 FP8 量化光权重就得占 70GB 左右。现在消费级显卡 24GB 是常态数据中心里 A100/H100 大多也就 80GB一张卡纯放权重都紧张更别说推理过程中还得留出 KV Cache、激活值、临时缓冲区的空间。我见过不少团队一开始的方案是“既然一张卡放不下那就拆到多张卡上呗”。这句话听上去没毛病但拆法有讲究。最常见的三种并行方式数据并行Data Parallel, DP、流水线并行Pipeline Parallel, PP、张量并行Tensor Parallel, TP。DP 是每张卡放一份完整模型处理不同的 batch它解决的是吞吐问题不解决显存问题PP 是把模型按层切开前一层算完传给后一层每张卡只负责其中几层显存压力确实下降不少但它天然有流水线气泡而且层与层之间是串行依赖的。TP 的思路更激进把某一层内部的矩阵运算切开多张卡一起算这一层。也就是说权重矩阵被横七竖八地拆到了多张卡上每张卡只持有完整矩阵的一个分片。前向计算时多张卡协同完成一次矩阵乘法然后再把结果拼起来或者叠加起来。听起来 TP 是最能解决显存问题的方案也确实如此。但这里埋着一个大坑矩阵被切开之后卡与卡之间需要频繁同步中间结果这个同步开销会随卡数增加而暴涨。很多人第一次跑 TP 推理时都会懵我加了卡速度怎么不升反降这篇文章就把这个反直觉的现象掰开揉碎讲清楚。1.2 TP 的两种切法按行切与按列切要说清楚 TP 为什么有通信开销得先看它到底怎么切矩阵。一次典型的前向计算可以简化为 Y X × WX 是输入激活矩阵W 是权重矩阵。TP 的切法有两种按行切 W 和按列切 W。按列切 W 比较简单。把 W 按列切成两块 W1 和 W2分别放在 GPU 0 和 GPU 1 上。输入 X 需要完整地发给两张卡各自计算 Y1 X × W1Y2 X × W2。因为 W1 和 W2 是 W 的左右两半所以 Y1 和 Y2 拼接起来就是完整的 Y。这个拼接动作在 All-Gather 类通信中完成通信量等于 Y 的完整大小。按行切 W 稍微绕一点。把 W 按行切成上下两块 W1 和 W2分别放在两张卡上。输入 X 也要按列切成 X1 和 X2分别送到对应的卡上。GPU 0 算 Z1 X1 × W1GPU 1 算 Z2 X2 × W2。注意Z1 和 Z2 是部分和要得到最终结果 Y Z1 Z2必须把两张卡的结果加起来。这个加法在 All-Reduce 通信中完成通信量也等于 Y 的完整大小。注意现代大模型普遍采用“按行切 All-Reduce”的方式因为它在后续的激活函数、Dropout 等操作上更自然。但无论哪种切法每一层 Transformer Block 里至少有一次跨卡同步一次 All-Reduce 意味着每张卡既要发数据也要收数据通信量随卡数线性打满。1.3 为什么 TP 需要“全局同步”Transformer Block 里有两个主要的矩阵运算块Attention 的 QKV 投影和输出投影MLP 的 up/gate 投影和 down 投影。每一处投影如果做了 TP就需要一次跨卡同步。以 MLP 为例up 投影先按列切各卡独立算完激活函数逐元素操作不需要通信但 down 投影按行切这时候必须做 All-Reduce把两张卡的部分结果累加起来。也就是说一个 MLP 块至少一次 All-ReduceAttention 块至少两次。算下来一层 Transformer 至少三次跨卡同步。有人可能会想All-Reduce 不就等所有卡算完然后互相发一下数据吗听起来很简单但实际开销远不止“发数据”本身。All-Reduce 需要经过 Reduce-Scatter 和 All-Gather 两个阶段每一阶段都是两两通信还有一个隐式的全局同步屏障——所有卡必须等最慢的那张卡算完才能进入通信。这就是问题的核心模型被拆到多张卡上之后计算从“单卡串行”变成了“多卡并行 跨卡同步”。如果计算时间降幅小于通信时间增幅总耗时反而上升。多加 GPU推理可能更慢——这句话不是玩笑是实测里经常发生的事。2. 多加 GPU 反而更慢的三个核心原因2.1 通信才是真正的瓶颈All-Reduce 的代价为了说清楚这个问题我拿一个具体例子做估算。假设我们有一个 13B 参数的模型每层 hidden size 为 5120MLP 的中间维度为 13824。用 FP16 推理时一个 Token 在 MLP down 投影上的激活矩阵大小大约是 5120 × 1单 Token 推理时序列长度视为 1。听起来很小对吧5120 个元素FP16 下才 10KB。但实际推理不会只处理一个 Token——生产环境里通常是连续批处理continuous batching一个 batch 可能包含几十甚至上百个序列每个序列又有几百个 Token。假设 batch size × 序列长度 4096 个 Token那么激活矩阵就是 5120 × 4096FP16 下约 40MB。在 8 卡 TP 配置里down 投影的 All-Reduce 需要将这 40MB 在 8 张卡之间做归约和广播。通信量不能简单算 40MB实际是每张卡发送约 7/8 的数据再接收 7/8 的数据总数据量接近 70MB 级别。拿数据说话单张 H100 的 NVLink 双向带宽约 900GB/s看起来很高但 70MB 在这种带宽下也要约 78 微秒。而 13B 模型在单卡上算一个 layer 的前向在 H100 上大概也就 100 到 200 微秒。也就是说一次 All-Reduce 的通信时间几乎相当于一次完整计算的时间——翻一倍开销。这里还没算通信发起时的延迟latency。NVLink 的点对点延迟通常在 1 到 2 微秒看似可以忽略但 All-Reduce 包含多个阶段并且每层都要做累计起来非常可观。一个 80 层的模型每层 3 次 All-Reduce总共 240 次每一次 80 微秒——光通信就要 19.2 毫秒这还不包括计算时间。2.2 同步屏障木桶效应被放大TP 天然带全局同步屏障。All-Reduce 的语义是所有卡先把本地结果算完然后才能开始归约。这意味着如果一个 batch 里某些 Token 的 attention 计算路径特别长或者某张卡因为显存带宽争抢而变慢所有卡都得等它。单卡推理时不同 Token 之间的计算差异会被 GPU 内部的并行调度消化掉一部分。但 TP 多卡环境下最慢的那张卡决定了整次迭代的耗时。这个现象在混合 batch 大小不均、或者序列长度差异很大的推理场景里特别明显。我实测过一个场景输入里有几个特别长的序列它们需要更大的 KV Cacheattention 计算量也更大。在单卡上有长序列的请求只是让整体延迟高一些但如果这些长序列恰好被分配到了同一张卡上TP 切分是按矩阵维度切的不是按序列维度切的所以理论上长序列的计算同样被切分到所有卡但不同卡上 KV Cache 的显存占用、内存访问模式可能有差异那张卡就变成瓶颈卡整个 TP 组的耗时被拉高。同步屏障还有一个容易被忽视的副作用它限制了批处理带来的吞吐提升。TP 的 batch size 变大时虽然计算时间增长但通信量也成比例增长。在通信占比已经很高的情况下增大 batch 带来的算力收益会被通信开销吃掉不少这也是为什么大 batch 下 TP 扩展性更差的原因之一。2.3 计算量没变通信量却叠加了关于 TP 效率一个常见的误解是8 张卡一起算计算时间不是应该变成 1/8 吗理论上确实如此。8 张卡各算 1/8 的矩阵乘法计算时间可以接近 1/8。但总耗时是“计算时间 通信时间 调度时间”通信时间不会因为卡变多而减少反而会增加。更扎心的是计算时间不太可能真的变成 1/8。因为矩阵被切小之后每张卡上的矩阵乘法规模变小GPU 的利用率会下降。矩阵乘法要跑得满需要足够的计算强度compute intensity。一个小矩阵的 GEMM 可能连 GPU 的 Tensor Core 都喂不饱算力利用率只能到 40% 甚至更低。这相当于计算时间没有变成 1/8可能是 1/4 或 1/5通信时间却从 0 变成了完整的一份甚至更多。整体自然不划算。实操心得把模型从单卡换到 2 卡 TP很多场景下速度是不会翻倍的甚至下降。我见过最极端的案例一个 7B 模型在单张 A100 上延迟 45ms换到 8 卡 TP 后延迟反而变成 68ms。8 张卡跑不过 1 张卡这就是通信开销压过算力收益的典型例子。3. 什么场景适合 TP什么场景碰都不该碰3.1 适合 TP 的场景特征说了半天 TP 的坏话但它绝不是一无是处。TP 是唯一能解决“单卡显存放不下”的并行方式在模型大到必须跨卡时是绕不开的。适合 TP 的第一类场景模型大到单卡放不下且没有量化空间。比如 70B 模型在 8 张 A100 上用 FP16 推理一张卡 80GB 放不下完整权重就只能用 TP 把权重分片。此时不做 TP 就没法做属于“没得选”。适合 TP 的第二类场景卡间互联带宽极高且节点内拓扑友好。8 张 H100 通过 NVLink 全互联通信开销相对可控。相比之下跨节点走 InfiniBand 做 TP 就非常痛苦带宽和延迟都差一个数量级强烈不推荐。适合 TP 的第三类场景单请求延迟敏感且 batch 比较小。TP 能降低单请求的计算延迟因为矩阵乘法被并行到多卡上。如果 batch 小到通信量相对可控2 卡或 4 卡 TP 确实能压低延迟。但要记住边界TP 卡数越多边际收益越低通常超过 4 卡就要非常谨慎。3.2 不适合 TP 的场景特征最不适合 TP 的场景有两个一是模型本身单卡就能放下二是对吞吐要求极高而延迟要求没那么苛刻。模型单卡能放下时TP 几乎总是纯亏。原因很简单单卡推理没有任何跨卡通信矩阵乘法全额利用 GPU 算力。TP 不但引入通信开销还因为矩阵变小导致算力利用率下降双重损失。很多人看网上说“多卡一定比单卡快”结果是给自己找麻烦。对吞吐要求高的场景数据并行DP或张量并行 数据并行的混合方案通常更好。DP 的处理方式是每张卡独立跑完整模型处理不同 batch卡之间只需要在反向传播时同步梯度推理时 DP 根本不需要同步天然可扩展。如果在 DP 之上加一点 TP 来分摊显存压力也可以但要注意别让 TP 的通信开销反噬吞吐。我见过一个典型案例某个团队用 8 卡 A100 做 13B 模型的推理服务先上了 TP8吞吐只有 120 tokens/s后来改了 DP8每卡放一份量化后的模型虽然显存紧张了些但吞吐直接跳到 380 tokens/s。这就是选型带来的数量级差距。3.3 TP、PP、DP 怎么选三种并行方式各有适用面合理的方案通常是组合使用而非只用一种。并行方式解决什么问题通信开销典型卡数适用场景DP吞吐扩展推理无通信8 到 64模型单卡放得下追求吞吐PP显存扩展流水线气泡2 到 8模型按层切分层间显存均衡TP显存扩展 降低单请求延迟每层高频通信2 到 8单节点超大模型卡间互联极好实践中的通用配方先 DP 横向扩展吞吐再 PP 纵向切层分担显存最后在单机内部用 TP 把单层切成更小块。比如 70B 模型做 8 卡部署常见方案是 DP2 × PP2 × TP2既保证吞吐又让卡间通信尽量收敛在 NVLink 域内。需要特别提醒TP 的卡数不要跨节点。跨节点的网络带宽再高也远不如 NVLinkTP 的通信频率注定了它必须在物理上紧耦合的卡之间跑。4. 实操优化让多卡推理真正做到 1114.1 通算重叠把 All-Reduce 拆开既然通信不可避免那就想办法让通信和计算同时发生而不是串行等待。这是 TP 性能优化里收益最大的一招。标准 All-Reduce 的朴素实现是全部算完 → 统一归约 → 全部广播。但 All-Reduce 本身可以分解成 Reduce-Scatter 和 All-Gather 两个阶段。Reduce-Scatter 是每张卡把本地结果按目标卡切块发给对应卡做归约All-Gather 是归约完成后每张卡再把完整结果广播给所有卡。聪明的做法是把矩阵乘法的行切分与 Reduce-Scatter 融合。比如 down 投影按行切成了 8 份每张卡算完自己的那份后不需要等到所有卡都算完才通信。可以先算完自己负责的那部分行立刻把这部分结果发给目标卡同时开始算下一个 Attention 块的 QKV 投影——这就是通信与计算的重叠。在 PyTorch 生态里这个优化通常由框架层完成。比如 Megatron-LM 的 Sequence Parallel 和 vLLM 的 TP 实现都花了大量精力做通算重叠。自己做推理框架的话最简单的切入点是检查通信是否被同步阻塞如果 All-Reduce 是阻塞式的后面所有计算都在等它那就考虑把矩阵乘法切成更细粒度的 chunk计算一个 chunk 后立刻通信这个 chunk流水线式地重叠。4.2 从 kernel 与框架层压开销通信优化的另一条线是把通信次数本身变少。Transformer Block 里有几次独立的 All-Reduce如果能把它们合并成一次通信延迟直接减半这就是 kernel fusion 的核心收益。具体来说Attention 输出投影和 MLP 的 down 投影之间可以尝试融合。只要中间没有必须的同步操作一次 All-Reduce 可以同时归约两个张量代价只是通信数据量略增但通信次数和同步屏障次数大幅下降。vLLM 里做得更绝它对连续的多个层做了通信合并把跨层通信合并成更少次数的批量操作。这不是简单的框架参数调整而是对底层通信原语的重新设计。自己做优化时至少要做到的一点是避免在每层内部引入多余的同步比如不要在矩阵乘法之后立刻做 All-Reduce再接一个只需要局部数据的操作——应该把 All-Reduce 尽量延迟到真正需要全局数据的那一刻。框架层还有一个容易被忽视的点通信后端的选择。NCCL 是首选但 NCCL 的环境变量对性能影响很大。比如 NCCL_P2P_LEVEL 决定是否走 NVLink P2PNCCL_BUFFSIZE 决定通信缓冲区大小这些参数在 TP 场景下直接决定通信效率。用默认配置跑 8 卡 TP和调过参数的跑延迟可以差 20% 到 30%。注意NCCL 参数调优不是无脑改大就好。NCCL_BUFFSIZE 改得太大可能挤占显存影响 KV Cache改小了则通信吞吐上不去。我的习惯是先跑一轮基准测试用 nvbandwidth 和 nccl-tests 摸清卡间实际带宽和延迟再反向调参数。4.3 实测流程与基准测试要点做 TP 性能调优第一步不是改代码而是建立可复现的基准测试流程。我自己固定使用的流程分三层第一层硬件摸底。用 nccl-tests 测 All-Reduce 在不同数据量下的带宽和延迟从 1KB 测到 512MB。这一步能直观看到当前环境的通信天花板。比如数据量 40MB 时 All-Reduce 延迟 80 微秒那后续预估推理延迟就按这个数据来不凭感觉。第二层单层前向测试。构造一个 Transformer Block 的单层模型分别测单卡和 TP 多卡下的前向时间。通过逐层分析定位是通信占比高还是计算切分导致利用率低。我建议用 PyTorch 的 profiler 导出 kernel 级别的时间线观察 All-Reduce 旁边是否有空白等待段。第三层端到端推理测试。跑真实模型和真实请求分布记录 P50、P99 延迟和吞吐。如果 P99 明显高于 P50大概率是某个 batch 里出现了长序列触发了同步屏障等待。基准测试最容易犯的错误是只看总延迟不拆解时间组成。总延迟变差到底是通信变差、计算变慢、还是负载不均衡拆开看才能对症下药。我踩过一次坑某次调 TP 参数延迟稳中有降但吞吐也降了后来发现是批处理大小被悄悄改了根本没对比到同一条基准线。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查方法TP 卡数增加延迟反而上升通信占比过高 / 卡间拓扑不佳nccl-tests 测带宽确认是否走 NVLink某张卡显存占用明显高于其他卡权重切分不均 / KV Cache 不均profiler 查看各卡显存分配检查切分逻辑P99 延迟波动剧烈长序列请求触发同步屏障等待按序列长度分组压测看延迟分布吞吐随 batch 增大不涨反降通信量与计算量同比例增长对比不同 batch 下通信时间占比8 卡 TP 比 4 卡 TP 更慢通信量线性增长计算不再线性加速逐卡数测试1/2/4/8找拐点具体到排查动作推理性问题用 profiler 最快。PyTorch 的 torch.profiler 能导出每个通信算子的耗时NCCL 的通信算子会标记为 ncclKernel_AllReduce。如果看到大量 ncclKernel 且旁边没有计算 kernel 重叠说明通算没有重叠需要做算子融合。如果通信时间本身很长先用 nvidia-smi topo 查看卡间拓扑。有些服务器是两两 NVLink 但不是全互联TP 卡选错会导致通信走 PCIe性能直接掉一个数量级。选卡时尽量选同一 NUMA 节点下、拓扑图上直连的卡。5.2 容易被忽略的显存分配坑TP 切分权重时大部分框架按维度均分。但 Attention 里的 KV Cache 是按层分配的不同层之间的 Cache 显存占用在不同卡上可能不均。有些模型的 embedding 和 lm_head 是共享参数的tied embeddingTP 切分时共享权重如果没有正确复制到所有卡会触发通信时额外的 All-Gather拖慢速度。还有个坑是框架层对中间缓冲区的分配。TP 推理时每层前向都需要额外的通信缓冲区这部分显存是隐藏开销。有些框架默认分配 2 到 4 倍模型大小的中间缓冲区直接把 KV Cache 的空间挤掉了。遇到显存明明够但 OOM 的情况优先检查是不是通信缓冲区吃掉了太多显存把 NCCL_BUFFSIZE 调小试试。5.3 我踩过的三个典型坑第一个坑只测吞吐不测延迟。有一次优化后吞吐涨了 15%我正高兴压测才发现 P99 延迟翻倍。原因是吞吐按 token 数计算优化让并发变高但单个请求的前向延迟因为通信串行反而变长了。推理服务既要看吞吐也要看延迟分位数两个指标一起看才靠谱。第二个坑忽略 CPU 侧的调度开销。TP 多卡推理需要 CPU 频繁发起通信原语每个 All-Reduce 都涉及 CPU 的 launch。如果 CPU 绑定不合理或者 numactl 没配好GPU 的 kernel launch 跟不上GPU 就在空转等 CPU 下发指令。后来把 CPU 绑核、设置 CUDA_LAUNCH_BLOCKING0默认就是异步一般不需要改再把 CPU 侧的 busy-loop 改成 blocking 等待性能立刻改善。第三个坑序列长度对 TP 性能的放大效应。短序列时 TP 通信占比高得离谱因为计算太小通信延迟占主导长序列时通信占比下降TP 效果更好。如果生产请求大多数是短文本TP 可能不仅没收益还是负资产。这时候不如干脆用 DP甚至单卡推理。收尾一点个人体会装机时总有人追求“卡越多越好”但做推理部署这几年我最大的体会是分布式不是灵丹妙药它只是在你不得不跨卡时帮你把损失降到最低的手段。TP 尤其如此——它解决了显存放不下的问题但代价是通信和同步开销。合理评估你的模型大小、卡间互联、请求分布再决定上不上 TP、上几卡比盲目堆卡重要得多。如果你正打算给推理服务加卡我的建议是先跑一轮 nccl-tests 和单层前向基准把通信成本和计算收益量化清楚。如果模型单卡放得下别碰 TP如果必须跨卡先从 2 卡 TP 起步测试逐步加卡找拐点别一步到位上 8 卡。最后所有优化都要以 P50/P99 延迟和吞吐的联合基准为准绳任何单指标优化都可能是陷阱。