跳到正文
SL Blog 技术探索 · 工程实践 · AI 时代思考
返回
🔍 RAG 系列 · 3 / 9 查看系列简介 →

RAG 系列(三):Embedding 检索评测与 Benchmark

文章目录
  1. 1. 为什么“问几个问题感觉不错”不算评测
  2. 2. 第一阶段为什么只测 Retriever
  3. 3. A/B 测试完整流程
  4. 4. 公平测试的核心:一次只改变一个变量
  5. 5. Benchmark 名词词典
  6. 5.1 Benchmark
  7. 5.2 Baseline 与 Candidate
  8. 5.3 Corpus
  9. 5.4 Snapshot
  10. 5.5 Question Set
  11. 5.6 Gold Chunk
  12. 5.7 Answerable 与 No Answer
  13. 5.8 问题类别
  14. 6. 为什么两个模型必须各搜各的 Corpus
  15. 7. 为什么保存 Top 10
  16. 8. 指标如何计算
  17. 8.1 Hit@K
  18. 8.2 MRR
  19. 8.3 Recall@5
  20. 8.4 Pairwise Comparison
  21. 8.5 Bootstrap 95% CI
  22. 8.6 P50 与 P95 延迟
  23. 9. 为什么不能横向比较两个模型的相似度绝对值
  24. 10. 本项目的文件与执行顺序
  25. 11. 当前真实结果怎么读
  26. 11.1 总体指标
  27. 11.2 按类别观察
  28. 11.3 Pairwise
  29. 11.4 当前可以得出的结论
  30. 11.5 当前不能得出的结论
  31. 12. 如何阅读失败案例
  32. 13. Benchmark 为什么也是回归测试
  33. 14. 初学者常见误区
  34. 15. 自测题

本篇回答一个工程问题:两个 Embedding 模型都能运行,怎样判断谁更适合当前真实知识库? 重点评测 Retriever,不把 LLM 的语言风格和随机性混进实验。

1. 为什么“问几个问题感觉不错”不算评测

人工随便问三五个问题,容易受到以下因素影响:

  • 只挑了简单问题;
  • 只看最终答案是否流畅,没有检查证据是否正确;
  • Prompt 或 LLM 同时发生变化;
  • 对一个模型使用了更合适的 Chunk 或维度;
  • 只报告成功案例,没有记录失败案例。

Benchmark 的价值不是产生一张漂亮分数表,而是建立一个可重复实验:当模型、切块或检索策略变化时, 我们能用同一把尺子判断真实提升与退步。

2. 第一阶段为什么只测 Retriever

完整 RAG 是:

Question → Retrieval → Top K Context → LLM → Answer

当前 Benchmark 截止到 Top K:

Question → Embedding → pgvector → Top K → 与人工 Gold 对比

暂时不加入 LLM,原因是我们要回答一个单一问题:

Embedding 模型变化后,正确 Chunk 是否排得更靠前?

如果同时比较最终答案,就会混入 LLM 随机性、Prompt 和生成模型能力,无法清楚归因。

3. A/B 测试完整流程

Embedding A/B Benchmark 公平评测流程

文字版流程:

同一份冻结 Chunk
├── text-embedding-v4       → Corpus A
└── qwen3.7-text-embedding  → Corpus B

同一个 Question
├── 用 v4 生成 Query Vector       → 只搜索 Corpus A → Top 10 A
└── 用 qwen3.7 生成 Query Vector  → 只搜索 Corpus B → Top 10 B

Top 10 A / B
→ 分别与事先标注的 Gold Chunk 比较
→ Hit@K、MRR、Recall@5、Pairwise、Bootstrap

两条实验路径只有 Embedding Model 不同。

4. 公平测试的核心:一次只改变一个变量

本次唯一变量:

Embedding Model

以下条件全部冻结:

  • Markdown 语料;
  • Chunk 算法、Size、Overlap;
  • 标题与正文拼接方式;
  • Embedding 维度(都为 1024);
  • 问题集合与 Gold Chunk;
  • 距离度量(cosine);
  • PostgreSQL、pgvector 与 SQL;
  • 保存的 Top K(10);
  • 精确检索,不使用 HNSW / IVFFlat。

如果把 v4/1024 与 qwen3.7/2048 比较,模型和维度同时改变,无法知道提升来自哪里。

5. Benchmark 名词词典

5.1 Benchmark

定义:在固定数据、固定流程和固定指标下,对系统能力进行可重复测量。

本项目中的位置:benchmark/ 目录保存语料快照、问题、两模型结果、指标和失败分析。

