发布周期手记 —— 第六部
某客户的医学问答模型上,一套打分式 QA 评测套件开始触发告警。头条指标——全切片综合质量——一夜之间下降了 6 个百分点。团队为此调试模型整整两天。他们重新跑了微调,回滚到上一版发布。数字纹丝不动。
第三天早上,有人注意到评测套件正是在回归开始的当晚被更新过。测试集中新加入了三条儿科剂量类提示,而这个模型在训练阶段从未见过儿科剂量场景。这场“QA 失败”并不是模型回归,而是一次切片覆盖事件:评测开始考察模型本就不该掌握的内容。
在我们经手的客户上线中,这种情况非常常见。“QA 失败”告警只是症状,真正源自模型的概率大约只有七分之一。 另外六次,问题都潜伏在上游:评测设计、评审器校准、提示词 SHA、预处理流水线、数据集版本,或检索索引中。这六类问题从告警上看几乎完全相同——某个数字下降了——但修复方式天差地别。
本文是我们在告警触发后按顺序走过的诊断决策树。先用六步排除非模型成因,最后第七步才考虑模型本身。每一步都对应一条具体的 API 调用或查询语句。走完前六步后,你要么准确知道该修哪里,要么获得了去看模型的资格。
决策树
之所以采用顺序结构,是因为各步成本由低到高。第 1 步只是对评测套件做一次 git diff;第 7 步则是一轮完整的微调。你应该在每个便宜的检查上花十分钟,然后再去花一周做最贵的那个。
第 1 步——评测是否覆盖了该切片?
症状。 综合质量下降,但按切片拆分时,只有一个切片暴跌,其他切片基本持平。或者更令人困惑——所有切片都略有下降,降幅相近。
诊断方法。 将本次发布的评测套件清单 SHA 与上一次发布做差异比较。如果评测套件变了,而模型没有改动,那么回归发生在评测,而不是模型。
# 比较两次发布间的评测套件清单 SHA
curl https://api.divinci.ai/v1/releases/rel_a01c66 | jq '.eval_suite_sha256'
curl https://api.divinci.ai/v1/releases/rel_8f72b1 | jq '.eval_suite_sha256'
# 不一致?评测变了。审查新增内容。
修复方法。 要么回滚评测套件的变更(如果是无意中改的),要么扩充训练覆盖以匹配新评测(如果新切片确属真实生产场景)。不要为评测覆盖问题去发布一次模型回归修复——那只会让模型在它原本擅长的任务上更糟。
在我们流水线中,问题藏在哪里。 阶段 1——登记 将评测套件 SHA 绑定到发布清单中。上面的诊断就是简单地对两份清单做差异比较。医学问答团队之所以花了两天,是因为他们没有清单级别的差异——他们比对的是模型 checkpoint,而不是发布清单。
第 2 步——评审器在该切片上是否对齐人工?
症状。 评测套件中新加入的切片得分很差,但对模型输出在该切片上的人工复核认为这些输出没有问题。评审器认为模型在失败,人却不这么看。
诊断方法。 在该失败切片上抽取 50 条人工评分样本,计算 LLM 评审器与之的 Spearman ρ。若 ρ < 0.4,则评审器在该切片上并未测量人类所测量的内容。
curl -X POST https://api.divinci.ai/v1/judges/<judge_id>/calibrate \
-d '{ "slice": "pediatric-oncology-dosing", "human_ratings_csv": "..." }'
# → { "spearman_rho": 0.31, "interpretation": "judge_uncalibrated_for_slice" }
修复方法。 要么为该切片选择不同的评审模型,要么使用带仲裁器的评审器链。MT-Bench[1]显示,GPT-4 作为评审器与人类的平均一致率超过 80%,但各类别方差很大——从编程类的 86% 到写作 / 人文类的 36–44%。真正起决定作用的是方差;“平均不错”掩盖了那些评审器明显出错的切片。
在我们流水线中,问题藏在哪里。 阶段 2——把关 要求每个切片都要有一个经过校准的评审器。Calibrating the AI Judge 一文记录了具体流程。如果新切片在加入评测时没有走校准步骤,那么这道关卡本身在结构上就不可信。
第 3 步——提示词模板 SHA 与生产一致吗?
症状。 质量下降,但 model_ref 和 dataset_ref 都没动。训练侧没有任何变化。模型还是那个模型。然而结果就是变了。
诊断方法。 将失败发布清单中的 prompt_template_ref SHA 与上一次发布对比。一处“为提升简洁度”而对系统提示做的 38 个字符的小改动,对下游行为的影响可能超过一次完整重训。
curl https://api.divinci.ai/v1/releases/rel_a01c66 | jq '.prompt_template_ref'
curl https://api.divinci.ai/v1/releases/rel_8f72b1 | jq '.prompt_template_ref'
# 不一致?把 diff 拉出来,逐行细看。
修复方法。 把提示词当代码对待。10 release failures 一文 涵盖了“后台改提示词”这一典型失败模式——Tianpan 的 Semver Lie 复盘[2]将其列为 2026 年最主导的失败模式。如果你能证明提示词改过,那 bug 就找到了。
第 4 步——预处理流水线与生产一致吗?
症状。 模型在本地能通过评测,完全相同的模型却在生产环境中失败。model_ref 一样,提示词一样,数据集一样。
诊断方法。 从生产清单中拉取 preprocessing_ref SHA,并核对评测使用的是不是同一个。最经典的情况:训练侧规范了空白字符并做了小写化,生产却没有。评测当时跑的是生产预处理,一切都对得上——直到某天有人单边更新了预处理。
curl https://api.divinci.ai/v1/releases/rel_a01c66 | jq '.preprocessing_ref'
# 与你的训练 / 评测流水线实际使用的预处理对比。
修复方法。 将预处理容器化为带版本的产物,在清单中引用它。一旦关卡的预处理 SHA 与生产不一致,就拒绝部署。
第 5 步——数据集 SHA 与生产一致吗?
症状。 失败发布在关卡评测中的得分,与同一个模型前一天的关卡评测得分不同。
诊断方法。 对比两次发布的 dataset_version 字段。评测套件名字没变,但数据集内容被更新并重新打标了。同名,不同 SHA,得分自然不同。
diff <(curl .../rel_a01c66 | jq '.dataset_version') \
<(curl .../rel_8f72b1 | jq '.dataset_version')
修复方法。 对数据集采用内容哈希。数据集名字不是版本;SHA 才是。
第 6 步——检索索引 SHA 与生产一致吗?
症状。 仅适用于 RAG 工作负载。依赖检索上下文的问题质量下降,直接作答的问题则不受影响。
诊断方法。 从清单中拉取 retrieval_index_ref SHA,与关卡评测当时使用的检索索引对比。RAG 索引在夜里重新抽取一次更新了;关卡评测缓存的是旧索引;生产灰度用的是新索引。
curl https://api.divinci.ai/v1/releases/rel_a01c66 | jq '.retrieval_index_ref'
修复方法。 将检索索引 SHA 像绑定预处理一样绑定到清单中。AutoRAG 自动化的索引轮换节奏让这一步尤其值得检查——如果你不固定索引,无论是否得到你的授权,它都会自行更新。
第 7 步——最后才是模型本身
六步走完。评测覆盖了切片。评审器经过校准。提示词 SHA 一致。预处理一致。数据集一致。检索索引一致。
到现在——且只到现在——你才获得了去看模型的资格。
这一步的诊断方法是:用清单固定的同一份数据集、同一套预处理、同一个检索索引,对失败发布和上一版发布分别评测,然后做按切片的 Spearman 比对。所得的数字是一个干净的信号:一次真正的按切片回归,没有上游干扰因素。
curl -X POST https://api.divinci.ai/v1/releases/<failing_id>/diff-eval \
-d '{ "baseline_release_id": "<prior_id>", "slices": ["legal-IP-licensing"] }'
# → { "spearman_rho_failing": 0.41, "spearman_rho_baseline": 0.68, "delta": -0.27 }
如果差值证实了真实回归:自动回滚就会触发(如果你还没有手动触发的话),失败模型将基于扩展过的切片覆盖语料重新训练。如果当初放行该发布的关卡一开始就漏掉了这个切片,那么关卡本身也是 bug——你的发布流水线缺失了能力 4。
真实分布是什么样的
前文那句“七分之一”不是修辞手法,而是大致刻画了我们在客户上线中观察到的分布。在每七次 QA 告警中: