云计算百科
云计算领域专业知识百科平台

《别只用向量搜索:BM25 + FAISS 打造工业级 RAG 检索系统》

LlamaAI本地部署实战专栏第7篇

上一篇我们已经实现了一个基础 RAG 系统,但很快会遇到一个非常现实的问题:

知识库里明明存在答案,为什么AI还是找不到?

本篇我们从底层检索原理出发,把简单的“向量搜索”升级成 BM25 + FAISS 混合检索,最后再介绍 Reranker,让本地RAG真正进入工程化阶段。


一、上一篇的RAG为什么还不够?

上一篇我们实现了:

PDF

文本切片

Embedding

FAISS

相似度搜索

Llama

看起来已经完整。

但实际使用时可能出现:

用户问:

CUDA 12.6 如何安装?

知识库里面明明存在:

CUDA Toolkit 12.6 Installation Guide

但是FAISS返回:

CUDA环境变量配置方法
GPU驱动安装方法
CUDA Runtime介绍

而不是最需要的:

CUDA Toolkit 12.6安装步骤

为什么?

因为:

向量相似 ≠ 关键词完全匹配。

这就是本篇要解决的问题。


二、RAG检索到底在做什么?

首先重新理解RAG。

用户输入:

CUDA 12.6如何安装?

系统实际上需要完成:

问题

检索

找到相关文档

交给LLM

生成答案

因此:

RAG最终效果,很大程度取决于检索质量。

如果检索阶段拿错了资料:

错误资料

LLM

错误答案

即使你的Llama模型非常强,也很难凭空解决。


三、为什么单纯FAISS会出现问题?

FAISS本质上是:

向量相似度搜索工具。

假设:

问题:

CUDA 12.6如何安装?

经过Embedding:

[0.21, 0.83, 0.17, …]

知识库:

文档A → [0.20, 0.81, 0.19, …]
文档B → [0.72, 0.14, 0.82, …]
文档C → [0.23, 0.80, 0.18, …]

FAISS计算距离:

Query

Vector Search

Top-K

它关注的是:

语义空间中的距离。

而不是:

“CUDA 12.6”这个关键词有没有出现。


四、什么是BM25?

BM25是一种经典的信息检索算法。

它不是Embedding。

也不是神经网络。

它主要考虑:

  • 关键词是否出现
  • 关键词出现频率
  • 文档长度
  • 关键词稀有程度

例如:

查询:

CUDA 12.6 安装

文档:

CUDA 12.6 Installation Guide

BM25会发现:

CUDA √
12.6 √
安装 语义相关

因此给它较高的相关性分数。


五、BM25和FAISS有什么区别?

可以简单理解:

FAISS:

“这两个文本意思像不像?”

BM25:

“这几个关键词是不是出现在这篇文档里?”


例如用户:

RTX 4090 显存多少?

知识库:

A:
RTX 4090拥有24GB显存。

B:
GPU显存管理技术。

C:
CUDA编程模型介绍。

BM25非常擅长找到:

RTX 4090
24GB
显存

而向量搜索则可能返回:

GPU显存管理
GPU架构
CUDA显存

两者各有优势。


六、为什么要做混合检索?

现在我们把两种方法结合:

用户问题
|
———————
| |
BM25 FAISS
| |
关键词检索 语义检索
| |
———– ———
|
结果融合
|
Top-K
|
Reranker
|
Llama

这就是:

Hybrid Search(混合检索)


七、BM25安装

Python环境:

pip install rank-bm25 jieba

中文文本建议进行分词。

例如:

import jieba

text = "CUDA是一种GPU计算平台"

tokens = list(jieba.cut(text))

print(tokens)

可能得到:

['CUDA', '是', '一种', 'GPU', '计算', '平台']


八、建立BM25索引

假设我们已经有:

chunks = [
"CUDA是一种GPU计算平台",
"RTX 4090拥有24GB显存",
"Transformer是一种神经网络架构",
"FAISS可以进行向量相似度搜索"
]

首先分词:

import jieba

tokenized_chunks = [
list(jieba.cut(chunk))
for chunk in chunks
]

然后:

