先量一遍单卡显存再决定切几张卡——DeepInfer 并行配置的取舍

文章导读
决定 DeepInfer 用几张卡,顺序建议反过来:先把模型、精度、最大并发、输入长度这四项固定住,用单卡跑出一组基线数字;再把张量并行、流水并行的档位从 1 逐档加到 2、4,每档重复同一轮压测。卡数一多,省下的是每张卡要装的模型权重和 KV cache,付出的是卡间通信和调度开销。只有「单卡显存确实放不下目标并发」或「加卡后吞吐确实还在涨」这两个条件里的一个成立时,加卡才值得。下面给的是可照着
📋 目录
  1. 一 固定模型、精度、最大并发和输入长度这四项变量
  2. 二 单卡跑出基线的吞吐与延迟
  3. 三 逐档增加卡数与并行度重复同一轮压测
  4. 四 检查卡间通信是否已经成为瓶颈
  5. 五 按数据确定停止加卡的临界点
A A

决定 DeepInfer 用几张卡,顺序建议反过来:先把模型、精度、最大并发、输入长度这四项固定住,用单卡跑出一组基线数字;再把张量并行、流水并行的档位从 1 逐档加到 2、4,每档重复同一轮压测。卡数一多,省下的是每张卡要装的模型权重和 KV cache,付出的是卡间通信和调度开销。只有「单卡显存确实放不下目标并发」或「加卡后吞吐确实还在涨」这两个条件里的一个成立时,加卡才值得。下面给的是可照着填的记录表、观察方法和停止加卡的判定规则。

先量单卡:固定模型、精度、最大并发、输入长度,跑一轮压测,记下单卡显存峰值、吞吐和 P99。之后每加一档卡数重复同一轮,只让并行度变化。停止加卡看三条:目标并发下每卡显存峰值是否已经留有余量;加卡后吞吐增量是否明显缩小;P99 是否比吞吐涨得更快。三条都指向不划算,就停在上一档。通信是否成为瓶颈,可以从日志的每步耗时分项和 GPU 利用率判断,但仍需结合卡间拓扑确认。

固定模型、精度、最大并发和输入长度这四项变量

几轮对照如果同时改了并发或者精度,吞吐变化就说明不了是卡数的功劳。所以先把下面四项写成一份配置副本,每档只改并行度一个字段。

  • 模型:权重路径必须同一份,换模型等于换实验。
  • 精度:fp16 / bf16 / int8 选一个并在所有档位保持一致,量化会同时改变显存和算力,不能作为对照变量。
  • 输入长度:压测的输入长度和服务的最大上下文长度都固定,长上下文对 KV cache 的影响远大于卡数。
  • 最大并发:服务端同时在跑的请求数上限,以及压测脚本发起的并发数,两处都要固定。
# 配置骨架示例,字段名请以你部署的 DeepInfer 版本实际配置为准
model:
  name_or_path: /models/your-model     # 固定项 1:模型
  dtype: float16                       # 固定项 2:精度
  max_model_len: 4096                  # 固定项 3:输入长度上限
engine:
  max_num_seqs: 32                     # 固定项 4:最大并发
  gpu_memory_utilization: 0.90         # 固定项,几轮对照不要动
  tensor_parallel_size: 1              # 唯一变量
  pipeline_parallel_size: 1            # 唯一变量

命令行覆盖通常也可以,例如 `--tensor-parallel-size` 2,但要用同一份基础配置文件去覆盖,避免手写参数时漏改。这里有一个取舍要提前想清楚:加卡后显存变多,理论上能开更大的 batch 换吞吐;但为了对照干净,第一轮先固定并发,等定位到瓶颈之后,再单独做一次 batch 扫描,不要把两件事混在同一张表里。

单卡跑出基线的吞吐与延迟

基线是所有后续对比的分母,通常建议跑两到三轮,取稳定那一轮的结果。需要记录三个指标:总吞吐(输出 token/s,或请求/s)、延迟分布(P50、P95、P99,其中 P99 是尾部延迟)、每卡显存峰值(包含权重和 KV cache 的部分)。

采集方式:吞吐和延迟由压测脚本输出;如果脚本不直接给分位数,可以读服务端 metrics 端点的累计计数,压测前后相减算平均吞吐,但分位数必须由客户端记录;显存用 nvidia-smi 定时采样,或读服务端日志里的显存占用行。

# 1) 起单卡服务
python -m deepinfer.serve `--config` baseline_tp1.yaml

# 2) 另开终端采样显存,1 秒一次
nvidia-smi `--query-gpu`=index,memory.used,utilization.gpu \
  `--format`=csv -l 1 > mem_tp1.csv

# 3) 压测,并发与长度必须与固定项一致
python -m deepinfer.bench \
  `--endpoint` http://127.0.0.1:8000/v1/completions \
  `--model` /models/your-model \
  `--num-requests` 200 `--concurrency` 32 \
  `--input-len` 1024 `--output-len` 256 \
  `--seed` 1234 \
  `--save-json` result_tp1.json

请求数不要压得太少,几十个请求算出来的 P99 基本是噪点。工具名和参数以你的环境为准,换成别的压测脚本时,只要并发、输入长度、输出长度、随机种子保持一致,结果就可以横向对比。

