4、工具核心概念:内存池(Memory Pool)、块(Block)、段(Segment)、分配器(Allocator)

好,咱们进入正题。这一章要聊的四个概念,是显存碎片治理工具的「四根柱子」。你搞懂了它们,再看任何工具的源码或配置,都不会觉得晕。

我个人习惯把内存池比作「仓库」,块是「货架」,段是「货架上的隔板」,分配器就是「仓库管理员」。嗯,这个比喻虽然糙,但理不糙。咱们一个一个拆开看。

4.1 内存池(Memory Pool)—— 你的专属显存仓库

内存池是什么?说白了,就是工具提前从 GPU 那里「批发」来的一大块显存。它不跟 CUDA 的 cudaMalloc 一个一个地要,而是一次性要一大片,然后自己管自己用。

为什么要这么干?

你想想看,每次调用 cudaMalloc 都是一次内核态的系统调用,开销不小。更关键的是,频繁地申请和释放不同大小的显存,会让 CUDA 驱动层的分配器产生大量外部碎片。内存池把这块地圈起来,内部怎么切分、怎么回收,都是工具说了算,碎片率能大幅降低。

核心要点: 内存池的大小通常由用户在工具启动时指定,或者工具根据模型加载时的峰值显存自动估算。我建议你手动设置一个略大于模型峰值显存的值,比如 1.1 倍,给池子留点余量。

我在项目中遇到过一种情况:有人把池子设得特别大,结果多个进程同时跑,显存直接 OOM。嗯,这里要注意,池子不是越大越好,得算总账。

4.2 块(Block)—— 池子里的基本存储单元

内存池拿到一大块连续显存后,会把它切成若干「块」。每个块是一段连续的显存地址空间,大小可以不同。块是分配器能直接操作的最小单位。

块的状态通常有三种:

  • 空闲(Free): 还没被用,等着被分配。
  • 已分配(Allocated): 已经被某个张量或算子占用了。
  • 分裂(Split): 一个大块被切成了多个小块,其中一部分被用了,另一部分还闲着。

我记得有一次调试一个模型,发现显存占用不高但碎片率爆表。一查,原来是很多大块被切成了极小的碎片块,每个块只有几百字节,根本没法复用。这就是典型的「块分裂」问题。

避坑指南: 我曾经因为没设置「最小块大小」,导致池子里全是细碎的小块。后来我强制要求所有块的大小必须是 256 字节的倍数,情况好多了。你可以试试这个经验。

4.3 段(Segment)—— 块的逻辑分组

段这个概念,不同工具的叫法可能不一样,有的叫 Region,有的叫 Chunk。但本质都一样:段是多个块的逻辑集合。

为什么要引入段?因为一个内存池可能服务于不同的用途。比如,有些块专门给计算密集型算子用(比如卷积),有些块专门给显存带宽敏感型算子用(比如数据传输)。把它们分到不同的段里,可以避免相互干扰。

段的典型划分方式:

段名称 用途 块大小策略
计算段 存放卷积、矩阵乘等算子的中间结果 大块为主(4MB+)
通信段 存放 AllReduce、Broadcast 等通信缓冲区 中等块(1MB ~ 4MB)
临时段 存放生命周期极短的临时张量 小块为主(< 1MB)

我个人习惯把临时段的大小设得小一点,因为临时张量来得快去得也快,用大块反而容易造成浪费。

4.4 分配器(Allocator)—— 真正的「大脑」

分配器就是那个决定「谁来用哪块显存」的算法。它不直接跟 GPU 打交道,而是操作内存池里的块和段。

常见的分配策略:

  • Best-Fit: 找一块大小最接近请求大小的空闲块。碎片少,但查找慢。
  • First-Fit: 找到第一块够大的空闲块就分配。速度快,但容易产生碎片。
  • Buddy System: 把内存按 2 的幂次切分,分配和释放都很快,但内部碎片率稍高。

我见过最常用的工具是采用 Best-Fit + 空闲块合并 的组合策略。分配时用 Best-Fit 找最合适的块,释放时检查相邻块是否空闲,如果是就合并成一个更大的块。这样既能减少碎片,又能保持较高的分配效率。

注意: 分配器的性能瓶颈往往不在算法本身,而在锁竞争。多线程场景下,如果所有线程都抢同一个分配器,性能会急剧下降。我建议你关注工具是否支持「线程本地缓存」(Thread-Local Cache),这能大幅减少锁冲突。

4.5 知识体系结构图

下面这张图,把四个概念的关系画清楚了。你可以把它当作本章的「思维导图」。

显存碎片治理工具核心概念关系图 内存池(Memory Pool) 从 GPU 批发的大块显存,内部自主管理 段 A(计算段) 块 1(4MB) 块 2(4MB) 块 3(已分配) 块 4(空闲) 块 5(分裂:部分已用) 段 B(临时段) 块 6 块 7 块 8 块 9 块 10 块 11 分配器(Allocator) 空闲块 已分配块 分裂块

从这张图你能看到:分配器位于最底层,它操作的是段和块;段是块的逻辑分组;块是实际存储数据的单元;而内存池是这一切的「容器」。搞懂这个层级关系,你就能理解工具内部是怎么运作的了。

一个小技巧: 当你用工具查看显存状态时,如果发现某个段里的空闲块数量特别多但总空闲容量很小,说明碎片化严重。这时候就该考虑调整块大小策略或触发碎片整理了。

好了,这四个概念就讲到这里。它们就像工具箱里的扳手、螺丝刀、锤子和电钻——你不需要每次都记住所有细节,但用的时候得知道该拿哪个。

可可精品资料库