from rank_bm25 import BM25Okapi

bm25 = BM25Okapi(
tokenized_chunks
)

BM25索引建立完成。


九、BM25检索

用户:

query = "RTX 4090显存多少"

分词:

query_tokens = list(
jieba.cut(query)
)

搜索:

scores = bm25.get_scores(
query_tokens
)

得到:

文档A:0.3
文档B:8.7
文档C:0.1
文档D:0.2

显然:

文档B

最相关。


十、FAISS检索

之前我们已经建立了FAISS:

distance, ids = index.search(
question_vector,
3
)

例如:

FAISS结果:

chunk_8
chunk_2
chunk_15

现在:

BM25:

chunk_2
chunk_7
chunk_21

FAISS:

chunk_8
chunk_2
chunk_15

可以发现:

chunk_2

同时被两种算法找到。

这通常意味着:

它很可能是高相关结果。


十一、最简单的结果融合

一种简单方法:

BM25分数
+
FAISS分数
=
最终分数

例如:

Document A

BM25 = 8.2
FAISS = 0.85

可以:

Final Score

= α × BM25
+ β × FAISS

例如:

α = 0.5
β = 0.5

但是这里存在一个问题:

BM25和向量距离的数值范围并不相同。

因此直接相加并不理想。


十二、RRF:更实用的融合方法

实际工程中可以使用:

Reciprocal Rank Fusion

简称:

RRF

核心思想:

不直接比较两个算法的原始分数。

而比较:

排名。

公式:

RRF(d)
=
Σ 1 / (k + rank(d))

例如:

BM25:

A → 第1
B → 第2
C → 第3

FAISS:

B → 第1
A → 第2
D → 第3

那么:

A:
BM25 Rank 1
FAISS Rank 2

B:
BM25 Rank 2
FAISS Rank 1

最终:

A和B都会获得较高分数。


十三、实现RRF

Python:

def rrf_score(results, k=60):

scores = {}

for result in results:

for rank, doc_id in enumerate(
result,
start=1
):

scores[doc_id] = (
scores.get(doc_id, 0)
+ 1 / (k + rank)
)

return scores

使用:

bm25_results = [
2, 7, 21
]

faiss_results = [
8, 2, 15
]

scores = rrf_score(
[
bm25_results,
faiss_results
]
)

排序:

sorted(
scores.items(),
key=lambda x: x[1],
reverse=True
)

结果可能:

2
8
7
15
21

这样:

chunk_2

被两个检索器同时发现,自然排在前面。


十四、为什么还需要Reranker?

到这里:

BM25
+
FAISS

Top-K

已经比单独FAISS强很多。

但还有一个问题:

Top-K不一定全部真正相关。

例如:

用户:

CUDA如何配置环境变量?

检索返回:

Top 10

里面可能:

真正相关:3个

一般相关:4个

无关:3个

如果把10个全部交给LLM:

10个Chunk

Prompt

Llama

会导致:

  • Context变长
  • 推理速度下降
  • 噪声增加
  • 模型更容易受到无关内容干扰

所以需要:

Reranker重新排序。


十五、Reranker是什么?

Reranker:

对Query和候选文档进行更精细的相关性判断。

流程:

用户问题
|
BM25 + FAISS
|
Top 20
|
Reranker
|
Top 5
|
Llama

也就是说:

第一阶段:

快速搜索。

第二阶段:

精确判断。


十六、为什么不能直接让Llama排序?

当然可以。

例如:

Query:

CUDA如何安装?

Document A
Document B
Document C

然后让Llama:

请按照相关性排序。

但是这样成本较高。

因为:

每次检索

调用LLM

大量Token计算

Reranker则专门解决:

Query-Document相关性判断。

因此速度和成本更加合适。


十七、完整工业级RAG架构

现在我们的架构升级成:

用户问题
|
———————-
| |
BM25 FAISS
| |
Top-K Top-K
| |
———RRF———-
|
Top-20
|
Reranker
|
Top-5
|
Prompt
|
llama-server
|
Llama
|
答案

这已经是非常典型的:

Hybrid RAG Pipeline


十八、实际项目中的代码结构

