一个推理服务从 2 卡扩到 8 卡,期望吞吐翻四倍,实际只涨了 1.6 倍。查下来三个原因叠在一起:单请求的 KV 缓存放不进"单卡"(于是"跨卡通信"吃掉了收益)、调度器把"批"拆到了每张卡(于是 batch 变小)、以及模型副本数没变(8 卡跑 2 个副本,其余在等)。
"加卡"这件事,收益取决于"你缺的是什么":算力、显存、还是卡之间的带宽。这个"缺什么"决定了"要不要切模型":缺算力(并发不够),加副本就行;缺显存(模型放不下),才必须"切模型"——而"切模型"几乎总带通信。
而"缺"的那一项,决定了"该怎么切"。
1. 三种"瓶颈"的判别
| 瓶颈 | 现象 |
|---|---|
| 算力 | GPU 利用率高、吞吐随卡数近线性 |
| 显存 | 批上不去、OOM、KV 不足 |
| 卡间带宽 | 利用率低、卡在 "all-reduce" |
第一项是"最理想"的:加卡真的能变快——而第二、三项会让"加卡"变成"加复杂度"。第二项与第三项的"代价"是"非线性":加卡越多,通信占比越高——于是"2→4 卡"还行,"4→8 卡"可能只有 1.3 倍。
"加卡前后"的三个数,能一次定位"收益去哪了":
// 三个数(同一负载,2 卡 vs 8 卡)
每卡 GPU 利用率:2 卡 85% → 8 卡 35% → 说明"批被切碎"
卡间通信占比: 2 卡 5% → 8 卡 45% → 说明"TP 通信吃收益"
每卡 batch: 16 → 4 → 说明"调度把批拆了"
// 判据:三个数至少一个"恶化" → 先修它,再谈"加卡"
2. "为什么加卡没变快"的三种情形
| 情形 | 机制 |
|---|---|
| 模型"放不进单卡"(张量并行) | 每层要"跨卡同步" |
| 副本数没增加(只加卡) | 并行度不变 |
| 调度把它"切碎" | 每卡 batch 变小 |
第一项的"量级"值得记:卡内带宽(HBM)是 TB/s 级,卡间 NVLink 是百 GB/s 级,PCIe 只有几十 GB/s——差一到两个数量级,于是"通信占比"很容易"超过 50%"。
第一项是"通信换显存":张量并行把一层拆到多卡——于是"每算一层"都要"跨卡 reduce",而"卡间带宽"远低于"卡内带宽"。
"副本与路由"是"最便宜的扩容",而它只要"调度器支持":
// 三种"扩副本"的配置(同一集群)
replicas: 4 # 每卡一副本(模型放得下时最优)
routing: least_pending # 路由到"最闲"的副本(避免忙闲不均)
batch_per_replica: 16 # 每副本的批大小(别让调度"按卡切")
// 判据:加副本后"每卡利用率"应保持,而不是"集体下降"
3. "三种并行"的适用
| 并行 | 切什么 |
|---|---|
| 数据并行(DP) | 批(每卡一份模型) |
| 张量并行(TP) | 层内(一层拆多卡) |
| 流水并行(PP) | 层间(不同层放不同卡) |
第一项是"最省心"的:**每卡一份完整模型,卡间只"传梯度"**第一项的"推理版"更简单:每卡一份模型,各卡独立处理各自请求——卡间"零通信",于是"吞吐随卡数近线性"(只要"路由均匀")。
——推理场景甚至"零通信"(各卡独立服务)。
"线性度"是个可算的验收指标,而它比"感觉快"可靠:
// 加卡线性度 = 吞吐倍数 / 卡数倍数
2→4 卡:吞吐 1.9 倍 → 线性度 95%(好)
4→8 卡:吞吐 1.3 倍 → 线性度 65%(差:通信或调度)
// 判据:线性度 < 80% 就"别再买卡",先修"通信/批大小"
4. "副本与并行"的取舍
| 方案 | 前提 |
|---|---|
| 多副本(DP) | 模型放得进单卡 |
| TP(一层拆卡) | 单卡放不下,且"卡间带宽高"(NVLink) |
| PP(层拆卡) | 层数多、单卡放不下 |
第二项的"判据"很直接:"TP 度 ≤ 单机 NVLink 域内的卡数"(如 8 卡 NVLink 域,TP 到 8 都还行;跨机 TP 基本不建议)。
第二项的"边界"很硬:"卡间走 PCIe"时,TP 的收益常被通信吃掉——于是"8 卡 PCIe 做 TP"可能比"2 卡 NVLink"更慢.
"KV 缓存"是"显存账"里最容易被漏的一项:
// 三段"显存占用"(同一模型)
权重: 14GB(TP=2 时每卡 7GB)
KV: 与"并发 × 序列长度"成正比(可能 8GB)
激活: 与"batch"成正比(1–2GB)
// 判据:TP 只切"权重",KV 与激活"仍按卡算"——所以"长上下文"要单独"KV 分片"
5. "调度"对吞吐的影响
| 现象 | 机制 |
|---|---|
| 每卡 batch 变小 | 调度按"卡"切请求 |
| 副本"忙闲不均" | 路由不均 |
| "等待同步" | TP 的每层屏障 |
第一项是"加卡后最常见"的:**原来"1 卡 batch 16",变成"8 卡各 batch 2"**第一项的"机制"是"每卡的 batch 决定"矩阵乘的利用率"":batch 2 时,GPU 的算力大量闲置——于是"卡多了、总 batch 没变",吞吐自然不动。
——于是"每卡每批"的利用率下降,而"总吞吐"未必上升.
6. 排错:五种"加卡无收益"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 吞吐涨不到线性 | 通信/调度 | 先量"卡间通信占比" |
| GPU 利用率低 | 批太小 | 调大"每卡批" |
| 延迟反而变差 | TP 通信 | 减小 TP 度,改多副本 |
| OOM 仍在 | 并行没覆盖到 KV | 加"KV 分片" |
| 有卡"闲着" | 副本数/路由 | 加副本或改路由 |
"延迟敏感"这条要单独强调:TP 会让"每层多一次通信",于是"单请求延迟"上升。
// 两种"扩"的延迟影响
多副本:单请求延迟不变(只是"更多请求能并行")
TP: 单请求延迟上升(每层都要等"最慢那张卡")
// 判据:SLO 是"P99 首字"还是"总吞吐"
7. 边界:什么情况"多卡"不值得
| 场景 | 为什么 | 做法 |
|---|---|---|
| 模型放得进单卡 | 多副本已够 | 加卡 = 加副本 |
| 低流量(QPS < 1) | 并行度用不上 | 单卡 |
| PCIe-only(无 NVLink) | TP 通信太贵 | 避免 TP |
| 延迟敏感(单请求 <1s) | TP 增加每层延迟 | 多副本优先 |
最后一条与"验收"有关的边界:"加卡前后"要量"每卡利用率 + 卡间通信时间占比 + 每卡 batch"三个数——三个数一起看,才知道"收益被谁吃掉了";只看"总吞吐",永远只能猜。
判断口径一句话:"我要的是"更多并发"还是"更大模型"。要并发(模型放得下),加卡=加副本,别碰 TP;要更大模型(放不下),才用 TP/PP,且要先确认"卡间带宽够不够"。
再补一个"看起来能省"的坑:"用更多卡跑更小模型"有时不如"少卡跑原模型"——因为"小模型"的算力利用率本来就低(batch 小、矩阵乘窄),而"多卡并行"要付"通信"的固定成本。于是"加卡 + 换小模型"的组合,可能"总成本不变、延迟变差"。而判断它的方式还是那三个数:利用率、通信占比、每卡 batch——没有哪一项因"换小模型"而变好,就不该换。
最后一个"决策"提醒:"加卡"之前先回答"瓶颈是哪一项"——答不出,就先"量三个数"。因为"加卡"是"最贵"的扩容手段(钱、机位、运维),而它"解决不了"显存与通信;而"加副本 + 调批大小"往往能"用同样的卡"拿到一半收益。
所以"先量后买",是这类扩容唯一靠谱的顺序。
8. 自测
- 三种瓶颈分别有什么现象?为什么"加卡真的能变快"属于最理想的情况?
- "张量并行"为什么会"用通信换显存"?卡内带宽与卡间带宽差多少量级?
- 三种并行分别"切"什么?为什么"推理场景的数据并行"几乎零通信?
- 为什么"8 卡 PCIe 做 TP"可能比"2 卡 NVLink"更慢?
- "每卡 batch 变小"怎么会让"总吞吐"不升反降?
- 判断"该多副本还是该 TP"的那个问题是什么?