4、碎片率量化:如何定义碎片率、计算碎片率的公式、使用nvidia-smi与Nsight Systems观测碎片、编写碎片监控脚本
聊到显存碎片,很多人第一反应是「我知道有碎片,但到底碎成什么样?」。嗯,这个问题问得好。没有量化,就没有治理。我见过不少团队,一听说碎片就拍脑袋说「加内存」,结果加完还是碎。说白了,你得先知道碎片率是多少,才能对症下药。
4.1 碎片率的定义
碎片率,简单来说,就是不可用内存占总内存的比例。但这里有个坑——「不可用」怎么定义?
我个人习惯用最大连续空闲块来衡量。举个例子:
- 总显存 16GB,空闲 8GB
- 但最大连续空闲块只有 2GB
- 你想分配一个 4GB 的 tensor,直接 OOM
这 8GB 空闲里,有 6GB 是「看得见吃不着」的。碎片率就是这 6GB 除以总显存。
核心定义:碎片率 = (总空闲内存 - 最大连续空闲块) / 总显存
为什么用这个定义?因为这才是真正影响你能否分配大块内存的指标。我在项目中遇到过,明明 nvidia-smi 显示还有 40% 空闲,但一跑大 batch 就崩。查了半天,就是碎片在作祟。
4.2 碎片率的计算公式
公式其实不复杂,但要注意几个变种。我常用的有三种:
| 指标 | 公式 | 适用场景 |
|---|---|---|
| 全局碎片率 | (总空闲 - 最大连续块) / 总显存 | 整体健康度评估 |
| 局部碎片率 | 1 - (最大连续块 / 总空闲) | 空闲内存的利用效率 |
| 分配失败率 | 分配失败次数 / 总分配次数 | 实际业务影响 |
你想想看,全局碎片率 30% 意味着什么?意味着你每 10GB 显存里,有 3GB 是废的。我见过最夸张的一次,全局碎片率飙到 65%,那基本等于显存缩水一半多。
我的经验:全局碎片率超过 20% 就要警惕了。超过 40% 基本必出 OOM。我曾经在一个推理服务上踩过这个坑,后来加了碎片监控,提前预警,再没出过事。
4.3 使用 nvidia-smi 观测碎片
nvidia-smi 本身不直接显示碎片率,但我们可以间接推断。我最常用的命令是:
# 查看显存使用概况
nvidia-smi --query-gpu=memory.total,memory.used,memory.free --format=csv
# 查看进程级显存占用
nvidia-smi --query-compute-apps=pid,used_memory --format=csv
但说实话,nvidia-smi 只能看到「总量」,看不到「分布」。它告诉你空闲 4GB,但不知道这 4GB 是连在一起的还是碎成渣的。所以,它只能做初步判断。
怎么判断?我有个土办法:
- 记录当前空闲内存
- 尝试分配一个略小于空闲内存的块
- 如果失败,说明碎片严重
当然,这方法有点粗暴。生产环境不建议这么干,会触发 OOM killer。
注意:nvidia-smi 显示的 free 内存是「当前未使用」,但不代表你能一次性分配那么多。碎片会导致实际可用远小于 free 值。
4.4 使用 Nsight Systems 观测碎片
Nsight Systems 才是真正的利器。它能捕获每个 CUDA 内存分配的地址和大小,然后我们就能画出内存布局图。
具体操作:
# 启动 Nsight Systems 采集
nsys profile -o trace_output -t cuda python your_script.py
# 生成报告
nsys stats trace_output.nsys-rep
拿到 trace 后,我会重点关注:
- cudaMalloc 的返回地址——看地址是否分散
- 分配大小分布——小分配太多容易碎
- 释放顺序——乱序释放是碎片元凶
我记得有一次,用 Nsight Systems 看到一个模型训练过程中,频繁分配 1MB 以下的临时 buffer,而且释放顺序完全随机。那碎片率,肉眼可见地往上涨。后来改成预分配池,问题就解决了。
关键洞察:Nsight Systems 能让你看到「碎片是怎么产生的」,而不仅仅是「碎片有多少」。这是它比 nvidia-smi 强的地方。
4.5 编写碎片监控脚本
光靠手动看不行,得自动化。我写过一个简单的 Python 脚本,用 py3nvml 库来实时监控碎片率。核心逻辑如下:
import pynvml
import time
pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
def get_fragmentation_rate():
# 获取显存信息
mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
total = mem_info.total
free = mem_info.free
# 这里需要更细粒度的信息
# 实际中可以通过 CUDA API 获取分配块列表
# 简化版:用分配/释放频率估算
alloc_count = pynvml.nvmlDeviceGetComputeRunningProcesses(handle)
# 估算碎片率(简化版)
# 真实场景需要遍历所有分配块
frag_rate = 1.0 - (free / total) # 这只是内存使用率,不是碎片率
return frag_rate
while True:
rate = get_fragmentation_rate()
print(f"当前碎片率估算: {rate:.2%}")
time.sleep(5)
这个脚本只是个框架。真正生产级的监控,我会加上:
- 历史趋势记录(存到 InfluxDB)
- 阈值告警(碎片率 > 30% 发钉钉)
- 自动触发碎片整理(调用 cudaMemAdvise 或重启进程)
避坑指南:我曾经写过一个监控脚本,每 1 秒查询一次 nvml,结果把 GPU 驱动搞挂了。后来改成 5 秒一次,加上重试机制,才稳定下来。别太频繁,驱动扛不住。
4.6 知识体系总览
下面这张图,是我自己总结的碎片率量化知识体系。你看一眼,心里就有谱了。
说白了,碎片率量化就三步:定义清楚、观测准确、监控到位。这三步走通了,你就能从「感觉有碎片」进化到「精确知道碎了多少、怎么碎的、什么时候会出问题」。
我个人习惯把碎片率监控做成一个 dashboard,每天上班先看一眼。碎片率超过 20% 就标记为黄色,超过 40% 直接红色告警。这样心里有底,再也不用担心半夜被 OOM 告警吵醒了。