跳到主要内容

LLM 升级的金丝雀发布:为什么模型上线与代码部署的失效方式完全不同

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的 CI 通过了。你的评估(evals)看起来没问题。你切换了流量开关,然后继续工作。三天后,一位客户提交了一个工单,称生成的每一份报告都不再包含 summary 字段。你翻阅日志发现,新模型开始稳定地生成 exec_summary —— 这是一个隐蔽的键名重命名,由于你忘记将其添加到发布门禁(rollout gates)中,你的 JSON schema 验证从未捕获到这一点。根本原因是模型升级。检测滞后时间为 72 小时。

这并非假设。在那些拥有复杂应用代码部署流水线,却将 LLM 版本升级视为基本“免费” —— 仅是配置更换而非部署 —— 的公司里,这种情况屡见不鲜。这种思维模型是错误的,由此导致的失败模式极其难以捕捉。

为什么 LLM 升级不只是配置更换

当你部署新版本的服务代码时,新旧版本之间的差异是完全可枚举的:你可以进行 diff 对比,针对已知输入进行测试,并对准确输出进行断言。但模型升级不会给你这些。新模型是一个拥有数十亿参数且无法检查的函数,在运行生产环境输入之前,它在所有生产输入分布下的表现都是不可知的。

有三种失败模式专门出现在模型升级过程中,而在代码部署中并不存在:

输出 Schema 偏移。 模型提供商在改进模型时,并不将输出格式视为一种具有破坏性变更(breaking-change)的契约。在前一个版本中被稳定命名为 category 的字段,现在可能显示为 ticket_category。受限解码(Constrained decoding)可以保证语法上正确的 JSON,但不能保证你特定的字段名称和结构得以保留。下游解析器如果使用 dict["category"],会在每个请求上静默抛出 KeyError

无表面回退的语义偏移。 新模型产生的输出可能在自动化评估中得分很高,但在你的评估套件未衡量的地方表现不同。模型可能会在支持工单分类器中开始将错误归因于错误的组件,或者开始在不同的位置生成法律免责声明,从而破坏下游的文本分割器(text splitter)。输出看起来是连贯的;评估分数保持稳定;但产品行为崩溃了。

延迟和成本特征的变化。 产生更高质量输出的模型通常伴随着更多的 token、更长的生成时间或不同的吞吐量特征。一次提升了质量得分的发布,可能会同时触发 SLA 违规或使你的推理账单翻 3 倍。这两点都不会在质量评估中体现出来。

影子测试:基于真实流量的零风险验证

任何模型升级最稳妥的第一步都是影子测试(shadow testing):将生产流量同时路由到现有模型和候选模型,仅向用户提供现有模型的响应,并收集候选模型的输出进行比较。用户感知不到任何变化,而你可以积累真实的生产查询和真实的模型行为。

影子测试优于基准评估(benchmark evaluation)的价值正是在于:生产流量并非你的基准分布。你的基准是你认为应该编写的查询。而生产流量是你的真实用户发送的查询 —— 这包括边缘案例、格式错误的输入、意外语言的查询,以及你的评估集永远无法完全代表的新型实体。

影子测试能发现一类只能在真实流量中找到的问题:候选模型针对最长输入生成的特定 JSON schema、并发负载下的延迟特征,以及用户实际使用的特定领域术语的幻觉率。在进行任何流量切分之前进行 24–72 小时的影子测试,能为你提供可操作的依据。

在基础设施层实现起来非常直接。在 API 网关或负载均衡器处,将每个请求复制到两个模型。现有模型的响应同步返回给用户。候选模型的响应则异步捕获,并记录相同的元数据:输入 token、输出 token、延迟、时间戳、用户细分。然后,你对这两组响应运行评分流水线并进行比较。

流量切分:确定性路由与渐进式分配

