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 是连在一起的还是碎成渣的。所以,它只能做初步判断。

怎么判断?我有个土办法:

  1. 记录当前空闲内存
  2. 尝试分配一个略小于空闲内存的块
  3. 如果失败,说明碎片严重

当然,这方法有点粗暴。生产环境不建议这么干,会触发 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 知识体系总览

下面这张图,是我自己总结的碎片率量化知识体系。你看一眼,心里就有谱了。

碎片率量化知识体系 碎片率量化 定义与公式 观测工具 监控脚本 全局碎片率 局部碎片率 分配失败率 nvidia-smi Nsight Systems 内存布局图 实时采集 阈值告警 自动整理 核心目标:从「感觉有碎片」到「精确知道碎了多少」 量化 → 预警 → 治理 → 闭环

说白了,碎片率量化就三步:定义清楚、观测准确、监控到位。这三步走通了,你就能从「感觉有碎片」进化到「精确知道碎了多少、怎么碎的、什么时候会出问题」。

我个人习惯把碎片率监控做成一个 dashboard,每天上班先看一眼。碎片率超过 20% 就标记为黄色,超过 40% 直接红色告警。这样心里有底,再也不用担心半夜被 OOM 告警吵醒了。

可可精品资料库