# LLM 的影子流量、Canary 上线与渐进式部署

> LLM 上线把软件部署里最难的几块凑到了一起：没有单元测试、失败模式弥散、信号延迟。顺序是 (1) 影子模式 —— 把生产请求复制给候选模型，记录、对比，对用户零影响；能抓明显的分布问题，但不是质量保证；(2) canary 上线 —— 渐进式流量切换 10% → 25% → 50% → 75% → 100%，每一步设闸门；跟踪延迟分位数、单请求成本、错误/拒答率、输出长度分布、用户反馈率；(3) 稳定性确认后对截然不同的备选做 A/B 测试。非确定性不可消除 —— 因 GPU 浮点非结合性加批大小方差，相同输入跨次跑会有最高 15% 的准确率波动。成本是个变量，不是常量 —— 一个好 20% 的模型每次调用可能贵 3 倍。回滚速度是决定性的：如果回滚需要重新部署，你就太慢了。策略放在 config/flag 里；模型放在带固定 digest 的 registry 里；回滚 = 翻策略 + 还原阈值 + 几秒内钉回旧模型。

**类型：** Learn
**语言：** Python（标准库，一个玩具级 canary 推进模拟器）
**前置要求：** 阶段 17 · 13（可观测性）、阶段 17 · 21（A/B 测试）
**预计时间：** ~60 分钟

## 学习目标

- 区分影子模式（零影响对比）、canary（实时流量渐进）和 A/B（稳定性确认后的对比）。
- 列举五个 LLM 专属的 canary 指标（延迟、单请求成本、错误/拒答、输出长度分布、用户反馈）。
- 解释为什么 LLM 非确定性（最高 15%）改变了上线中"稳定"的含义。
- 设计一条几秒（翻策略）而不是几小时（重新部署）的回滚路径。

## 问题背景

你上了一个新模型。离线 eval 显示 3% 准确率增益。你在生产里把它打开。24 小时内，成本涨了 40%，用户点踩涨了 8%，三张客户工单报告"答案怪怪的"。你回滚。重新部署花 3 小时。你的周末毁了。

这里每一环都本可避免。影子模式本能在任何用户看到之前抓住那 40% 的成本尖峰。canary 本能在点踩动起来时停在 10%。策略 flag 回滚本只需 30 秒。这套纪律就是填补"离线 eval 看起来不错"和"真实用户满意"之间那道缝的东西。

## 核心概念

### 影子模式

候选模型收到和生产一样的请求；输出被记录，不返回给用户。对用户零影响。记录：

- 输出内容（与生产做 diff）。
- token 数（成本差值）。
- 延迟。
- 拒答和错误。

能抓：成本爆炸、长度回退、明显的拒答变化、硬错误。抓不到：用户能感知到的质量差值。影子是个冒烟测试，不是质量测试。

### Canary 上线

带闸门的渐进式流量切换。典型推进：1% → 10% → 25% → 50% → 75% → 100%。每一步在 5 个指标上设闸门：

1. **延迟分位数** —— P50、P95、P99。越界：canary 的 P99 > 1.5x 基线。
2. **单请求成本** —— 混合 $。越界：高出基线 >20%。
3. **错误 / 拒答率** —— 5xx 加上显式拒答。越界：基线的 2 倍。
4. **输出长度分布** —— 均值 + P99。越界：分布偏移。
5. **用户反馈率** —— 点踩 / 工单提交。越界：基线的 1.5 倍。

### 非确定性是新的方差

相同输入产生不相同的输出。原因：

- GPU 浮点非结合性（浮点归约顺序随批变化）。
- 批大小方差（同一 prompt 在 128 的批里 vs 16 的批里）。
- 采样（temperature > 0）。

实测：相同 eval 集上跨次跑最高 15% 的准确率波动。上线中"稳定"意味着指标在预期方差内，不是和基线完全相同。把闸门设在噪声底之上。

### 成本是个变量

一个好 20% 的模型每次调用可能贵 3 倍。单请求成本是五个闸门之一。上一个打破单位经济性的"更好"模型，是个回滚理由。

### 回滚是武器

