ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MindSpore Transformers 训练监控:TensorBoard 实时可视化实战

MindSpore Transformers 训练监控:TensorBoard 实时可视化实战 1. 训练监控这件事为什么值得单独拎出来聊搞深度学习训练的人都有一个共识模型跑起来只是第一步真正折磨人的是“它到底学得怎么样”。尤其是用 MindSpore Transformers 跑大模型微调或者预训练的时候一次训练动辄几个小时甚至几天你不可能一直盯着终端刷日志。这时候 TensorBoard 就成了刚需——它能把 loss 曲线、学习率变化、梯度范数、权重分布这些关键指标实时画出来让你一眼看出模型是收敛了、发散了还是卡在某个平台期。MindSpore 作为国产深度学习框架生态里自带了对 TensorBoard 的支持但和 PyTorch 那套torch.utils.tensorboard的用法不完全一样。很多朋友从 PyTorch 转过来习惯性地from torch.utils.tensorboard import SummaryWriter结果在 MindSpore 项目里直接懵了。其实 MindSpore 有自己的回调机制和日志写入方式配合 MindSpore Transformers 的 Trainer 接口可以做到几乎零侵入地接入 TensorBoard 监控。这篇文章面向的是正在用或者准备用 MindSpore Transformers 做训练任务的开发者不管你是刚接触 MindSpore 的新手还是从其他框架迁移过来的老手我都会把 TensorBoard 在线监控这条链路从头到尾拆清楚。包括回调怎么写、日志目录怎么组织、指标怎么自定义、远程服务器上怎么查看、以及我实际踩过的那些坑。读完你就能直接在自己的项目里抄作业把训练过程看得明明白白。2. MindSpore Transformers 的监控体系拆解2.1 为什么 MindSpore 不直接用 PyTorch 那套 SummaryWriter这是很多人第一个疑问。PyTorch 的SummaryWriter确实好用一行代码就能写标量、图像、直方图。但 MindSpore 的设计哲学不太一样它更倾向于通过Callback 机制来解耦训练逻辑和监控逻辑。你想想如果监控代码直接嵌在训练循环里那换一个监控工具就要改一遍训练代码这显然不优雅。MindSpore 的 Callback 体系允许你在训练的不同阶段step 结束、epoch 结束、训练开始、训练结束插入自定义逻辑。TensorBoard 监控本质上就是“在每个 step 结束后把 loss 写进去”所以它天然适合做成一个 Callback。MindSpore Transformers 在mindformers.core.callback里已经内置了一些回调但 TensorBoard 相关的支持需要你自己稍微组装一下或者用 MindSpore 原生的SummaryCollector和SummaryLandscape来配合。注意MindSpore 的SummaryCollector是框架级别的日志收集器它写出来的格式是 MindSpore 自己的 protobuf 格式需要用mindinsight来查看。而 TensorBoard 需要的是 event file 格式。两者不是一回事别搞混了。2.2 TensorBoard 在 MindSpore 生态里的两种接入路径实际项目里我总结下来有两条路可以走路径一用 MindSpore 原生 Summary 接口 MindInsight 转换MindSpore 提供了mindspore.train.summary.SummaryRecord类你可以手动往里面写标量、图像、计算图等数据。写完之后得到的是 MindSpore 格式的日志然后通过mindinsight工具转换成 TensorBoard 能读的 event 文件。这条路的好处是原生支持、稳定坏处是多了一步转换实时性差一点。路径二直接用 TensorBoard 的 Python API 写入TensorBoard 本身就是一个独立的 Python 包你可以pip install tensorboard之后直接from torch.utils.tensorboard import SummaryWriter——等等这里有个坑torch.utils.tensorboard是 PyTorch 的封装你不想装 PyTorch 的话可以用tensorboardX或者 TensorBoard 原生的tensorboard.summary模块。实际上 TensorBoard 官方提供了tensorboard.summary.Writer可以直接写 event file不依赖任何深度学习框架。我个人的选择是路径二原因很简单实时性好不需要额外转换步骤而且 TensorBoard 的 event file 格式是通用的团队里用 PyTorch 的同事也能直接看。下面重点讲这条路怎么落地。2.3 核心组件与数据流梳理在动手之前先把整条链路的数据流理清楚训练循环产生指标MindSpore Transformers 的 Trainer 在每个 step 会计算 loss、学习率、grad norm 等。Callback 捕获指标自定义 Callback 的step_end方法里拿到run_context从中提取当前 step 的指标值。写入 event file通过 TensorBoard 的 Writer 把指标写成标量。TensorBoard 服务读取启动tensorboard --logdirxxx浏览器打开查看。这里面最关键的是第 2 步和第 3 步的衔接。MindSpore 的run_context里存的是CallbackManager和TrainOneStepCell等对象你需要知道从哪里取 loss。通常run_context.original_args()返回一个TrainArgs对象里面有net_outputs或者loss字段具体取决于你的网络定义。3. 从零搭建 TensorBoard 监控的完整实操3.1 环境准备与依赖安装先把基础环境搞定。假设你已经装好了 MindSpore 和 MindSpore Transformers版本建议 MindSpore 2.2 以上、MindSpore Transformers 1.0 以上这两个版本的 Callback 接口比较稳定。pip install tensorboard pip install tensorboardX # 可选如果你习惯用 tensorboardX 的 API验证 TensorBoard 是否安装成功tensorboard --version如果输出类似TensorFlow installation not found - running with reduced feature set.不用慌这是正常的TensorBoard 独立运行时不需要 TensorFlow。提示如果你的训练环境是离线的提前在有网的机器上下载好 whl 包用pip install xxx.whl离线安装。TensorBoard 的依赖不多主要是protobuf、grpcio、werkzeug这几个。3.2 自定义 TensorBoard Callback 的完整代码下面是我在实际项目里用的 Callback 实现你可以直接复制到自己的项目里改一下日志目录就行。import os from mindspore.train.callback import Callback from tensorboard.summary.writer.event_file_writer import EventFileWriter from tensorboard.compat.proto.event_pb2 import Event from tensorboard.compat.proto.summary_pb2 import Summary from tensorboard.compat.proto.tensor_pb2 import TensorProto from tensorboard.compat.proto.tensor_shape_pb2 import TensorShapeProto class TensorBoardCallback(Callback): def __init__(self, log_dir./tb_logs, flush_interval10): super().__init__() self.log_dir log_dir self.flush_interval flush_interval self.writer None self.step_count 0 def _make_scalar_event(self, tag, value, step): 手动构造一个标量 event避免依赖 torch summary Summary() scalar summary.value.add() scalar.tag tag scalar.simple_value float(value) event Event( wall_time__import__(time).time(), stepstep, summarysummary ) return event def begin(self, run_context): os.makedirs(self.log_dir, exist_okTrue) self.writer EventFileWriter(self.log_dir) print(f[TensorBoardCallback] 日志目录: {self.log_dir}) def step_end(self, run_context): cb_params run_context.original_args() loss cb_params.net_outputs # net_outputs 可能是 tuple 或 tensor统一处理 if isinstance(loss, (tuple, list)): loss loss[0] loss_val float(loss.asnumpy()) if hasattr(loss, asnumpy) else float(loss) lr cb_params.optimizer.learning_rate if hasattr(lr, asnumpy): lr_val float(lr.asnumpy()) else: lr_val float(lr) self.step_count 1 self.writer.add_event(self._make_scalar_event(train/loss, loss_val, self.step_count)) self.writer.add_event(self._make_scalar_event(train/lr, lr_val, self.step_count)) if self.step_count % self.flush_interval 0: self.writer.flush() def end(self, run_context): if self.writer: self.writer.flush() self.writer.close() print([TensorBoardCallback] 训练结束日志已写入)这段代码有几个关键点需要解释为什么不用torch.utils.tensorboard因为那会引入 PyTorch 依赖在一个纯 MindSpore 项目里装 PyTorch 纯属浪费空间而且版本冲突风险高。直接用 TensorBoard 原生的 protobuf 接口最干净。net_outputs的结构在 MindSpore 里cb_params.net_outputs通常是 loss 值但如果你用了TrainOneStepWithLossScaleCell它可能返回一个 tuple(loss, overflow, loss_scale)。所以代码里做了兼容处理取第一个元素。flush 策略TensorBoard 的 event file 是追加写入的如果不 flush数据可能还在缓冲区里TensorBoard 页面上看不到最新数据。我设置的是每 10 个 step flush 一次这个频率可以根据你的 step 速度调整。如果每个 step 很快比如 0.1 秒可以设大一点如果 step 很慢比如 10 秒设成 1 也行。3.3 把 Callback 挂到 Trainer 上写好 Callback 之后挂载非常简单。假设你用的是 MindSpore Transformers 的Trainerfrom mindformers import Trainer, TrainingArguments from mindformers.core.callback import ProfileCallback training_args TrainingArguments( output_dir./output, per_device_train_batch_size8, learning_rate1e-5, num_train_epochs3, logging_steps10, save_steps500, ) tb_callback TensorBoardCallback(log_dir./tb_logs/run_001, flush_interval10) trainer Trainer( argstraining_args, tasktext_generation, model_namegpt2, callbacks[tb_callback], ) trainer.train()如果你用的是 MindSpore 原生的Model.train()接口挂载方式类似from mindspore.train import Model from mindspore import nn model Model(network, loss_fn, optimizer, metrics{accuracy}) model.train(epoch10, train_datasetdataset, callbacks[tb_callback])注意callbacks参数是一个列表你可以同时挂多个 Callback比如一个写 TensorBoard一个做 checkpoint 保存一个做 early stopping。它们的执行顺序按照列表顺序来。3.4 日志目录的组织策略日志目录千万别随便设不然跑了几十次实验之后你会疯掉。我推荐按“项目/日期_实验名”的层级来组织tb_logs/ ├── gpt2_finetune/ │ ├── 20240115_lr1e-5_bs8/ │ ├── 20240116_lr5e-6_bs16/ │ └── 20240117_lr1e-5_bs8_warmup/ └── llama_pretrain/ ├── 20240120_bs32/ └── 20240121_bs64/这样启动 TensorBoard 的时候直接--logdirtb_logs/gpt2_finetune页面上会自动把不同实验的曲线画在一起对比非常直观。如果你用 wandb 或者 mlflow 做实验管理TensorBoard 的日志目录也可以和它们对齐方便交叉验证。4. 监控指标的设计与进阶技巧4.1 除了 loss 和 lr还应该监控什么很多人只监控 loss 和 learning rate这其实不够。以下是我在实际训练大模型时必看的指标指标名称含义异常信号train/loss训练损失持续上升或剧烈震荡train/lr学习率与预期调度不符train/grad_norm梯度范数突然爆炸或长期为 0train/loss_scale动态损失缩放系数频繁降到最低值train/throughput每秒处理的 token 数突然下降train/epoch当前 epoch与 step 不匹配梯度范数这个指标特别重要。大模型训练时如果梯度爆炸loss 会突然飙上去但如果你只看 loss可能以为是数据问题。加上 grad_norm 之后一眼就能定位是梯度问题。在 MindSpore 里获取梯度范数需要稍微绕一下可以在网络定义里加一个 hook或者在 Callback 里通过cb_params.train_network遍历参数计算import mindspore as ms import mindspore.ops as ops def compute_grad_norm(network): total_norm 0.0 for param in network.get_parameters(): if param.grad is not None: grad param.grad.asnumpy() total_norm (grad ** 2).sum() return total_norm ** 0.5不过这个方法在step_end里调用可能拿不到梯度因为梯度可能已经被清空了更稳妥的方式是在网络的反向传播之后、优化器更新之前插入计算逻辑。这个需要改网络定义稍微麻烦一点但值得。4.2 用 Summary 写直方图和图像TensorBoard 不只是画曲线还能看权重分布和图像。比如你想监控某一层权重的分布变化import numpy as np from tensorboard.compat.proto.summary_pb2 import Summary def make_histogram_event(tag, values, step): hist Summary() hist_value hist.value.add() hist_value.tag tag hist_value.histo.CopyFrom( tf.make_histogram_proto(values) # 需要 tensorflow 或手动构造 ) # 手动构造 histogram 比较复杂建议用 tensorboardX return hist说实话手动构造 histogram 的 protobuf 比较繁琐我建议直接用tensorboardXfrom tensorboardX import SummaryWriter writer SummaryWriter(log_dir./tb_logs/run_001) writer.add_histogram(weights/layer0, weight_np, step) writer.add_image(input/sample, image_np, step, dataformatsHWC)tensorboardX的 API 和 PyTorch 的SummaryWriter几乎一样但它是框架无关的不会引入 PyTorch 依赖。这是我更推荐的方式。4.3 远程服务器上的 TensorBoard 查看方案训练通常在远程 GPU 服务器上跑本地怎么查看 TensorBoard有几种方案方案一端口转发如果你用 SSH 连服务器可以用-L参数做本地端口转发ssh -L 6006:localhost:6006 userremote_server然后在服务器上启动tensorboard --logdir./tb_logs --port6006本地浏览器打开http://localhost:6006就能看到。方案二在服务器上启动通过内网 IP 访问如果服务器和你在同一内网直接tensorboard --logdir./tb_logs --host0.0.0.0 --port6006然后用http://服务器内网IP:6006访问。方案三把日志同步到本地用rsync或者scp定期把 event file 同步到本地本地启动 TensorBoard 查看。这种方式适合训练结束后复盘不适合实时监控。我平时用方案一最多简单直接。注意端口转发的时候如果本地 6006 被占用了可以换成-L 6007:localhost:6006然后本地访问 6007。提示TensorBoard 默认只监听 localhost如果想让同网段的其他机器访问必须加--host0.0.0.0。但要注意安全别暴露到公网。5. 常见问题与排查实录5.1 TensorBoard 页面打不开或者没有数据这是最常见的问题排查顺序如下确认 event file 是否生成去日志目录下ls -la看有没有events.out.tfevents.xxx文件。如果没有说明 Callback 没被调用或者写入失败。确认 TensorBoard 指向的目录是否正确--logdir要指向包含 event file 的目录不是 event file 本身。而且 TensorBoard 会递归扫描子目录所以指向父目录也行。确认 flush 是否执行如果 event file 有内容但页面不更新可能是缓冲区没 flush。在 Callback 里加writer.flush()或者等训练结束。确认端口是否被占用tensorboard --logdirxxx --port6007换一个端口试试。确认浏览器缓存有时候是浏览器缓存了旧页面强制刷新CtrlShiftR一下。5.2 loss 曲线锯齿严重怎么看loss 曲线震荡是正常的尤其是 batch size 比较小的时候。TensorBoard 右上角有个 smoothing 滑块默认是 0.6可以调高到 0.9 让曲线更平滑。但注意smoothing 只是视觉效果原始数据没变。如果你要看真实波动把 smoothing 调到 0。另外可以在 Callback 里对 loss 做移动平均再写入self.loss_ema 0.99 * self.loss_ema 0.01 * loss_val self.writer.add_scalar(train/loss_ema, self.loss_ema, self.step_count)这样 TensorBoard 上会同时显示原始 loss 和 EMA loss兼顾细节和趋势。5.3 多个实验的曲线混在一起怎么办TensorBoard 默认会把--logdir下所有子目录的 event file 都读进来不同实验的曲线会用不同颜色区分。但如果你目录结构乱了曲线会混在一起分不清。解决办法每个实验一个独立子目录目录名带上关键超参数。在 TensorBoard 左侧的 Runs 面板里可以勾选/取消勾选特定实验。如果还是乱用--logdir_spec参数手动指定每个实验的路径和显示名tensorboard --logdir_specexp1:./tb_logs/run1,exp2:./tb_logs/run25.4 训练中断后 event file 损坏如果训练过程中进程被 kill 掉event file 可能没有正常关闭导致 TensorBoard 读取时报错。这种情况下event file 通常还是能读的只是最后几条数据可能丢失。如果完全读不了可以尝试用tensorboard --logdirxxx --reload_multifiletrue强制重新加载。预防措施是在 Callback 的end方法里确保writer.close()被调用并且捕获异常def end(self, run_context): try: if self.writer: self.writer.flush() self.writer.close() except Exception as e: print(f[TensorBoardCallback] 关闭 writer 时出错: {e})5.5 性能开销太大怎么办写 TensorBoard 日志本身开销很小但如果你每个 step 都写几十个指标而且 step 速度很快比如每秒几十个 step累积起来也会有影响。优化策略降低写入频率每 N 个 step 写一次N 取 10 到 100。只写关键指标loss、lr、grad_norm 就够了别把每个参数的均值都写进去。异步写入TensorBoard 的 EventFileWriter 本身是异步的但 flush 是同步的控制好 flush 频率。我在实际项目里实测过每 10 个 step 写 3 个标量对训练速度的影响在 1% 以内基本可以忽略。6. 一些个人经验与后续扩展方向TensorBoard 监控这件事看起来简单但真正用顺手需要一点时间。我最大的体会是日志目录的结构比代码本身更重要。代码写一次就不用动了但目录结构如果一开始没设计好后面实验多了会非常痛苦。建议在项目启动前就定好命名规范比如{模型名}_{数据集}_{日期}_{关键超参}这样半年后回头看还能看懂。另外一个建议是把 TensorBoard 的启动命令写成一个 shell 脚本放在项目根目录#!/bin/bash # start_tb.sh tensorboard --logdir./tb_logs --port6006 --host0.0.0.0 --samples_per_pluginscalars10000samples_per_plugin这个参数可以控制每个图表保留多少个数据点默认是 1000对于长训练来说不够用调到 10000 可以看到更完整的曲线。后续如果想进一步扩展可以考虑把 TensorBoard 的 event file 解析出来做自动化的异常检测。比如 loss 连续 N 个 step 上升就发告警或者 grad_norm 超过阈值就自动降低学习率。这些都可以基于 event file 做二次开发TensorBoard 本身提供了event_accumulator和event_multiplexer这两个工具类解析起来很方便。最后分享一个小技巧如果你同时跑多个实验可以在 TensorBoard 的 Custom Scalars 面板里自定义图表布局把相关的指标放在一起对比。比如把所有实验的 loss 放一张图所有实验的 lr 放另一张图这样一眼就能看出哪个实验的配置更好。这个功能在实验对比阶段特别有用比一个个点开看效率高多了。
返回列表