换个模型上线,离线回归和线上 A/B 各管什么?

先离线、后线上。离线在冻结用例上配对比较,回答"会不会变差、哪几条变差";线上把真实流量随机分组,不配对,回答"真实用户那边怎么样"。

两道关的统计结构不同:线上要的样本量大一个数量级,还多了一个离线没有的坑:边跑边看结果。本页数据都是合成数据,用固定种子在本地生成,不调用任何模型。

1两道关,各管一件事

离线回归线上 A/B
数据冻结的用例集,工具响应可回放真实用户的会话
设计配对:同一条用例两个版本都跑不配对:每个会话只进一个组
检验McNemar、配对区间(第 1 周已讲)两比例 z 检验,或分组序贯检验
需要的量几百到几千条用例每组几千到几万个会话
能发现具体哪几条用例回归了用例集外的分布、真实接受率、时延和成本
不能发现用例集外的问题;用例集过期、泄漏小类别上的回归(被平均掉)

离线便宜、能定位到具体用例,先把明显的回归拦住;线上贵,只给离线已经过关的版本。两者不能互相替代。回放方式见本周「把工具响应录下来回放,评测就公平了吗?」一章。

2第一关:离线配对回归

400 条冻结的 BugHunt 用例,5 类各 80 条,模型 A、B 各跑一次(合成设定:真实通过率 A 73.6%、B 73.9%;B 在并发竞态类约 30% 的用例上明显更差)。

B 过B 挂合计
A 过a = 271b = 25(回归)296
A 挂c = 30(修好)d = 74104
合计30199400

A 296/400 = 74.00% → B 301/400 = 75.25%,差 \((c-b)/N = +1.25\) 个点,分歧率 13.8%,McNemar 精确 p = 0.590(推导见第 1 周「新 prompt 从 82% 涨到 86%,是真的变好了吗?」)。换模型要证明"没有明显变差",所以看差值区间的下界:事先定 margin = 3 个点,要求 95% 区间下界 > −3。

Newcombe(1998)方法 10:配对比例差的区间

出处:Newcombe RG, Improved confidence intervals for the difference between binomial proportions based on paired data, Statistics in Medicine 17:2635–2650。两个通过率 \(p_B=(a+c)/N\)、\(p_A=(a+b)/N\) 各取 Wilson 区间 \((l_B,u_B)\)、\((l_A,u_A)\),\(\hat\theta=(c-b)/N\):

$$L = \hat\theta - \sqrt{(p_B-l_B)^2 - 2\hat\phi(p_B-l_B)(u_A-p_A) + (u_A-p_A)^2}$$ $$U = \hat\theta + \sqrt{(u_B-p_B)^2 - 2\hat\phi(u_B-p_B)(p_A-l_A) + (p_A-l_A)^2}$$ $$\hat\phi = \frac{\max(ad-bc-N/2,\ 0)}{\sqrt{(a+b)(c+d)(a+c)(b+d)}}\quad(ad>bc\ \text{时})$$

这份数据:Newcombe [−2.43, +4.93] 个点,配对 bootstrap [−2.50, +4.75],下界都高于 −3,整体非劣效通过。实现和论文 Table III 的四组例子对到小数点后 4 位。

3门禁过了,不等于可以切

类别通过条数(每类 80)A → BbcMcNemar p
表单校验61 → 66(+6.25)270.180
权限62 → 65(+3.75)250.453
状态同步58 → 63(+6.25)380.227
并发竞态57 → 50(−8.75)1360.167
页面渲染58 → 57(−1.25)541.000

单看并发竞态也不显著(p = 0.167)。把 b 里的 25 条用例两个模型各重跑 5 次(阈值是笔者的经验值:A 至少 4/5 过、B 至多 1/5 过算真回归):真回归 8 条,全部在并发竞态;flaky / 待查 15 条;偶发(重跑后 A、B 都稳定通过)2 条。合成数据里没有第 1 周的第三类"用例 / 判定有问题",这里多出的"偶发"是 A、B 重跑都至少 4/5 通过的用例。

整体非劣效通过、单类检验不显著,只有逐条重跑把问题暴露出来。还要注意 400 条够不够:本文模拟(真实差 0、分歧率 14%、margin 3 个点,每个 N 模拟 100000 次),N = 200 / 400 / 800 / 1600 时门禁放行的概率是 20.3% / 35.6% / 61.8% / 89.2%。本章的 B 能过有运气成分:真实差只有 +0.34 个点,按合成设定重抽 20000 次,放行概率约 40%。

