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 的完整流程:
- 从
--input加载已完成请求的 JSONL 记录; - 将这些记录转换成训练样本;
- 只保留
target_model与--target-model匹配的样本; - 按配置的 holdout ratio,把每个 slot 切分为训练集与验证集;
- 对样本量达到阈值的 slot,逐个训练模型;
- 将
manifest.json、metadata.json、report.json与slots/*写入 staging 目录; - 先写 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.json 的 promotedSlots。
运行时加载条件
sidecar 启动时,只有同时满足以下条件,bundle 才是可加载的:
- 启动时传入了
--artifact-root、--target-model、--model-version; - bundle 位于
artifactRoot/targetModel/modelVersion; manifest.json与metadata.json都存在,且均为合法 JSON;manifest.json中的targetModel与--target-model一致;manifest.json中的modelVersion与--model-version一致;manifest.json与metadata.json都声明了backend,且两边取值完全一致;backend取值只能是xgboost或lightgbm;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,而不会直接中断路由。