一、GPU显存基础:你需要先理解这个“大水池”
做AI训练和推理,显存就像你的“作战阵地”。阵地有多大,能囤多少兵(模型参数),能放多少粮草(中间激活值),全看它。说白了,显存不够,任务就跑不动。我最早踩的坑,就是拿一块4GB显存的卡去跑BERT,结果OOM(内存溢出)报错直接把进程杀了。嗯,从那以后我花了不少时间搞明白显存到底是怎么回事。
1.1 显存长什么样?——显存架构
GPU的显存,本质上是DRAM(动态随机存取存储器),和电脑系统内存同宗同源。但GPU对吞吐要求极高,所以发展出了专门的图形显存标准。
我个人习惯把显存分为两层来看:
- 芯片内部:GPU核心芯片有一套片上缓存(L1/L2),容量很小但速度极快。
- 芯片外部:真正的“显存颗粒”,通过显存控制器(MC)与GPU核心相连。这部分就是我们常说的VRAM。
主流显存类型,按代际和带宽分:
| 类型 | 单芯片带宽 | 典型容量/颗 | 常见场景 |
|---|---|---|---|
| GDDR6 | ~64 GB/s | 8/16 Gb | 消费级显卡(RTX 30/40 系列) |
| GDDR6X | ~84 GB/s | 8/16 Gb | 高端游戏卡(RTX 3090/4090) |
| HBM2e | ~460 GB/s | 8/16 GB 每堆栈 | 数据中心卡(A100、V100) |
| HBM3 | ~800 GB/s | 16/32 GB 每堆栈 | H100、AMD MI300 |
1.2 显存带宽——真实速度到底有多快?
显存带宽是 GPU 搬运数据的能力。你想想看,模型参数要从显存搬到计算核心,如果带宽不够,计算单元就得干瞪眼等着。这就是所谓的“带宽瓶颈”。
我早年跑一个 30 亿参数模型,发现 GPU 利用率始终在 40% 以下。后来用 nvidia-smi 看了显存带宽利用率,乖乖,已经 95% 了。计算核心在“撑死”,显存通道在“累死”。
带宽计算公式很简单:
显存带宽 (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 ns | 70~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 显存层次与数据流动
1.5 总结:显存不足的几个底层原因
有了上面的基础,你现在应该能明白为什么显存不够用了:
- 模型太大:参数本身占显存。比如一个 7B 模型用 FP16 需要约 14GB,再加优化器状态、梯度,轻松超过 20GB。
- 带宽不够:即使显存够,带宽也能把 GPU 核心“饿死”。我见过很多同学买卡只看容量,不看带宽,结果跑训练比预期慢 5 倍。
- 显存和系统内存的物理隔离:你不能把系统内存直接当显存用,必须显式拷贝。一旦显存溢出,程序就得崩或启动 swap 机制(如 CUDA Unified Memory),但速度会下降到 PCIe 水平。