跳到主要内容

训练监控与可观测性

有效的监控和可观测性是保障训练任务稳定运行的基础。通过实时监控训练进度、资源使用和性能指标,可以及时发现问题、优化资源利用,并为后续的训练调优提供数据支撑。

为什么需要监控

AI 训练任务具有以下特点,使得监控尤为重要:

特点监控需求
运行时间长(小时到天级)需要持续追踪进度,避免长时间无响应
资源消耗大(GPU、显存)需要监控资源利用率,优化成本
故障影响大(重新训练)需要及时发现异常,快速恢复
参数调优复杂需要记录训练曲线,支持实验对比

一个完善的监控系统应该回答以下问题:

  • 训练进度如何?预计何时完成?
  • GPU 利用率和显存使用是否正常?
  • 损失曲线是否收敛?
  • 是否存在异常或错误?
  • 与历史实验相比性能如何?

监控体系架构

一个典型的训练监控体系包含多个层次:

┌─────────────────────────────────────────────────────────────┐
│ 可视化层 │
│ Grafana │ TensorBoard │ WANDB │
├─────────────────────────────────────────────────────────────┤
│ 存储层 │
│ Prometheus │ InfluxDB │ Elasticsearch │
├─────────────────────────────────────────────────────────────┤
│ 采集层 │
│ DCGM Exporter │ Node Exporter │ 自定义指标采集 │
├─────────────────────────────────────────────────────────────┤
│ 数据源 │
│ GPU 硬件 │ 训练框架 │ 操作系统 │ 应用日志 │
└─────────────────────────────────────────────────────────────┘

GPU 监控

GPU 是 AI 训练的核心资源,需要重点监控。

关键监控指标

指标类别具体指标说明
计算资源GPU 利用率计算单元的使用比例
Tensor Core 利用率矩阵运算单元的使用比例
内存资源显存使用量已分配和预留的显存
显存带宽利用率内存传输带宽使用比例
热管理GPU 温度核心温度,影响稳定性
功耗实时功耗,影响成本
互联NVLink 流量GPU 间通信量
PCIe 流量GPU 与 CPU/内存的数据传输

DCGM Exporter

NVIDIA DCGM(Data Center GPU Manager)Exporter 是收集 GPU 指标的标准工具。

安装部署

# 使用 Docker 部署
docker run -d --name dcgm-exporter \
--gpus all \
-p 9400:9400 \
nvcr.io/nvidia/k8s/dcgm-exporter:3.3.0-3.4.0-ubuntu22.04

# 验证指标
curl http://localhost:9400/metrics

Kubernetes 部署

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: dcgm-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: dcgm-exporter
template:
metadata:
labels:
app: dcgm-exporter
spec:
containers:
- name: dcgm-exporter
image: nvcr.io/nvidia/k8s/dcgm-exporter:3.3.0-3.4.0-ubuntu22.04
ports:
- containerPort: 9400
name: metrics
resources:
limits:
nvidia.com/gpu: 1
volumeMounts:
- name: gpu-resources
mountPath: /var/lib/kubelet/pod-resources
volumes:
- name: gpu-resources
hostPath:
path: /var/lib/kubelet/pod-resources

关键指标说明

# GPU 计算利用率(0-100)
DCGM_FI_DEV_GPU_UTIL

# GPU 显存使用量(字节)
DCGM_FI_DEV_FB_USED

# GPU 显存总量
DCGM_FI_DEV_FB_FREE

# GPU 温度(摄氏度)
DCGM_FI_DEV_GPU_TEMP

# GPU 功耗(瓦特)
DCGM_FI_DEV_POWER_USAGE

# NVLink 吞吐量
DCGM_FI_NVLINK_THROUGHPUT_TX
DCGM_FI_NVLINK_THROUGHPUT_RX

nvidia-smi 监控

基础的 GPU 监控可以通过 nvidia-smi 命令实现:

# 查看所有 GPU 状态
nvidia-smi

# 持续监控(每秒刷新)
watch -n 1 nvidia-smi

# 查看详细信息
nvidia-smi -q

# 只看计算模式和功耗
nvidia-smi --query-gpu=index,utilization.gpu,power.draw --format=csv

# 导出历史数据
nvidia-smi --query-gpu=timestamp,index,utilization.gpu,memory.used --format=csv -l 1 > gpu_log.csv

