Prediction Sidecar 训练指南

本文档介绍如何为 Hermes Router 的 prediction sidecar 训练、发布并校验 prediction artifact bundle(下文简称 bundle)。

如果你只想先看仓库里现成的示例 bundle,请直接参考 examples/prediction-model/README.md

训练流程概览

prediction sidecar 提供两个入口命令:

  • prediction-sidecar — 从已发布的 bundle 加载模型,对外提供 gRPC 预测服务。
  • prediction-train — 基于已完成的请求记录,构建一份新的 bundle。

prediction-train 的完整流程:

  1. --input 加载已完成请求的 JSONL 记录;
  2. 将这些记录转换成训练样本;
  3. 只保留 target_model--target-model 匹配的样本;
  4. 按配置的 holdout ratio,把每个 slot 切分为训练集与验证集;
  5. 对样本量达到阈值的 slot,逐个训练模型;
  6. manifest.jsonmetadata.jsonreport.jsonslots/* 写入 staging 目录;
  7. 先写 staging,再将其重命名到 artifact_root/target_model/model_version,确保 sidecar 不会读到只写了一半的 bundle。

训练输入要求

prediction-train 期望的输入是已完成请求的 JSONL 记录,且这些记录要能被当前 loader 解析为已完成请求记录与训练样本。

输入数据的质量会直接决定最终 bundle 的质量:

  • 无法解析的记录会在加载阶段被丢弃;
  • 在样本生成阶段,若记录缺少所需的 label 或角色相关的预测输入,也会被记入 dropReasons
  • report.json 汇总的是加载阶段与样本生成阶段合并后的 dropReasons,并不等同于严格意义上的“丢弃记录总数”;
  • 可用样本越少,能达到 promotion 阈值的 slot 也就越少。

完整训练命令

建议在 sidecar 目录下运行训练命令,以便复用项目的脚本入口:

cd sidecar/prediction
uv run prediction-train \
  --input /tmp/hermes-completed-records.jsonl \
  --artifact-root /tmp/hermes-prediction-artifacts \
  --target-model 'Qwen/Qwen3-8B' \
  --model-version baseline-conversation-data \
  --model-backend xgboost \
  --quantile 0.9 \
  --min-samples-per-slot 1000 \
  --min-holdout-samples-per-slot 200 \
  --holdout-ratio 0.2 \
  --bucket-cap 500

其中 --hyperparams 必须以 JSON object string 形式传入,例如 '{"learning_rate": 0.05}'

最关键的参数可归为三类:

  • bundle 标识--artifact-root--target-model--model-version
  • 后端行为--model-backend--quantile--hyperparams
  • slot 可晋升性--min-samples-per-slot--min-holdout-samples-per-slot--holdout-ratio--bucket-cap

制品格式与发布方式

发布后的 bundle 位于 artifact_root/target_model/model_version。以上文的示例命令为例,最终目录结构为:

/tmp/hermes-prediction-artifacts/
└── Qwen/
    └── Qwen3-8B/
        └── baseline-conversation-data/
            ├── manifest.json
            ├── metadata.json
            ├── report.json
            └── slots/

各文件职责:

  • manifest.json — 运行时标识、后端、promoted slot 名称,以及 slot 文件映射。
  • metadata.json — 特征列表、promotion 阈值、holdout 配置,以及每个 slot 的状态。
  • report.json — promoted slot 的指标,以及加载阶段与样本生成阶段合并后的 dropReasons
  • slots/ — 具体的后端模型文件,例如 *.xgboost.json*.lightgbm.txt

发布过程刻意设计成两阶段:trainer 先写入 staging 目录;替换已有 bundle 时,先把旧 bundle 移到一旁,再将 staging 重命名到目标位置,最后删除备份。这样做是为了避免 sidecar 在更新过程中读到只写了一半的 live bundle——而不是声称从旧版本到新版本是一次原子切换。

Slot 晋升规则

trainer 不会把每一个概念上的 slot 都导出为可用模型。

只有同时满足以下两个条件,slot 才会被 promote:

  • 该 slot 的总样本数不低于 minSamplesPerSlot
  • 该 slot 的 holdout 样本数不低于 minHoldoutSamplesPerSlot

未达到任一阈值的 slot 仍会保留在 metadata.json 中,并标记 status: "below_threshold"failingThreshold,但不会被写入 manifest.jsonpromotedSlots

运行时加载条件

sidecar 启动时,只有同时满足以下条件,bundle 才是可加载的:

  • 启动时传入了 --artifact-root--target-model--model-version
  • bundle 位于 artifactRoot/targetModel/modelVersion
  • manifest.jsonmetadata.json 都存在,且均为合法 JSON;
  • manifest.json 中的 targetModel--target-model 一致;
  • manifest.json 中的 modelVersion--model-version 一致;
  • manifest.jsonmetadata.json 都声明了 backend,且两边取值完全一致;
  • backend 取值只能是 xgboostlightgbm
  • promotedSlots 中的每个 slot 都能在 slotFiles 中找到映射;
  • 每个映射出的 slot 文件都真实存在于磁盘。

只要任一检查失败,sidecar 会以 prediction not-ready 状态启动,对预测请求返回 failed-precondition 错误,而不会提供基于制品的预测。

与 Hermes Router 的边界

bundle 可加载,是 prediction-guided routing 的必要条件,但并不充分

只有同时满足以下条件,Hermes Router 才会真正使用预测输出:

  • 请求携带非空的 target_model
  • 请求中的 target_model 与当前已加载 bundle 的目标模型一致;
  • 特征提取器能够构造出完整的端点输入;
  • 当前 Pod 类型与 queue family 所需的完整 slot 组合确实就绪:aggregate 需要对应 queue family 的 TTFT 与 TPOT 两个 slot;prefill 需要对应的 disagg TTFT slot;decode 需要对应的 disagg TPOT slot;
  • scorer 收到的输出通过 schema 校验,且能映射回已收集的输入。

只要其中任意一项不成立,预测评分器就会 fail open 到本地的 snapshot scoring,而不会直接中断路由。