- 策略 flag（feature flag 系统）：在 config 里翻百分比；几秒。
- 模型钉死（registry digest）：钉死的模型不自动升级。
- 回滚 = 还原 flag + 把钉死的 digest 设回上一个。是几秒，不是几小时。

如果你的栈需要重新部署才能回滚，在上线之前先修这点。

### 工具

**Argo Rollouts** / **Flagger** —— Kubernetes 渐进式交付控制器。与 Istio/Linkerd 加权路由集成。

**Istio 加权路由** —— 服务网格层的流量切分。

**KServe / Seldon Core** —— 内置 canary 的模型服务。

**Feature flag** —— LaunchDarkly、Flagsmith、Unleash。策略级翻转，无需重新部署。

### 指标节奏

canary 闸门视流量大小每 5-15 分钟检查一次。1% 流量、每分钟 10 个请求，每个窗口给 50-150 个数据点 —— 对延迟够了，但对用户反馈很噪。10% 给约 10 倍多。推进应该在每一步暂停得足够久，以积累足够样本。

### A/B 这一步是可选的

如果新模型截然不同（不同行为、不同成本曲线、不同语气），在 canary 通过后于 50% 处对它做 A/B 测试。如果它只是个改进版，canary 闸门通过后直接到 100%。

### 你该记住的数字

- Canary 推进：1% → 10% → 25% → 50% → 75% → 100%。
- 非确定性上限：相同输入跨次跑最高 15% 方差。
- 五个 canary 指标：延迟、成本、错误/拒答、输出长度、用户反馈。
- 成本闸门：高出基线 >20% 是越界。
- 回滚：几秒，不是几小时。

## 实际使用

`code/main.py` 模拟一个注入了回退的 canary 上线。报告上线在哪个阶段停下，以及哪个闸门触发了。

## 拿去用

这一课产出 `outputs/skill-rollout-runbook.md`。给定候选模型、基线和风险容忍度，设计 影子→canary→100% 的方案。

## 练习

1. 跑 `code/main.py`。注入一个 25% 的成本回退。canary 在哪个阶段停下？
2. 你的新模型离线有 3% 准确率增益，但单请求成本 +18%。该上吗？取决于策略 —— 把两条路径都写出来。
3. 设计一个端到端 60 秒以内的回滚。列出所需的基础设施。
4. 非确定性在你的 eval 上显示 ±7%。把 canary 闸门设得不会误报。你用什么乘数？
5. 影子模式在 canary 之前抓住一个 40% 的成本尖峰。写出在影子里触发的告警规则。

## 关键术语

| 术语 | 大家嘴上怎么说 | 它实际是什么 |
|------|----------------|------------------------|
| 影子模式 | "复制给新模型" | 零影响地发给候选用于记录 |
| Canary | "渐进流量" | 带闸门的逐步对用户暴露的上线 |
| 闸门 | "上线检查" | 阻止推进的指标阈值 |
| 非确定性 | "LLM 方差" | 不可消除的跨次跑差异 |
| 策略 flag | "翻 flag 回滚" | config 级回滚，几秒不是几小时 |
| 模型钉死 | "registry digest" | 对某个模型版本的不可变引用 |
| Argo Rollouts | "K8s 渐进" | Kubernetes 原生的 canary/回滚控制器 |
| KServe | "推理 K8s" | 带 canary 原语的模型服务 |
| Istio 加权 | "网格切分" | 服务网格流量切分器 |

## 延伸阅读

- [TianPan — Releasing AI Features Without Breaking Production](https://tianpan.co/blog/2026-04-09-llm-gradual-rollout-shadow-canary-ab-testing)
- [MarkTechPost — Safely Deploying ML Models](https://www.marktechpost.com/2026/03/21/safely-deploying-ml-models-to-production-four-controlled-strategies-a-b-canary-interleaved-shadow-testing/)
- [APXML — Advanced LLM Deployment Patterns](https://apxml.com/courses/mlops-for-large-models-llmops/chapter-4-llm-deployment-serving-optimization/advanced-llm-deployment-patterns)
- [Argo Rollouts docs](https://argo-rollouts.readthedocs.io/)
- [Flagger docs](https://docs.flagger.app/)