4切换开关:两组只差一个配置键(Codex)

源码基于 commit 7993248,CLI 在本机 codex-cli 0.159.2 上用临时 CODEX_HOME 实测,只跑不调模型的 codex doctor 和 codex debug prompt-input。

开关落到哪里优先级
-m model-bConfigOverrides.model,let model = model.or(cfg.model);(core/src/config/mod.rs:3970)高于所有配置层
-c key=valueSessionFlags 层;值先按 TOML 解析,失败就当字符串(utils/cli/src/config_override.rs:49-84)30
项目 .codex/config.toml目录不受信任时加载但禁用25
-p cand$CODEX_HOME/cand.config.toml 叠在用户层上21
config.toml用户基础配置20

优先级数值见 config/src/config_layer_source.rs:33-51;还有两种旧式托管配置是 40、50。下面是实测结果的摘要(model 由 codex doctor 读出,提示由 codex debug prompt-input 读出;完整输出见博客正文):

[基础配置]                           model-a      PROMPT_V1
[-c model=model-c]                   model-c
[-m model-d -c model=model-c]        model-d
[-p cand]                                         PROMPT_V2
[-p cand -c developer_instructions=PROMPT_CLI]    PROMPT_CLI
[-p cnad(拼错)]                                 PROMPT_V1   ← 静默跑了基线
profile 文件不存在时被当成空表(config/src/loader/mod.rs:547-551),拼错名字不报错,A/B 静默变成 A/A。config.toml 里的旧写法 [profiles.cand] 配合 -p cand 会报错(loader/mod.rs:302-322)。

5第二关:线上 A/B 的样本量与偷看

线上每个会话只进一个组,回到两个独立比例的比较。Kohavi 等人(2009,Controlled experiments on the web: survey and practical guide,Data Mining and Knowledge Discovery 18:140–181)公式 (2),置信度 95%、功效 80%,每组:

$$n = \frac{16\,\sigma^2}{\Delta^2},\qquad \sigma^2 = p(1-p)\ \text{(二值指标)}$$

原文注明出自 van Belle(2002)第 31 页,90% 功效时 16 换成 21。基线 40%、检测 2 个点:每组 9600 个会话。离线配对(Connor 公式)在分歧率 10% 时只要 1960 条用例。

偷看:每次查看都按 \(|Z_k|\ge 1.96\) 判,总的一类错误率会上升。Armitage、McPherson、Rowe(1969)最早算出这个膨胀,等间隔 5 次约 0.142。两种事先定好的边界能把它压回 5%:Pocock(1977)每次用同一个临界值 c;O'Brien & Fleming(1979)第 k 次用 \(c\sqrt{K/k}\)。c 没有闭式解,本页的 c 是本文数值积分求出的。

▶演示 A:离线配对门禁

改 2×2 表的四个格子和 margin,实时算 McNemar 精确 p(双侧)和 Newcombe 方法 10 的 95% 区间。门禁规则:区间下界 > −margin 才放行。

预设
B 过
B 挂
A 过
A 挂
A → B 通过率
差值 (c − b)/N
分歧率 ψ
McNemar 精确 p
Newcombe 95% 区间
门禁

▶演示 B:线上 A/B 要多少会话

每组样本量:Kohavi 等(2009)的 16σ²/Δ²(90% 功效用 21),旁边是系数不取整的 \(2\sigma^2(z_{0.975}+z_{1-\beta})^2/\Delta^2\)。下面一行是同样差距下离线配对设计(Connor 公式,第 1 周「要跑多少次才够?」)要的用例数。

功效
Kohavi:每组会话
系数不取整:每组会话
需要跑几天(按 Kohavi)
离线配对:用例数
线上 / 离线
σ² = p(1 − p)

▶演示 C:偷看模拟

把最大样本量分成 K 次等间隔查看,每次看累计数据的 z 统计量。用正态增量模拟(和本章 Python 的数值积分是同一个模型),最后一次查看时 \(Z\) 的期望由基线、真实差和总会话数算出。理论值来自 Python 的数值积分(只列真实差为 0 的一类错误率)。

停止规则

设定:基线 40%,每组共 9600 个会话(960 × 10 天,K 次查看平分)。