Python 获取 GPU 信息

import pynvml

pynvml.nvmlInit()
device_count = pynvml.nvmlDeviceGetCount()

for i in range(device_count):
handle = pynvml.nvmlDeviceGetHandleByIndex(i)

# 基本信息
name = pynvml.nvmlDeviceGetName(handle)
memory_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
utilization = pynvml.nvmlDeviceGetUtilizationRates(handle)
temperature = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
power = pynvml.nvmlDeviceGetPowerUsage(handle) / 1000 # 转换为瓦特

print(f"GPU {i}: {name.decode()}")
print(f" 显存: {memory_info.used / 1024**3:.1f}GB / {memory_info.total / 1024**3:.1f}GB")
print(f" 利用率: {utilization.gpu}%")
print(f" 温度: {temperature}°C")
print(f" 功耗: {power:.1f}W")

pynvml.nvmlShutdown()

训练指标监控

除了硬件指标,训练过程中的软件指标同样重要。

核心训练指标

指标说明健康状态判断
Loss训练损失持续下降,无异常波动
Learning Rate学习率按计划变化
Gradient Norm梯度范数不爆炸、不消失
Throughput吞吐量(samples/s)稳定或提升
Epoch ProgressEpoch 进度正常推进
Validation Metrics验证集指标与训练集同步改善

TensorBoard

TensorBoard 是 PyTorch 和 TensorFlow 内置的可视化工具。

PyTorch 集成

from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter(log_dir="runs/experiment_1")

for epoch in range(num_epochs):
for batch_idx, (data, target) in enumerate(train_loader):
# 训练步骤
loss = train_step(model, data, target)

# 记录训练损失
global_step = epoch * len(train_loader) + batch_idx
writer.add_scalar('Loss/train', loss, global_step)
writer.add_scalar('Learning_Rate', optimizer.param_groups[0]['lr'], global_step)

# 验证
val_loss, val_acc = validate(model, val_loader)
writer.add_scalar('Loss/validation', val_loss, epoch)
writer.add_scalar('Accuracy/validation', val_acc, epoch)

# 记录模型参数分布
for name, param in model.named_parameters():
writer.add_histogram(f'Parameters/{name}', param, epoch)
if param.grad is not None:
writer.add_histogram(f'Gradients/{name}', param.grad, epoch)

writer.close()

启动 TensorBoard

tensorboard --logdir=runs --port=6006

高级可视化

# 记录嵌入向量
writer.add_embedding(
features,
metadata=labels,
label_img=images,
global_step=epoch,
tag='embeddings'
)

# 记录模型图
writer.add_graph(model, input_sample)

# 记录文本
writer.add_text('hyperparameters', str(config), 0)

# 记录图像
writer.add_image('input', image_tensor, global_step)

# 自定义图表
from tensorboardX import SummaryWriter
writer.add_custom_scalars_multiline_chart(
'losses',
['Loss/train', 'Loss/validation']
)

Weights & Biases (W&B)

W&B 是专业的机器学习实验跟踪平台,提供更丰富的功能。

基本使用

import wandb

# 初始化实验
wandb.init(
project="my-project",
name="experiment-1",
config={
"learning_rate": 1e-4,
"batch_size": 32,
"model": "llama-7b"
}
)

# 训练循环
for epoch in range(num_epochs):
for batch in train_loader:
loss = train_step(model, batch)

# 记录指标
wandb.log({
"loss": loss,
"learning_rate": optimizer.param_groups[0]['lr'],
"epoch": epoch
})

# 保存模型
wandb.save("model.pt")

# 结束实验
wandb.finish()

记录模型和数据

# 记录模型检查点
artifact = wandb.Artifact('model', type='model')
artifact.add_file('model.pt')
wandb.log_artifact(artifact)

# 记录数据集
dataset_artifact = wandb.Artifact('dataset', type='dataset')
dataset_artifact.add_dir('data/')
wandb.log_artifact(dataset_artifact)

# 可视化预测
table = wandb.Table(columns=["image", "prediction", "label"])
for img, pred, label in samples:
table.add_data(wandb.Image(img), pred, label)
wandb.log({"predictions": table})

实验对比

W&B 提供了强大的实验对比功能,可以:

  • 并排比较多次实验的指标曲线
  • 分析超参数对结果的影响
  • 自动生成实验报告

