4、访存瓶颈分析:模型权重的显存占用、KV Cache的显存占用、访存带宽对延迟的影响

聊到大模型推理,很多人第一反应是“算力够不够”。

但我做了这么多年性能优化,说实话,真正卡脖子的往往不是算力,而是访存。你想想看,GPU算力这几年翻了多少倍?但显存带宽的增长速度,根本跟不上。这就导致了一个很尴尬的局面——计算单元经常在“等数据”。

这一节,我们就来掰扯清楚三个核心问题:

  • 模型权重到底吃了多少显存?
  • KV Cache又是怎么把显存撑爆的?
  • 访存带宽这个“隐形天花板”怎么影响延迟?

4.1 模型权重的显存占用:一个简单的乘法

先说权重。这个其实很好算。

一个模型,参数总量是 N,每个参数用 B 个字节存储。那么权重占用的显存就是:

显存占用 = N × B

举个例子。LLaMA-7B,70亿参数。如果用FP16(2字节),那就是:

7 × 10⁹ × 2 = 14 GB

嗯,14GB。一张A100 80G还能扛得住。但如果是LLaMA-65B呢?

65 × 10⁹ × 2 = 130 GB

130GB,单卡根本放不下。这时候就得做模型并行,或者用量化。

关键点:权重占用的显存是“一次性”的。模型加载完,这块内存就固定了。它不会随着推理过程动态增长。

我个人习惯,在评估一个模型能不能部署时,第一件事就是算这个数。如果单卡放不下,后面的优化方案都得重新考虑。

4.2 KV Cache的显存占用:动态增长的“显存黑洞”

权重是固定的,但KV Cache不是。它随着序列长度动态增长。这才是真正的“显存黑洞”。

KV Cache存的是什么?说白了,就是Transformer解码时,每一层、每个注意力头产生的Key和Value矩阵。为什么要存?因为自回归生成时,后面的token需要跟前面所有的token做注意力计算。如果不缓存,每次都得重新算一遍,那计算量就炸了。

它的显存占用公式稍微复杂一点:

KV Cache显存 = 2 × 层数 × 注意力头数 × 序列长度 × 每个头的维度 × 字节数

注意前面那个“2”,是因为Key和Value各存一份。

我举个例子,大家感受一下:

模型 层数 头数 头维度 序列长度 KV Cache (FP16)
LLaMA-7B 32 32 128 2048 2 × 32 × 32 × 2048 × 128 × 2 = 1.07 GB
LLaMA-7B 32 32 128 4096 2.15 GB
LLaMA-13B 40 40 128 4096 3.36 GB

看到了吗?序列长度翻倍,KV Cache也翻倍。而且这还只是一个请求的占用。如果同时服务多个用户,这个数字还要乘以并发数。

我曾经踩过的坑:有一次上线一个对话模型,测试时单请求延迟没问题。结果一上生产,并发一上来,显存直接OOM。查了半天,就是KV Cache没控制好。后来加了显存监控和动态batch调度,才稳住。

4.3 访存带宽:被忽视的延迟瓶颈

好,显存占用算清楚了。但还有一个更隐蔽的问题——访存带宽

你想想看,GPU要计算一个矩阵乘法,首先得把数据从显存搬到计算单元(SM)里。这个搬运的速度,就是访存带宽。A100的显存带宽大约是2 TB/s,听起来很快对吧?但跟它的算力(312 TFLOPS)一比,就完全不够看了。

我给大家算一笔账:

假设我们要做一个矩阵乘法,输入是 [1, 4096],权重是 [4096, 4096]。这个计算需要读取权重:

4096 × 4096 × 2 = 33.5 MB

以A100的带宽来算,读取时间大约是:

33.5 MB / 2 TB/s ≈ 16.7 μs

而计算这个矩阵乘法,A100只需要大约:

2 × 4096 × 4096 / 312 TFLOPS ≈ 0.1 μs

