张量并行度的选择与通信收益拐点
在一组 8 卡 ****(单卡 64GB HBM,卡间互联 392 GB/s)上,用 **** 的基础软件栈配合 vLLM 起一个 70B 级 BF16 模型服务,把 --tensor-parallel-size 从 1 逐档提到 8。每卡权重占用随档位下降,--max-model-len 能开到更高,但 decode 吞吐在某一档之后不再上升,再往上加卡反而低于前一档;启动日志无异常,resolved_backend 与基线一致,没有算子回退。问题集中在两处:每层前向要付的 all-reduce 通信量不随 TP 度下降,而 all-reduce 的耗时随参与卡数上升。
张量并行切的是什么
张量并行按维度切分同一层内的张量,不把层分给不同卡。被切的对象有三类:
| 被切对象 | 切分维度 | 每卡占比 |
|---|---|---|
| 注意力投影矩阵(Q/K/V/O) | 按注意力头切,每个头完整落在一张卡上 | 约 1 / tensor_parallel_size |
| MLP 的列并行与行并行矩阵(上、下投影) | 列投影按输出维切,行投影按输入维切 | 约 1 / tensor_parallel_size |
| KV 头 | 按 H_kv 切,每卡持有 H_kv / tensor_parallel_size 个 KV 头 |
约 1 / tensor_parallel_size |
| 词表相关层(embedding、LM head) | 按词表维切 | 约 1 / tensor_parallel_size |
注意力头按卡切分要求 tensor_parallel_size 能整除头数;GQA/MQA 下 KV 头数远小于 query 头数,KV 头往往无法整除同样的档位,框架会改用张量复制或退化切分,读日志时应以实际生效的切分形状为准。对采用 MLA 的结构,KV 被压缩为潜向量,H_kv 与单头维度 D 的口径与 GQA 不同,不能直接套用注意力头数。
权重按参数量线性摊到各卡上,每卡权重下限可写成:
每卡权重下限 ≈ 参数量 × 每参数字节数 ÷ tensor_parallel_size
量级示例(估算,非实测):70B 参数、BF16(每参数 2 字节)时单卡需约 140 GB,TP=8 时每卡约 17.5 GB。显存查询工具报的是整卡剩余量,不是权重基线所需量。
每层前向要付的通信
张量并行在每层引入固定次数的 all-reduce。以标准 decoder 层为例,一次前向付两次:
- attention 的输出投影(O 矩阵)之后一次,把各卡分头计算的部分结果求和;
- MLP 的下投影(行并行矩阵)之后一次,把各卡按输入维切分的部分结果求和。
列并行矩阵之后不需要 all-reduce,因为输出维本来就按卡分开;行并行矩阵之后必须 all-reduce,因为每张卡只算到了部分和。层数越多,付的次数越多。
每层 all-reduce 次数 = 2
单次 all-reduce 数据量 ≈ batch × seq × hidden × 每元素字节数 # 与 TP 度无关
ring all-reduce 单卡通信量 ≈ 2 × (N-1)/N × 单次数据量 # N 为 TP 度
两处要点:单次 all-reduce 的数据量由激活张量大小决定,调大 tensor_parallel_size 并不会让它下降;而 all-reduce 的耗时包含一个随参与卡数增长的项,卡越多、跨的链路层级越高,这一项越突出。计算时间随 TP 度按 1/N 下降,通信时间随 N 上升,两条曲线相交处就是收益拐点。
拐点由互联带宽决定
拐点位置由计算时间与通信时间的比决定,而通信时间取决于互联带宽。同一拓扑下带宽越高,通信项越平,TP 度可以推到越高才触顶。
| 互联层级 | 单卡互联带宽(示意量级) | 适合承载的并行 |
|---|---|---|
| 机内全互连总线 | 数百 GB/s 至 1.8 TB/s | 张量并行(每层都要通信,需要最高带宽) |
| PCIe 点对点 | 约 100–128 GB/s | 低 TP 度或对带宽不敏感的切分 |
| 跨机以太网 / RDMA | 低于机内一个量级 | 张量并行收益急剧下降,应改用管线或数据并行 |
提高 TP 度只在权重放不下时才带来净收益。显存不足以容纳权重时,多卡分摊让模型起得来;权重放得下之后,继续加卡的收益由互联带宽与拓扑层级封顶,不再提高吞吐。
现象
同一处预算不足会以几种形态出现:
- 每卡显存够了、服务能起,decode 吞吐不升甚至下降,首 token 延迟随档位抬高;
- 单参数调优(改
--gpu-memory-utilization、调 block size)对吞吐几乎无改善; - 利用率曲线里计算单元时高时低,卡间同步等待占据明显空档;
- 跨机张量并行时恶化最快,退回到机内档位后曲线回正。
定位步骤
- 确认实际生效的并行度与切分形状:读启动日志里的并行配置行与权重的实际分片形状,核对
tensor_parallel_size是否被整除约束改写。部分框架把张量并行数记在worldSize字段,字段名与含义须按实际输出对照。 - 分段计时:分别取 TTFT 与 TPOT / ITL,判断吞吐下降落在 prefill 段还是 decode 段。decode 段对通信更敏感,因为是逐 token 反复付 all-reduce。
- 测通信占比:用 profiler 抓一段 decode,统计集合通信算子的等待时间与计算算子时间之比;跨机时把 all-reduce 单独拆出来计时。
- 看互联带宽利用率:按「带宽利用率 = 网络带宽实际使用量 ÷ 网络带宽」读链路计数器,利用率长期贴近上限说明受带宽约束,利用率很低却仍慢说明受固定延迟或同步点约束。
- 扫 TP 度画曲线:固定模型、精度与负载,把
tensor_parallel_size从 1 逐档扫到 8,记录每档的 decode 吞吐。曲线由升转平、再转降的那个点就是本机拐点。
# 示意:定位时固定 TP 度的启动命令(参数名取自框架,取值按本机规格填)
vllm serve <model> \
--tensor-parallel-size 4 \
--max-model-len 32768 \
--gpu-memory-utilization 0.9 \
--kv-cache-dtype auto \
--max-num-seqs 64
处置:参数与代价
| 动作 | 作用 | 代价 / 前提 |
|---|---|---|
调低 --tensor-parallel-size 到刚好放得下 |
通信项随卡数下降,拐点前区间吞吐回升 | 每卡权重上升,可能装不下更大模型或更长上下文 |
| KV cache 转 fp8 | 每卡 KV 占用减半,用更少卡即可放下 | 需目标 attention backend 支持该 dtype,否则失格回退 |
| 权重做 W8A8 / W4A16 量化 | 每卡权重下降,可少用卡 | 引入量化误差,需单独核精度 |
| 换更高带宽的机内全互连 | 抬高拐点,让更大 TP 度仍有利可图 | 受硬件与拓扑约束,属机内档位 |
| 把 TP 限制在机内、跨机用管线并行 | 避免跨机付每层 all-reduce 的固定延迟 | 引入管线气泡,见并行组合的取舍 |
开 --enable-chunked-prefill |
压平 prefill 峰值,给通信让出重叠空间 | 调度更复杂,须确认后端支持 |
调整顺序建议:先确认模型在更低 TP 度下放得下,放得下就不要再加卡;放不下再考虑 KV dtype 与权重量化,把每卡占用压下来;只有当显存确实不够、且机内拓扑允许时,才把 TP 度往上推。
记录字段
# 示意:一条张量并行度结论的记录(字段名示意)
model:
params: # 参数量,用于反推每卡权重下限
precision: # FP16 / BF16 / W8A8 / FP8 / W4A16
hardware:
card_count: # 卡数(8 卡,单卡 64GB HBM)
interconnect: # 机内全互连 / PCIe / 跨机 RDMA,注明单卡互联带宽
parallelism:
tp_size: # tensor_parallel_size 实际生效值
world_size: # 若框架把张量并行数记在此字段,注明口径
workload:
input_len:
output_len:
max_num_seqs:
perf:
ttft:
tpot:
decode_throughput: # 逐 TP 度扫描后的曲线取值
comm:
allreduce_ratio: # 集合通信等待时间占 decode 的比例
link_utilization: # 互联带宽利用率
status: # 拐点前 / 拐点后;注明结论在哪种负载下成立
小结
张量并行按维度切分注意力头、MLP 投影与 KV 头,每卡权重下限为参数量乘每参数字节数再除以 tensor_parallel_size。每层前向固定付两次 all-reduce,单次数据量由激活张量大小决定、不随 TP 度下降,而 all-reduce 的耗时随参与卡数上升,两条曲线相交处即收益拐点,拐点位置由互联带宽与拓扑层级决定。遇到「每卡显存够了、吞吐却不升反降」时,先分段计时与测通信占比,再按机内带宽与每卡占用决定是把 TP 度调低,还是换 KV dtype 与量化把占用压下来。