一、显存基础与瓶颈分析
大家好,我是你们的老朋友。今天咱们聊聊显存——这个在TensorRT-LLM推理中绕不开的话题。
说实话,我刚开始接触大模型推理时,也踩过不少坑。有一次部署一个70B的模型,明明算力够,但就是跑不起来。后来一查,显存爆了。嗯,从那以后,我养成了一个习惯:先搞清楚显存怎么用,再动手优化。
1.1 GPU显存架构:HBM与L2 Cache
先说说GPU的显存架构。你想想看,GPU处理数据,就像工厂流水线。HBM(高带宽内存)是原材料仓库,L2 Cache是车间里的临时货架。
HBM的特点:
- 容量大——通常几十GB,能装下整个模型
- 带宽高——A100的HBM带宽约2TB/s,H100能到3.35TB/s
- 延迟高——访问一次要几百个时钟周期
L2 Cache的特点:
- 容量小——A100的L2 Cache是40MB,H100是50MB
- 带宽极高——是HBM的几倍
- 延迟低——几十个时钟周期就能拿到数据
我个人习惯把L2 Cache看作「热数据缓冲区」。那些频繁访问的权重、激活值,如果能留在L2里,推理速度能快不少。我在项目中遇到过,同样的模型,L2命中率从60%优化到85%,端到端延迟直接降了30%。
核心观点:显存优化的本质,就是让数据尽量待在L2 Cache里,少去HBM「搬砖」。
下面这张图,是我自己总结的GPU显存层次结构:
1.2 TensorRT-LLM推理中的显存占用模型
搞清楚了硬件,咱们再看看软件层面。TensorRT-LLM推理时,显存主要被三样东西吃掉:模型权重、KV Cache、激活内存。
模型权重:
- 就是模型参数本身,比如70B模型,FP16精度下约140GB
- 量化后能降到70GB(INT4)甚至35GB(INT8)
- 这部分是「死内存」,加载后基本不变
KV Cache:
- 存储Attention层的Key和Value
- 随序列长度线性增长——序列越长,吃得越多
- 我见过最夸张的情况:一个8K长度的对话,KV Cache占了总显存的60%
激活内存:
- 中间计算结果,比如LayerNorm的输出、FFN的中间激活
- 跟batch size强相关——batch越大,激活内存越大
- 这部分是「临时内存」,用完就释放
个人经验:我曾经优化过一个对话系统,发现激活内存占了40%的显存。后来通过「激活重计算」技术,把峰值降到了15%。说白了,就是用时间换空间——多算一次,少存一点。
下面这个表格,是我整理的不同配置下的显存占用估算:
| 模型规模 | 精度 | 权重显存 | KV Cache(4K长度) | 激活内存(batch=1) | 总计 |
|---|---|---|---|---|---|
| 7B | FP16 | 14 GB | 2 GB | 1 GB | 17 GB |
| 13B | FP16 | 26 GB | 4 GB | 2 GB | 32 GB |
| 70B | INT4 | 35 GB | 8 GB | 4 GB | 47 GB |
| 70B | FP16 | 140 GB | 8 GB | 4 GB | 152 GB |
注意:上面的估算只是「裸模型」的占用。实际部署时,还要加上TensorRT-LLM的运行时内存、CUDA context、以及各种临时缓冲区。我建议至少预留20%的余量。
1.3 显存瓶颈的Profiling方法
光知道理论没用,得能「看见」显存到底怎么用的。Profiling就是干这个的。
我常用的工具有三个:
- nvidia-smi——最基础,看个大概
- Nsight Systems——时间线视图,看显存分配和释放
- Nsight Compute——深入kernel级别,看L2命中率、带宽利用率
举个例子,用Nsight Systems抓显存分配:
# 命令行启动
nsys profile -o trace_output -t cuda,nvtx \
--gpu-metrics-device=0 \
python run_inference.py
# 然后打开生成的 .nsys-rep 文件
# 在「CUDA Memory」视图里,你能看到:
# - 每个alloc/free的时间点
# - 峰值显存占用
# - 哪些操作在「吃」显存
我个人习惯先跑一次nvidia-smi看看整体情况:
# 每0.5秒刷新一次,显示显存使用
watch -n 0.5 nvidia-smi --query-gpu=memory.used,memory.total --format=csv
# 输出示例:
# memory.used [MiB], memory.total [MiB]
# 45230, 81920
# 45310, 81920
# ...
如果发现显存占用异常高,我会用Nsight Compute深入看kernel的L2命中率:
# 对特定kernel做profiling
ncu --set full -o kernel_profile \
--target-processes all \
python run_inference.py
# 关键指标:
# L1命中率:越高越好,目标>80%
# L2命中率:目标>60%
# HBM带宽利用率:目标>70%
避坑指南:我曾经遇到过一个情况,nvidia-smi显示显存只用了60%,但程序报OOM。后来发现是「显存碎片化」导致的——虽然总量够,但连续的大块内存不够。这种情况,用Nsight Systems的「Memory Fragmentation」视图才能看出来。
最后,我总结一下Profiling的「三步走」策略:
- 第一步:nvidia-smi看总量,确认有没有爆显存
- 第二步:Nsight Systems看时间线,找到显存峰值点
- 第三步:Nsight Compute看细节,定位L2命中率、带宽瓶颈
嗯,这一章的内容就到这里。显存基础打牢了,后面咱们才能谈优化。记住一句话:你没法优化你看不见的东西。先学会Profiling,再谈优化策略。