2. 显存监控工具

做压测的时候,我最怕什么?不是代码报错,而是显存悄悄爆了,程序直接 OOM 挂掉。你想想看,跑了一晚上的压测,早上来一看进程没了,那感觉...嗯,所以监控工具必须玩透。

这一章,我带你把这四个工具吃透。从命令行到编程接口,从基础监控到深度诊断,一条龙搞定。

2.1 nvidia-smi:你每天都在用,但你真的会用吗?

说实话,nvidia-smi 是每个做 GPU 开发的人最先接触的命令。但我见过太多人只会敲个 nvidia-smi 看看显存占用就完事了。其实它的能力远不止这些。

2.1.1 基础用法

先来个最基础的:

# 查看基本信息
nvidia-smi

# 每秒刷新一次,持续监控
nvidia-smi -l 1

# 只看某个 GPU
nvidia-smi -i 0

我个人习惯用 -l 1 配合压测脚本一起跑。开个终端窗口专门跑这个,显存变化一目了然。

2.1.2 进阶参数

这里有几个我压测时必用的参数:

参数 作用 我的使用场景
--query-gpu 查询指定字段 只抓显存和温度,减少输出干扰
--format=csv 输出 CSV 格式 配合脚本做数据采集
--loop-ms 毫秒级刷新 需要高精度监控时用

举个例子,压测时我只关心显存和功耗:

nvidia-smi --query-gpu=index,memory.used,power.draw --format=csv -l 1

这样输出干净多了,不会一堆进程信息刷屏。

我的小技巧: 把这条命令重定向到文件,压测结束后直接分析趋势图。我曾经靠这个抓到过一个内存泄漏的 bug——显存每 10 分钟涨 200MB,跑 8 小时就爆了。

2.1.3 避坑指南

我曾经踩过的坑: 用 -l 参数时,如果 GPU 数量多,输出会非常慢。有一次我监控 8 卡机器,刷新间隔设了 0.5 秒,结果 nvidia-smi 自己就占了 5% 的 CPU。后来我改成 2 秒刷新一次,问题解决。

2.2 dcgmi:数据中心级别的监控利器

如果你管理的是多卡集群,nvidia-smi 就不够用了。这时候 dcgmi 上场。它是 NVIDIA Data Center GPU Manager 的命令行工具,能监控更细粒度的指标。

2.2.1 安装与启动

# 安装 DCGM
sudo apt-get install datacenter-gpu-manager

# 启动服务
sudo systemctl start nvidia-dcgm

# 查看是否运行正常
dcgmi discovery -l

我第一次用 dcgmi 是在一个 32 卡的大规模压测项目里。nvidia-smi 根本看不过来,dcgmi 的组监控功能帮了大忙。

2.2.2 核心命令

这几个命令我压测时必用:

# 查看所有 GPU 的健康状态
dcgmi health -g 0

# 监控指定组的显存带宽利用率
dcgmi dmon -e 1001,1002,1003 -d 1000

# 查看历史统计
dcgmi stats -g 0

其中 dcgmi dmon 是我最常用的。它能监控到显存带宽利用率、PCIe 吞吐量这些 nvidia-smi 看不到的指标。

事件 ID 指标含义 压测中的意义
1001 GPU 利用率 看计算单元是否跑满
1002 显存带宽利用率 看是否带宽瓶颈
1003 PCIe 读写速率 看数据传输是否卡脖子
关键点: 显存带宽利用率这个指标,我压测大模型推理时特别关注。如果带宽利用率超过 90%,说明显存带宽是瓶颈,加再多的计算单元也没用。

2.3 pyNVML:用 Python 写自己的监控工具

命令行工具虽好,但有时候我们需要更灵活的监控方案。比如压测过程中自动记录显存变化,或者触发告警。这时候 pyNVML 就派上用场了。

2.3.1 安装与初始化

pip install nvidia-ml-py

注意,这个库叫 nvidia-ml-py,不是 pyNVML。我第一次装的时候就搞混了,找了半天。

import pynvml

# 初始化
pynvml.nvmlInit()

# 获取 GPU 数量
device_count = pynvml.nvmlDeviceGetCount()
print(f"检测到 {device_count} 块 GPU")

2.3.2 实战:显存监控脚本

这是我压测时用的一个简化版脚本:

import pynvml
import time
import sys

pynvml.nvmlInit()