5.2 Baseline 与 Candidate

  • Baseline(基线):当前参照方案,本项目为 text-embedding-v4。
  • Candidate(候选):准备评估的新方案,本项目为 qwen3.7-text-embedding。

基线不是“落后模型”的同义词,它是判断变化是否真正改善的参照物。

5.3 Corpus

定义:参与评测的全部冻结 Chunk 及其 Embedding 输入文本。

本项目在 corpus_chunks.jsonl 中保存 35 篇文档的 1165 个 Chunk。两个模型收到的字符串逐字一致。

5.4 Snapshot

定义:记录一次实验所用语料和参数的快照。

snapshot.json 包含:

  • 文档数、Chunk 数;
  • Chunk Size、Overlap 和算法;
  • 维度、距离、Top K;
  • 两个模型名;
  • 精确检索说明;
  • Snapshot Hash。

Snapshot Hash 是整个冻结语料的指纹。文档或 Chunk 变化后,哈希会变化,旧结果不能再假装来自 同一实验条件。

5.5 Question Set

定义:用于检索评测的问题集合。本项目共 100 题,其中 90 题可回答、10 题不可回答。

问题不能全是原文复述,否则只能证明模型能匹配明显关键词,不能代表真实用户表达。

5.6 Gold Chunk

定义:人工事先确认、确实能够回答某个问题的正确 Chunk。

{
  "id": "Q001",
  "question": "Spring Cache 中缓存多久会失效?",
  "gold_chunk_ids": [128],
  "answerable": true
}

Gold 必须在运行模型之前确定。不能把模型返回的 Top 1 当 Gold,否则模型在用自己的答案给自己判分。

一个问题可以有多个 Gold Chunk,表示完整答案可能由多个片段支持。

5.7 Answerable 与 No Answer

  • Answerable:冻结语料中存在能够回答问题的 Chunk。
  • No Answer:知识库明确没有答案。

向量搜索无论有没有答案都会返回“最接近”的 Top K。因此 No Answer 主要用于观察错误内容会得到怎样的 相似度,并为未来按模型校准拒答阈值提供数据;它不进入 Hit@K 主指标。

5.8 问题类别

类别测什么例子特点
Direct基本语义检索问法接近原文
Paraphrase改写理解不直接复述关键词
Cross Language中英混合中文问题含英文类名或术语
Code/API精确技术符号类名、命令、配置项、API
Hard Negative相似主题区分多篇文档都含相同关键词
No Answer无答案行为语料中没有相关知识

只看 Overall 会掩盖模型到底改善了哪类问题。技术博客尤其需要关注 Code/API 与 Hard Negative。

6. 为什么两个模型必须各搜各的 Corpus

Embedding 模型定义了自己的向量空间:

v4 Query Vector      ↔ v4 Document Vectors
qwen3.7 Query Vector ↔ qwen3.7 Document Vectors

禁止:

qwen3.7 Query Vector → 搜索 v4 Document Vectors

即使维度相同,两套坐标也不具有共同语义。benchmark_embeddings 以 (chunk_id, model) 为主键,正是为了让同一 Chunk 同时保存两套独立向量。

7. 为什么保存 Top 10

生产系统可能只把 Top 5 交给 LLM,但 Benchmark 保存 Top 10,便于一次查询后计算:

Hit@1 / Hit@3 / Hit@5 / Hit@10
MRR
Recall@5

如果只保存 Top 1,未来想计算 Hit@5 就必须重新调用查询 API;保存 Top 10 是低成本的实验记录。

8. 指标如何计算

8.1 Hit@K

问题:至少一个 Gold Chunk 是否出现在前 K 名?

假设 Gold 是 Chunk 128,返回排名为:

Rank 1: Chunk 430
Rank 2: Chunk 128  ← Gold
Rank 3: Chunk 900

则:

Hit@1 = 0
Hit@3 = 1
Hit@5 = 1

对所有可回答问题取平均。例如 90 题中有 75 题的 Gold 排第一:

Hit@1 = 75 / 90 = 83.3%

Hit@1 强调第一名是否最准确;Hit@5 更接近“Retriever 是否把正确资料交给后续 LLM”。

8.2 MRR

MRR 是 Mean Reciprocal Rank,平均倒数排名。它关心第一个正确结果整体排得多靠前。

第 1 名命中 → 1 / 1 = 1.000
第 2 名命中 → 1 / 2 = 0.500
第 3 名命中 → 1 / 3 = 0.333
Top 10 未命中 → 0

再对全部问题求平均。MRR 比 Hit@5 更能区分“正确资料虽然都在前五,但一个总在第一、另一个总在第五”。

