影子流量、金丝雀与 LLM 渐进部署 本节摘要:LLM 上线集合了软件部署最难的部分:没有单元测试、失败模式弥散、信号延迟。顺序是(1)影子模式——把生产请求复制给候选模型,记录、对比,零用户影响;能抓明显的分布问题但非质量保证;(2)金丝雀——渐进流量切换 10% → 25% → 50% → 75% → 100%,每步设门;跟踪延迟百分位、成本/请求、错误/拒绝率、输出长度分布、用户反馈率;(3)A/B 测试,用于稳定性确认后的不同备选。非确定性不可消除——同输入跨运行最高 15% 准确率变化,源于 GPU 浮点非结合性加批次大小方差。成本是变量不是常量——一个 20% 更好的模型可能每调用贵 3 倍。回滚速度是决定性的:若回滚要重新部署,你太慢。
本节摘要:LLM 上线集合了软件部署最难的部分:没有单元测试、失败模式弥散、信号延迟。顺序是(1)影子模式——把生产请求复制给候选模型,记录、对比,零用户影响;能抓明显的分布问题但非质量保证;(2)金丝雀——渐进流量切换 10% → 25% → 50% → 75% → 100%,每步设门;跟踪延迟百分位、成本/请求、错误/拒绝率、输出长度分布、用户反馈率;(3)A/B 测试,用于稳定性确认后的不同备选。非确定性不可消除——同输入跨运行最高 15% 准确率变化,源于 GPU 浮点非结合性加批次大小方差。成本是变量不是常量——一个 20% 更好的模型可能每调用贵 3 倍。回滚速度是决定性的:若回滚要重新部署,你太慢。策略住在配置/flag;模型住在带固定摘要的注册表;回滚 = 翻策略 + 还阈值 + 秒级钉回旧模型。
对应原课程:Phase 17 · Lesson 20 ·
20-shadow-canary-progressive(原英文phases/17-infrastructure-and-production/20-shadow-canary-progressive/docs/en.md)。
阅读完本节,你应当能够:
你上线一个新模型。离线评测显示 3% 准确率提升。你生产里翻开关。24 小时内,成本涨 40%、用户踩涨 8%、三个客户工单报「奇怪答案」。你回滚。重新部署花 3 小时。你的周末毁了。
每一步都可避免。影子模式会在任何用户看到前抓到 40% 成本尖峰。金丝雀会在踩涨时停在 10%。策略 flag 回滚会花 30 秒。纪律,就是填补「离线评测看着好」与「真实用户满意」之间鸿沟的东西。
候选接收与生产相同的请求;输出被记录,不返回用户。零用户影响。记录:
能抓:成本爆涨、长度回归、明显拒绝变化、硬错误。不能抓:用户会感知的质量差。影子是冒烟测试,不是质量测试。
渐进流量切换带门。典型进度:1% → 10% → 25% → 50% → 75% → 100%。每步对 5 指标设门:
相同输入产生不同输出。原因:
实测:同评测集跨运行最高 15% 准确率变化。上线中「稳定」指指标在预期方差内,而非与基线相同。门设在噪声底之上。
一个 20% 更好的模型可能每调用贵 3 倍。成本/请求是五门之一。上线一个「更好」却破坏单位经济的模型,是回滚案例。
若你的栈要重新部署才能回滚,先修这个再上线。
Argo Rollouts / Flagger —— Kubernetes 渐进交付控制器,集成 Istio/Linkerd 加权路由。
Istio 加权路由 —— 服务网格级流量切分。
KServe / Seldon Core —— 带内建金丝雀的模型服务。
特性 flag —— LaunchDarkly、Flagsmith、Unleash。策略级翻转,无重新部署。
金丝雀门依流量每 515 分钟检查一次。1% 流量在 10 请求/分下,每窗口 50150 数据点——够看延迟,但用户反馈噪声大。10% 给约 10 倍样本。进度应在每步停够久以累积足够样本。
若新模型明显不同(行为不同、成本曲线不同、语气不同),金丝雀通过后在 50% 做 A/B。若只是改进版,金丝雀门通过就直接到 100%。
原课程 code/main.py 模拟带注入回归的金丝雀上线,报告上线停在哪个阶段、哪个门触发。下面给最小可读的门判定骨架。
def canary_gate(metrics, baseline, multipliers): """判定金丝雀在某阶段是否过关。 metrics: {'p99_latency_ms':, 'cost_per_req':, 'error_rate':, 'output_len_mean':, 'thumbs_down_rate':} baseline: 同结构的基线值 multipliers: 各指标的违规乘数,如 {'p99_latency_ms':1.5, 'cost_per_req':1.2, ...} 返回: (是否过关, 触发的违规指标列表) """ breaches = [] checks = [ ("p99_latency_ms", "P99 延迟", multipliers.get("p99_latency_ms", 1.5)), ("cost_per_req", "每请求成本", multipliers.get("cost_per_req", 1.2)), ("error_rate", "错误/拒绝率", multipliers.get("error_rate", 2.0)), ("thumbs_down_rate", "踩率", multipliers.get("thumbs_down_rate", 1.5)), ] for key, label, mult in checks: if metrics[key] > baseline[key] * mult: breaches.append(f"{label}: {metrics[key]} > 基线×{mult}") return len(breaches) == 0, breaches # 案例:候选 P99 480ms(基线 300),成本 0.12(基线 0.10,×1.2 门=0.12 边界) m = {"p99_latency_ms": 480, "cost_per_req": 0.125, "error_rate": 0.01, "thumbs_down_rate": 0.05} b = {"p99_latency_ms": 300, "cost_per_req": 0.10, "error_rate": 0.008, "thumbs_down_rate": 0.04} ok, breaches = canary_gate(m, b, {"p99_latency_ms": 1.5, "cost_per_req": 1.2}) print(f"过关={ok}, 违规={breaches}") # 延迟门(300×1.5=450)被 480 触发 → 停在此阶段
💡 金丝雀的门必须设在非确定性噪声底之上。若你的评测本就有 ±7% 方差,「成本 +5%」不是违规,「成本 +20%」才是——否则你天天误报。
| 阶段 | 流量 | 用户影响 | 抓什么 | 工具 |
|---|---|---|---|---|
| 影子模式 | 100% 复制 | 零 | 成本爆、长度回归、硬错误 | 自建日志 + diff |
| 金丝雀 | 1→100% 渐进 | 部分 | 五指标门 | Argo Rollouts / Flagger / Istio |
| A/B 测试 | 50/50 | 部分 | 质量差异(稳定性后) | GrowthBook / Statsig(第 21 节) |
| 回滚 | 翻 flag | 即时 | — | LaunchDarkly / Flagsmith / 特性 flag |
心法:影子抓成本/长度,金丝雀抓延迟/错误/反馈,A/B 抓质量差异。回滚靠策略 flag,不靠重新部署。
本节产出 outputs/skill-rollout-runbook.md(原课程目录)。给定候选模型、基线、风险容忍,它设计影子→金丝雀→100% 计划:
跑通模拟器:运行 code/main.py。注入 25% 成本回归。金丝雀停在哪个阶段?
成本 vs 准确率:你的新模型离线 +3% 准确率,但成本/请求 +18%。该不该上?依政策——写两条路径。
60 秒回滚:设计一条端到端 60 秒内的回滚。列出所需基础设施。
非确定性门:你的评测显示 ±7% 方差。设金丝雀门让你不误报。各乘数用多少?
影子告警:影子模式在金丝雀前抓到 40% 成本尖峰。写出在影子中触发的告警规则。
下一节,我们把 A/B 测试单独拆开——看 LLM 特性的 A/B 测试为何有「感觉问题(vibes problem)」,以及 GrowthBook、Statsig 如何用统计严谨对抗它。