第4章:内部碎片与外部碎片

两种碎片的定义、产生原因、对显存利用率的影响——这是显存碎片分析里最基础,也最容易被混淆的一对概念。我刚开始接触GPU显存管理时,也花了不少时间才理清楚它们的区别。

说白了,内部碎片和外部碎片就像你搬家时遇到的两类问题:一种是箱子太大,小东西放进去空荡荡的;另一种是箱子太小,大东西塞不进去。嗯,咱们一个一个来看。

4.1 内部碎片:分配了但用不上的空间

定义:内部碎片是指显存分配器给某个请求分配了比实际需求更大的内存块,导致块内未被使用的部分。

产生原因:

  • 对齐要求:GPU硬件通常要求内存地址对齐到128字节或256字节。你申请65字节,分配器给你128字节,多出来的63字节就是内部碎片。
  • 固定分配粒度:显存分配器有最小分配单元(比如4KB)。你申请1字节,它给你4KB,剩下4095字节全浪费了。
  • 缓存行对齐:某些GPU架构要求张量数据按缓存行对齐,多出来的填充字节也是内部碎片。
核心公式:内部碎片 = 分配块大小 - 实际请求大小

我在项目中遇到过这样一个场景:训练一个BERT模型时,每个张量申请的大小都刚好比对齐边界多几个字节。结果呢?每个张量都多浪费了128字节。模型有上万个张量,累计下来就是几百MB的浪费。说实话,当时排查了很久才发现是这个问题。

4.2 外部碎片:空闲但无法合并的空间

定义:外部碎片是指显存中存在多个小的空闲块,它们各自都太小无法满足新请求,但总和却足够大。

产生原因:

  • 分配与释放顺序不匹配:先分配A、B、C,然后释放B,再申请一个比B大的D。D放不进B的位置,只能去别处找空间。
  • 生命周期交错:不同张量的存活时间不同,释放后留下的空洞无法被后续大请求利用。
  • 内存池碎片化:频繁的分配释放操作会让空闲块越来越小、越来越多。
核心特征:总空闲空间足够,但无法满足单个大请求

我曾经调试过一个推理服务,显存占用显示还有2GB空闲,但分配一个1.5GB的batch时却OOM了。查了半天,发现那2GB空闲被分割成了上百个几十MB的小碎片。这就是典型的外部碎片问题。

4.3 两种碎片的对比

对比维度 内部碎片 外部碎片
碎片位置 在已分配的块内部 在已分配的块之间
产生原因 对齐、粒度、填充 分配释放顺序、生命周期
能否被利用 不能,已分配给当前请求 理论上可以,但太小无法满足新请求
主要影响 浪费显存容量 降低分配成功率
缓解方法 减小对齐粒度、使用紧凑分配 内存整理、伙伴系统、分池管理

4.4 对显存利用率的影响

内部碎片和外部碎片对显存利用率的影响方式完全不同。你想想看:

  • 内部碎片直接降低了有效利用率。你申请了100MB,实际用了80MB,那20MB就是纯浪费。利用率只有80%。
  • 外部碎片间接降低了利用率。虽然空闲空间总量很大,但无法被有效利用,导致你不得不提前触发OOM或者换出操作。

我个人的经验是:在训练大模型时,内部碎片通常占总显存的5%-15%,而外部碎片在极端情况下可以达到30%以上。两者加起来,显存的实际利用率可能只有60%-70%。

避坑指南:我曾经在一个项目中只关注了内部碎片,觉得对齐浪费个几百MB无所谓。结果模型跑着跑着就OOM,排查了三天才发现是外部碎片在作祟。所以两种碎片都要监控,缺一不可。

4.5 知识体系图

显存碎片分类与影响 显存碎片 内部碎片 外部碎片 对齐要求 固定粒度 缓存行对齐 分配释放顺序 生命周期交错 内存池碎片化 直接浪费显存容量 降低分配成功率 两者共同导致显存利用率下降

4.6 实际案例分析

拿一个实际场景来说。假设你有一个8GB显存的GPU,运行一个训练任务:

  • 模型参数占用:3GB
  • 优化器状态占用:3GB
  • 激活值占用:1.5GB
  • 总计:7.5GB

看起来还剩0.5GB空闲,对吧?但实际上:

  • 内部碎片:因为对齐和粒度问题,浪费了约0.4GB
  • 外部碎片:频繁的梯度累积和释放操作,产生了约0.3GB的碎片空洞
  • 实际可用空闲:0.5GB - 0.4GB - 0.3GB = -0.2GB

结果就是OOM。我遇到过好几次这种情况,明明算着显存够用,跑起来就崩了。

我的建议:监控显存碎片时,不要只看总空闲量。用nvidia-smi看的是总空闲,但内部碎片和外部碎片都藏在这些数字背后。我习惯用CUDA的cudaMemGetInfo配合自定义分配器日志,才能看到真正的碎片情况。

嗯,这一章的内容就到这里。内部碎片和外部碎片是显存管理的两个基本概念,理解清楚它们,后面讲碎片分析方法时你才能游刃有余。

可可精品资料库