RAG 实战
大模型很聪明,但它不知道你公司内部的文档写了什么,也不知道昨天刚发布的公告。RAG(Retrieval-Augmented Generation,检索增强生成)就是为解决这个问题而生的工程范式。
这篇文章不做概念科普式的泛泛而谈,而是按工程落地的顺序讲四件事:RAG 为什么存在、它在生产环境里会怎么坏以及怎么修、如何量化地知道它到底好不好、作为其基座的 Milvus 该怎么选索引。
一、RAG 的技术背景
1.1 大模型的四个先天缺陷
| 缺陷 | 具体表现 | 典型翻车场景 |
|---|---|---|
| 知识截止 | 训练数据有截止日期,之后的事一无所知 | 问「上个月发布的新版本有什么变化」→ 编造或拒答 |
| 幻觉 | 对不知道的事倾向于「合理编造」而非承认不知道 | 编出不存在的 API、法条、论文引用 |
| 私有知识缺失 | 训练语料不含企业内部文档、代码库、工单 | 问内部系统怎么申请权限 → 一本正经胡说 |
| 上下文成本与容量 | 窗口有限;且长上下文存在「迷失在中间」效应 | 把 100 页手册全塞进去,准确率反而下降 |
最后一条值得展开:Liu et al. 的 Lost in the Middle 实验表明,当关键信息位于长上下文的中部时,模型召回它的准确率显著低于位于开头或结尾时——也就是说,「把相关文档全塞进 prompt」并不是一个可靠的方案,位置和噪声都会伤害效果。
1.2 RAG 与微调、长上下文的取舍
这三者不是互相替代,而是各有边界。工程上要先判断「知识问题」还是「能力问题」:
| 维度 | RAG | 微调(SFT / LoRA) | 长上下文硬塞 |
|---|---|---|---|
| 知识更新速度 | 改知识库即生效,分钟级 | 需重新训练,天级 | 改 prompt 即生效 |
| 成本结构 | 存储 + 检索算力,低 | GPU 训练 + 标注数据,高 | token 费用随长度线性增长 |
| 可溯源 | 强,可精确给出引用段落 | 弱,知识混在参数里 | 中 |
| 幻觉抑制 | 中~强(有据可依) | 弱(仍可能编造) | 中 |
| 适合场景 | 知识密集、频繁更新的问答 | 固定风格 / 输出格式 / 领域语感 | 单篇长文档的一次性分析 |
一句话结论:微调改变的是「怎么说」,RAG 补充的是「说什么」。 绝大多数企业知识问答场景,RAG 的性价比远高于微调。
1.3 RAG 的原理
RAG 最早由 Lewis et al. 在 2020 年提出,本质是把生成建模为对隐变量(检索到的文档)的边缘化:
1 | p(y | x) = Σ_z p(z | x) · p(y | x, z) |
x是问题,z是检索到的文档片段,y是答案- 检索器
p(z|x)负责从海量知识中挑出可能相关的片段 - 生成器
p(y|x,z)负责基于这些片段组织答案
工程上我们并不真的做这个求和,而是用 Top-K 检索近似它——这也解释了为什么 RAG 的效果对「检索质量」极度敏感:检索错了,生成器再强也只能基于错误材料作答。
1.4 三代演进
| 阶段 | 核心思路 | 典型手段 | 局限 |
|---|---|---|---|
| Naive RAG | 索引 → 检索 → 生成,一条直线 | 固定长度切块、单路向量检索、Top-K 拼 prompt | 切块割裂语义、召回不准、无优化手段 |
| Advanced RAG | 在检索前后加优化环节 | 查询改写 / 扩展、混合检索、Rerank、上下文压缩 | 链路变长,调参成本高 |
| Modular RAG | 模块化可编排,按问题类型路由 | 路由、记忆、多跳检索、自我反思、工具调用 | 架构复杂,需要评测体系支撑 |
1.5 典型架构
flowchart LR
subgraph OFFLINE["离线:知识入库"]
A["原始文档<br/>PDF / Markdown / 网页 / 数据库"] --> B["解析与清洗<br/>去页眉页脚 / 表格还原"]
B --> C["分块 Chunking<br/>按语义切分 + 重叠"]
C --> D["向量化 Embedding"]
D --> E[("Milvus<br/>向量库 + 标量字段")]
end
subgraph ONLINE["在线:检索增强生成"]
Q["用户问题"] --> R["查询优化<br/>改写 / 扩展 / 分解"]
R --> S["混合检索<br/>向量 + BM25"]
E -.-> S
S --> T["重排 Rerank"]
T --> U["上下文组装<br/>去重 / 压缩 / 排序"]
U --> V["LLM 生成"]
V --> W["带引用的答案"]
end
各环节的职责与常见坑:
| 环节 | 职责 | 常见失败模式 |
|---|---|---|
| 解析 | 把异构文档变成纯文本 | 表格/公式被拍平、扫描件无 OCR |
| 分块 | 切成语义完整的检索单元 | 切在句子中间、块过大稀释语义 |
| 向量化 | 把文本映射到语义空间 | 模型与语种/领域不匹配 |
| 索引存储 | 支持高效相似度检索 | 索引选型不当导致慢或不准 |
| 检索 | 召回相关片段 | 只做单路向量检索,专有名词召回差 |
| 重排 | 精排候选,提升精度 | 缺失,导致噪声挤占上下文 |
| 生成 | 基于证据组织答案 | 无视证据自由发挥、引用错位 |
二、RAG 常见问题与解决方案
2.1 问题全景
按链路把问题分成四段,先建立一张「排障地图」:
| 阶段 | 典型问题 | 症状 | 首选手段 |
|---|---|---|---|
| 检索前 | 分块不合理 | 答案被切断、检索不到 | 语义/递归分块 + 重叠 + 小块检索大块生成 |
| 元数据缺失 | 无法按部门/时间过滤 | 入库时补齐标量字段 | |
| Embedding 不匹配 | 语义相近却检索不到 | 换多语/领域模型,或走混合检索 | |
| 检索中 | 召回不足 | 正确答案不在 Top-K | 混合检索、多路查询、提高召回数 |
| 专有名词失效 | 产品名/编号搜不到 | BM25 关键词通道 + 同义词词典 | |
| 排序不准 | 相关文档排在很后面 | Cross-Encoder Rerank | |
| 检索后 | 上下文过载 | 噪声挤掉关键信息 | 上下文压缩、去重、重排后截断 |
| 位置偏差 | 关键信息在中部被忽略 | 把最相关的放首尾 | |
| 生成 | 幻觉 | 答案无据可依 | 强制引用、无依据则拒答 |
| 不忠实 | 与原文矛盾 | 降低温度、few-shot 约束、事实性校验 |
2.2 分块:最容易被低估的一步
分块策略直接决定检索上限——块切错了,后面所有优化都是在错误的候选集上做精排。
| 策略 | 做法 | 适用 | 代价 |
|---|---|---|---|
| 固定长度 | 按 token/字符数硬切 | 快速验证 | 语义割裂严重 |
| 递归切分 | 按 段落→句子→词 逐级降级切分 | 通用文档(推荐默认) | 需调 chunk_size |
| 语义切分 | 用相邻句向量相似度找断点 | 长论述、技术文档 | 建库慢 |
| 结构化切分 | 按 Markdown 标题 / HTML 层级 | 结构化文档(最优) | 依赖文档规范 |
| 父子块 | 小块检索、大块(或父段)喂给 LLM | 几乎所有场景 | 需额外存储映射 |
实践参数建议(以中文技术文档为例):
chunk_size:300~600 token,overlap:10%~15%- 每个块必须携带元数据:来源文件、章节标题、更新时间、权限标签
- 在块前拼接「文档标题 + 章节名」再向量化,可显著提升短块的语义完整性
一个常被忽略的技巧是父子块(Small-to-Big):用 128~256 token 的小块做检索以保证精度,命中后返回其所属的父段落(或前后各扩一块)给 LLM 提供完整上下文。这样同时缓解了「小块语义不全」和「大块检索不准」的矛盾。
2.3 检索:从单路向量到混合检索
纯向量检索的软肋是精确匹配:产品型号 Milvus 2.4.9、错误码 ERR_0x5A、人名缩写,向量模型往往抓不住。而 BM25 恰好擅长这些。
混合检索(Hybrid Search) 把两路结果融合,主流融合算法:
| 融合方式 | 公式 / 做法 | 特点 |
|---|---|---|
| RRF(倒数排名融合) | score = Σ 1 / (k + rank_i),k 常取 60 |
无需分数归一化,鲁棒,推荐默认 |
| 加权求和 | score = α·vec + (1-α)·bm25 |
需先归一化,α 要调 |
| 级联 | 先向量召回,再 BM25 过滤 | 简单,但会丢掉单路独有的结果 |
RRF 之所以好用,是因为它只看排名不看分数,从而绕开了「向量相似度」和「BM25 分数」量纲不可比的问题。
2.4 查询优化:性价比最高的一环
在检索结果不理想时,优化查询往往比换更大的模型更有效。 常见手段按复杂度递增:
| 手段 | 做什么 | 什么时候用 | 成本 |
|---|---|---|---|
| 查询改写 | 补全指代、去口语、纠错 | 多轮对话(”它”、”这个”指什么) | 1 次小模型调用 |
| 同义词扩展 | 加入领域同义词/缩写 | 用户用词与文档不一致 | 词典维护 |
| HyDE | 先让 LLM 生成假想答案,用答案去检索 | 问题太短、与文档表述差异大 | 1 次 LLM 调用 |
| Multi-Query | 生成 N 个不同角度的查询,多路召回后融合 | 单查询召回不足 | N 次检索 |
| 查询分解 | 把多跳问题拆成子问题依次求解 | “A 和 B 相比,谁在 C 上更强” | 多次 LLM + 检索 |
| Step-back | 先问一个更抽象的元问题,再回到具体问题 | 细节问题缺乏背景知识 | 1 次 LLM 调用 |
| 意图路由 | 先判断问题类型,走不同链路 | 知识库混合了 FAQ/文档/表格 | 1 次分类 |
关键实现细节
HyDE 的核心洞察是「假想答案」比「原始问题」更接近文档的表述分布——因为文档里写的是答案,不是问题。所以让模型先写一段可能的答案,再用它去检索,召回率常有明显提升。代价是增加一次 LLM 调用,且假想答案跑偏时会引入偏差。
Multi-Query + RRF 是工程上最稳的组合:并行发起 3~5 个改写查询,每路各召回 20~50 条,用 RRF 融合后取 Top-20 交给 Rerank。
查询分解处理多跳问题的思路是「自问自答」:先检索并回答子问题 1,把答案作为子问题 2 的上下文,逐步逼近最终答案。
2.5 重排与上下文压缩
Rerank(重排) 用 Cross-Encoder 模型对 (query, doc) 成对打分,精度远高于向量内积,但无法预先建索引,因此只能作用于小候选集:
1 | 向量/混合检索召回 Top-50 → Rerank 精排 → 取 Top-5 喂给 LLM |
这是投入产出比最高的一步。中文场景常用 BGE-Reranker 系列,英文可用 Cohere Rerank 等。
上下文压缩则是在有限的上下文预算里塞进更多有效信息:抽取式做法只保留与问题相关的句子,或直接让小模型摘要每个片段。注意压缩会带来信息损失,务必纳入评测。
2.6 完整的查询优化链路
flowchart LR
Q["原始 Query"] --> A{"意图路由"}
A -->|"事实型"| B["直接检索"]
A -->|"多跳复杂"| C["问题分解"]
A -->|"口语 / 指代"| D["查询改写"]
B --> E["多路召回<br/>向量 + BM25"]
C --> E
D --> E
E --> F["RRF 融合"]
F --> G["Cross-Encoder Rerank"]
G --> H["上下文压缩与去重"]
H --> I["LLM 生成"]
2.7 生成阶段:抑制幻觉
| 手段 | 做法 | 效果 |
|---|---|---|
| 强制引用 | 要求每个论断标注来源块编号 | 可溯源,便于人工核验 |
| 无据拒答 | prompt 明确「材料中没有就说不知道」 | 显著降低编造 |
| 降低温度 | temperature 设 0~0.3 |
减少发散 |
| Few-shot 约束 | 给 2~3 个「有据/无据」的示例 | 稳定输出格式 |
| 后置校验 | 用小模型判断答案是否被原文支持 | 可拦截大部分不忠实输出 |
一个可直接使用的生成 Prompt 骨架:
1 | 你是严谨的技术助理。请仅依据下面的【参考资料】回答问题。 |
三、RAG 测评方法与实例
「感觉回答变好了」不是工程结论。RAG 评测的关键是分层归因——先确认是检索错了还是生成错了,否则优化方向全靠猜。
3.1 三层评测框架
flowchart TB
subgraph L1["第一层:检索质量(有没有找到)"]
A1["Recall@K / Precision@K"]
A2["MRR / NDCG@K"]
A3["Hit Rate"]
end
subgraph L2["第二层:生成质量(有没有用好)"]
B1["Faithfulness 忠实度"]
B2["Answer Relevancy 答案相关性"]
B3["Context Precision / Recall"]
end
subgraph L3["第三层:端到端与业务"]
C1["答案正确率"]
C2["引用准确率"]
C3["人工评分 / 线上 A/B"]
end
L1 --> L2 --> L3
归因逻辑:Recall@K 低 → 检索问题,先别动 prompt;Recall@K 高但 Faithfulness 低 → 生成问题,改 prompt 或换模型。
3.2 第一层:检索指标
设对某个问题,标准答案片段集合为 G(gold),系统返回的 Top-K 列表为 R。
| 指标 | 公式 | 含义 | 适用 |
|---|---|---|---|
| Recall@K | ` | R ∩ G | / |
| Precision@K | ` | R ∩ G | / K` |
| Hit Rate@K | 1 if R ∩ G ≠ ∅ |
至少命中一个的比例 | 快速健康度检查 |
| MRR | 1 / rank_first_hit |
第一个正确答案排多前 | 关注首条质量 |
| NDCG@K | DCG@K / IDCG@K |
考虑排序位置的增益 | 多级相关性 |
其中 DCG@K = Σ (2^rel_i - 1) / log2(i + 1)。
3.3 第二层:生成指标(RAGAS)
RAGAS 是当前使用最广的 RAG 自动评测框架,核心指标:
| 指标 | 衡量什么 | 怎么算(思路) | 低分说明 |
|---|---|---|---|
| Faithfulness | 答案是否忠于检索到的上下文 | 把答案拆成若干论断,逐条判断能否由 context 推出 | 模型在编造 |
| Answer Relevancy | 答案是否切题 | 用答案反推可能的问题,与原问题比相似度 | 答非所问、绕圈子 |
| Context Precision | 检索到的上下文里,有用的是否排在前面 | 判断每个 context 是否支撑答案,加权平均 | 噪声排在前面 |
| Context Recall | 标准答案所需信息是否都被检索到 | 用标准答案的论断去核对每个 context | 检索漏了 |
| Answer Correctness | 与标准答案的事实一致性 | 事实重叠 + 语义相似度 | 综合偏低 |
前四个指标不需要人工标注答案也能算(需要问题 + 答案 + 上下文),这让它在没有标注团队时也能跑起来;Answer Correctness 和 Context Recall 则需要标准答案。
3.4 完整实例:从建评测集到定位问题
第一步:构造评测集
不需要几千条,50~200 条覆盖核心场景的问题就能发现大部分问题。关键是每条要有 question + ground_truth + gold_chunk_ids。
示例(节选自一个技术文档问答场景):
| id | question | ground_truth | gold_chunk_ids |
|---|---|---|---|
| 1 | Milvus 中 HNSW 的 M 参数控制什么? | 控制图中每个节点的最大出边数,越大召回越高、内存越大 | c_1042 |
| 2 | 如何把 IVF_FLAT 的召回率调高? | 增大 nprobe | c_2210 |
| 3 | 混合检索的 RRF 公式是什么? | score = Σ 1/(k + rank),k 通常取 60 | c_3301 |
| 4 | 数据量超过内存时推荐哪种索引? | DiskANN,索引主要落盘 | c_4102, c_4103 |
| 5 | 父子块检索是什么? | 小块检索、返回父块给 LLM | c_1180 |
构造原则:每条问题必须是知识库里真实有答案的,并且标注出「答案应该来自哪个块」——这是计算 Recall@K 的前提。可以从线上真实提问里采样,再人工补 gold 标注。
第二步:跑检索层评测
1 | from pymilvus import MilvusClient |
第三步:跑生成层评测(RAGAS)
1 | from datasets import Dataset |
第四步:读结果、做归因
假设第一轮跑出这样一张表(示例数据):
| 实验 | 配置 | Recall@5 | Context Precision | Faithfulness | Answer Relevancy |
|---|---|---|---|---|---|
| 基线 | 单路向量检索 + Top-5 | 0.68 | 0.61 | 0.94 | 0.88 |
| 实验 A | + 混合检索(RRF) | 0.81 | 0.66 | 0.95 | 0.89 |
| 实验 B | A + BGE-Reranker | 0.83 | 0.88 | 0.96 | 0.90 |
| 实验 C | B + Multi-Query 改写 | 0.91 | 0.87 | 0.96 | 0.92 |
怎么读这张表:
- 基线 的 Faithfulness 已经 0.94,说明模型没有乱编——问题不在生成端,不要急着换模型或改 prompt。
- **瓶颈在
Recall@5 = 0.68**:有三成问题,正确答案压根没被检索出来。此时优化 prompt 是无效的,因为材料都没给到。 - 实验 A 提升 +0.13:说明相当一部分问题是「专有名词精确匹配」失败,BM25 通道补上了。这一步验证了「混合检索」这个假设。
- 实验 B 的
Context Precision从 0.66 跳到 0.88,但 Recall 只涨 0.02:Rerank 不增加召回(候选集没变),它只是把有用的排到前面、把噪声挤出去——数据完全符合预期,说明 Rerank 正确生效了。 - 实验 C 让 Recall 从 0.83 → 0.91:Multi-Query 从不同角度发问,覆盖了原本漏掉的表述方式。这才是「提召回」的正确手段。
这张表最有价值的地方,是每一步验证了一个独立假设,而不是把所有优化一起上然后看总分涨没涨——那样一旦变差,你根本不知道该回退哪一步。
第五步:人工抽查不可省
自动指标会骗人。必须每周固定抽 20 条 badcase 人工看,重点关注三类:
- 指标高但体验差:
Faithfulness很高,但答案是「正确的废话」,没有解决问题——说明指标选错了 - 检索到了但没用上:gold chunk 在 Top-5 里,答案却没用它——生成端问题
- 答案对但引用错:内容正确,标注的来源编号对不上——
Faithfulness会漏掉这类错误
3.5 评测落地建议
| 建议 | 说明 |
|---|---|
| 先建 50 条金标集 | 不要等大而全,先有基线才能谈优化 |
| 固定评测集,只改一个变量 | 每次实验只动一个环节,否则无法归因 |
| 上线前回归 | 把评测集接入 CI,改动 prompt/模型/索引后自动跑 |
| 线上埋点闭环 | 记录用户追问率、点踩率、引用点击率,回流成新的评测样本 |
| 区分「检索失败」与「生成失败」 | 这是整个评测体系最重要的分流 |
四、Milvus 向量数据库选型与索引结构
4.1 为什么需要向量数据库
理论上用 FAISS + 自己写个服务也能跑。但当数据量上去、要求变多时,会陆续需要:增删改查、标量过滤、多租户隔离、水平扩展、高可用、一致性控制——这些正是一个数据库该干的事。
选型时按这几个维度评估:
| 维度 | 关注点 |
|---|---|
| 性能 | 召回率、QPS、延迟 P99、建索引耗时 |
| 容量与成本 | 内存占用、是否支持磁盘索引、能否用对象存储 |
| 功能 | 标量过滤、混合检索、多向量、分区、一致性级别 |
| 运维 | 部署复杂度、水平扩展、备份恢复、监控 |
| 生态 | SDK、与 LangChain / LlamaIndex 的集成、社区活跃度 |
4.2 主流方案对比
| 方案 | 定位 | 优势 | 局限 |
|---|---|---|---|
| Milvus | 分布式专用向量数据库 | 索引类型最全、存算分离、支持十亿级、GPU 索引 | 组件多,部署较重(已有 Lite 版缓解) |
| Qdrant | Rust 实现的向量库 | 单机性能好、过滤强、部署简单 | 超大规模生态不如 Milvus |
| Weaviate | 带模块化的向量库 | 内置向量化模块、GraphQL | 资源占用偏高 |
| pgvector | PostgreSQL 扩展 | 复用现有 PG、事务与 SQL 完整 | 数据量大后性能衰减明显 |
| FAISS | 向量检索库(非数据库) | 极致的检索性能、算法全 | 无持久化/增删改/过滤,需自建服务 |
| Chroma | 轻量嵌入式 | 上手极快,适合原型 | 生产级能力有限 |
| Elasticsearch | 搜索引擎 + 向量 | 全文检索成熟、混合检索自然 | 向量性能与成本不如专用库 |
选择建议:原型验证用 Chroma / Milvus Lite;已有 PG 且数据量在千万级以内用 pgvector;千万级以上或需要混合检索、多租户、GPU 加速,选 Milvus。
4.3 Milvus 架构
flowchart TB
C["客户端 SDK / Attu / RESTful"] --> P["接入层 Proxy<br/>负载均衡 · 鉴权 · 结果聚合"]
P --> CO["协调服务<br/>Root / Data / Query / Index Coordinator"]
CO --> W["工作节点(无状态)<br/>Query Node · Data Node · Index Node"]
W --> S1[("etcd<br/>元数据与服务发现")]
W --> S2[("对象存储<br/>MinIO / S3")]
W --> S3[("WAL<br/>Pulsar / Kafka / RocksMQ")]
| 层 | 组件 | 职责 |
|---|---|---|
| 接入层 | Proxy | 对外统一入口,负责鉴权、路由、结果归并 |
| 协调层 | 四类 Coordinator | 管理元数据、调度、负载均衡、DDL |
| 执行层 | Query / Data / Index Node | 无状态,可水平扩展;分别负责查询、写入、建索引 |
| 存储层 | etcd / 对象存储 / WAL | 元数据、数据文件、预写日志三分离 |
存算分离 + 日志即数据(Log-as-Data)是 Milvus 的关键设计:数据先写 WAL 保证持久性,再异步落对象存储;查询节点无状态,因此扩容和故障恢复都很快。另外还有部署形态的选择:
| 形态 | 适用 | 说明 |
|---|---|---|
| Milvus Lite | 原型、单机、百万级以内 | pip install pymilvus 即用,进程内嵌 |
| Standalone | 中小规模生产 | 单机 Docker Compose 部署 |
| Distributed | 大规模生产 | K8s 集群,组件独立扩缩容 |
4.4 索引的三要素
Milvus 中每种索引都可以拆成三个部分,理解这个模型比死记索引名有用得多:
| 组成部分 | 作用 | 常见实现 |
|---|---|---|
| 数据结构 | 粗过滤,快速缩小候选范围 | 倒排文件 IVF、图结构 HNSW / Vamana |
| 量化器(可选) | 压缩向量,降低内存与计算量 | SQ8(标量量化)、PQ(乘积量化) |
| 精化器 Refiner(可选) | 用高精度向量重算距离,找回精度 | FP32 精化 |
- IVF 系列:用聚类把向量分到
nlist个桶,查询时只扫描质心最近的nprobe个桶。桶数越多越省时间,但召回会降。 - 图结构(HNSW):多层导航小世界图,从稀疏的上层逐层下降定位近邻,查询复杂度接近对数级,延迟低、召回高,代价是内存占用大。
- DiskANN:基于 Vamana 图 + PQ 压缩,把索引放在磁盘上,用少量内存支撑十亿级数据,代价是可能受 IOPS 限制。
量化的取舍很直观:
| 量化方式 | 压缩率 | 内存(128 维) | 特点 |
|---|---|---|---|
| 不量化(FP32) | 1× | 512 B | 精度最高 |
| SQ8 | 4× | 128 B | 每维 8 位,速度快,精度损失小 |
| PQ(8 子量化器) | 32× | 16 B | 压缩比高,同压缩率下召回优于 SQ,但计算更慢 |
4.5 索引类型速查
| 索引 | 类型 | 内存 | 建索引 | 召回 | 典型场景 |
|---|---|---|---|---|---|
FLAT |
暴力检索 | 高 | 无 | 100% | 小数据、要求绝对准确 |
IVF_FLAT |
IVF | 中 | 快 | 高 | 通用大规模,可调 nprobe |
IVF_SQ8 |
IVF+SQ | 低(1/4) | 快 | 中高 | 内存受限,精度可让步 |
IVF_PQ |
IVF+PQ | 极低 | 中 | 中 | 内存极度受限 |
HNSW |
图 | 高 | 慢 | 很高 | 低延迟、高召回的生产主力 |
HNSW_SQ / HNSW_PQ |
图+量化 | 中/低 | 中 | 高 | 想省内存又要图索引 |
DiskANN |
磁盘图 | 低 | 慢 | 高 | 十亿级、内存装不下 |
SCANN |
IVF+各向异性量化 | 中 | 中 | 高 | 与 HNSW 类似的替代 |
GPU_CAGRA |
GPU 图 | 显存 | 快 | 高 | 高吞吐批量检索 |
SPARSE_INVERTED_INDEX |
稀疏倒排 | 低 | 快 | — | 稀疏向量 / BM25 混合检索 |
BIN_FLAT / BIN_IVF_FLAT |
二值 | 极低 | 快 | — | 二值向量(汉明距离) |
AUTOINDEX |
自动 | 自动 | 自动 | 自动 | 不想调参(Zilliz Cloud) |
度量类型要和索引匹配:
| 度量 | 适用 | 说明 |
|---|---|---|
L2 |
浮点向量 | 欧氏距离,越小越近 |
IP |
浮点向量 | 内积,必须先归一化才能当余弦用 |
COSINE |
浮点向量 | 余弦相似度,文本检索最常用(Milvus 内部会归一化) |
JACCARD / HAMMING |
二值向量 | 集合相似度 / 汉明距离 |
常见坑:向量已经归一化时用
IP和COSINE等价;但未归一化就用 IP,会得到「模长大的向量分数虚高」的错误排序。
4.6 关键参数与内存估算
| 索引 | 参数 | 取值范围 | 调优方向 |
|---|---|---|---|
IVF_* |
nlist |
1~65536 | 常用 4×sqrt(N);越大越快越不准 |
nprobe |
1~nlist | 在线可调,调大提召回、降 QPS | |
HNSW |
M |
4~64 | 每个节点最大出边数;越大越准越占内存 |
efConstruction |
8~512 | 建索引时的候选队列;越大索引质量越好、建得越慢 | |
ef |
top_k~32768 | 查询时参数,越大越准越慢,需 ≥ top_k | |
DiskANN |
max_degree |
1~512 | 图度数 |
search_list |
top_k~65535 | 查询候选数,越大越准越慢 |
内存估算实例(100 万条 128 维向量,来源:Zilliz 官方索引选型):
| 索引 | 组成 | 内存 |
|---|---|---|
| IVF_FLAT | 原始向量 1M × 128 × 4B | 512 MB |
| IVF_SQ8 | SQ8 压缩 1M × 128 × 1B + 质心 ≈ 1 MB | ≈ 129 MB |
| IVF_PQ | PQ 压缩 1M × 16 × 1B + 质心 | ≈ 9 MB |
| HNSW | 图结构 1M × 32 × 4B + 原始向量 512 MB | ≈ 640 MB |
| HNSW + PQ | 图结构 128 MB + PQ 压缩 8 MB | ≈ 136 MB |
这解释了为什么「数据量上不去」几乎总是内存问题:HNSW 的图结构本身就要额外占用一半以上的空间,而量化能把内存压下一个数量级。
4.7 选型决策
结合官方经验规律,给出一张可直接查的表:
| 条件 | 推荐索引 | 理由 |
|---|---|---|
| 数据量 < 100 万,追求简单 | HNSW 或 FLAT |
内存充足,直接上最稳的 |
| 100 万 ~ 1 亿,低延迟 | HNSW |
QPS 与召回最优 |
| 一百万级但内存紧张 | HNSW_SQ / IVF_SQ8 |
牺牲少量召回换 4 倍内存 |
| 内存极度受限 | IVF_PQ |
压缩 32 倍 |
| 十亿级 / 内存装不下 | DiskANN |
索引落盘,内存只需 1/4 数据量级 |
| top-K 很大(> 2000) | IVF_* |
图索引在大 top-K 下优势衰减 |
| 过滤比例 < 85% | 图索引 | 过滤后候选仍多,图遍历更优 |
| 过滤比例 85%~95% | IVF_* |
倒排桶更适应高过滤 |
| 过滤比例 > 98% | FLAT |
候选已经很少,暴力最准 |
| 需要 BM25 / 稀疏检索 | SPARSE_INVERTED_INDEX |
与稠密向量做混合检索 |
以上是经验起点,最终必须以你自己的数据做召回率-延迟曲线来定。
4.8 工程实践要点
① 建表时就把标量字段设计好。 检索往往要带过滤条件(只看某部门、某时间之后、有权限的文档):
1 | from pymilvus import MilvusClient, DataType |
② 用 ef / nprobe 在线调「召回-延迟」平衡,不用重建索引:
1 | res = client.search( |
③ 混合检索用内置的 RRF,避免自己写融合逻辑:
1 | from pymilvus import AnnSearchRequest, RRFRanker |
④ 一致性级别按场景选,别一律用默认值:
| 级别 | 语义 | 适用 |
|---|---|---|
Strong |
读到最新写入 | 写入后立即要查(如去重校验) |
Bounded |
有界滞后(默认) | 大多数生产场景 |
Eventually |
最终一致 | 高吞吐、可容忍延迟 |
Session |
保证读到本会话写入 | 用户写入后自己查看 |
⑤ 分区(Partition)做租户隔离。 多租户场景按租户分区,检索时指定分区,可显著减少扫描量;但分区数不宜过多(建议不超过数百个),否则元数据开销上升。
五、总结
把全文压缩成几条可以立刻用的结论:
- RAG 的本质是「用检索近似边缘化」,它的上限由检索决定——检索漏了,生成端再强也救不回来。
- 排障先分层归因:
Recall@K低就修检索(混合检索、多路查询、调分块),Faithfulness低才修生成(强制引用、无据拒答)。 - 性价比最高的三个动作:加 Rerank、加混合检索(BM25 + 向量 + RRF)、优化分块(父子块)。
- 评测必须分层且单变量:50 条金标集起步,每次只改一个环节,让每一步验证一个独立假设。
- Milvus 选索引先问三件事:数据量多大、内存有多少、要多大 top-K 和多高召回。默认
HNSW,内存不够降级到HNSW_SQ/IVF_PQ,十亿级上DiskANN。 - 索引不是一劳永逸的:
ef/nprobe是查询期参数,可以随时在线调整来平衡召回与延迟。
RAG 是一个系统工程,不是调一个模型就完事。真正拉开差距的,往往是分块策略、混合检索和评测闭环这些「不性感」的工程细节。
参考
- Lewis et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks, 2020
- Gao et al. Retrieval-Augmented Generation for Large Language Models: A Survey, 2023
- Liu et al. Lost in the Middle: How Language Models Use Long Contexts, 2023
- Gao et al. Precise Zero-Shot Dense Retrieval without Relevance Labels(HyDE), 2022
- Milvus 官方文档 — 索引说明
- Zilliz — 索引选不对,成本贵十倍!一文读懂向量索引选型
- RAGAS 官方文档 — 评测指标