先量一遍单卡显存再决定切几张卡——DeepInfer 并行配置的取舍

逐档增加卡数与并行度重复同一轮压测

每档都要把并行度、每卡显存峰值、总吞吐、P50、P95、P99 记进同一张表。「相对上一档增量」按 (本档吞吐 − 上一档吞吐) / 上一档吞吐 计算后填入,不要写成「提升明显」这类无法比较的描述。

档位TP / PP每卡显存峰值吞吐 tok/sP50 msP95 msP99 ms吞吐增量观察
基线1 / 1—单卡是否已够放目标并发
第 2 档2 / 1显存降了多少、吞吐涨了多少
第 3 档4 / 1增量是否明显变小
第 4 档8 / 1P99 是否已经明显变差

填写时有几个地方值得留意:如果并发固定不变,加卡后每卡显存下降是预期现象,说明 KV cache 被切开了;如果每卡显存几乎没降,要看是不是并行切分没生效,去日志里确认实际加载的并行配置。如果固定并发下加卡后吞吐几乎不动,说明这个并发还没有把计算压满,加卡解决的不是你的问题,先回去调并发或 batch。

检查卡间通信是否已经成为瓶颈

加了卡但吞吐不涨,常见原因有两类:一是通信开销抵消了切分收益,二是模型或 batch 本身太小,计算根本没到瓶颈。日志里可以观察这些线索:每步耗时的拆分行(prefill 与 decode 分开)、all-reduce 或 all-gather 的耗时、通信与计算是否重叠、以及启动时打印的通信后端与拓扑信息(NVLink 还是走 PCIe)。

# 查看每步耗时与通信相关的日志行,字段名以实际日志为准
grep -E "step|all_reduce|allreduce|comm|nccl" serve_tp2.log | tail -n 50

判断瓶颈落在通信还是计算,可以用这几个对照:

先量一遍单卡显存再决定切几张卡——DeepInfer 并行配置的取舍
  • GPU 利用率:加卡后利用率下降、显存占用下降,而吞吐不涨,通常是在等通信。
  • 单步时间:把 TP=1 时一次 decode 的单步耗时,与 TP=2 时一次 decode 的单步耗时对比。如果切分之后单步几乎没变快甚至变慢,说明通信把切分收益吃掉了。
  • 通信占比:日志里通信耗时占单步总耗时的比例,如果随卡数上升而变大,就是明确的瓶颈信号。
  • 拓扑:TP 组跨 NUMA、跨 PCIe 交换机或跨机时,通常不划算,建议把 TP 组限制在 NVLink 域内,超出的部分用 PP 或水平扩展多实例。

如果灌满并发后 GPU 利用率接近饱和、单步耗时也随卡数改善,那瓶颈在计算,继续加卡仍有空间;但此时要同时看 P99 是否已经变差。

按数据确定停止加卡的临界点

把取舍写成规则,下次换模型或换卡型重跑一遍就能复用。下面几条是常用判定,阈值需要你按业务能接受的成本自己设定,不要照抄别人的数字:

  1. 显存门槛:目标并发下每卡显存峰值已经留出安全余量(比如低于上限的 85%~90%,比例自定),单卡就够,直接停在 TP=1。
  2. 吞吐增量门槛:从 TP=N 到 TP=2N,吞吐增量低于你设定的阈值,说明继续加卡的边际收益已经很小。
  3. 尾部延迟门槛:P99 的恶化幅度大于吞吐的提升幅度,相当于用稳定性换吞吐,通常不划算。
  4. 通信占比门槛:日志里通信耗时占单步的比例随卡数持续上升并超过设定上限,应当停止。

两档数据的对比,建议在记录表下方固定写成这个格式,数值压测后填:

TP2 -> TP4 对比(数值留空,压测后填写)
吞吐:T2 = ____ tok/s   T4 = ____ tok/s   ΔT = (T4-T2)/T2 = ____
P99 :P2 = ____ ms      P4 = ____ ms      ΔP = (P4-P2)/P2 = ____
每卡显存峰值:M2 = ____ GiB   M4 = ____ GiB
结论:若 ΔT 低于阈值且 ΔP 为正,停在 TP2,并在备注写明依据的是哪一列
      若 ΔT 仍明显为正、ΔP 变化不大且每卡显存仍逼近上限,进入下一档

也可以用一段循环把判定固化下来,阈值仍然是自行填写的:

threshold_gain = 0.20   # 自行设定:吞吐增量低于此值视为不划算
threshold_p99  = 0.10   # 自行设定:P99 恶化超过此值视为不划算
prev = None
for tp in [1, 2, 4, 8]:
    mem, tput, p99 = run_bench(tp)   # 同一份固定配置,只改并行度
    if prev:
        dt = (tput - prev[1]) / prev[1]
        dp = (p99 - prev[2]) / prev[2]
        if dt < threshold_gain and dp > 0:
            print(f"停在 TP={prev[0]}")
            break
    prev = (tp, tput, p99)

记录表和日志建议一起存档。换模型、换卡型、换推理精度之后,旧结论不能直接复用,需要重新走一遍单卡基线到逐档加卡的流程;显存够不够、通信划不划算,都跟具体的模型结构和机器拓扑绑在一起,这一点需要结合你自己的环境确认。