推理资源账:一次生成需要多少显存、算力与带宽
1. 场景:别只算权重
上线一个模型之前,最常被问的三个问题是:这模型要几块卡?响应多快?QPS 能扛多少?回答它们需要同一件事——把一次生成拆成显存、算力、带宽三本账。最容易犯的错是只算权重:模型 70B、FP16 就是 140GB,以为两块 80GB 卡够了,结果 KV 缓存一涨、工作区一占,直接 OOM。
本文沿用知识库"从约束推导设计"的方法:先列清楚搬什么、搬多少、搬几次、经过哪里、谁必须等它,再对照硬件容量、带宽与算力做数量级估算。所有公式均可复算,文末附 Python 算账函数。
2. 第一本账:显存
一次推理实例的显存占用 = 权重 + KV 缓存 + 激活/工作区,缺一不可。
权重(已加载并驻留):
| 精度 | bytes/参数 | 70B 模型 |
|---|---|---|
| FP32 | 4 | 280 GB |
| FP16 / BF16 | 2 | 140 GB |
| INT8 | 1 | 70 GB |
| INT4(GPTQ/AWQ) | 0.5~0.6 | 35~42 GB |
KV 缓存:每个 token 的键值对大小为
其中 L=层数、K=KV heads 数、H=每个 head 的维度。GQA 下 K 远小于 Q heads,是 KV 缓存大小的关键开关。示例(BF16,即 bytes=2):
| 模型 | L | K | H | KV/token | 4K 上下文 | 32K 上下文 |
|---|---|---|---|---|---|---|
| 7B(GQA) | 28 | 4 | 128 | 28 KB | 115 MB | 917 MB |
| 70B(GQA) | 80 | 8 | 128 | 160 KB | 655 MB | 5.2 GB |
激活/工作区:prefill 阶段的大激活张量与引擎工作区(vLLM 默认预留显存的 90% 给 KV,gpu_memory_utilization 可调)。这部分随引擎与 batch 变化,估算时按权重 + KV 后的余量计。
合计口径:显存需求 ≈ 权重 + KV(按最大并发上下文规划) + 余量(≥10%)。这就是 vLLM 部署文档里"为什么 70B 要 4 卡起步、KV 量化能省 20-50%"的出处。
3. 第二本账:算力
生成分两个阶段,计算量完全不同:
- prefill(预填充):处理整段输入,一次并行算完。每 token 约 FLOPs(N=参数量,乘 2 是乘加各一次)。
- decode(解码):逐 token 生成,每步只算 1 个 token,同样约 FLOPs,但复用 prefill 已算好的 KV,所以单步算力需求小、带宽需求大(见下)。
一次请求(输入 S token,输出 T token)的总算力:
实例(70B,S=1024,T=512): FLOPs。对照一张卡 FP16 算力(如 H800 ≈ 989 TFLOPs BF16,扣除算子效率按 50% 估),prefill 吞吐可达数十~上百 tokens/ms 级,而 decode 的瓶颈通常不在算力而在带宽。
4. 第三本账:带宽
decode 是典型的"带宽饥饿"阶段:每生成 1 个 token,都要把全部权重从显存读一遍(激活参数前向一次)。单卡带宽账:
| 模型 | FP16 权重读取/ token | HBM 带宽 3TB/s 的理论 TPOT |
|---|---|---|
| 7B | 14 GB | 约 4.7 ms/token |
| 70B | 140 GB | 约 47 ms/token |
这个"每 token 读全部权重"正是 TPOT 的下限来源,也是为什么单卡 decode 快不过物理带宽、为什么 MoE 只激活部分专家(见第 2 篇)、为什么小 batch 下算力闲置而大 batch 下带宽被摊薄(见第 3 篇)。
prefill 则相反:输入 token 并行计算,读权重次数少、计算密集,瓶颈通常是算力而非带宽。两阶段背道而驰,推理系统因此要分别优化——这就是 vLLM continuous batching 拆 prefill/decode 的物理动机。
5. 算账工具:Python 函数(可直接复算)
def kv_bytes(num_layers, kv_heads, head_dim, seq_len, bytes_per=2):
return 2 * num_layers * kv_heads * head_dim * seq_len * bytes_per
def weights_bytes(num_params, bytes_per=2):
return num_params * bytes_per
def flops_request(num_params, input_tokens, output_tokens):
return 2 * num_params * (input_tokens + output_tokens)
def decode_tpot(num_params, bytes_per, hbm_bw):
"""带宽约束的每 token 时间下限(秒)"""
return num_params * bytes_per / hbm_bw
# 例:70B,GQA(80 层/8 KV heads/128 dim),FP16,4K 上下文,HBM 3TB/s
N = 70e9
print(weights_bytes(N) / 1e9, "GB 权重") # 140 GB
print(kv_bytes(80, 8, 128, 4096) / 1e6, "MB KV") # 655 MB
print(decode_tpot(N, 2, 3e12) * 1e3, "ms/token") # ~46.7 ms6. 实战检查清单
- [ ] 显存账三件套(权重 + KV + 工作区)都算了,没有只算权重
- [ ] KV 按最大并发上下文规划(不是平均长度),并确认 GQA 层配置
- [ ] decode 瓶颈用带宽核对(TPOT ≥ 权重字节/ HBM 带宽),不要只看算力峰值
- [ ] prefill/decode 分开估:prefill 看算力,decode 看带宽
- [ ] 量化收益用公式复核:INT4 减的是权重账,KV 量化减的是缓存账,别混为一谈
下一篇《从模型架构到资源需求》把注意力、KV 复用与 MoE 稀疏激活的量化影响接上这组公式。
方法参考:数量级估算与"数据搬移五问"框架参考开源书稿《深入理解 AI Infra:量化分析与系统设计》(李博杰,Apache-2.0,bojieli.github.io/ai-infra-book);本文公式与算例为本站自行推导与整理。