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步打印一次碎片化因子。如果连续三次上涨,就触发告警。这招帮我提前发现过好几次内存泄漏。
好了,碎片分类和度量就聊到这儿。记住一句话:没有完美的分配策略,只有适合场景的权衡。大页还是小页,内部碎片还是外部碎片,你得根据实际负载来调。别指望一个配置打天下。
想要更多资料,联系可可