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 碎片问题的本质

碎片问题的本质,是分配策略与释放模式的不匹配。

分配器不知道未来谁会释放、谁会申请。它只能根据当前状态做决策。而深度学习训练的张量生命周期又高度动态,这就导致了碎片不可避免。

我个人习惯把碎片问题分成三个层次来看:

  1. 时间维度:分配和释放的时序交错
  2. 空间维度:不同大小的块混杂在一起
  3. 策略维度:分配器的 buddy system、slab 等算法各有优劣

理解了这三个维度,后面我们讲解决方案时,你就能明白为什么有的方法有效,有的只是治标不治本。

1.5 本章知识体系

下面这张图,是我自己总结的碎片问题全景图。你看一眼,就能对碎片问题有个整体把握:

显存池碎片问题全景图 定义 空闲总量足够 但无法满足连续分配 产生原因 内部碎片:分配粒度浪费 外部碎片:地址不连续 影响 显存利用率低 训练OOM 时间维度 分配与释放的时序交错 生命周期差异导致碎片 空间维度 不同大小块混杂 大块被小块分割 策略维度 buddy system slab 分配器 本质:分配策略与释放模式的不匹配 解决方案方向:预分配、池化、碎片整理、定制分配器

我的建议:刚开始接触碎片问题,不要急着看解决方案。先把定义和原因吃透。你想想看,连敌人长什么样都不知道,怎么打?我见过太多人一上来就调参数、换分配器,结果越调越糟。先理解,再动手,这是铁律。

好了,碎片问题的概述就到这里。记住三个关键词:定义、原因、影响。后面我们会一步步深入,看看怎么用代码和策略来解决这个头疼的问题。