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
↓
判断需要什么工具
↓
调用工具
↓
获得结果
↓
继续思考
↓
生成最终答案
这将是整个专栏从“本地大模型”迈向“本地智能体”的关键一篇。
网硕互联帮助中心

评论前必须登录!
注册