离线评测分很高,上线效果差——可能出了什么问题?

Q5-02评测与可观测高频归因离线在线鸿沟分布脱节数据泄漏排障上下文

谁在问:二三面·追问;简历里写了「评测 XX 分」之后的必然一刀

口语化问法

  • 你们离线评测 90 多分,那上线之后用户反馈怎么样?如果不一致,会是什么原因?
  • 有没有遇到过测的时候好好的、上线就不行的情况?后来查出来是什么?
  • 假设现在线上投诉翻倍但你的评测集分数一点没掉,你先干什么?

考察意图

这道题几乎不会单独出现,它是「你们怎么评测的」之后的第二刀。面试官在判断:

  1. 你的第一个动作是什么。 没排过障的人第一反应是罗列可能原因(像背清单);排过障的人第一反应是做一个能把问题一分为二的动作
  2. 你能不能区分「评测集的问题」和「系统的问题」。 这两类的修法完全不同,混在一起谈说明没真查过。
  3. 你有没有意识到「分数没掉」本身就是一条重要证据。 分数没掉说明你测的东西和线上失败的东西不是同一件事——这比分数掉了更值得警惕。

参考答案

图 2 · 60 分与 90 分差在哪:代价、演进、怎么验证

60

60 分答案(及格线)

可能的原因有几个:一是评测集和线上真实用户的问法不一样,评测集偏理想化;二是判分标准和用户真实感受不一致,比如我们判「要点命中」,用户在意的是有没有解决问题;三是可能有数据泄漏,prompt 是对着评测集调出来的,有点过拟合;四是线上环境不一样,多轮对话、上下文更长。我会先看一下线上的 bad case,然后补充评测集。

这个答案的原因罗列是对的,也覆盖了主要方向,所以能过。

它止步于 60 分是因为——它是一份清单,不是一个流程。 面试官接一句「那你先查哪个」,就会发现候选人没有次序,只有并列。而在真实排障里,次序就是全部

90

90 分答案(有生产经验的回答)

我不会先罗列原因,我会先做一个动作把问题切成两半。

第一刀:把线上失败的那批输入,原样搬到离线环境跑一遍。 注意是「原样」——连同它当时的对话历史、系统提示、检索到的文档、工具返回值一起搬,不能只把用户最后那句 query 抠出来。然后看结果:

  • 离线也错 → 这是评测集侧的问题:系统的能力本来就不行,只是你的评测集根本没测这类输入。
  • 离线是对的 → 这是环境侧的问题:同样的输入,两边行为不同,差异在链路里,不在模型能力里。

这一刀和 RAG 排障里「手工把正确文档塞进 prompt、切开检索侧和生成侧」是同一个手法:固定住一个变量,把问题一分为二。

如果落在评测集侧,往下切三条:

  1. 分布不同——最常见,而且不报错。做法是把评测集和线上采样各自统计长度分布、轮次分布、意图占比,摆在一张表里。我们真踩过一次:评测集 87% 是完整问句,线上 61% 是残句和关键词;线上 44% 发生在第二轮以后带指代,评测集里一条都没有;线上 12% 问的是知识库里根本没有的内容,评测集里也是零。指标为零的维度在看板上不是低分,是不可见。
  2. 判据不同——离线判「要点命中」,用户判「有没有帮我把事办了」。要点全命中但结论埋在第三段,离线满分线上被骂。而且这个偏差会随着你对着判据优化而放大。
  3. 泄漏或过拟合——prompt 是对着评测集一轮轮调出来的,或者评测集和开发集同源。这时分数上涨里有多少是真提升,你分不清。判断办法是拿从不参与迭代的保留集跑一次。

如果落在环境侧,往下切三条:

  1. 上下文不同——离线是干净的单轮调用,线上是第 7 轮,前面堆了三次工具返回、一次压缩、一条用户中途改主意的指令。这对应上下文的四种失败形态(污染 / 干扰 / 混淆 / 冲突),只测第一轮的评测集一种都测不出来
  2. 版本不同——模型被上游静默升级、prompt 模板走的是另一个分支、检索索引没同步、工具的返回 schema 变了。
  3. 运行时不同——超时降级到了小模型、限流触发、输出打到 max_tokens 被截断、并发下的缓存串味。这类的共同特征是只在高峰期出现,用平峰的采样永远抓不到。

最后一句我一定会说:分数没掉本身就是最强的证据——它说明我测的东西和线上失败的东西不是同一件事。 所以这次事故的收尾,一定包含往评测集里补进这批真实失败,否则同一个问题会换个形式再来一次。


追问链

