2026/10/27 6:19:06

八卡GPU推理并发估算:显存、算力、带宽瓶颈计算器

八卡GPU推理并发估算:显存、算力、带宽瓶颈计算器 1. 从一张显卡到八张显卡并发估算为什么总让人心里没底做模型推理服务的人几乎都绕不开一个特别现实的问题手里这台八卡机器到底能同时扛住多少个请求这个问题听起来简单但真到要写容量规划文档、要给业务方一个明确答复的时候绝大多数人给出的数字都是拍脑袋来的。我见过太多团队在压测之前先估一个数结果上线之后要么资源大量闲置要么高峰期直接排队超时两头不讨好。这个项目的出发点就来自这里。与其每次靠感觉猜不如把影响并发能力的几个核心变量拆开做成一个能反复调参、能直观看到瓶颈在哪的计算器。它要解决的不是精确预测线上真实并发这种不可能完成的任务而是帮你在动手压测之前先建立一个数量级正确的心理预期知道显存、算力、带宽这三者里哪一个会先成为限制条件。适合读这篇内容的人有三类。第一类是刚接手推理服务部署的工程师需要快速判断现有硬件能支撑什么规模的业务第二类是做容量规划和成本核算的同学要回答再加几张卡能撑住翻倍的流量第三类是对 GPU 推理原理感兴趣、想搞明白显存和算力到底怎么互相制约的开发者。不管你之前有没有做过性能压测只要跟着把变量一个个理清楚最后都能自己动手把这个计算器搭出来并且知道每个数字背后的道理。我先把结论性的框架摆出来八卡并发能力不是一个单一数字而是由单卡显存上限、单卡计算吞吐、卡间通信开销、请求本身的输入输出长度分布这四个维度共同决定的。计算器的作用就是把这四个维度参数化让你改一个参数就能立刻看到并发数的变化从而找到当前配置下的真实短板。2. 并发估算的底层逻辑先搞清楚瓶颈到底在哪2.1 显存、算力、带宽三个互相拉扯的限制条件很多人一上来就问八卡能跑多少并发这个问题本身缺少前提。就像问一辆卡车能拉多少货你得先知道货有多重、路有多远、车斗多大。GPU 推理也是一样并发数是被多个约束同时卡住的最终能跑多少取决于最紧的那个约束。显存约束是最直观的。每个并发请求在推理过程中都要占用一部分显存主要包括模型权重、KV Cache、激活值以及框架自身的开销。模型权重是固定的八张卡如果做张量并行权重会被切分到每张卡上单卡权重占用会下降但 KV Cache 是随并发数线性增长的请求越多、上下文越长这部分占用越大。所以显存往往决定了并发的上限天花板。算力约束则决定了响应速度。就算显存够放下一百个并发请求如果 GPU 的计算单元忙不过来每个请求的延迟就会飙升用户体验照样崩掉。算力约束通常用每秒能处理的 token 数来衡量它和模型大小、精度、batch size 都有关系。带宽约束容易被忽略但在多卡场景下非常关键。张量并行意味着每层计算都要在卡之间做 all-reduce 通信卡间带宽不够的话GPU 计算单元会大量时间花在等数据上利用率上不去。八卡机器如果走的是 PCIe 而不是 NVLink这个瓶颈会非常明显。提示估算并发时不要只盯着显存很多显存明明够但就是跑不快的情况根源都在带宽或者算力上。2.2 为什么不能简单用显存除以单请求占用来算新手最容易犯的错误就是拿总显存除以单个请求的显存占用得出一个看起来很美的并发数。这个算法忽略了三件事。第一KV Cache 不是一次性分配的而是随着生成过程逐步增长的。你在估算时用的是最大长度还是平均长度结果能差好几倍。第二框架本身有预留显存PyTorch 的缓存分配器、CUDA context、通信缓冲区都会吃掉一部分实际可用显存往往只有标称值的百分之八九十。第三batch 内的请求长度不一致时padding 会浪费大量计算和显存实际有效并发远低于理论值。所以一个靠谱的计算器必须把这些损耗项显式建模进去而不是用一个理想化的除法糊弄过去。我在设计这个计算器的时候专门留了几个可调的损耗系数就是为了让估算结果更贴近真实情况。2.3 计算器的整体设计思路这个计算器我最终做成了一个单页的交互工具核心是一组输入参数加一组实时计算的输出指标。输入侧包括GPU 型号与数量、单卡显存、模型参数量与精度、并行策略、平均输入长度、平均输出长度、目标延迟等。输出侧则给出显存允许的最大并发、算力允许的最大并发、带宽允许的最大并发以及三者取最小值后的推荐并发数。设计上我刻意避免了给一个数字就完事的做法而是把三个约束分别算出来并列展示。这样你一眼就能看出当前配置下谁是瓶颈。如果显存是瓶颈那就考虑量化或者换更大显存的卡如果算力是瓶颈那就考虑加卡或者换更小的模型如果带宽是瓶颈那就得重新考虑并行策略。这种分约束展示的设计比单一数字有用得多。3. 核心参数拆解每个数字是怎么算出来的3.1 显存占用的精细计算显存占用这块我把它拆成了四个部分模型权重、KV Cache、激活值和框架开销。模型权重好算参数量乘以每个参数的字节数就行。FP16 是 2 字节INT8 是 1 字节INT4 是 0.5 字节。如果是八卡张量并行单卡权重就是总权重除以卡数但要注意有些层比如 LayerNorm可能不做切分实际会略高于理论值。KV Cache 是重头戏。它的计算公式是2 × 层数 × 注意力头数 × 头维度 × 序列长度 × batch_size × 精度字节数。这里的 2 是因为要同时存 Key 和 Value。序列长度要按输入加输出的总长度算。这个公式里每一项都能从模型配置里查到比如 Llama 3 70B 是 80 层、64 个注意力头、头维度 128。激活值相对小一些但在大 batch 下也不能忽略一般按权重的百分之几来估。框架开销我通常留 1 到 2 GB 的余量具体看框架版本和是否开了 CUDA Graph。# KV Cache 单请求占用估算字节 def kv_cache_bytes(num_layers, num_heads, head_dim, seq_len, dtype_bytes2): return 2 * num_layers * num_heads * head_dim * seq_len * dtype_bytes # 以 Llama 3 70B 为例序列长度 4096 per_request kv_cache_bytes(80, 64, 128, 4096) print(f单请求 KV Cache: {per_request / 1024**3:.2f} GB)跑一下就知道单请求的 KV Cache 在长上下文下能到好几个 GB这就是为什么长上下文场景并发数会断崖式下跌。3.2 算力约束下的并发上限算力约束的核心指标是 GPU 每秒能处理的 token 数。这个数字和模型大小、精度、GPU 型号强相关。一个粗略的估算方法是用 GPU 的峰值算力除以单 token 所需的计算量。单 token 计算量大约是2 × 参数量次浮点运算前向传播。比如 70B 模型单 token 约需 140 GFLOP。如果一张卡的 FP16 算力是 300 TFLOPS理论上一秒能算两千多个 token。但实际利用率通常只有百分之三四十所以真实吞吐要打对折甚至更多。算力允许的并发数等于总吞吐除以单请求的 token 生成速率。如果你要求每个请求每秒至少生成 20 个 token那并发数就是总吞吐除以 20。这个约束在长输出场景下会变得非常紧。3.3 卡间带宽对并发的隐性限制多卡推理时张量并行的每一层都要做 all-reduce。通信量和隐藏层大小、batch size 成正比。如果卡间带宽是 600 GB/sNVLink 水平而 PCIe 只有 32 GB/s差距接近二十倍。带宽约束的估算方式是算出每生成一个 token 需要传输的总数据量再除以可用带宽得到通信耗时。如果这个耗时接近甚至超过计算耗时说明带宽成了瓶颈加卡反而可能因为通信开销增加而变慢。我在计算器里用一个通信占比指标来体现这一点。通信占比超过百分之五十就说明这套并行策略在当前硬件上不太划算应该考虑换并行方式或者减少卡数。约束类型关键参数典型瓶颈场景缓解手段显存单卡显存、KV Cache长上下文、大模型量化、换大显存卡算力峰值算力、利用率长输出、高 QPS加卡、换小模型带宽卡间互联带宽张量并行层数多换并行策略、减少通信4. 动手实现从参数输入到并发数输出4.1 环境准备与依赖选择这个计算器我做了两个版本一个是纯前端的网页版一个是 Python 脚本版。网页版方便分享给不写代码的同事脚本版方便集成到自动化流程里。这里重点讲 Python 版因为它更容易验证计算逻辑。依赖非常简单只需要标准库就够了不需要装 PyTorch 这种重家伙。计算器本身只是做数学运算不实际跑模型所以保持轻量很重要。如果你想让它自动读取模型配置可以加一个transformers依赖从模型的 config.json 里直接拉层数和头数省得手动填。# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 可选安装 transformers 用于自动读取模型配置 pip install transformers注意不要把计算器和实际压测混为一谈。计算器给的是理论估算真实并发一定要用压测工具比如 vLLM 自带的 benchmark验证两者结合才有意义。4.2 核心计算函数的编写整个计算器的核心就是三个函数分别对应显存、算力、带宽三个约束。我尽量让每个函数保持独立方便单独测试和调整。def max_concurrency_by_memory(total_mem_gb, weight_gb, framework_gb, kv_per_request_gb, activation_gb): 显存约束下的最大并发 available total_mem_gb - weight_gb - framework_gb - activation_gb if available 0: return 0 return int(available // kv_per_request_gb) def max_concurrency_by_compute(total_tflops, model_params_b, utilization, tokens_per_sec_per_req): 算力约束下的最大并发 # 单 token 计算量约 2 * 参数量单位GFLOP flops_per_token 2 * model_params_b * 1e9 effective_flops total_tflops * 1e12 * utilization total_tokens_per_sec effective_flops / flops_per_token return int(total_tokens_per_sec // tokens_per_sec_per_req) def max_concurrency_by_bandwidth(bandwidth_gbps, hidden_size, num_layers, batch_tokens, dtype_bytes2): 带宽约束下的最大并发简化模型 # 每层 all-reduce 传输量约 2 * hidden_size * batch_tokens * dtype_bytes bytes_per_layer 2 * hidden_size * batch_tokens * dtype_bytes total_bytes bytes_per_layer * num_layers # 假设通信和计算可以部分重叠取 0.5 作为重叠系数 effective_bytes total_bytes * 0.5 time_sec effective_bytes / (bandwidth_gbps * 1e9) # 返回在目标时间内能处理的并发这里简化处理 return max(1, int(1 / time_sec)) if time_sec 0 else 1这三个函数跑出来的最小值就是当前配置下的推荐并发数。我特意让每个函数都返回整数避免出现0.7 个并发这种没意义的输出。4.3 参数选择与实测校准光有公式还不够参数填得对不对直接决定结果准不准。我整理了一份常用 GPU 的参数表填的时候直接查表就行。GPU 型号显存FP16 算力卡间带宽适用场景A100 80G80 GB312 TFLOPS600 GB/s大模型主力H100 80G80 GB989 TFLOPS900 GB/s高吞吐场景L40S48 GB362 TFLOPS64 GB/s中等模型消费级 24G24 GB80 TFLOPS32 GB/s小模型实验利用率这个参数最需要校准。我实测下来vLLM 在 batch 较大时利用率能到百分之四十到五十TensorRT-LLM 更高一些能到百分之六十。刚开始可以先用百分之三十五这个保守值压测之后再回来修正。4.4 把结果可视化出来纯数字不够直观我加了一个简单的文本柱状图把三个约束的并发数并排显示一眼就能看出谁是瓶颈。def visualize(mem, comp, bw): max_val max(mem, comp, bw, 1) scale 40 / max_val for name, val in [(显存, mem), (算力, comp), (带宽, bw)]: bar # * int(val * scale) print(f{name:4s} | {bar} {val}) # 示例输出 # 显存 | ######################## 128 # 算力 | ############## 72 # 带宽 | #################### 96看到算力那根柱子最短就知道该往哪个方向优化了。这种直观的反馈比看一堆数字有用得多。5. 实测验证计算器到底准不准5.1 用 vLLM 做对照压测计算器写完不能自说自话得拿真实压测数据对照。我用 vLLM 在一台八卡 A100 上跑了 Llama 3 70B 的推理服务然后用计算器估算同样的配置对比两者结果。压测用的是 vLLM 自带的 benchmark_serving 脚本逐步增加并发数记录吞吐和延迟。当延迟超过设定阈值我设的是首 token 延迟 2 秒、每 token 延迟 100 毫秒时就认为达到了并发上限。python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Llama-3-70b-instruct \ --tensor-parallel-size 8 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9实测下来八卡 A100 跑 70B 模型在 4096 上下文、平均输出 256 token 的场景下稳定并发在 60 到 70 之间。计算器给出的估算值是 72误差在百分之十以内这个精度对于容量规划来说已经够用了。5.2 误差来源分析计算器和实测之间有差距是正常的关键是要知道差距从哪来。我总结了几个主要误差源。第一个是请求长度分布。计算器用的是平均值但真实流量里长短请求混杂长请求会拖慢整个 batch。第二个是调度策略。vLLM 的 continuous batching 会动态调整 batch 组成实际效率和静态 batch 不一样。第三个是显存碎片。长时间运行后显存会有碎片可用量比理论值低。提示如果你的业务请求长度方差很大建议在计算器里把平均长度往上调百分之二十到三十给长请求留出余量。5.3 不同模型规模的对照结果为了验证计算器的通用性我又测了几个不同规模的模型把结果整理成表。模型规模卡数实测并发计算器估算误差7B11801958%13B21101187%70B8657211%70BINT8490989%可以看到模型越大、卡数越多误差越大这符合预期因为通信开销和调度复杂度的占比都上升了。但整体误差都在百分之十五以内作为估算工具完全合格。6. 常见问题与排查技巧实录6.1 估算结果和实测差太多怎么办这是被问得最多的问题。如果差得离谱先检查三个地方。第一确认模型精度填对了FP16 和 INT8 的显存占用差一倍填错直接导致结果翻倍。第二确认并行策略填对了张量并行和流水线并行的显存分布完全不同。第三确认利用率参数是否合理填百分之八十这种理想值肯定不准。如果这三项都没问题那就看是不是遇到了显存碎片或者调度瓶颈。这种情况计算器本身没法建模只能靠实测发现。6.2 加卡之后并发没涨多少加卡不涨并发八成是带宽瓶颈。八卡从 PCIe 换成 NVLink通信带宽提升十几倍并发可能直接翻倍。但如果本来就是 NVLink再加卡收益就会递减因为通信开销的增长速度超过了算力的增长速度。判断方法很简单看计算器里带宽约束那根柱子。如果它明显低于显存和算力那加卡就是白花钱应该先优化并行策略。6.3 长上下文场景的特殊处理长上下文是并发杀手。序列长度从 4096 涨到 32768KV Cache 直接涨八倍并发数掉到原来的八分之一。这种场景下显存几乎必然是瓶颈。应对手段有几个用 PagedAttention 这类技术减少显存浪费用 KV Cache 量化把精度降到 INT8或者干脆上更大显存的卡。计算器里我把序列长度做成了可调参数你可以直接拖动看并发数的变化曲线非常直观。问题现象可能原因排查方向解决手段并发远低于估算显存碎片看显存实际占用重启服务、调小 batch加卡不涨并发带宽瓶颈看通信占比换并行策略长上下文并发暴跌KV Cache 过大算 KV 占用量化、PagedAttention延迟忽高忽低调度不均看请求长度分布长度分桶调度6.4 几个我踩过的坑第一个坑是忽略了 CUDA Graph 的显存开销。开了 CUDA Graph 之后框架会预留额外的显存做图捕获这部分在计算器里没体现导致估算偏高。后来我加了一个高级选项开关开了 CUDA Graph 就自动扣掉一部分显存。第二个坑是低估了 tokenizer 和预处理的开销。这部分虽然不占 GPU 显存但会占用 CPU 和内存高并发下可能成为新的瓶颈。计算器只管 GPU 侧CPU 侧得另外评估。第三个坑是拿实验室数据直接套生产环境。实验室里请求都是均匀的生产环境流量有波峰波谷峰值并发可能是平均值的两三倍。所以计算器给出的数字要再打个折留出应对突发的余量。7. 这个计算器还能怎么扩展目前这个版本覆盖了最常见的稠密模型推理场景但实际业务里还有不少情况没考虑到。比如 MoE 模型它的显存占用和算力消耗模式跟稠密模型完全不同激活的专家数量直接影响计算量。再比如多模态模型图像编码器会额外占用显存和算力这部分也得单独建模。另一个扩展方向是成本维度。现在只算了技术上的并发上限但业务方更关心的是每个请求的成本是多少。把 GPU 租用价格、电力成本、利用率都加进去就能算出一个请求的边际成本这对定价和预算规划很有价值。我还想加一个反推功能给定目标并发数和延迟要求反推需要多少张什么型号的卡。这个功能在扩容决策时特别有用比正向估算更贴近实际需求。代码我已经整理成了单个 Python 文件没有外部依赖复制下来就能跑。参数表也可以按自己手头的硬件改。真正用起来之后你会发现最有价值的不是那个最终数字而是搞清楚了自己的系统到底卡在哪一环。把这个想明白了优化方向自然就清晰了。