宣布显著的比例
理论一类错误率(真实差 0)
平均停在第几次(共 K 次)
第 1 次查看的临界值
最后一次的临界值
最后一次 Z 的期望

✎练习

题 1(算):离线 2×2 表 a = 271、b = 25、c = 30、d = 74。候选 B 减基线 A 的通过率差是多少个百分点?
个百分点
(c − b)/N = (30 − 25)/400 = 1.25%。a 和 d 在相减时抵消,只有 55 条分歧用例带信息。
题 2(判断):整体非劣效通过(Newcombe 区间下界 −2.43 > −3),但回归清单重跑后 8 条真回归全在"并发竞态"一类。最合适的做法是?
每类只有 80 条,单类检验功效很低,"不显著"不等于没问题;重跑 5 次 A 稳定过、B 稳定挂,是真回归。整体的提升会把小类别的回归平均掉,线上 A/B 也很难看出来,要在离线解决。
题 3(算):线上基线 30%,想检测 3 个百分点的变化,按 Kohavi 的 16σ²/Δ²(95% 置信、80% 功效),每组至少多少个会话?(向上取整)
个会话
σ² = 0.3 × 0.7 = 0.21;16 × 0.21 / 0.03² = 3.36 / 0.0009 ≈ 3733.3,向上取整 3734。
题 4(偷看):A/A 实验(两组完全一样)跑 10 天,每天看一次累计数据,哪天双侧 p < 0.05 就宣布"显著"并停下。被宣布显著的概率大约是?
等间隔看 10 次,数值积分得 0.193(本章模拟 19.5%)。不是 5%,也不是 1 − 0.95¹⁰ ≈ 40%:各次检验用的是累计数据,彼此高度相关,膨胀没有独立检验那么快。
题 5(Codex):$CODEX_HOME 里有 config.toml 和 cand.config.toml。实验组的命令把 -p cand 误写成了 -p cnad。在 commit 7993248 / 本机 0.159.2 上会发生什么?
加载函数把不存在的配置文件当成空表(config/src/loader/mod.rs:547-551),本机实测 -p cnad 生效的仍是基础配置里的 PROMPT_V1。A/B 静默变成 A/A,所以每次运行都要把生效配置打进 trace 并断言。

6面试要点

一句话讲清楚

换模型、改 prompt 分两道关。离线:冻结用例、工具响应回放,两个版本跑同一批用例做配对比较,事先定非劣效 margin,看配对差值区间下界,旧过新挂的用例逐条重跑归类、按类别看集中回归。线上:按会话随机分流,事先按最小可检测差算样本量(基线 40%、检测 2 个点每组约 9600),中途要看就用 Pocock 或 O'Brien-Fleming 边界。切换只改配置,每次运行把生效配置打进 trace。

追问准备

  1. 离线过了为什么还要 A/B?用例集只覆盖你想到的情况;真实分布、开发者是否接受报告、真实时延和成本只有线上能看到。反过来,线上平均值会把小类别的回归平均掉,离线回归清单不能省。
  2. 线上为什么要的样本量大这么多?不配对。离线每条用例和自己比,难度在相减时抵消;线上每个会话只有一个版本的结果,难度差异全算进了噪声。
  3. 每天看结果有什么问题?每看一次就是一次检验。等间隔看 5 次总误报约 14%,10 次约 19%。用事先定好的分组序贯边界,或 always-valid 的方法(Johari 等,KDD 2017)。
  4. Pocock 和 O'Brien-Fleming 怎么选?Pocock 每次一样严,容易提前停,但最大样本量不变时功效损失大(本章 80.7% → 68.9%);O'Brien-Fleming 前期极严、最后接近 1.96,功效几乎不损失(79.2%),适合"主要担心变差、出大问题才提前停"的场景。

常见错误说法

❌ "离线 p = 0.59,说明两个模型一样好":不显著不等于一样,看差值区间下界。
❌ "整体非劣效过了就能切":小类别的真回归会被平均掉,要逐条重跑回归清单。
❌ "线上 A/B 也用 McNemar":线上没有配对,用两比例检验。
❌ "每天看一眼、p < 0.05 就停,还是 5% 显著性":10 次查看约 19%。
❌ "profile 名字写错会报错":不存在的 profile 文件被当成空表,静默跑基线。

公式显示依赖 KaTeX(CDN),断网时公式会显示成原始 LaTeX 源码,交互部分不受影响。