1. 碎片问题概述
1.1 什么是显存池碎片?
显存池碎片,说白了就是显存被切得七零八落,导致明明还有空闲空间,却分配不出一个连续的大块内存。
我习惯把显存想象成一块大蛋糕。你切一块我切一块,时间长了,蛋糕上全是小碎块。这时候有人要一大块完整的蛋糕,对不起,给不了。虽然碎块加起来的总量够,但拼不成一整块。
在深度学习训练中,显存池通常由 CUDA 的缓存分配器管理。它会预先申请一大块显存,然后按需切分给各个张量。问题是——张量的生命周期各不相同,释放和分配交错进行,碎片就慢慢出现了。
核心定义:显存池碎片是指显存中可用空闲块的总量足够,但无法满足某个连续内存分配请求的现象。
1.2 碎片是怎么产生的?
嗯,这里要分两种碎片来看:内部碎片和外部碎片。
1.2.1 内部碎片
分配器给你一个 512MB 的块,你实际只用 500MB。那 12MB 就浪费了。这就是内部碎片。说白了就是分配粒度太粗,给多了。
我在项目中遇到过,PyTorch 默认的分配粒度是 512 字节。看起来很小对吧?但架不住张量数量多。一个大型 Transformer 模型可能有上万个张量,每个浪费几十字节,加起来就不少了。
1.2.2 外部碎片
这才是真正的头疼问题。外部碎片是指空闲块的总量够,但它们在地址空间上不连续。
举个例子:
显存布局(每个字母代表一个分配块):
初始: [A][B][C][D][E]
释放 B 和 D: [A][ ][C][ ][E]
现在空闲块有两个:位置2(大小2)和位置4(大小2)
总量空闲 = 4,但最大连续块 = 2
如果要分配大小=3的块,失败!
为什么会这样?因为深度学习训练中,张量的生命周期差异很大:
- 模型参数:全程存活,从不释放
- 梯度:每个 step 更新后可能释放
- 激活值:前向计算时创建,反向传播后释放
- 临时变量:算子内部创建,用完即丢
你想想看,这些生命周期不同的张量交错分配释放,就像在显存里打地鼠。刚空出一块,又被另一个张量占了。久而久之,显存就变成了筛子。
1.3 碎片对深度学习训练的影响
我刚开始做分布式训练时,就吃过碎片的亏。一个 70B 的模型,理论显存需求 140GB,我准备了 160GB 的显存,结果跑着跑着就 OOM 了。查了半天,原来是碎片在作祟。
具体影响有三个方面:
| 影响维度 | 具体表现 | 严重程度 |
|---|---|---|
| 显存利用率 | 实际可用显存远小于总量,浪费严重 | 高 |
| 训练稳定性 | 训练中途突然 OOM,无法恢复 | 极高 |
| batch size 限制 | 不敢开大 batch,怕碎片导致分配失败 | 中 |
| 训练速度 | 碎片整理需要额外开销,分配变慢 | 中低 |
避坑指南:我曾经在训练一个 175B 模型时,显存碎片率高达 30%。也就是说,80GB 的显存,有 24GB 是碎片。最后不得不把 batch size 从 32 降到 16,训练时间直接翻倍。所以碎片问题不是小事,它直接影响你的训练效率和成本。
1.4 碎片问题的本质
碎片问题的本质,是分配策略与释放模式的不匹配。
分配器不知道未来谁会释放、谁会申请。它只能根据当前状态做决策。而深度学习训练的张量生命周期又高度动态,这就导致了碎片不可避免。
我个人习惯把碎片问题分成三个层次来看:
- 时间维度:分配和释放的时序交错
- 空间维度:不同大小的块混杂在一起
- 策略维度:分配器的 buddy system、slab 等算法各有优劣
理解了这三个维度,后面我们讲解决方案时,你就能明白为什么有的方法有效,有的只是治标不治本。
1.5 本章知识体系
下面这张图,是我自己总结的碎片问题全景图。你看一眼,就能对碎片问题有个整体把握:
我的建议:刚开始接触碎片问题,不要急着看解决方案。先把定义和原因吃透。你想想看,连敌人长什么样都不知道,怎么打?我见过太多人一上来就调参数、换分配器,结果越调越糟。先理解,再动手,这是铁律。
好了,碎片问题的概述就到这里。记住三个关键词:定义、原因、影响。后面我们会一步步深入,看看怎么用代码和策略来解决这个头疼的问题。