def monitor_memory(gpu_id=0, interval=1):
    handle = pynvml.nvmlDeviceGetHandleByIndex(gpu_id)
    
    try:
        while True:
            mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
            util_info = pynvml.nvmlDeviceGetUtilizationRates(handle)
            
            used_gb = mem_info.used / 1024**3
            total_gb = mem_info.total / 1024**3
            util_percent = util_info.gpu
            
            print(f"显存: {used_gb:.1f}/{total_gb:.1f} GB | GPU利用率: {util_percent}%")
            
            # 告警逻辑
            if used_gb / total_gb > 0.9:
                print("⚠️ 显存使用超过 90%,注意!")
                
            time.sleep(interval)
            
    except KeyboardInterrupt:
        print("\n监控结束")
        pynvml.nvmlShutdown()

if __name__ == "__main__":
    monitor_memory()
我的经验: 这个脚本我加了告警阈值。压测时如果显存超过 90%,自动记录时间戳和当时的请求量。这样事后排查问题时,能精确知道是哪个时间段的压力导致了显存飙升。

2.3.3 更多实用 API

pyNVML 能拿到的信息比 nvidia-smi 多得多:

  • nvmlDeviceGetTemperature() - 获取温度,我用来监控散热是否跟得上
  • nvmlDeviceGetPowerUsage() - 获取功耗,看是否被限频
  • nvmlDeviceGetComputeRunningProcesses() - 获取正在跑的进程,排查谁在占显存

2.4 自定义监控脚本:把一切串起来

工具学了一堆,最终要落地到实际压测中。我一般会写一个综合监控脚本,把 nvidia-smi、dcgmi 和 pyNVML 的优势结合起来。

2.4.1 脚本设计思路

我的脚本长这样:

  1. 数据采集层:用 pyNVML 采集显存、温度、功耗
  2. 数据存储层:写入 CSV 文件,方便后续分析
  3. 告警层:超过阈值时打印警告并记录日志
  4. 可视化层:实时打印进度条或简单图表

2.4.2 完整示例

import pynvml
import time
import csv
from datetime import datetime

pynvml.nvmlInit()

def comprehensive_monitor(gpu_ids=[0], interval=2, output_file="gpu_monitor.csv"):
    handles = [pynvml.nvmlDeviceGetHandleByIndex(i) for i in gpu_ids]
    
    with open(output_file, 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(["时间", "GPU", "显存使用(GB)", "显存总量(GB)", 
                        "GPU利用率(%)", "温度(°C)", "功耗(W)"])
        
        try:
            while True:
                timestamp = datetime.now().strftime("%H:%M:%S")
                
                for idx, handle in enumerate(handles):
                    mem = pynvml.nvmlDeviceGetMemoryInfo(handle)
                    util = pynvml.nvmlDeviceGetUtilizationRates(handle)
                    temp = pynvml.nvmlDeviceGetTemperature(handle, 0)
                    power = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000
                    
                    used_gb = mem.used / 1024**3
                    total_gb = mem.total / 1024**3
                    
                    writer.writerow([timestamp, idx, f"{used_gb:.2f}", 
                                   f"{total_gb:.2f}", util.gpu, temp, f"{power:.1f}"])
                    
                    # 实时告警
                    if used_gb / total_gb > 0.85:
                        print(f"[{timestamp}] GPU {idx}: 显存告警!{used_gb:.1f}/{total_gb:.1f} GB")
                    if temp > 80:
                        print(f"[{timestamp}] GPU {idx}: 温度告警!{temp}°C")
                
                time.sleep(interval)
                
        except KeyboardInterrupt:
            print(f"\n监控数据已保存至 {output_file}")

if __name__ == "__main__":
    comprehensive_monitor(gpu_ids=[0, 1, 2, 3])
注意: 监控脚本本身也会消耗资源。我建议采集间隔不要小于 1 秒,否则会影响压测结果。另外,生产环境记得加异常处理,别因为监控脚本挂了导致整个压测中断。

2.5 本章知识体系

说了这么多,我画了张图帮你理清思路:

显存监控工具知识体系 nvidia-smi 基础监控 显存/温度/功耗 CSV 格式输出 毫秒级刷新 dcgmi 数据中心级监控 显存带宽利用率 PCIe 吞吐量 多卡组管理 pyNVML Python 编程接口 自定义监控逻辑 告警阈值设置 进程级监控 自定义脚本 数据采集层 数据存储层 告警层 可视化层 压测监控最佳实践 1. 压测前:用 nvidia-smi 确认 GPU 状态正常 2. 压测中:用 pyNVML 脚本实时采集 + 告警 3. 压测后:分析 CSV 数据,定位显存瓶颈

说白了,这四个工具各有侧重。nvidia-smi 适合快速查看,dcgmi 适合大规模集群,pyNVML 适合深度定制,自定义脚本则是把前三个的优势整合起来。我建议你从 nvidia-smi 入手,逐步过渡到 pyNVML 编程,最后形成自己的监控体系。

嗯,工具就介绍到这里。下一节我们聊聊压测中的显存分配策略,那才是真正考验功底的地方。