一、显存基础与瓶颈分析:显存架构、带宽与容量、显存溢出原因、性能分析工具使用

各位同学好,我是老K。今天咱们聊聊显存优化这个硬骨头。说实话,我入行那会儿,显存还是个不太被重视的东西——模型小,数据少,一张卡跑得飞起。现在呢?大模型动不动几百GB,显存成了真正的「卡脖子」环节。

这一章,咱们把显存的基础打牢。你只有知道它怎么工作的,才能知道怎么优化它。

1.1 显存架构:GPU的「临时仓库」

显存,全称是「视频随机存取存储器」。说白了,它就是GPU干活时临时放数据的地方。CPU有内存,GPU就有显存,道理是一样的。

我习惯把显存比作一个仓库。仓库越大,能存的货越多;仓库通道越宽,进出货越快。但仓库再大,也有装满的时候——这就是显存溢出。

现代GPU的显存架构,主流是GDDR6和HBM两种:

类型 带宽 容量 功耗 典型卡型
GDDR6 中等(~500 GB/s) 大(8~24 GB) 较高 RTX 3090, RTX 4090
HBM2e 高(~1.2 TB/s) 中等(16~32 GB) 低 A100, V100
HBM3 极高(~3.2 TB/s) 大(40~80 GB) 低 H100, B200

嗯,这里要注意:带宽和容量往往是矛盾的。GDDR6容量大但带宽低,HBM带宽高但容量受限。你选卡的时候,得看你的任务更吃带宽还是更吃容量。

我的经验:做推理部署时,我一般优先看带宽。因为推理时模型参数是固定的,带宽决定了你能跑多快。做训练时,容量更重要——模型、梯度、优化器状态,哪个都吃显存。

1.2 带宽与容量:谁才是瓶颈?

咱们先看一个公式:

显存带宽 = 显存频率 × 位宽 × 2(GDDR6双倍数据率)

举个例子,RTX 4090的显存频率是21 Gbps,位宽384 bit,那么:

带宽 = 21 × 384 / 8 × 2 = 2016 GB/s ≈ 2 TB/s

看起来很高对吧?但你要知道,一个大模型推理一次,需要把整个模型参数从显存搬到计算单元。假设模型是70B参数,每个参数是FP16(2字节),那就是140 GB的数据。2 TB/s的带宽,光搬运就要70毫秒——这还没算计算时间。

为什么会这样?说白了,计算单元跑得飞快,但数据搬不过来。这就是「内存墙」问题。

我在项目中遇到过这样一个案例:部署一个13B的LLM,用A100(80GB显存)跑,按理说容量够用,但推理速度就是上不去。一分析,发现瓶颈在带宽——模型太大,每次推理都要从显存搬大量数据,带宽成了天花板。

核心结论:推理场景,带宽往往是第一瓶颈。训练场景,容量是第一瓶颈,带宽是第二瓶颈。

1.3 显存溢出原因:到底谁在吃显存?

显存溢出(OOM)是部署中最常见的错误之一。我刚开始做部署时,经常被OOM搞得焦头烂额。后来我总结了一下,显存主要被这几类东西吃掉:

  • 模型参数:权重、偏置,这是大头。一个70B的FP16模型,光参数就140 GB。
  • 激活值:前向传播时每层的中间结果。推理时还好,训练时这个量很大。
  • 梯度:训练时反向传播产生的梯度,大小和参数差不多。
  • 优化器状态:Adam优化器需要保存动量和方差,是参数量的2倍。
  • 临时缓冲区:框架内部申请的临时空间,比如注意力计算的中间结果。

我曾经踩过一个坑:部署一个BERT模型,显存明明够用,但一跑就OOM。查了半天,发现是PyTorch的torch.cuda.empty_cache()没及时调用,临时缓冲区越积越多,最后爆了。

避坑指南:我曾经在部署一个对话模型时,因为没注意优化器状态,训练时显存直接翻了三倍。后来改用Adafactor优化器,优化器状态从2倍参数降到了接近0,才把模型塞进去。所以,训练时一定要算清楚:参数 + 梯度 + 优化器状态 + 激活值,这四样加起来才是真实显存占用。

1.4 性能分析工具使用:别靠猜,靠数据

我见过太多人,显存爆了就盲目调batch size,或者换小模型。其实,你应该先用工具看看,到底谁在吃显存。

这里推荐几个我常用的工具:

1. nvidia-smi

最基础的,但够用。看显存总量、已用、进程占用:

nvidia-smi -l 1  # 每秒刷新一次

2. torch.cuda.memory_summary()

PyTorch自带的显存分析工具,能告诉你每个张量占了多少显存:

import torch
print(torch.cuda.memory_summary())

3. NVIDIA Nsight Systems

这个工具能看时间线上的显存分配和释放。我习惯用它来定位「显存泄漏」——就是显存只增不减的那种情况。

4. 自定义显存监控

有时候框架自带的工具不够细,我会自己写个监控:

import pynvml
pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
info = pynvml.nvmlDeviceGetMemoryInfo(handle)
print(f"已用: {info.used / 1024**3:.2f} GB")
print(f"空闲: {info.free / 1024**3:.2f} GB")
我的习惯:每次部署新模型,我都会先跑一个空跑(不加载数据),看看模型本身占多少显存。然后再逐步加数据,看激活值占多少。这样心里有底,知道瓶颈在哪。

1.5 本章知识体系总览

下面这张图,是我自己画的显存优化知识体系。你看一眼,心里有个框架:

显存优化知识体系 显存架构 带宽与容量 溢出原因 GDDR6 / HBM2e / HBM3 推理吃带宽 / 训练吃容量 参数 / 激活 / 梯度 / 优化器 性能分析工具:nvidia-smi / memory_summary / Nsight Systems 模型量化(INT8/FP4) 梯度检查点 显存复用 / 卸载 核心原则:先分析,后优化。用数据说话,别靠感觉。 带宽不够 → 量化 / 剪枝 | 容量不够 → 梯度检查点 / 卸载

这张图把本章的核心逻辑串起来了。你看,显存架构决定了你能用什么类型的卡,带宽和容量决定了瓶颈在哪,溢出原因告诉你要监控什么,工具帮你定位问题,最后优化方向是解决方案。

嗯,这一章就到这里。记住一句话:显存优化不是玄学,是工程。你掌握了基础,后面的优化策略才能落地。

课后小作业:用nvidia-smi看看你手头GPU的显存占用情况,然后算一下你的模型参数占了多少显存。你会发现,很多时候显存不是被参数吃掉的,而是被临时缓冲区吃掉的。
可可精品资料库