一、显存碎片全景:从现象到本质

大家好,我是你们的老朋友。今天咱们聊聊显存碎片——这个让无数GPU开发者头疼的问题。

先问个问题:你有没有遇到过这种情况?明明显存还有几十GB空闲,但一分配新张量就报OOM。我当年第一次遇到时,差点把键盘砸了。后来才明白,这背后就是显存碎片在作祟。

1.1 什么是显存碎片?

说白了,显存碎片就是显存空间被分割成许多不连续的小块。就像一块完整的蛋糕,被切得七零八落,虽然总重量没变,但你想切一块完整的方形出来,却发现哪块都不够大。

显存碎片分两种:

  • 内部碎片:分配器给你一块内存,但你用不完。比如你申请100MB,分配器给了128MB的块,多出来的28MB就是内部碎片。
  • 外部碎片:空闲内存总量够,但分散在各处,无法满足连续大块分配。这是最坑爹的。

核心区别:内部碎片是分配粒度问题,外部碎片是内存布局问题。实际项目中,外部碎片才是显存治理的头号敌人。

1.2 碎片产生的根本原因

为什么会碎?我总结了三座大山:

  1. 分配释放模式不匹配:训练过程中,张量不断创建和销毁。比如前向计算时分配大量中间结果,反向传播时又释放掉。这种频繁的分配-释放操作,就像在内存里打地鼠,打完一个洞又冒出一个。
  2. 张量生命周期差异大:模型参数活在整个训练周期,优化器状态活几个step,中间激活值可能只活几毫秒。不同生命周期的对象交错分配,内存自然被切得稀碎。
  3. 分配器策略局限:CUDA默认分配器是贪心的——它尽量复用最近释放的块。这导致大块空闲被切碎,后续大分配只能去申请新内存。

我记得有一次调一个LLM训练脚本,发现显存占用曲线像锯齿一样上下跳动。一查,原来是某个中间张量的生命周期没控制好,导致分配器反复切分大块内存。嗯,这就是典型的碎片制造机。

1.3 碎片对训练推理的影响

碎片不是小事,它带来的影响很实在:

影响维度 具体表现 严重程度
训练吞吐 频繁OOM导致batch size被迫缩小,训练效率下降30%-50% 高
推理延迟 碎片导致内存分配耗时增加,单次推理延迟可能翻倍 中
模型规模 碎片浪费20%-40%显存,同等硬件下能跑的模型变小 高
稳定性 碎片积累到临界点,训练中途崩溃,白跑几小时 极高

我曾经在一个8卡A100集群上跑175B模型训练,因为碎片问题,每跑12小时就得重启一次。你想想看,重启一次光加载checkpoint就要半小时,这谁受得了?

1.4 碎片治理的宏观策略

治理碎片,说白了就三板斧:

  • 预防:从源头减少碎片产生。比如统一张量生命周期、预分配大块内存池。
  • 整理:碎片已经产生了,就主动整理。比如定期压缩、迁移活跃张量。
  • 规避:绕开碎片问题。比如用更聪明的分配器、调整分配策略。

下面这张图是我自己画的碎片治理全景图,帮你快速建立整体认知:

显存碎片治理全景图 显存碎片治理 策略一:预防 策略二:整理 策略三:规避 统一生命周期 预分配内存池 对齐分配粒度 内存压缩 张量迁移 碎片合并 自定义分配器 伙伴算法 延迟释放

我的建议:不要一上来就搞花里胡哨的整理策略。先做好预防——统一张量生命周期、预分配内存池,这两招能解决80%的碎片问题。我在实际项目中,光靠这两招就把OOM频率从每天3次降到了每周1次。

避坑指南:我曾经在某个项目里,为了追求极致的内存利用率,自己写了个内存整理模块。结果整理过程中频繁触发cudaMemcpy,反而把训练速度拖慢了40%。后来我学乖了——整理策略要权衡收益和开销,不是所有场景都适合做内存整理。

好了,这一章我们建立了显存碎片的全局认知。下一章,我会带你深入CUDA内存分配器的源码,看看它到底是怎么工作的——知己知彼,才能百战不殆。

可可精品资料库