跳转到主要内容
custom/custom 只跑一个文件:experiments/<name>/train.sh。平台不读 FRAMEWORK 变量,不去找 run.py,也从不因为别的适配器失败而回退到 custom。那个文件做什么完全由你决定。
不要为了改一个学习率就转 custom。catalog 方法已经接好了指标、checkpoint 和镜像,转 custom 把这 三件事又交回给你。如果只是一个跑在带标签数据上的有监督 PyTorch 循环, pytorch/image-classification 在 train.py 里给你同样的自由,另外 还带多节点 torchrun、已检查的超参和平台镜像。

脚手架

你会得到 train.sh,以及 config.yaml、recipe.lock.json 和 README.md。recipe 的入口按名字就是 train.sh,所以改名会让提交失败并报 custom entrypoint is missing or escapes the experiment directory。 适配器执行 bash <train.sh 的绝对路径>。工作目录是作业包的根,不是实验目录,所以要把 Python 指向 "${TUNEPLANE_EXP_DIR}/train.py",而不是一个相对路径。 custom 适配器只编译训练那一种操作。对 custom 实验用 tp export 和 tp eval 会被拒绝。

环境契约

脚手架用 :? 在缺任何一项时直接退出。不要改这些名字。 部署方有的话,HF_HOME、HF_TOKEN 和 *_DATA_DIR 变量也会到。完整清单在 作业环境变量参考。 配额和看门狗是按 TUNEPLANE_CLUSTER_* 衡量的。占用超过这个数会让作业被告警、甚至被停掉,所以把 同样的数字传给 accelerate launch --num_processes 或 torchrun --nproc_per_node。

你的脚本必须做的三件事

  1. 把你想留下的一切写到 $TUNEPLANE_OUT_DIR 下。容器退出时临时目录就没了。
  2. 通过 tuneplane.report 上报标量,或声明 TensorBoard/JSONL 输出,由 独立采集器采集。只有 stdout 日志不会产生指标点。
  3. 尊重 TUNEPLANE_CLUSTER_*。
平台会在输出目录下找这些路径。它们会做 realpath 检查,必须留在目录内:

写 train.sh

set -euo pipefail 已经在里面了。把脚手架的 exit 1 换成一次启动。
不要 cd 到某处然后写 ./checkpoints。用这些变量。

上报指标

PyPI 名字是 tuneplane,import 也是 tuneplane。上报模块既不引 transformers 也不引 Ray,而且它 从不往你的训练循环里抛异常。没有 TUNEPLANE_JOB_TOKEN 时它什么都不做,这正是你在作业之外想要的。
init() 是幂等的。嵌套字典会被拉平,无法归约成均值的非标量会被丢掉。键本身没带命名空间时, prefix= 会给它加上。 TunePlaneCallback 是鸭子类型的,不是 TrainerCallback 的子类。它在 train begin 调 init,在 on_log 调 log,在 evaluate 调 log(..., prefix="validation"),在结束时调 finish。 硬件采样用 init(monitor_hardware=True) 开启,只在 rank 0 上 —— 一个作业一个采样器,不是一张卡 一个。间隔是 TUNEPLANE_JOB_MONITOR_INTERVAL,单位秒,默认 10。 两件不要做的事:不要自己往 ingest API 发 POST,因为 stdout 已经在转发了,第二条通路会让日志重复; 不要硬编码控制台地址,因为容器里已经有 TUNEPLANE_JOB_ENDPOINT。

为什么 —observability-url 是必填的

已发布的 custom/custom recipe 声明 observability: external。两个后果:
  • 提交必须带 --observability-url。没有它,编译会失败并报 custom external observability requires spec.framework.observability_url。你们团队用的任何 URL 都行;平台不会替你起 wandb。 它以 TUNEPLANE_EXTERNAL_OBSERVABILITY_URL 进入作业。
  • 适配器不会改写 PYTHONPATH。只有当 wheel 在你的镜像里、或你自己把它放进 PYTHONPATH, import tuneplane 才能用。
声明 observability: platform 的 recipe 行为正好相反:--observability-url 会被拒绝,runner 会把 capsule 的根目录前置到 PYTHONPATH,于是镜像里没有 wheel 也能 from tuneplane.report import log。这个开关你没法从实验目录里翻 —— 它是已发布 recipe 的属性。 此 recipe 如使用 SDK 上报曲线,需要在镜像里安装 tuneplane,并继续传入 --observability-url。 原生采集则需要独立的输出声明和采集器部署配置。 TUNEPLANE_JOB_ENABLED=1 是另一件事。那是作业有 ingest 令牌时服务端总会设的绑定。

提交

--image 就是镜像本身:custom 在 catalog 里没有钉子,它的 runtime.default_version 是 user-managed。标签可用;生产上钉 @sha256:…,这样一个移动的标签不会在准入之后改变作业。 TUNEPLANE_ALLOWED_IMAGE_REGISTRIES 为空会拒绝 custom 用户镜像,而 catalog 框架仍然可以用 部署的默认镜像。让管理员把你的仓库主机名加上。见自定义镜像。

确认成功

tp job logs 会显示你自己那行启动命令,然后是你训练器的输出。Charts 标签页在你第一次调 log() 之后拿到第一个点。 在容器里:

不成功的时候

config.yaml 是你的

custom 适配器里没有任何东西会读它。custom recipe 没有声明参数,所以 tp validate 没什么可查, --set 的值会落进 spec.hyperparams 但不会进到你的 argv。在 train.py 里自己解析这个文件。 --model 和 --train-data 是 verl 和 TRL 的绑定。custom 忽略它们:用环境变量和你打包进去的文件。

下一步

自定义镜像 · 作业环境变量 · 上报 API