MLflow

MLflow 是开源的机器学习生命周期管理平台。

import mlflow

# 开始实验
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("my-experiment")

with mlflow.start_run():
# 记录参数
mlflow.log_param("learning_rate", 1e-4)
mlflow.log_param("batch_size", 32)

# 训练
for epoch in range(num_epochs):
loss = train_epoch(model, train_loader)
val_loss = validate(model, val_loader)

# 记录指标
mlflow.log_metric("train_loss", loss, step=epoch)
mlflow.log_metric("val_loss", val_loss, step=epoch)

# 保存模型
mlflow.pytorch.log_model(model, "model")

Prometheus + Grafana

Prometheus 和 Grafana 是监控领域的黄金组合,适合生产环境。

Prometheus 配置

prometheus.yml

global:
scrape_interval: 15s
evaluation_interval: 15s

scrape_configs:
# DCGM Exporter(GPU 指标)
- job_name: 'dcgm-exporter'
static_configs:
- targets: ['dcgm-exporter:9400']

# Node Exporter(系统指标)
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']

# 训练任务指标
- job_name: 'training-metrics'
static_configs:
- targets: ['training-server:8000']

自定义训练指标导出

from prometheus_client import Counter, Gauge, Histogram, start_http_server
import time

# 定义指标
training_samples = Counter(
'training_samples_total',
'Total training samples processed'
)
training_loss = Gauge(
'training_loss',
'Current training loss'
)
training_duration = Histogram(
'training_step_duration_seconds',
'Time spent on each training step'
)
gpu_memory_used = Gauge(
'gpu_memory_used_bytes',
'GPU memory used',
['gpu_id']
)

# 启动 HTTP 服务器
start_http_server(8000)

# 训练循环
for batch in train_loader:
start_time = time.time()

loss = train_step(model, batch)

# 更新指标
training_samples.inc(len(batch))
training_loss.set(loss)
training_duration.observe(time.time() - start_time)

# GPU 显存
for i in range(torch.cuda.device_count()):
mem = torch.cuda.memory_allocated(i)
gpu_memory_used.labels(gpu_id=str(i)).set(mem)

Grafana Dashboard

创建 GPU 监控 Dashboard:

{
"dashboard": {
"title": "GPU Training Monitor",
"panels": [
{
"title": "GPU Utilization",
"type": "graph",
"targets": [
{
"expr": "avg(DCGM_FI_DEV_GPU_UTIL)",
"legendFormat": "Average"
},
{
"expr": "DCGM_FI_DEV_GPU_UTIL",
"legendFormat": "GPU {{gpu}}"
}
]
},
{
"title": "GPU Memory",
"type": "graph",
"targets": [
{
"expr": "DCGM_FI_DEV_FB_USED / 1024 / 1024 / 1024",
"legendFormat": "GPU {{gpu}}"
}
]
},
{
"title": "Training Loss",
"type": "graph",
"targets": [
{
"expr": "training_loss",
"legendFormat": "Loss"
}
]
}
]
}
}

常用 Grafana 查询

# GPU 平均利用率
avg(DCGM_FI_DEV_GPU_UTIL)

# GPU 显存使用率
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) * 100

# 训练吞吐量(样本/秒)
rate(training_samples_total[1m])

# P95 训练步骤延迟
histogram_quantile(0.95, rate(training_step_duration_seconds_bucket[5m]))

# GPU 温度告警
DCGM_FI_DEV_GPU_TEMP > 85

日志管理

日志是故障排查和性能分析的重要数据源。

结构化日志

使用结构化日志格式便于后续分析:

import structlog

logger = structlog.get_logger()

# 配置日志
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.JSONRenderer()
]
)

# 使用日志
logger.info(
"training_step_completed",
step=global_step,
loss=loss,
learning_rate=lr,
gpu_memory=torch.cuda.memory_allocated() / 1e9
)

logger.error(
"cuda_error",
error_code=error.code,
gpu_id=gpu_id,
details=str(error)
)

日志收集

使用 Filebeat 收集日志