8.3 Recall@5

当一个问题有多个 Gold Chunk 时,Recall@5 衡量前五名找回了多少正确片段。

Gold = {128, 129, 130}
Top 5 = {128, 430, 129, 500, 600}

命中 2 个,共 3 个
Recall@5 = 2 / 3 = 0.667

它与 Hit@5 不同:上例只要命中一个,Hit@5 就是 1;Recall@5 会继续关心是否找全。

8.4 Pairwise Comparison

逐问题比较两个模型第一个 Gold 的排名:

Q017: v4 Rank 7,qwen3.7 Rank 1 → qwen3.7 win
Q025: v4 Rank 1,qwen3.7 Rank 3 → v4 win
Q030: 都是 Rank 1              → tie

Pairwise 能看见整体平均分背后的具体胜负案例,便于继续阅读失败文档。

8.5 Bootstrap 95% CI

当前 90 道可回答问题只是潜在真实问题的一份样本。Bootstrap 会从这 90 题中进行 1000 次“有放回 重采样”,每次重新计算指标,观察结果在不同抽样下有多稳定。

95% CI 可以直观理解为:在当前题集与重采样方法下,指标的主要波动范围。它不是“模型有 95% 概率 正确”,也不能消除题集偏差或 Gold 标注错误。

8.6 P50 与 P95 延迟

  • P50:一半请求比这个值快,一半比它慢,接近典型体验。
  • P95:95% 请求比这个值快,用来观察较慢的尾部请求。

平均值可能被极慢或极快的少量请求扭曲,故性能报告常同时使用百分位。

9. 为什么不能横向比较两个模型的相似度绝对值

错误结论:

模型 A Top1 similarity = 0.88
模型 B Top1 similarity = 0.82
所以 A 更好

不同模型的向量空间与分数分布不同,0.88 和 0.82 没有统一刻度。可以比较的是:

  • 各自空间内正确 Chunk 的排名;
  • 同一模型中 Answerable 与 No Answer 的分布;
  • 经过每个模型独立校准后的阈值效果。

不能把一个模型的拒答阈值直接复制给另一个模型。

10. 本项目的文件与执行顺序

文件作用
snapshot.json冻结实验条件与语料指纹
corpus_chunks.jsonl全部 Chunk 和逐字一致的 Embedding 输入
questions_*.jsonl各类别候选问题
questions.jsonl合并校验后的统一问题集
results_v4.jsonlv4 每题 Top 10 与延迟
results_qwen37.jsonlqwen3.7 每题 Top 10 与延迟
summary.json机器可读的指标汇总
REPORT.md人类可读的整体报告
FAILURES.md逐问题胜负与失败案例

执行顺序:

# 1. 冻结语料、建独立表、生成 Snapshot
./.venv/bin/python -m benchmark.scripts.prepare

# 2. 合并问题并检查 Gold
./.venv/bin/python -m benchmark.scripts.validate_questions

# 3. 分别生成两套 Corpus Embedding
./.venv/bin/python -m benchmark.scripts.embed_corpus both

# 4. 每个问题分别查询两套 Corpus
./.venv/bin/python -m benchmark.scripts.run both

# 5. 计算指标
./.venv/bin/python -m benchmark.scripts.evaluate

# 6. 生成人类可读报告
./.venv/bin/python -m benchmark.scripts.report

11. 当前真实结果怎么读

测试条件:

35 documents / 1165 chunks
100 questions(90 answerable + 10 no-answer)
两个模型均为 1024 维
精确 cosine search

11.1 总体指标

指标text-embedding-v4qwen3.7-text-embedding差值
Hit@168.9%83.3%+14.4 个百分点
Hit@394.4%98.9%+4.4 个百分点
Hit@595.6%98.9%+3.3 个百分点
MRR0.8110.907+0.096
Recall@592.7%94.9%+2.2 个百分点

“百分点”与“相对提升”不同。68.9% 到 83.3% 是增加 14.4 个百分点,不应含糊写成“提升 14.4%”。

11.2 按类别观察

类别v4 Hit@1qwen3.7 Hit@1观察
Direct90.0%85.0%简单直接问题略有回退
Paraphrase75.0%90.0%改写问题改善明显
Cross Language80.0%93.3%中英混合改善
Code/API60.0%86.7%技术符号相关问题改善最大
Hard Negative40.0%65.0%相似主题区分改善,但仍最难

这比“新模型赢了”更有价值:当前知识库是技术博客,Code/API 和中英混合问题占实际使用的重要部分, 因此这些类别的改善具有业务意义。

