在线评测 vs 离线评测:场景差异与指标选择
离线评测依赖固定测试集(如 MMLU、HumanEval)计算 BLEU、ROUGE、GPT-4 评分等指标,优点是成本低、可重复,但存在两大天然鸿沟:静态数据集 永远无法覆盖线上长尾分布(用户真实提问的风格、上下文长度、领域混合度),指标本身与用户体验弱相关(高分未必意味着用户喜欢)。例如一个回答 BLEU 得分 0.85 但带有事实性错误,用户可能直接点踩;而另一个回答虽然转述略有不同但正确且友好,用户会点赞。
在线评测则直接使用真实用户流量,通过 A/B 实验比较两个模型版本在以下维度的差异:
| 维度 | 离线指标 | 在线指标 | 典型采集方式 |
|---|---|---|---|
| 回答质量 | BLEU / ROUGE / GPT-4 Score | 用户显式反馈(赞/踩)、隐式信号(重问率、会话终止率) | 客户端埋点 |
| 延迟 | 忽略(测评通常不计时) | P50 / P95 / P99 响应时间 | 服务端 Prometheus histogram |
| 对话连贯性 | 无标准离线指标 | 对话轮次、上下文召回率 | 会话日志分析 |
| 安全性 | 注入测试集 | 真实用户越狱尝试检出率 | 实时内容审核日志 |
实践中,我们通常先跑离线评测作为 Gate(通过率低于 90% 则不上线),再通过在线 A/B 做最终验证。
A/B测试实验设计:流量分层、样本量与MDE
流量分层架构
线上流量按 user_id 或 session_id 哈希分桶,确保同一个用户的多次请求始终落在同一实验组(保持一致性)。主流方案采用两层分桶 :第一层 Pre-split 将用户均匀切分为 100 个桶,第二层 Experiment Layer 从这些桶中随机选取一部分参与某个实验。不同实验之间可以共享桶 ID 但实验参数隔离(类似 Google Overlapping Experiment Infrastructure)。
样本量计算:平均偏好胜率
假设我们要检测模型 A 对模型 B 的胜率(Win Rate) 提升 2%(绝对值,例如从 50% 到 52%),显著性水平 α=0.05,统计功效 1-β=0.8,胜率近似正态分布,则单臂所需样本量为:
n = (Z_α/2 + Z_β)^2 * p * (1-p) / (MDE)^2
其中 p 为基线胜率(通常假设 0.5),MDE 为最小可检测效应。
python
import statsmodels.stats.proportion as smp
import numpy as np
def min_sample_size(mde=0.02, base_rate=0.5, alpha=0.05, power=0.8):
"""
计算每组所需用户数(A/B各一组)
"""
effect_size = mde / (base_rate * (1 - base_rate)) ** 0.5 # Cohen's h 近似
nobs = smp.samplesize_proportions_2indep_onetail(
diff=mde,
prop2=base_rate,
power=power,
alpha=alpha,
ratio=1.0
)
return int(np.ceil(nobs))
print(f"需要每组用户数: {min_sample_size(mde=0.02)}")
# 输出: 需要每组用户数: 9604
这意味着当 MDE=2% 时,每组需要约 9600 个样本,总共近 2 万条有效反馈才能得到可靠结论。如果 MDE 放大到 5%,样本量降至约 1500。
注意事项 :
-
用户行为是自相关的,严格来说需要用更保守的方差估计(聚类稳健标准误),但实践中直接用二项分布近似已足够。
-
避免过早停止 (反复检查 p 值达到 0.05 就下线实验),这会导致假阳性飙升。建议预设最小曝光天数(如 7 天)或采用序贯检验(Sequential Analysis)。
-
网络效应:如果模型 A 的回答会影响后续对话(例如上下文变化),则同用户的前后请求并非独立,实验组内控需通过 session-level 分层。
自动化回归流水线:每日模型迭代的评测触发、基线对比与告警
流水线结构
[模型注册中心MLflow] -> 新模型版本触发 -> [离线评测Gate] -> [灰度A/B实验创建]
|
[Prometheus告警]
触发条件 :当 MLflow 模型 Registry 中的模型版本从 Staging 升级到 Production 时,自动创建 A/B 实验(对照组为当前生产版本,实验组为新版本),流量比例通常在 95:5 开始(5% 够累积样本,不伤主要用户)。
基线对比与自动停止:每 15 分钟从 Prometheus 拉取指标,计算当前实验组与对照组的胜率差值及其 95% 置信区间。如果置信区间完全落在 -1%, +1% 之外(即显著差异),则触发告警;如果超过预定义阈值(例如胜率下降 3%),自动回滚并通知开发者。
python
# 伪代码:每日回归检查脚本
import requests, json
def check_ab_experiment(experiment_id):
# 从Prometheus获取两个组的胜率序列(最近24小时)
prom_ql = f'''
rate(ab_win_count{{exp="{experiment_id}", group="control"}}[1h])
/ rate(ab_total_count{{exp="{experiment_id}", group="control"}}[1h])
'''
control_rate = query_prometheus(prom_ql)
treatment_rate = query_prometheus(...)
# 计算Wilson置信区间
from statsmodels.stats.proportion import proportion_confint
import numpy as np
n_t = sum(treatment_rate) # 实际样本量
n_c = sum(control_rate)
p_t = np.mean(treatment_rate)
p_c = np.mean(control_rate)
lo, hi = proportion_confint(p_t * n_t, n_t, alpha=0.05, method='wilson')
diff_ci = (p_t - p_c - (hi - p_t) / 2, p_t - p_c + (hi - p_t) / 2) # 简化
if diff_ci[0] > 0.01: # 实验组显著优于对照组 1% 以上
auto_adopt(experiment_id) # 全量上线
elif diff_ci[1] < -0.03: # 实验组显著差 3% 以上
auto_rollback(experiment_id, reason="胜率下降超过3%")
alert_developer("模型迭代回归", "胜率显著下降")
check_ab_experiment("llm-v2.1.3-abtest-20250301")
核心告警规则 :
-
胜率下降 > 3%→ P0 告警(立即回滚) -
P95 延迟增加 > 20%→ P1 告警(需人工确认是否牺牲延迟换质量) -
样本量不足→ 提示实验继续运行
关键指标实现:胜率、偏好得分、上下文连贯性
胜率(Win Rate)与 Wilson 置信区间
用户反馈通常是非连续的:每轮对话后用户点击"赞/踩"。我们定义实验组胜率 = 实验组点赞数 / 实验组总反馈数,对照组同理。由于点赞率通常很低(< 5%),直接用正态近似会低估方差,推荐使用 Wilson Score 区间:
python
import numpy as np
from scipy.stats import norm
def wilson_score_interval(n_success, n_total, z=1.96):
p_hat = n_success / n_total if n_total > 0 else 0
denominator = 1 + z**2 / n_total
centre = (p_hat + z**2 / (2 * n_total)) / denominator
margin = z * np.sqrt((p_hat * (1 - p_hat) + z**2 / (4 * n_total)) / n_total) / denominator
return centre - margin, centre + margin
# 例:实验组 200 个点赞 out of 5000 次反馈,对照组 150 个 out of 4800
exp_ci = wilson_score_interval(200, 5000) # (3.48%, 4.57%)
ctrl_ci = wilson_score_interval(150, 4800) # (2.66%, 3.65%)
# 若两个区间不重叠,说明差异显著(更严谨的做法是用两样本 Wilson 检验)
偏好得分(Preference Score)
胜率只能反映显式反馈,但大多数用户不会主动点赞。偏好得分通过隐式信号构建:例如"用户是否在 10 秒内重问"、"是否复制回答内容"、"是否继续追问同一话题"。将这些二值信号做加权逻辑回归得到连续分值,然后在 A/B 实验中比较分值均值。
上下文连贯性自动化评估
传统方法计算 Perplexity 或 BERTScore,但无法捕捉上下文一致性。当前主流使用 LLM-as-Judge:让一个强大的 LLM(如 GPT-4-turbo)对两个模型(匿名)的回答进行评判。
python
import openai
def judge_coherence(context, response_a, response_b):
prompt = f"""以下是一个对话历史(为了节省token只列出最后三轮)。
有两个回答A和B,请判断哪个回答更符合上下文逻辑、不引入矛盾、且延续之前的话题。
输出格式: "A" 或 "B" 或 "tie"\n
对话上下文: {context}\n
回答A: {response_a}\n
回答B: {response_b}"""
resp = openai.chat.completions.create(
model="gpt-4-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.0 # 确定性
)
return resp.choices[0].message.content.strip()
注意事项 :
-
需要控制顺序偏差(response_a 和 response_b 随机交换)。
-
评判模型本身有偏见(比如更偏好更长、更华丽的回答),所以最好用多个评判模型取多数。
-
成本:一个判断大约需要 300-600 tokens,百万级评测可能花费数百美元。可以通过只抽样子集(如每日 1000 对)来降低成本。
工程实战:基于MLflow与Prometheus搭建评测看板
MLflow 管理实验与模型
bash
mlflow run . --env-manager=local \
-P model_uri="models:/llm-chat/Staging" \
-P online_ab_config='{"traffic_percent":5,"duration_hours":168}'
MLflow 会记录实验参数、在线指标(胜率、延迟 P95)及最终是否采纳。每个候选模型在其 artifact 路径下保存 A/B 实验结果 JSON。
Prometheus + Grafana 看板
在模型服务层暴露以下 metrics(带实验组标签 group 和 model_version):
-
llm_response_seconds_bucket{group="control|treatment"}--- 响应时间分布 histogram -
llm_user_feedback_total{group, type="like|dislike"}--- 用户反馈计数器 -
llm_win_rate_ratio--- 实时胜率(通过rate函数计算)
Grafana 面板配置:
-
时间序列图:两个组的胜率随时间变化,附带 Wilson 置信区间(通过将上下界也作为 metric 输出)。
-
"决策状态"面板:显示当前实验是否显著,建议系统自动推荐(绿色=全量,红色=回滚)。
-
延迟热力图:按模型版本分组的 P95/P99,精确到毫秒。
处理随机性:频率学派 vs 贝叶斯
- 频率学派(上述 Z 检验):需要预设样本量,重复检验会导致多重比较问题。我们的做法:每天只运行一个实验(新 vs 旧),连续跑 7 天,在最后一天做一次最终检验。
- 贝叶斯方法:使用 Beta-Binomial 模型,计算每个组的后验胜率分布,再计算 P(实验组 > 对照组)。优点是可以随时停止而不扭曲结果,适合自动化流水线。
python
# 贝叶斯胜率概率
from scipy.stats import beta
def posterior_probability(n_success_a, n_total_a, n_success_b, n_total_b):
# 无信息先验 Beta(1,1)
a_alpha = 1 + n_success_a
a_beta = 1 + n_total_a - n_success_a
b_alpha = 1 + n_success_b
b_beta = 1 + n_total_b - n_success_b
# 蒙特卡洛采样
samples_a = beta.rvs(a_alpha, a_beta, size=100000)
samples_b = beta.rvs(b_alpha, b_beta, size=100000)
return np.mean(samples_a > samples_b)
prob = posterior_probability(200, 5000, 150, 4800)
print(f"实验组胜率大于对照组的概率: {prob*100:.1f}%") # >95% 即可认为显著
核心建议:对于高频迭代场景(每天发版),采用贝叶斯方法配合序贯停止规则(当后验概率 > 95% 或 < 5% 时自动决策)。
总结与实战建议
- 从离线到在线渐进:离线的 GPT-4 Score 作为 Gate,通过后(>80 分)才允许进入在线 A/B,避免浪费线上流量。
- 胜率指标优先于绝对指标:用户偏好是最直接的信号;延迟和成本作为辅助判断。
- 统计严谨性不可忽视:样本量不足时 p 值噪音极大,建议使用 Wilson 区间或贝叶斯方法,并禁止手动 "盯 p 值" 停车。
- 自动化回滚是刚需:胜率下降 3% 或延迟增加 30% 应自动回滚,无需人工介入,可显著降低线上事故时长。
- 工具链推荐 :
- 实验平台:自建(基于 Redis + Prometheus)或使用开源 LaunchDarkly / GrowthBook
- 模型管理:MLflow + DVC(数据集版本)
- 监控:Prometheus + Grafana + Alertmanager
最终,一个稳定的 LLM 在线评测体系能让模型迭代周期从周级缩短到天级,同时保证每次上线具有统计可信的用户价值提升。