1. 显存碎片问题概述

做AI训练和推理的工程师,十有八九都遇到过这么个场景:

明明显存还有好几个G,但一跑新模型就报OOM(Out of Memory)。

你盯着nvidia-smi看半天,心里直犯嘀咕——这显存到底被谁吃了?

嗯,大概率是显存碎片在搞鬼。

什么是显存碎片

显存碎片,说白了就是显存被切成了很多小块。

就像一块完整的蛋糕,被人东挖一勺西挖一勺,最后剩下很多小坑。

这些小坑单独看都不大,但加起来总量不少。

问题是,你想放一块大蛋糕上去,放不下了。

显存碎片分两种:

  • 内部碎片:分配了比实际需要更大的块。比如你只要10MB,系统给了16MB,那6MB就浪费了。我见过不少框架默认对齐到2MB或4MB,小张量多了,内部碎片能吃掉10%以上的显存。
  • 外部碎片:空闲显存总量够,但不连续。就像上面说的蛋糕坑,每个坑都小,凑一起挺大,但放不下一个连续的大块。

核心观点:显存碎片不是显存不够用,而是显存「不好用」。

碎片产生的根本原因

为什么会碎?

答案其实很简单:分配与释放不匹配。

你想想看,深度学习模型跑起来,张量的生命周期千差万别:

  • 有些张量活得很短,比如中间激活值,用完就扔
  • 有些张量活得很长,比如模型参数,从头到尾都在
  • 有些张量大小变化,比如动态shape的输入

这种「你方唱罢我登场」的节奏,让显存分配器疲于奔命。

我举个例子你就明白了:

# 模拟显存分配与释放
tensor_a = allocate(100MB)  # 分配100MB
tensor_b = allocate(200MB)  # 分配200MB
free(tensor_a)              # 释放100MB,留下一个空洞
tensor_c = allocate(150MB)  # 需要150MB,但空洞只有100MB
                           # 只能从后面再分配,空洞保留

你看,tensor_a释放后留下的100MB空洞,因为不够放150MB的tensor_c,就一直空在那里。

随着训练进行,这样的空洞越来越多,越来越大。

我个人习惯把这种现象叫做「显存血栓」——血流(显存)总量不少,但血管堵了,过不去。

避坑指南:我曾经在一个大模型训练项目中,发现显存利用率只有65%。排查了三天,最后发现是PyTorch的缓存分配器在频繁分配释放小张量时,产生了大量外部碎片。换成自定义的预分配策略后,利用率直接拉到92%。

碎片对训练推理性能的影响

碎片不是小事,它从两个层面拖垮你的性能:

1. 显存利用率下降

这是最直接的后果。

碎片多了,有效显存就少了。

你可能本来能跑batch size=32,现在只能跑24。

或者本来能塞下更大的模型,现在只能缩水。

我做过一个统计,在典型的Transformer训练中:

场景 显存利用率 有效可用显存(24GB卡)
无碎片优化 60%-75% 14.4GB - 18GB
基础优化 80%-88% 19.2GB - 21.1GB
深度优化 92%-97% 22.1GB - 23.3GB

看到差距了吗?

同样的硬件,优化前后能差出4-8GB的可用显存。

这可不是小数目,足够你塞一个更大的模型或者更大的batch了。

2. 分配延迟增加

碎片多了,分配器找一块合适的内存就要花更多时间。

就像你在一个乱糟糟的仓库里找一个小零件——

仓库越大越乱,找起来越慢。

我实测过,在碎片率超过30%的情况下:

  • 单次显存分配延迟从微秒级飙升到毫秒级
  • 训练一个epoch,光分配开销就多了几十秒
  • 推理场景更敏感,延迟抖动会明显增加

注意:推理场景对延迟要求极高。碎片导致的分配延迟抖动,可能让你的P99延迟从5ms跳到50ms。这在线上服务里是不可接受的。

一张图看懂显存碎片

下面这张图,我画了显存碎片从产生到影响的全过程:

显存碎片产生与影响流程图 初始状态:连续空闲显存(如24GB连续块) 分配与释放 参数(2GB) 中间激活(3GB) 梯度(1.5GB) 空闲(17.5GB) 释放中间激活 参数(2GB) 空洞(3GB) 梯度(1.5GB) 空闲(17.5GB) 新分配4GB张量 参数(2GB) 空洞(3GB) 梯度(1.5GB) 新张量(4GB) 结果:3GB空洞无法利用,有效显存减少

这张图展示了碎片产生的典型过程:

  1. 一开始显存是连续的,整整齐齐
  2. 分配了参数、中间激活、梯度等张量
  3. 中间激活被释放,留下一个3GB的空洞
  4. 新来的4GB张量放不进这个3GB空洞,只能往后排
  5. 空洞就这么留下来了,越积越多

说白了,碎片问题的本质就是:显存分配器不够聪明,不知道如何「整理房间」。

而我们后面要讲的算子优化,就是要教它怎么整理。

一个小建议:如果你刚开始排查显存问题,先别急着优化算子。先用 torch.cuda.memory_summary() 看看碎片率。如果碎片率超过20%,那大概率是碎片在作祟。

好了,这一章我们搞清楚了碎片是什么、怎么来的、有什么危害。