看到了吗?访存时间比计算时间高了两个数量级。这就是典型的“访存瓶颈”。

核心结论:大模型推理,绝大多数算子是“访存密集型”的,而不是“计算密集型”的。延迟的瓶颈,不在计算,而在数据搬运。

4.4 一张图看懂访存瓶颈

下面这张SVG图,把整个访存瓶颈的逻辑串起来了。我建议你仔细看看,尤其是“权重读取”和“KV Cache读写”这两条路径。

大模型推理访存瓶颈分析 模型权重 参数数量 × 字节数 LLaMA-7B: 14GB (FP16) LLaMA-65B: 130GB (FP16) KV Cache 2 × L × H × S × D × B 序列长度2048: ~1GB 序列长度4096: ~2GB 访存带宽 A100: 2 TB/s H100: 3.35 TB/s 算力增长 > 带宽增长 访存瓶颈导致的问题 计算单元等待数据 → 利用率低 延迟主要由访存时间决定,而非计算时间 优化方向1 模型量化 (INT8/INT4) 减少权重体积 优化方向2 KV Cache量化 PageAttention等 优化方向3 算子融合 减少访存次数 图:大模型推理访存瓶颈分析及优化方向

4.5 怎么量化访存瓶颈?

光定性分析不够,我们得定量。这里有个常用的指标叫“计算访存比”(Arithmetic Intensity)。

公式很简单:

计算访存比 = 计算量 (FLOPs) / 访存量 (Bytes)

这个比值越高,说明算子越“计算密集”,GPU算力能充分发挥。比值越低,说明算子越“访存密集”,带宽就成了瓶颈。

我拿大模型里最常见的两个算子来对比:

算子 计算量 (FLOPs) 访存量 (Bytes) 计算访存比 瓶颈类型
矩阵乘法 (M=N=K=4096) ~137 GFLOPs ~67 MB ~2048 计算密集
Attention (S=2048, D=128) ~0.5 GFLOPs ~8 MB ~64 访存密集
LayerNorm (D=4096) ~8 KFLOPs ~16 KB ~0.5 严重访存密集

看到了吗?LayerNorm这种小算子,计算访存比只有0.5。说白了,GPU大部分时间都在等数据,真正算的时间微乎其微。

我的建议:做性能分析时,别只看算力利用率。先看看“计算访存比”。如果这个值低于100,那大概率是访存瓶颈。这时候堆算力没用,得从减少访存入手。

4.6 实战:怎么测访存瓶颈?

理论说完了,来点实际的。我一般用NVIDIA的Nsight Systems来抓profile。重点关注两个指标:

  • Memory Throughput:显存带宽的实际利用率
  • Compute Utilization:计算单元的利用率

如果Memory Throughput接近峰值(比如A100的2 TB/s),但Compute Utilization很低(比如低于30%),那基本可以断定是访存瓶颈。

举个例子,我之前优化一个LLaMA-7B的推理服务,Nsight抓出来的数据是这样的:

Memory Throughput: 1.8 TB/s (90% of peak)
Compute Utilization: 15%

嗯,很明显。GPU的显存带宽快跑满了,但计算单元大部分时间在闲着。这就是典型的访存瓶颈。

后来怎么解的?做了两件事:

  1. 把权重从FP16量化到INT8,访存量直接减半
  2. 把多个小算子融合成一个,减少中间结果的读写

优化后,Memory Throughput降到了1.2 TB/s,但Compute Utilization提升到了45%。延迟反而降了40%。

核心思路:访存瓶颈的优化,不是让带宽跑得更快,而是让每次搬运的数据发挥更大的价值。说白了,就是“少搬数据,多算数”。

好了,这一节的内容就到这。访存瓶颈是个大话题,但核心逻辑其实不复杂。记住三个数:权重占多少、KV Cache占多少、带宽够不够用。把这三点理清楚,性能优化的方向就有了。