一旦影子测试通过了你的质量门禁,就可以转入 live 流量切分。这里的常见错误是使用随机流量分配。随机分配意味着同一个用户在一次请求中看到模型 A,而在下一次请求中看到模型 B,这使得你无法判断质量差异是由模型引起的,还是由会话级上下文变化引起的。

请改用基于哈希(hash)的路由。对用户 ID(或会话 ID、客户 ID —— 任何稳定的标识符)进行哈希处理,并确定性地分配桶(bucket)。0–9 号桶中的用户始终看到候选模型;其他所有人看到现有模型。这能保持个人用户体验的一致性,并使你的 A/B 比较在统计上是严谨的。

推进计划与路由逻辑同样重要。从 5–10% 的流量开始。在转向 25% 之前监测 24 小时。只有在确认候选模型在 25% 比例下表现稳定后,再转向 50%。这并非是对百分比的迷信 —— 而是为了确保有足够的时间观察尾部故障(tail failures)。Schema 违规和语义回退通常出现在特定的输入类别中,而这些类别可能仅占你流量的 2–3%。一个持续 24 小时的 10% 灰度测试能让你接触到这些长尾情况的代表性样本。

在每个阶段,都要跟踪群组(cohort)级别的指标,而不仅仅是汇总指标。汇总指标会掩盖特定细分领域的失败。候选模型可能在英语查询上表现相同,但在法语查询上却悄悄退化。在一个代表性不足的细分领域中,5% 的质量下降在汇总数据中是看不出来的,但却会影响到真实用户。

真正能捕获模型回归的指标

通用的“质量评分”监控无法捕获上述失败。你需要部署的指标是专门针对 LLM 实际容易失败的地方:

输出 Schema 符合率。 跟踪响应中所有必填字段是否存在、命名是否正确以及类型是否符合预期的百分比。在解析层而非模型层进行埋点——你需要了解你的实际下游消费者是否成功。如果你的解析器抛出了 KeyError 异常,统计并针对它们发出告警。Schema 符合率的下降是你对字段名偏移(field-name drift)问题的最早预警。

下游解析器成功率。 与 Schema 符合率不同:这是指完整的解析流水线是否端到端成功。一个响应可能通过了 Schema 验证,但由于解析器对值格式或嵌套结构深度的假设,仍然会导致解析器崩溃。将 parse_success 作为每个请求的二元指标进行跟踪,如果它低于你的基准线,则发出告警。

与原模型的语义相似度。 使用句子嵌入(sentence embeddings)来计算候选模型与原模型在匹配输入下的余弦相似度。在滑动窗口内汇总这些分数。在灰度发布期间,平均相似度的突然下降表明候选模型的行为发生了显著变化——即使单个分数看起来尚可。这不是一个质量信号;而是一个变更检测信号。

分段延迟和成本。 跟踪每个用户段和每个输入长度桶(bucket)的 p50、p95 和 p99 延迟。新模型在处理长输入时往往具有不同的生成特性。在输入超过 2,000 个 token 时,p99 延迟的激增在总体的 p95 中可能是不可见的,但它会导致真实的一小部分用户出现超时失败。

拒绝率和格式错误率。 统计模型拒绝回答、格式失败(模型忽略了输出 Schema 指令)或产生退化生成结果的响应比例。模型在拒绝模糊输入的主动程度以及遵循结构化输出指令的可靠性方面存在差异。一个比原模型拒绝率高 0.5% 的候选模型在整体上可能看起来相似,但在大规模生产环境下,0.5% 意味着每天有数万个失败的请求。

自动回滚:让回滚成本低到足以被真正使用

回滚只有在触发门槛足够低时才有效。大多数 LLM 事故中的失败模式是,团队设置回滚阈值过于保守——他们不想因为噪声而回滚一个好的升级——而等到指标超过阈值时,低影响回滚的机会窗口已经关闭了。

