AI AI infra · 推理侧
书库 第 1 章
AI infra · 推理侧

第 2 章 显存账本

先备检查:7B 的 FP16 权重约多大?一张 80GB 卡的显存是不是全都能用?能算 4 × 8 吗?
先修:第 1 章 · 深度:L2 · 预计阅读:30 分钟
一句话:一张卡能同时跑几条 8K 请求,是算出来的,不是试出来的。

一张 80GB 的卡,到底能放几条

二十个同事一起发 8K 合同,你手上是一张 80GB 的卡。

直觉是 80 除以 4,能放 20 条。一上线,OOM。

账有三笔:权重、缓存、余量。第一笔固定,第二笔随并发线性涨,第三笔最容易被忘。

第一笔:权重,不随请求数变

7B 参数按 FP16 存,每个参数 2 字节:

7 × 10⁹ × 2 = 14 GB ≈ 13 GiB

换成 INT8 是 7 GB,INT4 是 3.5 GB。这笔账只跟模型有关,来一个请求和来一百个请求,它都不动。

顺便把单位定死。GB 是十进制的 10⁹ 字节,GiB 是二进制的 2³⁰。差 7%,平时没感觉,卡在 45 GiB 对 48GB 的时候就要命。下面权重写 GB,显存账写 GiB,混着算的时候先统一。

第二笔:缓存,随并发线性涨

KV cache 的完整式子:

KV_bytes = 2 × L × H_kv × d_head × T × B × bytes_per_element
符号含义7B 例子
2Key 和 Value 各一份固定
L层数32
H_kvKV 头数32(GQA 时更少)
d_head每个头的维度128
T序列长度,输入加已生成8192
B同时处理的请求数1 到 20
bytesFP16 是 2,INT8 是 12

一条 8K 请求:

2 × 32 × 32 × 128 × 8192 × 1 × 2 = 4,294,967,296 字节 = 4 GiB

二十条就是 80 GiB。单这一笔就超过了整张 80GB 卡。

换个结构,账立刻不一样。Llama-3-8B 参数量差不多,但只有 8 个 KV 头。同样 8K:

4 GiB × (8 / 32) = 1 GiB

二十条 20 GiB,放得下。参数一样,并发差四倍,差在头数。

再往上走一层。70B 级模型,80 层、8 个 KV 头、FP16:

2 × 80 × 8 × 128 × 8192 × 2 = 2.5 GiB 一条

缓存不是问题了,权重 140 GB 才是:至少要两张 80GB 卡,然后跨卡通信冒出来。

第三笔:余量

剩下的位置给三样东西:激活值、框架自己的开销、碎片。

推理不像训练那样要存一堆中间结果,但每层还是会临时分配几十上百 MB,并发越高越明显。框架本身也要占:vLLM 启动时按 gpu_memory_utilization(默认 0.9)先把 90% 的显存圈走,建成 KV 池。

按 80GB 算,账是这样的:

可用 72 GiB − 权重 13 − 余量 3 = 56 GiB
56 / 4 = 14 条

二十四 GB 的卡:

可用 22 − 13 − 3 = 6 GiB
6 / 4 = 1 条,剩 2 GiB

同样换成 8 个 KV 头的模型:

6 / 1 = 6 条

这就是为什么"24GB 能不能跑 7B"不是一个能直接回答的问题。要问的是哪一张 7B、多长上下文、几条并发。

为什么能加载不等于能服务

你在 llama.cpp 里加载 13 GiB 的模型,看到 model loaded,以为大功告成。那只是说权重放得下。

服务端还要回答另一个问题:缓存放哪、放多少。vLLM 一启动就会打印 KV cache 块数,这个数字才是你真正的并发上限。max_model_len 设成 32K,调度器就按 32K 给每个请求留位置,可用并发会直接掉下来——具体行为随版本变,这里只有 B 级证据,按你手上的版本核一遍日志。

算术强度:把"先撞哪堵墙"算出来

第 1 章说 decode 撞带宽、prefill 撞算力,那是个观察。这一节把它变成除法。

算术强度是每读一字节能做多少次浮点运算。模型的每步运算量除以读取字节数,就是它;硬件的峰值算力除以显存带宽,是机器的平衡点。前者小于后者,带宽受限;大于,算力受限。

decode 一步,生成一个 token:

运算量 ≈ 2 × 7 × 10⁹ = 14 GFLOP
读取量 ≈ 14 GB(权重)
算术强度 ≈ 1 FLOP / 字节

A100 的平衡点:

312 TFLOPS / 2,039 GB/s ≈ 153 FLOP / 字节

1 远小于 153,所以 decode 一定卡在带宽上,换更强的算力也没用。

prefill 一次处理 8K:

运算量 ≈ 2 × 7 × 10⁹ × 8192 ≈ 114 TFLOP
算术强度 ≈ 8,150 FLOP / 字节

8,150 远大于 153,算力受限。同一个模型,两段代码,两个结论。

什么时候这个账本会算错

五种情况,遇到了要重算。

量化只压权重。INT4 把 13 GiB 变成 3.5,但缓存还是 FP16 的 4 GiB。省下的 9.5 GiB 只能换两条半请求,不是四倍并发。

MoE 的账不一样。总参数很大,但每个 token 只走几个专家。权重必须全放,算力却按激活参数算,所以"参数量 ÷ 显存"这个除法在这里失效。

多卡张量并行。权重切开了,缓存也切开了,但每张卡还要留通信缓冲,实际可用的显存比纸面少。

前缀缓存命中。二十个人用同一个 system prompt,那部分缓存只存一份,实际占用低于估算。

输出长度不可控。"最大 512"是你写的,用户不遵守。按 512 算的账,遇到一个 4K 输出的请求就崩。

经验:先算三条线

拿到任何一张卡、任何一个模型,先算三行:权重多少、一条请求的缓存多少、余量留多少。

三行里错一行,batch 上限就是错的,后面调参全是在错的基线上调。这三行十分钟能算完,比压测一小时便宜。

练习

六道题,先闭卷,再翻 ANSWERS.md。

  1. 7B 模型用 GQA,8 个 KV 头,8K 上下文,20 条并发。缓存多少 GiB?
  2. 一张 24GB 卡,7B、32 个 KV 头、8K,最多几条并发?把三条式子写出来。
  3. 把权重压到 INT4 之后,24GB 卡的并发为什么没有涨四倍?
  4. 找错:"80GB 卡能放 20 条 8K 请求,因为 4 × 20 = 80。"
  5. 用第 1 章的 14 GB 权重和 A100 的 2,039 GB/s,算 decode 和 prefill 的算术强度,判断各撞哪堵墙。
  6. 起一次 vLLM,从启动日志里找到 KV cache 块数,用块数乘块大小,反算你这张卡能放几条 8K 请求。

这一章就记一句

显存账本三行:权重固定,缓存按 2·L·H_kv·d·T·B·字节 线性涨,余量留 2–4 GiB。80GB 卡配 7B、8K,实际只能并 14 条;24GB 卡只能 1 条。

真只记一句:并发上限是算出来的,不是试出来的;算错了,后面所有调参都是在错的基线上调。

下一章把这 14 条塞满:batching 怎么把权重读取摊薄,为什么总吞吐涨了、单条却更慢,以及尾延迟到底从哪里来。