GenStudio LLM API 部分模型价格调整公告GenStudio LLM API 部分模型价格调整公告 ,新价格 2025 年 11 月 1 日生效调价公告
Skip to content

启用任务功能内置的 TensorBoard 服务

本指南将帮助您了解如何在平台「任务」功能中使用内置的 TensorBoard 功能来监控模型训练过程。

阅读本文后,您将能够:

  • 配置训练脚本的 TensorBoard 日志目录
  • 在平台提交任务时正确填写日志路径
  • 使用平台内置的 TensorBoard 服务监控训练

概述

平台「任务」功能内置 TensorBoard 服务,支持实时监控训练指标。使用流程如下:

  1. 配置训练脚本:指定日志写入目录,训练过程中生成事件文件
  2. 提交任务:在控制台「任务可视化」中填写日志目录的上级目录(包含所有实验子目录的根目录)
  3. 查看可视化:平台自动启动 TensorBoard 服务,扫描该目录下所有实验并提供 Web 界面

重要

日志必须写入共享高性能存储

平台的 TensorBoard 服务只能读取挂载的共享存储路径(如 /mnt/shared/)。如果日志写入容器本地目录(如 /tmp/),平台将无法发现这些文件。

提示

如果只需查看当前实验,也可以在「任务可视化」中填写具体的实验子目录。但填写上级目录可以同时查看和对比多个实验的训练曲线。

TensorBoard 核心概念

在开始配置之前,理解 TensorBoard 如何组织实验数据至关重要。

Runs 与目录的关系

TensorBoard 使用文件系统的目录结构来识别实验。每个包含 events.out.tfevents.* 文件的目录被识别为一个 run(实验运行)。

  • Run:一次实验执行,对应文件系统中的一个目录
  • Tag:run 内部的指标名称,例如 Loss/trainAccuracy/train

注意

writer.add_scalar("exp1/loss", ...) 创建的是一个名为 exp1/loss 的 tag,而不是新的 run。要创建多个 runs,必须使用不同的目录。

--logdir 参数的作用

注意

任务运行期间,平台会自动使用您填写的「日志存储路径」启动 TensorBoard 服务,无需手动操作。平台默认启用 --reload_multifile=true,即使长时间训练产生多个事件文件,也能显示完整历史数据。

以下内容适用于任务结束后,您需要自行启动 TensorBoard 查看持久化日志的场景。详见使用开发机运行 TensorBoard 服务

TensorBoard 的 --logdir 参数决定了扫描哪些目录来发现 runs。该参数接受任意层级的目录路径:

传入路径扫描行为适用场景
父目录(base_dir递归扫描所有子目录,发现全部 runs对比多个实验
具体 run 目录(log_dir只加载该目录的数据查看单个实验

在平台场景中,日志存储路径应填写 base_dir(父目录),使 TensorBoard 能够发现所有 runs。

目录结构示例

以下表格展示了目录结构与 TensorBoard 显示行为的关系:

结构类型目录示例TensorBoard 行为
推荐runs/exp1/runs/exp2/ 各含事件文件显示多个可对比的 runs
避免runs/ 直接包含事件文件只显示一个 run,无法区分实验

推荐多 run 目录结构

以下代码为每个实验创建独立的子目录:

python
base_dir = "/mnt/shared/tensorboard/runs"

# 实验 1: MNIST 分类
run_name = "mnist_exp1"
writer = SummaryWriter(f"{base_dir}/{run_name}")

# 实验 2: MNIST 不同超参数
run_name = "mnist_exp2"
writer = SummaryWriter(f"{base_dir}/{run_name}")

# 实验 3: LLM 微调
run_name = "llm_finetune"
writer = SummaryWriter(f"{base_dir}/{run_name}")

结果目录结构:

text
/mnt/shared/tensorboard/runs/
  ├── mnist_exp1/
  │   └── events.out.tfevents.*
  ├── mnist_exp2/
  │   └── events.out.tfevents.*
  └── llm_finetune/
      └── events.out.tfevents.*

TensorBoard 左侧面板将显示三个 runs:mnist_exp1mnist_exp2llm_finetune,可以勾选对比。

避免单 run 目录结构

以下代码直接写入父目录,没有创建子目录:

python
base_dir = "/mnt/shared/tensorboard/runs"

# 错误:没有指定 run_name,直接写入 base_dir
writer = SummaryWriter(base_dir)

结果目录结构:

text
/mnt/shared/tensorboard/runs/
  └── events.out.tfevents.*

所有日志写入同一目录时,TensorBoard 只显示一个 run,无法区分不同实验。

Step 0 创建训练脚本

请在您自己的训练脚本中指定 TensorBoard 日志目录(logdir)。训练过程中,模型的日志(如损失、准确率等)将写入该目录。

以下是一个使用 PyTorch 的简单 Python 训练脚本示例,展示如何配置 TensorBoard 日志目录:

python
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.tensorboard import SummaryWriter
from datetime import datetime

# 推荐:使用 3 层目录结构(父目录/实验名/时间戳)
# base_dir 是父目录(根目录),需要提供给平台作为「日志存储路径」
base_dir = "/mnt/shared/tensorboard/runs"

# 方式一(推荐):实验名 + 时间戳,便于组织多类型实验
experiment_name = "my_experiment"
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
log_dir = os.path.join(base_dir, experiment_name, timestamp)
# 最终路径:/mnt/shared/tensorboard/runs/my_experiment/20231215-143022/

# 方式二:使用环境变量(适合平台任务克隆/重跑场景)
# experiment_name = "my_experiment"
# run_id = os.getenv('RUN_ID', 'default_run')
# log_dir = os.path.join(base_dir, experiment_name, run_id)

writer = SummaryWriter(log_dir=log_dir)

# 示例模型
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(10, 16)
        self.fc2 = nn.Linear(16, 1)
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.relu(self.fc1(x))
        x = self.sigmoid(self.fc2(x))
        return x

model = SimpleNet()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 模拟数据
x_train = torch.rand(1000, 10)
y_train = torch.randint(0, 2, (1000, 1)).float()

# 训练循环
epochs = 5
for epoch in range(epochs):
    model.train()
    optimizer.zero_grad()
    outputs = model(x_train)
    loss = criterion(outputs, y_train)
    loss.backward()
    optimizer.step()

    # 记录日志到 TensorBoard
    writer.add_scalar('Loss/train', loss.item(), epoch)
    writer.add_scalar('Accuracy/train', (outputs.round() == y_train).float().mean().item(), epoch)

# 关闭 writer
writer.close()

以下是关键点:

  • 日志目录(log_dir:必须指向共享高性能存储路径(如 /mnt/shared/tensorboard/runs/...),否则平台无法读取日志。使用时间戳或环境变量确保每次实验生成唯一子目录,详见使用环境变量动态设置 run 名称
  • SummaryWriter:使用 PyTorch 的 SummaryWriter 来记录日志到 TensorBoard。您可以记录标量(如损失、准确率)、计算图等。
  • 关闭 writer:训练完成后调用 writer.close() 以确保日志正确写入。

多任务场景:共享父目录,各用子目录

在同一可用区内,多个训练任务可以挂载相同的共享高性能存储。这意味着不同任务(例如 MNIST 分类和 LLM 微调)可以将日志写入同一个父目录,但必须各自写入不同的子目录

python
# 推荐:使用 3 层结构,自动避免冲突
SummaryWriter("/mnt/shared/runs/mnist/20231215-140000/")      # MNIST 训练
SummaryWriter("/mnt/shared/runs/llm/20231215-140030/")       # LLM 微调
python
# 错误:两个任务写入同一目录
SummaryWriter("/mnt/shared/runs")  # 数据会混乱!

危险

多个任务同时写入同一目录会导致:

  • 不同实验的 loss/accuracy 曲线混在一起
  • step 空间冲突,曲线变得无意义
  • 无法区分哪条曲线属于哪个实验

高级:使用环境变量同步日志路径

克隆或重跑任务时,训练脚本的日志写入路径和平台「日志存储路径」容易出现不同步的问题。平台支持在两处使用相同的环境变量,确保写入路径和读取路径始终一致。

注意

何时使用此功能

此功能适用于希望 TensorBoard 只显示当前任务的实验数据的场景。如果您希望查看和对比多个实验,请在「日志存储路径」中填写父目录(如 /mnt/shared/runs/),无需使用变量替换。

工作原理

步骤操作示例
1. 定义环境变量在平台任务配置页面添加环境变量RUN_ID=exp_v1
2. 脚本读取变量训练脚本使用 os.getenv() 构建写入路径log_dir = f"{base_dir}/{run_id}"
3. 平台引用变量「日志存储路径」使用 ${VAR} 语法引用变量/mnt/shared/runs/my_experiment/${RUN_ID}
4. 变量替换平台替换变量后启动 TensorBoard只读取 /mnt/shared/runs/my_experiment/exp_v1

配置示例

训练脚本:

python
import os
from torch.utils.tensorboard import SummaryWriter

base_dir = "/mnt/shared/runs"
experiment_name = "my_experiment"
run_id = os.getenv('RUN_ID', 'default_run')  # 从环境变量读取
log_dir = os.path.join(base_dir, experiment_name, run_id)

writer = SummaryWriter(log_dir)

平台控制台配置:

  1. 环境变量:添加 RUN_ID,值设为 exp_v1
  2. 日志存储路径:填写 /mnt/shared/runs/my_experiment/${RUN_ID}

克隆任务时的操作

克隆任务后,只需修改环境变量 RUN_ID 的值(如 exp_v2),脚本写入路径和 TensorBoard 读取路径会同步更新:

text
/mnt/shared/runs/
  └── my_experiment/
      ├── exp_v1/    ← 原任务
      ├── exp_v2/    ← 克隆任务(修改 RUN_ID 后)
      └── exp_v3/

提示

环境变量方法的优势:

  • 单一变量源:变量在一处定义,脚本和平台同步使用
  • 克隆友好:重跑任务时只需修改环境变量值,无需改动脚本
  • 避免不同步:消除脚本写入 exp_v2 但 TensorBoard 读取 exp_v1 的风险

Step 1 提交训练任务

在完成训练脚本后,您需要通过平台的控制台网页提交训练任务,并指定 TensorBoard 日志目录。

任务可视化的「日志存储路径」中,请填写 父目录(根目录)路径,例如 /mnt/shared/tensorboard/runs

重要

填写父目录,而非具体 run 目录

  • 如果脚本写入 /mnt/shared/runs/mnist/20231215-140000/
  • 在平台填写 /mnt/shared/runs/(父目录)
  • TensorBoard 会自动发现该目录下的所有子目录(runs),包括 mnist/llm/

Step 2 查看 TensorBoard 可视化

一旦训练任务启动,平台会自动为您的任务启动一个 TensorBoard 服务。该服务会读取您指定的日志目录,并提供一个可视化界面,让您可以实时监控训练进度。

您可在任务列表页的右侧操作栏中找到可视化按钮,直接跳转 TensorBoard 看板。也可以在任务详情页找到可视化按钮。

alt text

注意

  • 任务结束后,任务可视化按钮失效。如果任务日志持久化保存在共享存储上,您可以自行运行 TensorBoard 看板服务查看日志。例如,可以使用开发机运行 TensorBoard 服务,并指定日志路径为当前训练任务的日志。
  • 如果您提交了多个训练任务,每个任务的 TensorBoard 服务是独立的,互不干扰。

修改日志读取路径

在任务运行过程中,如果发现创建任务时填写了错误的日志路径,您可以直接修改「日志存储路径」配置,平台会自动重启 TensorBoard 服务以读取新路径下的日志。

操作步骤如下:

  1. 进入运行中任务的详情页。
  2. 找到「任务可视化」配置区域。
  3. 修改「日志存储路径」。
  4. 点击提交。

平台会重启 TensorBoard 服务,新路径生效后即可查看更新后的日志数据。

注意

  • 修改日志路径仅影响 TensorBoard 的读取位置,不会改变训练脚本的日志写入路径。如需同步修改写入路径,请更新训练脚本或环境变量配置。
  • 仅支持在任务运行中时修改日志存储路径。任务处于「删除中」、「运行成功」、「运行失败」、「清理中」状态时无法修改。

常见问题

为什么 TensorBoard 左侧只显示一个 run?

原因:日志直接写入了父目录,而不是子目录。TensorBoard 将每个包含事件文件的目录识别为一个 run。

诊断步骤

  1. 检查日志目录结构:

    bash
    find /mnt/shared/runs -type f -name "events*"
  2. 查看输出结果:

    问题情况(事件文件在父目录):

    /mnt/shared/runs/events.out.tfevents.1702627200.host1

    正常情况(事件文件在子目录):

    /mnt/shared/runs/exp1/events.out.tfevents.1702627200.host1
    /mnt/shared/runs/exp2/events.out.tfevents.1702627300.host1

解决方案

修改训练脚本,确保每次实验写入独立的子目录:

python
# 错误:直接写入父目录
writer = SummaryWriter("/mnt/shared/runs")

# 正确:写入子目录
writer = SummaryWriter("/mnt/shared/runs/exp1")

SummaryWriter() 不指定路径时,日志写到哪里了?

如果调用 SummaryWriter()SummaryWriter("runs") 而不使用绝对路径,日志会写入当前工作目录(CWD)下的 runs/ 文件夹。

警告

当前工作目录取决于 Python 进程的启动位置,不是脚本文件所在目录。在容器、Jupyter、Slurm 任务中,CWD 可能出乎意料。

建议:始终使用绝对路径,避免混乱。

python
# 避免
writer = SummaryWriter()
writer = SummaryWriter("runs")

# 推荐
writer = SummaryWriter("/mnt/shared/tensorboard/runs/mnist/20231215-140000")

多 GPU(DDP)训练如何正确记录日志?

在使用 DistributedDataParallel 进行多 GPU 训练时,只应在 rank 0 进程写入 TensorBoard 日志,否则会导致重复记录:

python
import torch.distributed as dist

# 只在主进程创建 writer
if dist.get_rank() == 0:
    writer = SummaryWriter(log_dir)
else:
    writer = None

# 训练循环中
if writer is not None:
    writer.add_scalar('Loss/train', loss.item(), step)

参考资源