解决方案是将回滚与版本撤销(reverting)分离:设计你的流水线,使回滚到上一个模型版本的时间控制在 5 分钟以内,且无需代码更改。模型版本应该是一个运行时参数,而不是一个部署产物。如果切回版本只是写入一次配置,那么你的阈值就可以设置得很激进,因为误报的代价很低。

显式地为你的灰度进展设置门禁(gate)。在从 10% 推进到 25% 之前,流水线应检查:Schema 符合率 ≥ 基准线,下游解析成功率 ≥ 基准线,p95 延迟在基准线的 10% 以内,分段质量分数在可接受范围内。这些门禁自动运行。如果任何门禁失败,进展将停止并触发告警。如果多个门禁同时失败,将自动触发回滚到 0%,无需等待人工审核。

在每个日志事件中记录 model_versionschema_versionprompt_version。当事故在发布几天后浮出水面时,你需要能够将失败的时间戳与产生它的精确模型和 prompt 组合关联起来。如果没有这些,事故后的分析无异于考古。

Prompt 耦合问题

大多数渐进式交付文章都忽略了一个细节:模型版本和 prompt 版本是耦合的,独立部署它们会破坏系统。

Prompt 是针对特定模型进行调优的。当你升级模型时,在旧模型上运行良好的 prompt 在新模型上的表现可能很差——不同的指令遵循行为、不同的默认详细程度、不同的遵循结构约束的倾向。反之,针对新模型测试的 prompt 改进可能会在原模型上发生回归。

实际的影响是,你的灰度流水线应该将 (model_version, prompt_version) 作为一个联合部署单元,而不是两个独立的维度。对这个组合进行影子测试和灰度发布。如果你需要回滚模型,也请同时回滚到匹配的 prompt 版本。将它们作为联合产物存储在你的模型注册表中,而不是分开跟踪,可以防止由于将它们独立对待而导致的版本偏移(version-skew)失败。

生产级发布流程是什么样的

在汲取了这些教训的生产系统中,模型升级的完整流水线如下:

  1. 影子测试 48 小时。 在影子模式下将候选模型运行在 100% 的生产流量上。根据 Schema 符合率、语义相似度和质量指标对输出进行评分。在继续之前通过所有门禁。

  2. 5% 流量灰度并持续 24 小时。 将 5% 的流量(通过用户哈希确定性路由)引导至候选模型。根据门禁监控分段指标。在门禁通过前不推进。

  3. 分阶段推进:10% → 25% → 50% → 100%。 每个阶段都需要手动或自动的门禁检查。每个阶段至少保持 12–24 小时。

  4. 如果任何阶段门禁失败,则自动回滚。 回滚是参数写入,而不是部署。只需几分钟。

  5. 完整的审计轨迹。 每个事件都携带 model_version 和 prompt_version。事故后分析可以准确重建哪些请求是由候选模型在何时处理的。

这个流水线比传统的代码部署需要更长的时间。从影子测试到 100% 流量的完整发布可能需要一周时间。这是正确的权衡。一周谨慎发布的成本远低于发生 72 小时事故的成本,在事故中,当你的监控仍在寻找信号时,数千名用户已经默默承受了质量下降。

更广泛的工程规范

适用于模型版本升级的思路同样适用于 Prompt 变更。Prompt 修改是对非确定性函数的行为变更。它值得拥有同样的影子测试、金丝雀发布流程以及自动回滚基础设施。那些为模型升级投入这些流水线的团队通常会发现,生产事故更大的源头是 Prompt 漂移 —— 即那些看似无害但最终累积成回归问题的微小调整。

这里需要的心智模型转变是从“将 LLM 视为无状态 API 调用”转向“将 LLM 视为具有行为契约的已部署服务”。代码服务有部署流水线,LLM 服务也同样需要,而且是专为它们特有的失败方式构建的:静默地、逐渐地,且以聚合指标通常会忽略的方式发生。

References:Let's stay in touch and Follow me for more thoughts and updates