# filebeat.yml
filebeat.inputs:
- type: log
paths:
- /var/log/training/*.log
fields:
type: training
json.keys_under_root: true

output.elasticsearch:
hosts: ["elasticsearch:9200"]
index: "training-logs-%{+yyyy.MM.dd}"

使用 Fluentd

<source>
@type tail
path /var/log/training/*.log
tag training
<parse>
@type json
</parse>
</source>

<match training>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix training
</match>

日志分析

使用 Elasticsearch 和 Kibana 进行日志分析:

# 搜索错误日志
from elasticsearch import Elasticsearch

es = Elasticsearch(['http://elasticsearch:9200'])

# 查询最近的 CUDA 错误
result = es.search(
index="training-logs-*",
body={
"query": {
"bool": {
"must": [
{"match": {"level": "ERROR"}},
{"match": {"message": "CUDA"}}
]
}
},
"sort": [{"@timestamp": "desc"}],
"size": 100
}
)

告警配置

及时发现异常是监控的核心目的。

Prometheus 告警规则

groups:
- name: training-alerts
rules:
# GPU 温度过高
- alert: HighGPUTemperature
expr: DCGM_FI_DEV_GPU_TEMP > 85
for: 5m
labels:
severity: warning
annotations:
summary: "GPU 温度过高"
description: "GPU {{ $labels.gpu }} 温度 {{ $value }}°C 超过 85°C"

# GPU 利用率过低
- alert: LowGPUUtilization
expr: avg(DCGM_FI_DEV_GPU_UTIL) < 30
for: 10m
labels:
severity: warning
annotations:
summary: "GPU 利用率过低"
description: "平均 GPU 利用率 {{ $value }}% 低于 30%"

# 训练损失异常
- alert: TrainingLossSpike
expr: training_loss > 10 * training_loss offset 5m
for: 1m
labels:
severity: critical
annotations:
summary: "训练损失异常上升"
description: "训练损失突然上升超过 10 倍"

# 显存不足
- alert: GPUOOMWarning
expr: (DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)) > 0.95
for: 2m
labels:
severity: warning
annotations:
summary: "GPU 显存即将耗尽"
description: "GPU {{ $labels.gpu }} 显存使用率超过 95%"

# 训练卡住
- alert: TrainingStuck
expr: increase(training_samples_total[10m]) == 0
labels:
severity: critical
annotations:
summary: "训练可能已卡住"
description: "过去 10 分钟没有训练任何样本"

Alertmanager 配置

global:
resolve_timeout: 5m
smtp_smarthost: 'smtp.example.com:587'
smtp_from: '[email protected]'

route:
group_by: ['alertname', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receiver: 'team-email'
routes:
- match:
severity: critical
receiver: 'team-pagerduty'

receivers:
- name: 'team-email'
email_configs:
- to: '[email protected]'
send_resolved: true

- name: 'team-pagerduty'
pagerduty_configs:
- service_key: 'your-service-key'

监控最佳实践

指标选择原则

监控指标应该遵循 USE 和 RED 方法:

USE 方法(资源指标)

  • Utilization:资源使用率(如 GPU 利用率)
  • Saturation:饱和度(如显存使用率)
  • Errors:错误数(如 CUDA 错误)

RED 方法(服务指标)

  • Rate:请求速率(如训练样本/秒)
  • Errors:错误率
  • Duration:延迟(如训练步骤耗时)

Dashboard 设计原则

  1. 层次分明:从概览到详细,逐层深入
  2. 重点突出:关键指标放在显眼位置
  3. 关联展示:相关指标放在一起便于分析
  4. 时间范围:支持灵活的时间范围选择

性能考虑

监控系统本身也会消耗资源,需要平衡监控粒度和开销:

监控频率适用场景开销
1s实时调试、故障排查较高
10s日常监控中等
1m长期趋势分析较低

减少开销的方法

  • 使用聚合指标而非原始指标
  • 适当增加采集间隔
  • 使用 downsampling 保留长期数据

小结

完善的监控体系是保障训练稳定运行的基础。本章介绍了:

  1. GPU 监控:DCGM Exporter 和 nvidia-smi 的使用
  2. 训练指标监控:TensorBoard、W&B、MLflow 的集成
  3. Prometheus + Grafana:生产级监控方案
  4. 日志管理:结构化日志和日志收集
  5. 告警配置:及时发现和响应异常

选择合适的工具组合,建立完善的监控告警体系,可以显著提升训练任务的可靠性和运维效率。

参考资料

官方文档

相关论文