一、GPU显存基础:你需要先理解这个“大水池”

做AI训练和推理,显存就像你的“作战阵地”。阵地有多大,能囤多少兵(模型参数),能放多少粮草(中间激活值),全看它。说白了,显存不够,任务就跑不动。我最早踩的坑,就是拿一块4GB显存的卡去跑BERT,结果OOM(内存溢出)报错直接把进程杀了。嗯,从那以后我花了不少时间搞明白显存到底是怎么回事。

1.1 显存长什么样?——显存架构

GPU的显存,本质上是DRAM(动态随机存取存储器),和电脑系统内存同宗同源。但GPU对吞吐要求极高,所以发展出了专门的图形显存标准。

我个人习惯把显存分为两层来看:

  • 芯片内部:GPU核心芯片有一套片上缓存(L1/L2),容量很小但速度极快。
  • 芯片外部:真正的“显存颗粒”,通过显存控制器(MC)与GPU核心相连。这部分就是我们常说的VRAM。

主流显存类型,按代际和带宽分:

类型单芯片带宽典型容量/颗常见场景
GDDR6~64 GB/s8/16 Gb消费级显卡(RTX 30/40 系列)
GDDR6X~84 GB/s8/16 Gb高端游戏卡(RTX 3090/4090)
HBM2e~460 GB/s8/16 GB 每堆栈数据中心卡(A100、V100)
HBM3~800 GB/s16/32 GB 每堆栈H100、AMD MI300
小经验:别只看容量。显存架构决定了你能跑多大的 batch。比如HBM的堆叠设计,虽然单颗容量有限,但带宽高得吓人,特别适合大模型矩阵计算。
核心概念:显存带宽 = 显存时钟频率 × 数据位宽 × 传输倍率。数据位宽通常是 32-bit(每颗芯片),多颗并联后达到 256/384/512-bit。GDDR6X 用到了 PAM4 编码,等效频率翻倍,所以带宽数据很好看。

1.2 显存带宽——真实速度到底有多快?

显存带宽是 GPU 搬运数据的能力。你想想看,模型参数要从显存搬到计算核心,如果带宽不够,计算单元就得干瞪眼等着。这就是所谓的“带宽瓶颈”。

我早年跑一个 30 亿参数模型,发现 GPU 利用率始终在 40% 以下。后来用 nvidia-smi 看了显存带宽利用率,乖乖,已经 95% 了。计算核心在“撑死”,显存通道在“累死”。

避坑提醒:曾经我有一个同事,买显卡只看显存大小(8G、16G),完全不管带宽。结果拿来跑大模型,batch size 小得可怜,吞吐量还不如老款 1080 Ti(11G + GDDR5X 484 GB/s)。

带宽计算公式很简单:

显存带宽 (GB/s) = 显存时钟频率 (MHz) × 显存位宽 (bit) / 8 × 传输倍率 / 1000

例如 RTX 4090:
- 显存频率:21 Gbps(等效于 2100 MHz × 10? 其实是 PAM4 等效)
- 位宽:384-bit
- 倍率:GDDR6X 用 2× 传输(上下边沿)
计算:21 × 384 / 8 × 2 / 1000 = 2016 GB/s

实际上,NVIDIA 官方给的带宽是 1008 GB/s(因为 GDDR6X 已经内部做了倍增,计算时用实际频率 21 Gbps 直接乘位宽再除 8 即可)。我习惯用 nvidia-smi --query-gpu=memory.bandwidth --format=csv 来查实际跑出来的带宽,心里更有数。

1.3 显存 vs 系统内存——关键维度对比

说白了,显存和系统内存是两种完全不同的“仓库”。

维度显存 (VRAM)系统内存 (DDR)
物理位置焊在 GPU 卡上,通过显存控制器直连 GPU Core插在主板 DIMM 槽,通过内存控制器 + 总线连 CPU
典型带宽500~2000 GB/s(GDDR6X / HBM3)25~80 GB/s(DDR5-4800 双通道)
延迟(ns)100~400 ns70~120 ns(CPU 侧)
容量上限单卡 24~80 GB(消费级) 80~192 GB(专业卡)普通桌面 128~256 GB,服务器 4TB+
访问方式CPU 不能直接读写,必须通过 PCIe 拷贝CPU 直接寻址访问
用途深度学习激活值、权重、梯度、中间结果操作系统、程序代码、CPU 数据处理

这里有个关键点——GPU 不能直接访问系统内存。你想让 GPU 用系统内存里的数据,必须先调用 cudaMemcpy 或 cudaMemcpyAsync 把数据从主存复制到显存。我见过不少新手直接传指针给 kernel,结果程序崩溃——因为 GPU 核函数只认显存地址。

// 错误示范
float *h_data = (float*)malloc(N * sizeof(float));
// ... 填充 h_data ...
my_kernel<<<grid, block>>>(h_data);   // 崩溃!h_data 是 CPU 虚拟地址

// 正确方式
float *d_data;
cudaMalloc(&d_data, N * sizeof(float));
cudaMemcpy(d_data, h_data, N * sizeof(float), cudaMemcpyHostToDevice);
my_kernel<<<grid, block>>>(d_data);
经验之谈:当你跑大模型时,如果显存不够,可以考虑把部分权重放到系统内存里,用 torch.cuda.Stream 做异步分片拷贝。但要注意 PCIe 的瓶颈——PCIe 4.0 x16 只有 31.5 GB/s,和显存带宽差两个数量级。所以这招只适合“分布式显存不足”的临时方案。

1.4 可视化:GPU 显存层次与数据流动

GPU 显存层次与数据流动图 系统内存 (DDR) ~80 GB/s, 几百GB容量 PCIe ~32 GB/s GPU 卡 (Graphics Card) 显存颗粒 (GDDR6X / HBM) ~1 TB/s 带宽, 8~80 GB 容量 存储: 模型权重, 激活值, 梯度 显存控制器 (Memory Controller) L2 Cache (片上) ~10 MB SM (CUDA Core) 箭头: 数据流动方向 层级越低, 带宽越高

1.5 总结:显存不足的几个底层原因

有了上面的基础,你现在应该能明白为什么显存不够用了:

  1. 模型太大:参数本身占显存。比如一个 7B 模型用 FP16 需要约 14GB,再加优化器状态、梯度,轻松超过 20GB。
  2. 带宽不够:即使显存够,带宽也能把 GPU 核心“饿死”。我见过很多同学买卡只看容量,不看带宽,结果跑训练比预期慢 5 倍。
  3. 显存和系统内存的物理隔离:你不能把系统内存直接当显存用,必须显式拷贝。一旦显存溢出,程序就得崩或启动 swap 机制(如 CUDA Unified Memory),但速度会下降到 PCIe 水平。
一句话记住:显存是离 GPU 最近、最快的存储池,理解它的架构和带宽,是排查显存不足问题的第一步。后面几章我们会具体讲如何监控、定位和优化。