3. 显存管理基础:cudaMalloc、cudaFree 与碎片化问题

显存管理,说白了就是给 GPU 分配和回收「工作台」。你想想看,一张显卡就那么多显存,模型要放、中间结果要存、多路并发还要抢。搞不好,显存就炸了。

我个人习惯,在写任何推理服务之前,先把显存管理这块理清楚。不然上线之后,OOM(Out of Memory)会像定时炸弹一样,随时爆给你看。

3.1 cudaMalloc:分配显存

cudaMalloc 是 CUDA 最基础的显存分配函数。它的用法跟 C 语言的 malloc 很像,只不过分配的是 GPU 上的内存。

float *d_ptr;
size_t size = 1024 * sizeof(float);
cudaError_t err = cudaMalloc((void**)&d_ptr, size);
if (err != cudaSuccess) {
    printf("分配失败: %s\n", cudaGetErrorString(err));
}

这里有个坑,我刚开始做 CUDA 开发时踩过——cudaMalloc 返回的是设备指针,你不能在 CPU 代码里直接读写它。必须用 cudaMemcpy 来搬运数据。

注意:cudaMalloc 是同步操作。也就是说,调用返回时,显存已经真的分配好了。但分配本身开销不小,频繁调用会影响性能。

3.2 cudaFree:释放显存

有分配就得有释放。cudaFree 就是干这个的。

cudaFree(d_ptr);
d_ptr = nullptr;  // 我习惯释放后立即置空,防止野指针

嗯,这里要特别提醒一句:忘记释放显存,是显存泄漏的根源。我在项目中遇到过好几次,服务跑了一整天,显存占用从 4GB 慢慢涨到 24GB,最后直接 OOM 挂掉。查了半天,发现是某个循环里分配了显存但没释放。

我的习惯:每次写 cudaMalloc,立刻写上对应的 cudaFree。就像写括号一样,成对出现,不容易漏。

3.3 显存碎片化问题

碎片化,说白了就是显存被切得七零八落,虽然总空闲空间够用,但找不到一块连续的大块来分配。

为什么会这样?

你想想看,如果频繁地分配和释放不同大小的显存块,就像在内存条上反复插拔不同尺寸的积木。时间一长,空闲的小块散落在各处,但最大的连续块可能只有 100MB,而你想分配 200MB——对不起,分配失败。

碎片化的典型表现:
  • cudaMalloc 返回 out of memory,但 nvidia-smi 显示显存还有大量空闲
  • 服务刚启动时正常,运行一段时间后频繁分配失败
  • 重启服务后问题消失,过一阵又出现

我曾经在一个并发推理项目中,遇到的就是这种情况。8 张 A100,每张 80GB 显存,跑着跑着就报 OOM。用 nvidia-smi 一看,每张卡还剩 30GB,但就是分配不出一个 2GB 的 tensor。这就是典型的显存碎片化。

3.4 碎片化的成因与影响

因素 说明 影响程度
频繁分配/释放 每次 cudaMalloc/cudaFree 都会在堆上留下痕迹 高
分配大小不均 大块和小块交替分配,容易产生碎片 高
长时间运行 碎片随时间累积,最终导致分配失败 中
多流并发 不同流分配释放相互交织,加剧碎片化 中

3.5 知识体系:显存管理核心逻辑

下面这张图,是我自己总结的显存管理核心逻辑。你看一眼,就能明白整个流程和风险点在哪。

显存管理核心逻辑 cudaMalloc 分配显存块 cudaFree 释放显存块 碎片化 连续大块不足 碎片化成因 频繁分配/释放 大小不均 长时间运行 多流并发 解决方案 显存池(预分配) 提前申请,重复使用 统一管理 避免零散分配 碎片整理 合并空闲块 核心思路:减少 cudaMalloc/cudaFree 调用次数,用显存池替代

3.6 如何应对碎片化

碎片化这个问题,说白了就是「分配太随意」造成的。解决方案也很直接:减少分配次数,尽量复用。

我常用的几个方法:

  • 显存池(Memory Pool):提前申请一大块显存,自己管理内部的分配和回收。这样 cudaMalloc 只调用一次,后续都在池子里操作。
  • 固定大小分配:尽量让每次分配的 tensor 大小一致,减少碎片产生。
  • 延迟释放:不立即 cudaFree,而是把释放的块缓存起来,下次分配时优先复用。
一个小技巧:如果你用的是 CUDA 11.2 以上版本,可以试试 cudaMallocAsync 和 cudaMemPool。这是官方提供的异步分配和显存池机制,能自动减少碎片化。我在项目中试过,效果不错。

3.7 避坑指南

我曾经犯过一个低级错误:在推理循环里每次请求都 cudaMalloc 一个临时 buffer,用完就 cudaFree。结果并发一高,显存碎片化直接导致服务崩溃。

后来我改成预分配一个足够大的 buffer,整个生命周期只分配一次。碎片化问题再也没出现过。

嗯,这里总结几条经验:

  • 不要在热路径(hot path)上频繁分配释放显存
  • 能用显存池就别裸调 cudaMalloc
  • 监控显存碎片率,可以用 cudaMemGetInfo 看空闲总量和最大连续块
  • 如果碎片化严重,重启服务是最快的解决办法(但治标不治本)

显存管理这块,说白了就是「预则立,不预则废」。提前规划好分配策略,能省掉后面一大堆排查的麻烦。

可可精品资料库