2. 推理任务显存画像
要给一次推理任务做显存画像,核心不是背几个公式,而是回答一个问题:这块 GPU 上,显存到底被谁拿走了,各自拿走多少,谁随请求变化,谁在启动时就固定下来。只有把占用拆成可解释的几块,才能判断“还能不能再加一个并发”“为什么 batch 从 8 加到 16 就 OOM”“为什么空闲时显存也降不下来”。
从工程视角看,一次推理的显存占用可以拆成五类:模型权重、KV Cache、激活值、临时缓冲区、CUDA Graph 及其捕获资源。前两类是主体,后三类是容易被忽略、却经常在边界条件下压垮显存的“隐性开销”。
一、模型权重:最“老实”的一块,但量化会改变全局
模型权重是显存画像里最容易算的一块:参数量 × 每个参数的字节数。一个 7B 参数的模型,FP16 下每个参数 2 字节,权重约 14 GB;如果换成 INT8,约 7 GB;INT4 则约 3.5 GB。这部分在模型加载完成后基本固定,不随请求数变化,属于“静态占用”。
但权重的意义不只是它自己占多少。它决定了剩余显存的上限。24 GB 卡上放 14 GB 权重,只剩约 10 GB 给 KV Cache、激活值和缓冲区;如果权重压到 7 GB,就多出 7 GB 可分配给并发。所以量化往往不是“省一点”,而是直接决定并发能力翻倍与否。需要注意的是,量化后部分算子可能引入额外的反量化临时张量,实际节省会略小于理论值。
二、KV Cache:并发扩展的真正瓶颈
KV Cache 是自回归解码的核心缓存。每生成一个 token,都要把该层该头的 Key 和 Value 存下来,供后续 token 做注意力计算。它的容量公式可以写成:
KV Cache 字节数 =
2(K 和 V)
× 层数 L
× KV 头数 H_kv
× 头维度 d
× 序列长度 S
× 并发请求数 B
× 每元素字节数(FP16 为 2)
以一个示例配置说明:32 层、32 个 KV 头、头维 128、序列长度 2048、并发 8、FP16。代入得 2×32×32×128×2048×8×2 ≈ 6.0 GB。可以看到,KV Cache 与并发数、序列长度都成正比。并发从 8 提到 16,这一项就翻倍到约 12 GB,很容易直接 OOM。这也是为什么长上下文场景下,分页 KV Cache(如 PagedAttention)和 KV 量化会成为关键手段——它们改变的是同一公式里的“每元素字节数”和“碎片浪费”。
三、激活值与临时缓冲区:峰值往往藏在这里
激活值是前向传播过程中产生的中间张量。在 prefill 阶段,整段 prompt 一次性过网络,激活值峰值与 batch、序列长度强相关;在 decode 阶段,每次只处理一个 token,激活值相对小。因此显存峰值通常出现在 prefill 长序列叠加高并发时,而不是 decode 稳态。
临时缓冲区则包括通信 all-reduce 的 workspace、采样与排序的临时空间、cuBLAS/cuDNN 的 workspace 等。它们单块不大,但种类多、生命周期短,容易造成碎片。工程上常见的现象是:nvidia-smi 显示已用 22 GB,但真正活跃的张量只有 18 GB,差额就是碎片和缓存池。
四、CUDA Graph:用显存换启动开销
CUDA Graph 把一串 kernel 启动捕获成图,回放时几乎无 CPU 启动开销,对 decode 阶段的小 kernel 密集场景收益明显。但它的代价是:捕获时使用的输入、输出和中间张量地址会被固定下来,必须常驻显存。如果为多个 batch size 分别捕获图,每个图都占一份池子,累积起来可能达到 GB 级。因此启用 CUDA Graph 后,必须重新做一次显存画像,不能沿用未捕获时的并发上限。
torch.cuda.memory_summary() 对照实际分配,差额就是碎片与框架预留。五、把画像落到可执行的判断上
假设 24 GB 卡、7B FP16 权重 14 GB、框架预留约 1 GB、CUDA Graph 池约 1 GB,则可用于 KV Cache 与激活值的约 8 GB。若单请求 2048 长度占 0.75 GB KV,则理论上限约 10 并发;但 prefill 峰值还要占激活值,实际安全并发应留 20% 余量,取 8 左右。这个“先算静态、再算动态、最后留余量”的过程,就是显存画像的落地方式。
| 占用项 | 典型量级(示例) | 是否随请求变化 | 主要优化手段 |
|---|---|---|---|
| 模型权重 | 14 GB(7B FP16) | 否 | INT8/INT4 量化、张量并行 |
| KV Cache | 6 GB(8 并发×2048) | 是,正比于并发×长度 | 分页 KV、KV 量化、限制并发 |
| 激活值 | 1~2 GB(prefill 峰值) | 是,prefill 时高 | chunked prefill、限制 batch |
| 临时缓冲区 | 0.5~1 GB | 是,短时波动 | 复用 workspace、减少通信 |
| CUDA Graph | 0.5~2 GB | 否(捕获后固定) | 减少捕获的 batch size 种类 |
把这张表当作起点,每次调整模型精度、并发上限或 Graph 策略后重新估算一遍,就能在 OOM 发生之前知道是哪一项越了界,而不是等到服务报错再回头猜。