建议把项目组织成:

rag-project/

├── data/
│ └── documents/

├── models/
│ └── embedding/

├── vectorstore/
│ └── faiss.index

├── bm25/
│ └── index.pkl

├── ingest.py
├── retriever.py
├── reranker.py
├── llm.py
└── main.py

职责:

ingest.py

处理文档

retriever.py

BM25 + FAISS

reranker.py

重排序

llm.py

调用llama-server

main.py

整体流程

这比把所有代码写在一个Python文件里更适合后续扩展。


十九、完整查询流程

假设用户:

RTX 4060运行7B模型需要多少显存?

第一步:

Query

第二步:

BM25:

找到包含:

RTX 4060
7B
显存

的文档

第三步:

FAISS:

找到语义相似:

GPU显存需求
模型量化
Q4模型

第四步:

RRF:

融合两个结果

第五步:

Reranker:

Top 20

Top 5

第六步:

构建Prompt:

请根据以下资料回答问题。

资料1:
……

资料2:
……

资料3:
……

问题:
RTX 4060运行7B模型需要多少显存?

第七步:

Prompt

llama-server

本地Llama

答案


二十、为什么混合RAG通常比单一检索更加可靠?

可以从三个维度理解。

FAISS

擅长:

语义搜索

例如:

“显卡内存”

“GPU显存”


BM25

擅长:

精确关键词

例如:

RTX 4060
CUDA 12.6
Q4_K_M

这些技术名词非常重要。


Reranker

擅长:

精细相关性判断

因此:

BM25
+
FAISS
+
Reranker

形成三级检索体系:

第一层:

快速召回

第二层:

混合融合

第三层:

精确排序


二十一、RAG性能优化的核心参数

真正做RAG时,不要只盯着模型。

下面这些参数同样重要:

Chunk Size

例如:

500
1000
1500
2000

过小:

上下文不完整

过大:

噪声增加


Chunk Overlap

例如:

50
100
200

用于减少切片边界造成的信息丢失。


Top-K

例如:

Top 3
Top 5
Top 10
Top 20

一般可以:

召回Top 20

Reranker

Top 5


二十二、一个非常重要的工程经验

很多人做RAG:

Embedding模型换一个

向量数据库换一个

LLM换一个

但效果还是不好。

实际上:

RAG效果的瓶颈经常不是LLM,而是检索。

一个强模型:

错误资料

强LLM

错误答案

一个普通模型:

正确资料

普通LLM

高质量答案

因此:

先把检索做好,再谈模型。


二十三、最终RAG架构

到这里,我们的本地AI已经变成:

用户
|
Query
|
——————-
| |
BM25 FAISS
| |
Top-K Top-K
| |
——- RRF ——-
|
Top 20
|
Reranker
|
Top 5
|
Prompt
|
llama-server
|
GGUF模型
|
CUDA GPU
|
答案

这已经不再是一个简单的:

PDF + LLM

而是一个完整的:

本地Hybrid RAG系统。


二十四、本篇总结

这一篇我们解决了基础RAG最重要的问题:

如何提高检索准确率?

核心技术路线:

FAISS

语义搜索

BM25

关键词搜索

RRF

结果融合

Reranker

精确排序

Llama

生成答案

最终形成:

BM25
+
FAISS

Hybrid Search

RRF

Reranker

Llama.cpp


下一篇预告

《本地Llama Agent开发:让大模型学会调用工具》

到这里,我们的AI已经可以:

理解问题 + 搜索知识 + 生成答案。

但是它仍然只能“说”。

下一步,我们让它真正:

做事情。

下一篇将实现:

  • 什么是AI Agent
  • Function Calling原理
  • Tool Calling
  • 文件操作工具
  • Python工具
  • 搜索工具
  • Agent循环
  • 本地Llama + RAG + Tools
  • 构建第一个本地AI Agent

最终实现:

用户

Llama

判断需要什么工具

调用工具

获得结果

继续思考

生成最终答案

这将是整个专栏从“本地大模型”迈向“本地智能体”的关键一篇。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 《别只用向量搜索:BM25 + FAISS 打造工业级 RAG 检索系统》
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!