LLM在线评测:A/B测试与回归自动化实战

在线评测 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_idsession_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(带实验组标签 groupmodel_version):

  • llm_response_seconds_bucket{group="control|treatment"} --- 响应时间分布 histogram

  • llm_user_feedback_total{group, type="like|dislike"} --- 用户反馈计数器

  • llm_win_rate_ratio --- 实时胜率(通过 rate 函数计算)

Grafana 面板配置:

  1. 时间序列图:两个组的胜率随时间变化,附带 Wilson 置信区间(通过将上下界也作为 metric 输出)。

  2. "决策状态"面板:显示当前实验是否显著,建议系统自动推荐(绿色=全量,红色=回滚)。

  3. 延迟热力图:按模型版本分组的 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% 时自动决策)。

总结与实战建议

  1. 从离线到在线渐进:离线的 GPT-4 Score 作为 Gate,通过后(>80 分)才允许进入在线 A/B,避免浪费线上流量。
  2. 胜率指标优先于绝对指标:用户偏好是最直接的信号;延迟和成本作为辅助判断。
  3. 统计严谨性不可忽视:样本量不足时 p 值噪音极大,建议使用 Wilson 区间或贝叶斯方法,并禁止手动 "盯 p 值" 停车。
  4. 自动化回滚是刚需:胜率下降 3% 或延迟增加 30% 应自动回滚,无需人工介入,可显著降低线上事故时长。
  5. 工具链推荐
    • 实验平台:自建(基于 Redis + Prometheus)或使用开源 LaunchDarkly / GrowthBook
    • 模型管理:MLflow + DVC(数据集版本)
    • 监控:Prometheus + Grafana + Alertmanager

最终,一个稳定的 LLM 在线评测体系能让模型迭代周期从周级缩短到天级,同时保证每次上线具有统计可信的用户价值提升。

相关推荐
为你学会写情书1 小时前
RAG 性能总卡在检索?先搞懂 Milvus 向量数据库再说
llm·agent
Token炼金师1 小时前
千卡月崩五次:loss spike、硬件故障、checkpoint 与断点续训 —— 训练可靠性的生死线
人工智能·深度学习·llm
Token炼金师1 小时前
指令的塑形:从野生语言模型到听话助手 —— 监督微调 SFT 全链路
人工智能·深度学习·llm
武子康1 小时前
🔥 codex-plugin-cc 原理全拆:四层桥接 + JSON-RPC 2.0 + JSONL + 后台任务状态机
人工智能·llm·claude
qcx233 小时前
SpaCellAgent:用 LLM 多智能体怎么用于单细胞轨迹分析的?效率提升了多少?
人工智能·gpt·安全·ai·机器人·llm·agent
AndrewHZ4 小时前
【LLM技术全景】混合精度与分布式训练:训练大模型的工程奥秘
人工智能·分布式·深度学习·算法·ai·语言模型·llm
吴佳浩4 小时前
为什么 MCP、Skill、RAG 能工作?从 Conversation Loop 看现代 Agent 的底层架构
人工智能·llm·agent
柒和远方1 天前
从等待到流式:LLM 流式输出的原理、协议与工程实践
javascript·llm
AI小白Lin1 天前
我和一个德国程序员互不知道对方,却写出了完全一样的AI架构 🤯
架构·llm