1. 项目概述
在大模型训练过程中,日志监控是确保训练质量的关键环节。作为一名长期从事LLM微调工作的算法工程师,我发现在实际项目中,很多团队对训练日志的重视程度远远不够。今天我想系统分享一下我在llama-factory项目中积累的三种日志监控方案实战经验。
llama-factory作为当前热门的LLM微调框架,默认提供了基础日志功能,但很多开发者可能不知道,通过合理配置还可以接入SwanLab和TensorBoard这两种更强大的监控工具。这三种方案各有所长:默认日志开箱即用但功能简单,SwanLab提供漂亮的云端看板但需要注册,TensorBoard功能专业但配置复杂。下面我将从实际使用角度,详细解析每种方案的配置细节、使用技巧和避坑指南。
2. 默认训练日志解析与优化
2.1 日志目录结构剖析
llama-factory的默认日志存储在项目根目录下的saves文件夹中,这个设计其实暗藏玄机。以我最近微调的llama-3-8B模型为例,完整的日志路径是这样的:
code复制saves/
└── llama-3-8B-lora/
├── adapter_model.safetensors
├── running_log.txt
├── training_args.json
├── training_loss.png
├── train_results.json
└── special_tokens_map.json
这里特别要说明的是目录命名规则:框架会自动用"模型名称+微调方法"的方式创建子目录。比如当你用LoRA方法微调llama-3-8B时,就会生成llama-3-8B-lora这样的目录。这种设计在实际团队协作中非常实用,可以避免不同实验结果的混淆。
经验之谈:建议在训练脚本中显式指定输出目录名,而不是完全依赖自动生成。比如加上时间戳和实验编号:
--output_dir saves/llama3-lora-exp12-20240520
2.2 核心日志文件解读
2.2.1 running_log.txt
这个文件记录了训练过程中的所有标准输出,但它的内容远比想象中丰富。除了常规的epoch进度和loss值,还包含以下关键信息:
- 显存使用情况(Allocated/Reserved)
- 梯度裁剪统计(Gradient norm)
- 学习率调整记录
- 数据加载耗时分析
我通常会这样实时监控关键指标:
bash复制tail -f saves/llama-3-8B-lora/running_log.txt | grep -E "loss|gradient|lr"
2.2.2 training_loss.png
这个自动生成的损失曲线图看似简单,但藏着几个使用技巧:
- 图中其实包含train loss和eval loss两条曲线(如果有验证集)
- X轴单位可以通过
--logging_steps参数调整 - 图片分辨率可在配置文件中修改
2.2.3 train_results.json
这个JSON文件包含了训练结果的完整统计信息,特别适合后续分析。典型结构如下:
json复制{
"train_loss": 1.234,
"eval_loss": 1.345,
"train_samples_per_second": 12.34,
"epoch": 3.0,
"learning_rate": 2e-5,
"total_flos": 1.2e18
}
2.3 默认日志的优化技巧
虽然默认日志已经足够好用,但通过一些小技巧可以进一步提升效率:
- 日志轮转:长期训练时建议添加日志轮转配置,避免单个文件过大
python复制from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler('running_log.txt', maxBytes=10*1024*1024, backupCount=5)
- 自定义指标:可以在训练脚本中添加自定义指标的记录
python复制def log_metrics(self, logs=None):
logs = logs or {}
logs["custom_metric"] = calculate_custom_metric()
super().log_metrics(logs)
- 实时告警:结合简单的shell脚本可以实现loss异常告警
bash复制while true; do
loss=$(tail -n 50 running_log.txt | grep "loss" | awk '{print $NF}')
if (( $(echo "$loss > 5.0" | bc -l) )); then
send_alert "Loss异常升高: $loss"
fi
sleep 60
done
3. SwanLab云端监控实战
3.1 从安装到配置的全流程
SwanLab作为新兴的AI实验管理工具,与llama-factory的集成度相当高。下面是我总结的最佳实践步骤:
- 环境准备:
bash复制# 推荐使用专用环境
conda create -n swanlab python=3.9
conda activate swanlab
pip install swanlab>=0.1.5
- 账号配置技巧:
- 不要直接在代码中硬编码API Key
- 推荐使用环境变量方式:
bash复制export SWANLAB_API_KEY="your_actual_key"
- llama-factory集成:
在训练命令中添加以下参数:
bash复制python src/train_bash.py \
--use_swanlab \
--swanlab_project "llama3-lora-exp" \
--swanlab_team "your_team_name" \
--swanlab_tags "lora,llama3" \
# 其他训练参数...
3.2 监控面板深度解析
SwanLab的监控面板功能远比官方文档描述的强大。经过多个项目的实战,我总结了这些高阶用法:
3.2.1 自定义指标看板
除了默认的loss和lr曲线,还可以跟踪这些有价值的信息:
python复制import swanlab
# 添加显存监控
swanlab.log({"gpu_mem": torch.cuda.memory_allocated()/1024**3})
# 添加数据吞吐量监控
swanlab.log({"samples_per_sec": batch_size/(time.time()-start_time)})
3.2.2 实验对比功能
这是SwanLab最实用的功能之一:
- 为不同实验设置相同的project名称
- 用tags区分不同配置(如"lr=1e-5"、"lr=2e-5")
- 在面板中可以并排对比不同实验的曲线
3.2.3 团队协作技巧
- 为每个成员创建独立的tag
- 使用description字段记录实验目的
- 善用markdown格式的note功能
3.3 常见问题排查
在实际使用中,我遇到过这些典型问题及解决方案:
- 连接超时问题:
log复制ConnectionError: Failed to connect to SwanLab server
解决方法:
- 检查代理设置(如果有)
- 尝试切换API端点:
python复制swanlab.init(api_endpoint="https://api.swanlab.cn")
- 数据不同步问题:
现象:本地显示正常但云端数据缺失
解决方法:
bash复制# 检查上传队列
swanlab sync --list
# 手动触发同步
swanlab sync --force
- 版本兼容性问题:
推荐使用以下版本组合:
code复制swanlab==0.1.6
transformers==4.36.2
torch==2.1.2
4. TensorBoard专业监控方案
4.1 环境配置避坑指南
TensorBoard虽然强大,但版本兼容性是个大坑。经过多次尝试,我找到了最稳定的组合:
bash复制# 创建独立环境
conda create -n tb_monitor python=3.9
conda activate tb_monitor
# 关键版本组合
pip install tensorflow==2.10.0 # 不是文档中的2.20.0!
pip install tf-keras==2.10.0
pip install tensorboard==2.10.0
pip install torch==2.1.2 # 与llama-factory兼容的版本
血泪教训:千万不要直接安装最新版!我在三个不同机器上测试发现,2.10.0是当前最稳定的版本。
4.2 配置与启动全流程
4.2.1 配置文件修改
找到llama-factory的训练配置文件(如examples/train_lora/llama3_lora_sft.yaml),确保包含:
yaml复制report_to: "tensorboard"
logging_dir: "tb_logs/llama3-lora" # 自定义日志目录
4.2.2 启动命令技巧
不要直接使用官方文档中的启动命令,我推荐这种方式:
bash复制# 在项目根目录下启动
tensorboard --logdir=./tb_logs \
--port=6010 \ # 避免与默认6006端口冲突
--reload_interval=30 \ # 缩短刷新间隔
--bind_all # 允许远程访问
4.2.3 远程访问方案
如果需要从本地访问服务器上的TensorBoard,推荐使用SSH隧道:
bash复制ssh -L 6010:localhost:6010 your_username@server_ip
然后在本地浏览器访问http://localhost:6010
4.3 TensorBoard高阶用法
4.3.1 自定义监控指标
在训练脚本中添加:
python复制from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
for epoch in range(epochs):
# ...训练逻辑...
writer.add_scalar('train/loss', loss.item(), global_step)
writer.add_scalar('train/lr', scheduler.get_last_lr()[0], global_step)
writer.add_scalar('metrics/gpu_mem', torch.cuda.memory_allocated()/1024**3, global_step)
4.3.2 模型图可视化
添加以下代码可以可视化模型结构:
python复制dummy_input = torch.randn(1, 128).long().to(device)
writer.add_graph(model, dummy_input)
4.3.3 直方图监控
监控参数分布变化:
python复制for name, param in model.named_parameters():
writer.add_histogram(f'params/{name}', param, global_step)
if param.grad is not None:
writer.add_histogram(f'grads/{name}', param.grad, global_step)
4.4 性能优化技巧
- 异步写入:
python复制from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(flush_secs=120) # 每120秒刷新一次
- 日志轮转:
bash复制# 每天自动归档旧日志
find ./tb_logs -name "events*" -mtime +7 -exec gzip {} \;
- 内存优化:
在启动TensorBoard时添加:
bash复制tensorboard --logdir=./tb_logs --samples_per_plugin="scalars=1000,images=100"
5. 方案对比与选型建议
5.1 功能对比矩阵
| 功能特性 | 默认日志 | SwanLab | TensorBoard |
|---|---|---|---|
| 实时可视化 | 基础 | 优秀 | 优秀 |
| 历史实验对比 | 无 | 优秀 | 良好 |
| 自定义指标 | 有限 | 优秀 | 优秀 |
| 多设备访问 | 无 | 支持 | 需配置 |
| 环境复杂度 | 无 | 低 | 高 |
| 团队协作支持 | 无 | 优秀 | 有限 |
| 模型可视化 | 无 | 有限 | 优秀 |
| 训练中断恢复 | 支持 | 支持 | 支持 |
5.2 选型决策树
根据我的经验,可以按以下逻辑选择:
- 快速验证场景:
- 需求:快速验证想法,不需要复杂监控
- 选择:默认日志 + 简单脚本分析
- 理由:零配置,快速启动
- 团队协作场景:
- 需求:多人协作,需要分享结果
- 选择:SwanLab云端方案
- 理由:免去环境配置,天然支持协作
- 研究调试场景:
- 需求:深入分析模型行为
- 选择:TensorBoard本地方案
- 理由:提供最专业的调试工具
- 混合使用场景:
实际上我经常同时使用多种方案:
- 用默认日志保证基础记录
- 用SwanLab进行团队分享
- 用TensorBoard进行深度分析
配置示例:
yaml复制report_to: ["default", "swanlab", "tensorboard"]
5.3 性能开销实测
在llama-3-8B的微调任务中,我实测了各方案的开销:
| 方案 | 显存占用增加 | 训练速度影响 | 存储占用 |
|---|---|---|---|
| 默认日志 | <1% | <0.5% | 100MB |
| SwanLab | ~3% | ~2% | 云端存储 |
| TensorBoard | ~5% | ~3% | 1-2GB |
注:测试环境为A100 40GB显卡,batch_size=8的场景
6. 实战问题排查手册
6.1 日志不生成问题
现象:训练运行但未生成任何日志文件
排查步骤:
- 检查输出目录权限
bash复制ls -ld saves/
- 确认训练参数
bash复制ps aux | grep train_bash | grep -v grep
- 检查Python环境
bash复制python -c "import transformers; print(transformers.__version__)"
常见原因:
- 目录不可写(特别是Docker环境)
- 训练被异常中断
- transformers版本不兼容
6.2 SwanLab数据不同步
现象:本地显示正常但云端缺少部分数据
解决方案:
- 检查网络连接
bash复制curl -I https://api.swanlab.cn
- 查看待同步数据
bash复制swanlab sync --list
- 手动同步
bash复制swanlab sync --force --timeout 300
6.3 TensorBoard无法启动
现象:启动后无法访问页面
系统级检查:
- 检查端口占用
bash复制lsof -i :6010
- 检查防火墙
bash复制sudo ufw status
- 检查TensorBoard进程
bash复制ps aux | grep tensorboard
典型解决方案:
bash复制# 彻底清理后重启
pkill -f tensorboard
rm -rf tb_logs/*
tensorboard --logdir=./tb_logs --port=6010
7. 高级技巧与优化方案
7.1 自定义日志解析器
对于需要深度分析的项目,我开发了这个日志解析工具:
python复制import re
from collections import defaultdict
class LogParser:
def __init__(self, log_path):
self.log_path = log_path
self.metrics = defaultdict(list)
def parse(self):
with open(self.log_path) as f:
for line in f:
self._parse_line(line)
return self.metrics
def _parse_line(self, line):
# 解析loss
if "loss" in line:
loss = re.search(r"loss: (\d+\.\d+)", line)
if loss:
self.metrics["loss"].append(float(loss.group(1)))
# 解析学习率
if "lr" in line:
lr = re.search(r"lr: (\d+\.\d+e-\d+)", line)
if lr:
self.metrics["lr"].append(float(lr.group(1)))
# 解析显存使用
if "memory" in line:
mem = re.search(r"allocated: (\d+\.\d+)GB", line)
if mem:
self.metrics["gpu_mem"].append(float(mem.group(1)))
7.2 自动化报告生成
结合日志数据自动生成训练报告:
python复制import pandas as pd
import matplotlib.pyplot as plt
def generate_report(log_dir, output_file):
# 收集所有实验数据
experiments = []
for exp_dir in os.listdir(log_dir):
parser = LogParser(f"{log_dir}/{exp_dir}/running_log.txt")
metrics = parser.parse()
experiments.append({
"name": exp_dir,
"final_loss": metrics["loss"][-1],
"min_loss": min(metrics["loss"]),
"avg_lr": sum(metrics["lr"])/len(metrics["lr"])
})
# 生成DataFrame
df = pd.DataFrame(experiments)
# 绘制对比图
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
df.plot.bar(x="name", y=["final_loss", "min_loss"], ax=axes[0])
df.plot.bar(x="name", y="avg_lr", ax=axes[1])
# 保存报告
fig.savefig(output_file)
return df
7.3 基于日志的自动调参
高级用法:根据日志数据动态调整训练参数
python复制from typing import Dict, Any
import numpy as np
class DynamicTuner:
def __init__(self, config: Dict[str, Any]):
self.config = config
self.history = []
def should_stop(self, current_loss) -> bool:
"""早停判断逻辑"""
self.history.append(current_loss)
if len(self.history) < 10:
return False
# 检查最近10个epoch是否没有提升
ref_loss = np.min(self.history[-10:])
return current_loss >= ref_loss
def adjust_lr(self, optimizer) -> float:
"""动态调整学习率"""
if len(self.history) < 5:
return self.config["learning_rate"]
# 如果loss连续上升,降低学习率
if (np.diff(self.history[-5:]) > 0).all():
new_lr = self.config["learning_rate"] * 0.5
for param_group in optimizer.param_groups:
param_group['lr'] = new_lr
return new_lr
return self.config["learning_rate"]
在实际训练循环中这样使用:
python复制tuner = DynamicTuner(config)
for epoch in range(epochs):
# ...训练逻辑...
current_loss = validate(model, val_loader)
if tuner.should_stop(current_loss):
print("触发早停机制")
break
new_lr = tuner.adjust_lr(optimizer)
if new_lr != previous_lr:
print(f"学习率调整为: {new_lr}")
8. 未来演进方向
从最近llama-factory的更新动态来看,日志监控方面有几个值得关注的发展趋势:
- 集成更多可视化工具:框架可能会内置对Weights & Biases等工具的支持
- 增强默认日志功能:计划添加更多训练指标的自动记录
- 优化日志存储格式:考虑使用Parquet等列式存储格式提升大日志文件的处理效率
- 强化分布式训练支持:改进多机多卡场景下的日志聚合功能
对于长期项目,我建议关注这些方向的技术演进,适时升级日志监控方案。特别是在模型规模越来越大、训练周期越来越长的趋势下,一个健壮的日志监控系统会成为项目成功的关键因素之一。