评估闭环与评估脚本编写
训练集上的最低 loss 不等于 held-out 集上的最佳泛化。GPUPlane 的评估闭环要求:你写一个普通的 eval 脚本,平台把它当作 EVALUATE 类型的 job 排队执行,结果回挂到对应 checkpoint,之后 get_best_checkpoint 会优先使用评估结果排序。
为什么训练 best 不等于泛化 best
Section titled “为什么训练 best 不等于泛化 best”- 训练过程中保存的 “best” checkpoint 是按训练/验证流式指标挑选的,本质上仍是训练期间的观测值。
- 真正的 held-out 评估通常在独立数据上跑完整前向,batch、augmentation、metric 计算方式都可能不同。
get_best_checkpoint的策略是:先找 SUCCEEDED 的 checkpoint 评估,找不到才回退到训练 run 的 best 指标。回退更弱,宣布胜出前请先跑评估。
EVALUATE job 的契约
Section titled “EVALUATE job 的契约”POST /api/v1/checkpoints/{ckpt_id}/evaluations 会创建一个 EVALUATE job,并继承训练 job 的 working_dir 与 resources(除非显式覆盖)。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 脚本必须:
- 从
GPUCTL_EVAL_CHECKPOINT加载模型权重。 - 在 held-out 数据上跑完整评估,计算指标。
- 把结果写成 JSON,路径使用
GPUCTL_EVAL_OUTPUT:
{ "metrics": { "eval/loss": 0.312, "eval/accuracy": 0.884 }, "runtime_s": 12.3}如果脚本 exit 0 但没有产出合法结果,agent 会把 job 标记为 FAILED,failure_reason=EVALUATION_RESULT_INVALID,避免 checkpoint 的评估永远卡在 QUEUED。
suite / dataset / dataset_version 的语义
Section titled “suite / dataset / dataset_version 的语义”这三个字段都是自由文本标签,用于分组和过滤,本身不会影响脚本执行:
suite:评估集合,例如heldout、test、ood。dataset:数据集名称,例如imdb-test、c4-en。dataset_version:数据版本,例如v1.2。
compare_runs、compare_checkpoints、get_best_checkpoint 都可以按这三个字段过滤。如果同一个比较范围内存在多种 protocol 组合又没加过滤,语义层会抛出 ValidationError,提示你先指定 suite/dataset/dataset_version。
一个最小但完整的 eval.py
Section titled “一个最小但完整的 eval.py”import jsonimport os
import torchfrom 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()排队评估的三种入口
Section titled “排队评估的三种入口”1. MCP(Agent 推荐)
Section titled “1. MCP(Agent 推荐)”# 在 Claude Code 等 MCP 客户端里自然语言调用,或直接调用工具await evaluate_checkpoint( checkpoint_id="ckpt_...", command=["python", "eval.py"], suite="heldout", dataset="imdb-test",)返回一个 TaskHandle,taskId 就是 job id。随后用 get_job 轮询,list_evaluations / compare_checkpoints 看结果。需要 write-scope token。
2. REST
Section titled “2. REST”# 需要 write-scope tokencurl -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_dir、env、resources、priority、node_selector、max_attempts。
3. Web UI
Section titled “3. Web UI”在 Run Detail 页的 Checkpoint 列表里,每个 checkpoint 旁有 “Evaluate” 按钮,填写 suite/dataset 后提交。UI 会展示评估 job 的状态,完成后指标直接显示在 checkpoint 卡片上。
结果在哪里看
Section titled “结果在哪里看”MCP 语义工具
Section titled “MCP 语义工具”list_evaluations(run_id=...)或list_evaluations(checkpoint_id=...):列出该 run 或 checkpoint 的所有评估记录。compare_checkpoints(ids=[...]):按 primary metric 横向对比多个 checkpoint 的评估结果,返回best_checkpoint_id和next_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}/evaluationsGET /api/v1/checkpoints:recommend?run_id=...GET /api/v1/experiments/{exp_id}/best-checkpoint
Web UI
Section titled “Web UI”- Run Detail 的 Checkpoints tab 会展示每个 checkpoint 的评估指标。
- Experiments 页的对比视图会按 primary metric 给 run 排序,评估值优先于训练 best。
常见失败排查
Section titled “常见失败排查”| 现象 | 原因 | 处置 |
|---|---|---|
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 过滤 |