跳转到内容

评估闭环与评估脚本编写

训练集上的最低 loss 不等于 held-out 集上的最佳泛化。GPUPlane 的评估闭环要求:你写一个普通的 eval 脚本,平台把它当作 EVALUATE 类型的 job 排队执行,结果回挂到对应 checkpoint,之后 get_best_checkpoint优先使用评估结果排序。

  • 训练过程中保存的 “best” checkpoint 是按训练/验证流式指标挑选的,本质上仍是训练期间的观测值。
  • 真正的 held-out 评估通常在独立数据上跑完整前向,batch、augmentation、metric 计算方式都可能不同。
  • get_best_checkpoint 的策略是:先找 SUCCEEDED 的 checkpoint 评估,找不到才回退到训练 run 的 best 指标。回退更弱,宣布胜出前请先跑评估。

POST /api/v1/checkpoints/{ckpt_id}/evaluations 会创建一个 EVALUATE job,并继承训练 job 的 working_dirresources(除非显式覆盖)。job 启动时 runner 会注入以下环境变量:

环境变量 说明
GPUCTL_EVAL_CHECKPOINT 被评估 checkpoint 的绝对路径
GPUCTL_EVAL_ID 评估 id,用于结果关联
GPUCTL_EVAL_OUTPUT 结果 JSON 的写入路径;若未显式设置,runner 默认设为 ~/.gpuctl/jobs/{job_id}/attempt-{attempt}/eval_result.json

你的 eval 脚本必须:

  1. GPUCTL_EVAL_CHECKPOINT 加载模型权重。
  2. 在 held-out 数据上跑完整评估,计算指标。
  3. 把结果写成 JSON,路径使用 GPUCTL_EVAL_OUTPUT
{
"metrics": {
"eval/loss": 0.312,
"eval/accuracy": 0.884
},
"runtime_s": 12.3
}

如果脚本 exit 0 但没有产出合法结果,agent 会把 job 标记为 FAILEDfailure_reason=EVALUATION_RESULT_INVALID,避免 checkpoint 的评估永远卡在 QUEUED

suite / dataset / dataset_version 的语义

Section titled “suite / dataset / dataset_version 的语义”

这三个字段都是自由文本标签,用于分组和过滤,本身不会影响脚本执行:

  • suite:评估集合,例如 heldouttestood
  • dataset:数据集名称,例如 imdb-testc4-en
  • dataset_version:数据版本,例如 v1.2

compare_runscompare_checkpointsget_best_checkpoint 都可以按这三个字段过滤。如果同一个比较范围内存在多种 protocol 组合又没加过滤,语义层会抛出 ValidationError,提示你先指定 suite/dataset/dataset_version

import json
import os
import torch
from my_model import Model, eval_loader
def main():
ckpt_path = os.environ["GPUCTL_EVAL_CHECKPOINT"]
eval_id = os.environ["GPUCTL_EVAL_ID"]
out_path = os.environ.get("GPUCTL_EVAL_OUTPUT") or f"{eval_id}.result.json"
model = Model().cuda()
model.load_state_dict(torch.load(ckpt_path, map_location="cuda"))
model.eval()
total_loss = 0.0
total_acc = 0.0
n = 0
with torch.no_grad():
for x, y in eval_loader:
logits = model(x.cuda())
loss = torch.nn.functional.cross_entropy(logits, y.cuda())
pred = logits.argmax(dim=-1)
total_loss += loss.item() * len(y)
total_acc += (pred == y.cuda()).sum().item()
n += len(y)
metrics = {
"eval/loss": total_loss / n,
"eval/accuracy": total_acc / n,
}
with open(out_path, "w") as f:
json.dump({"metrics": metrics, "runtime_s": 15.0}, f)
if __name__ == "__main__":
main()
# 在 Claude Code 等 MCP 客户端里自然语言调用,或直接调用工具
await evaluate_checkpoint(
checkpoint_id="ckpt_...",
command=["python", "eval.py"],
suite="heldout",
dataset="imdb-test",
)

返回一个 TaskHandletaskId 就是 job id。随后用 get_job 轮询,list_evaluations / compare_checkpoints 看结果。需要 write-scope token。

Terminal window
# 需要 write-scope token
curl -s -X POST http://gpu-host:8600/api/v1/checkpoints/ckpt_.../evaluations \
-H "Authorization: Bearer $GPUCTL_WRITE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"command": ["python", "eval.py"],
"suite": "heldout",
"dataset": "imdb-test",
"dataset_version": "v1"
}'

可选覆盖字段:working_direnvresourcesprioritynode_selectormax_attempts

在 Run Detail 页的 Checkpoint 列表里,每个 checkpoint 旁有 “Evaluate” 按钮,填写 suite/dataset 后提交。UI 会展示评估 job 的状态,完成后指标直接显示在 checkpoint 卡片上。

  • list_evaluations(run_id=...)list_evaluations(checkpoint_id=...):列出该 run 或 checkpoint 的所有评估记录。
  • compare_checkpoints(ids=[...]):按 primary metric 横向对比多个 checkpoint 的评估结果,返回 best_checkpoint_idnext_actions
  • get_best_checkpoint(run_id=...) / get_best_checkpoint(experiment_id=...):按 primary metric 推荐一个最佳 checkpoint,优先评估结果。
  • GET /api/v1/evaluations?run_id=...
  • GET /api/v1/checkpoints/{ckpt_id}/evaluations
  • GET /api/v1/checkpoints:recommend?run_id=...
  • GET /api/v1/experiments/{exp_id}/best-checkpoint
  • Run Detail 的 Checkpoints tab 会展示每个 checkpoint 的评估指标。
  • Experiments 页的对比视图会按 primary metric 给 run 排序,评估值优先于训练 best。
现象 原因 处置
EVALUATION_RESULT_INVALID exit 0 但没写合法 JSON 检查 GPUCTL_EVAL_OUTPUT 文件内容
FAILED + OOM 评估 batch 太大 改小 eval batch,或启用 eval 时的 gradient checkpointing
FAILED + missing file/path working_dir 或 checkpoint 路径不对 确认路径在 agent 主机上存在
compare_checkpoints 报 protocol 冲突 同一批 checkpoint 用了不同的 suite/dataset suite/dataset 过滤