11.3 Pairwise

qwen3.7 wins: 21
v4 wins:       6
ties:         63
both miss:     0

大多数问题打平;新模型在 21 个问题上把 Gold 排得更靠前,但也有 6 个问题退步。升级模型不意味着 每一道题都改善,FAILURES.md 中的退步案例应继续保留为回归测试。

11.4 当前可以得出的结论

在这份冻结的技术博客语料、这 100 道题、相同 1024 维和相同精确检索流程下, qwen3.7-text-embedding 的 Hit@1、MRR,以及 Code/API、改写和中英混合类别表现更好, 因此当前报告建议升级。

11.5 当前不能得出的结论

  • 不能推断所有中文 RAG 都会提升 14.4 个百分点;
  • 不能推断最终 LLM 答案也会等比例提升;
  • 不能说 qwen3.7 每类问题都更好,Direct 类别存在小幅回退;
  • 不能把两模型 No Answer 的相似度均值直接比较后定胜负;
  • 不能证明当前 Chunk 策略已经最优;
  • 不能忽略题集规模、类别比例和 Gold 标注质量。

这就是“局部证据”与“普遍结论”的边界。

12. 如何阅读失败案例

不要只问“哪个模型错了”,应继续分类根因:

失败标签含义可能的下一步
EMBEDDING_SEMANTIC改写语义没有靠近正确片段换模型或改输入表示
EXACT_TERM错误码、参数名等精确词匹配弱BM25 / Hybrid Search
CODE_SYMBOL类名、API、命令召回差Code 向量模型或稀疏检索
CROSS_LANGUAGE中英文对应关系差更强多语言模型
SIMILAR_TOPIC_CONFUSION多篇相似文章难区分标题元数据、Reranker
CHUNK_BOUNDARY答案被切散调整 Chunk/Overlap
INSUFFICIENT_CONTENT原文就没有完整答案补充知识库
AMBIGUOUS_QUERY问题本身含义不清Query Rewrite / 追问

如果两个模型都失败,问题不一定在 Embedding。先检查 Gold、原文、Chunk 边界和问题表达,再决定 是否引入新组件。

13. Benchmark 为什么也是回归测试

以后任何改动都可能让部分问题变好、另一些变差:

  • 更换 Embedding;
  • 调整 Chunk Size / Overlap;
  • 加标题或元数据;
  • 引入 BM25 / Hybrid;
  • 加 Reranker;
  • 改查询重写策略。

保持同一 Question Set 与 Gold,可以比较修改前后指标和逐问题排名。Benchmark 因而不只是一次模型 选型报告,也是 RAG 的 Regression Test Suite(回归测试集)。

但只要语料或切块发生变化,就必须生成新 Snapshot,并认真处理 Chunk ID 与 Gold 的变化。

14. 初学者常见误区

  1. 只看最终答案评分 Embedding。 LLM 会掩盖或放大检索问题,应先单独测 Retriever。
  2. 只看 Hit@5。 两模型都接近满分时,Hit@1 和 MRR 更能看出排序差异。
  3. 把模型自己的结果当 Gold。 这会形成自证循环。
  4. 跨模型比较 similarity。 两个向量空间没有统一分数刻度。
  5. 同时改模型、维度和 Chunk。 无法归因提升来源。
  6. 只报告 Overall。 必须按类别和逐问题检查改善与退步。
  7. 忽略 No Answer。 向量数据库永远会返回最近结果,最近不代表有答案。
  8. 把一次 Benchmark 当永久真理。 语料、用户问题和业务目标变化后需要重新评测。

15. 自测题

  1. 为什么本 Benchmark 不把 LLM 最终回答纳入主测试?
  2. 什么是 Gold Chunk,为什么必须在运行模型之前确定?
  3. Hit@5=1 能否说明多个 Gold 都找全了?为什么?
  4. 一个问题的首个 Gold 在第 4 名,它对 MRR 的贡献是多少?
  5. 为什么两个 1024 维模型的向量仍然不能混用?
  6. 为什么不能用 0.88 与 0.82 的相似度直接判断两个模型谁更好?
  7. 当前结果支持什么结论,又不支持什么结论?
  8. Code/API 问题持续失败时,为什么 BM25 可能比继续增大向量维度更值得研究?

能解释这些问题,你已经从“会运行 RAG”迈向“能判断 RAG 为什么好或不好”。


上一篇:RAG 系列(二):源码导读与动手实验


RAG 智能问答

针对本文继续提问:《RAG 系列(三):Embedding 检索评测与 Benchmark》