3. 显存碎片分类:内部碎片与外部碎片、大页与小页的分配策略、碎片化度量指标

好,咱们进入第三章。说实话,碎片化这个问题,我早年做推理引擎的时候被它坑过不止一次。你明明看着显存还剩好几GB,一申请就OOM,气得想砸键盘。后来我才明白,这玩意儿得从根上拆解——碎片到底分几类?怎么量化的?怎么从分配策略上防一手?

3.1 内部碎片 vs 外部碎片

先搞清楚两个基本概念。说白了,碎片就两种:内部碎片和外部碎片。

内部碎片,是分配器给你的一块内存,你没用完,剩下的那截就浪费了。比如你只要7KB,但分配器最小粒度是8KB,那1KB就是内部碎片。我见过有人用cudaMalloc一次只申请几十字节,结果每个小请求都带一堆内部碎片,积少成多,显存就炸了。

外部碎片,是显存里东一块西一块的空闲区域,单独看每块都不大,但加起来总量不少。你想申请一块连续的大内存,结果哪块都不够。这就像你衣柜里塞满了衣服,每件之间都有缝,但就是放不进一个行李箱。

关键区别:

  • 内部碎片:分配出去但没用的空间
  • 外部碎片:空闲但不连续的空间

我当年调一个多路视频模型服务,显存32GB,跑着跑着报OOM。一查,外部碎片率高达40%。说白了,就是频繁创建销毁临时tensor,把显存切成了豆腐渣。

3.2 大页与小页的分配策略

怎么防碎片?分配粒度是关键。这里就要聊大页和小页的策略了。

小页策略:比如按4KB、8KB分配。优点是灵活,不浪费。缺点是容易产生外部碎片。你想想看,频繁分配释放,显存就像被狗啃过一样。

大页策略:比如按2MB、1GB分配。优点是连续性好,外部碎片少。缺点是内部碎片可能变大。你只要1MB,它给你2MB,那1MB就浪费了。

实际工程里怎么选?我个人习惯是混合策略:

  • 大块、长生命周期的tensor(比如模型权重)用大页
  • 小块、临时性的tensor(比如中间激活值)用小页

我曾在某个项目中,把默认分配粒度从4KB改成64KB,外部碎片率从35%降到了12%。代价是内部碎片从2%涨到了5%。但整体显存利用率反而提升了,因为OOM次数少了。

避坑指南:我曾经在NVIDIA A100上试过直接用1GB大页分配所有tensor,结果模型权重占了大头,但中间激活值只有几十MB,内部碎片率飙到60%以上。嗯,这招只适合权重占绝对主导的场景。

3.3 碎片化度量指标

光知道概念不够,你得能量化。我常用的指标有三个:

指标 公式 含义
内部碎片率 内部碎片总量 / 已分配总量 分配出去但没用的比例
外部碎片率 1 - (最大连续空闲块 / 总空闲空间) 空闲空间的不连续程度
碎片化因子 外部碎片率 × 内部碎片率 综合碎片化程度

举个例子。你显存总共16GB,已分配12GB,内部碎片1GB,空闲4GB但最大连续块只有1.5GB。那么:

  • 内部碎片率 = 1/12 ≈ 8.3%
  • 外部碎片率 = 1 - (1.5/4) = 62.5%
  • 碎片化因子 = 8.3% × 62.5% ≈ 5.2%

外部碎片率62.5%,说明空闲空间被切得很碎。这时候你申请一块2GB的连续内存,基本没戏。

注意:碎片化因子超过10%就要警惕了。我一般设个监控,超过15%就触发碎片整理或重启服务。别等到OOM再处理,那时候已经晚了。

下面这张图是我自己总结的碎片分类与度量体系,你一看就明白:

显存碎片分类与度量体系 显存碎片 内部碎片 外部碎片 小页分配 对齐浪费 元数据开销 频繁分配释放 大小不均 生命周期交错 碎片化度量指标 内部碎片率 已分配中的浪费 外部碎片率 空闲空间不连续度 碎片化因子 综合碎片程度

这张图把碎片分类和度量串起来了。你从上往下看:先判断是内部还是外部碎片,再找根因,最后用指标量化。我每次排查显存问题,都按这个流程走一遍,基本不会漏。

小技巧:我习惯在代码里埋个显存快照hook,每1000步打印一次碎片化因子。如果连续三次上涨,就触发告警。这招帮我提前发现过好几次内存泄漏。

好了,碎片分类和度量就聊到这儿。记住一句话:没有完美的分配策略,只有适合场景的权衡。大页还是小页,内部碎片还是外部碎片,你得根据实际负载来调。别指望一个配置打天下。

想要更多资料,联系可可