图 1 · 五层追问树:面试官会往哪儿挖
追问全长在归因树上:每一层都在问「你按什么次序往下切」

  1. 「原样搬到离线跑」听起来简单,实际有什么坑?

    期望最大的坑是搬不全 —— 只搬了最后一句 query,没搬对话历史、系统提示的实际取值、检索命中的文档、工具真实返回,于是复现不出来、误判成「环境问题」。由此反推 trace 要能一键导出可离线重放的输入包;时间/状态相关的输入要冻结外部依赖
    信号说「就把 query 拿过来跑一遍」→ 最常见的失手点,没真做过;提「今天的汇率、当时的权限这类依赖要冻结」→ 很强的信号
  2. 确认是评测集分布脱节,你怎么量化这个「脱节」?

    期望给可执行的对比维度:长度、轮次、意图、库里有没有答案、语言/渠道;再把线上 queryembedding 聚类,找评测集完全没覆盖的簇 —— 新簇通常就是新意图或新渠道。判据不是分布相同,是主要类别都非零、高风险类别不欠采样
    信号只说「感觉不太一样」→ 没做过;承认「有意过采样高风险类别,所以总分不能当线上表现的估计」→ 理解得很透
  3. 离线复现是对的、落在环境侧,接下来怎么切?

    期望可排除的成本排,不按可能性:① 比版本(模型 ID / prompt 模板 / 索引 / 工具),看配置就行 → ② diff 上下文(长度、条数、有没有触发过压缩)→ ③ 查运行时(降级、限流、截断、超时重试),只能靠 trace 标记位。运行时问题只在高峰出现,要按时间段切
    信号上来就怀疑模型能力 → 方向反了,能力问题第一刀就该被复现出来;主动提 max_tokens 截断与静默降级 → 最容易漏也最常中的两条
  4. 这次修完,怎么保证下次不再发生?

    期望收尾的强制动作是把这批真实失败补进评测集,且补最小可复现输入而不是整条 trace;再把「评测集 vs 线上采样」的分布对账做成每月例行;上线上采样评测,让「离线涨、线上不动」被自动发现。边界要认:这套只防同类复发,新形态靠拒答率、转人工率、点踩率兜底
    信号只说「加强测试」→ 空话;说「补的是最小可复现输入」→ 维护过评测集,知道整条 trace 补进去会又慢又脆
  5. 线上还在出问题、老板要求今天恢复,但你判断根因是评测集要重建、至少两周,这两件事怎么排?

    期望止血优先于根因,而且止血不需要知道根因。按代价从小到大:① 回滚到上一个已知稳定版本 → ② 网关分流,把受影响最大的那类流量切回旧版或转人工,缩小爆炸半径 → ③ 加一条最窄兜底:检索置信度低于阈值就回「我不确定,帮你转人工」,宁可拒答不要乱答。这三条都可逆,重建评测集不可逆,两条线并行不该互等;止血期间捞到的失败样本正好是重建的原料
    信号坚持「先查清根因再动」→ 技术没错,工程上是灾难;把可逆的止血与不可逆的重建拆成两条并行线 → 真在生产里扛过事故
前四层考的是次序:第 1 层验你会不会「固定一个变量、把问题一分为二」,第 3 层(按排除成本排、不按可能性)才是分水岭;第 5 层换成止血与根因谁先谁后。

评分要点

  1. 第一动作是切一刀(把线上失败输入原样搬到离线跑),不是罗列原因清单
  2. 明确区分评测集侧环境侧两大类,并各自能往下展开
  3. 评测集侧说得出:分布 / 判据 / 泄漏三条,且知道分布问题不报错
  4. 环境侧说得出:上下文 / 版本 / 运行时三条,且知道运行时问题只在高峰出现
  5. 意识到「原样搬」的最大坑是搬不全,并由此反推 trace 的记录要求
  6. 收尾包含把真实失败补进评测集,且补的是最小可复现输入
  7. 压力面能把**止血(可逆)根因(不可逆投入)**分开并行

常见错误

罗列一串可能原因,但说不出先查哪个有清单没流程。真实排障里次序就是全部
「应该是评测集不够全,多加点数据就好了」把归因跳过直接给结论。可能根本不是评测集的问题
「线上环境比较复杂」典型含糊回答,等于没说
只把用户最后一句 query 搬去离线跑复现不出来会误判成环境问题,方向直接跑偏
完全没提「分数没掉」这件事本身的含义漏掉了最强的一条证据
先做根因分析再止血工程判断错误。线上流血时归因是最贵的选择
修完就结束,不补评测用例同类问题一定会换个形式回来

关联学习