by 雪隐_上班了 from juejin.cn/user/143341... 欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权。
前言:从OCR到RAG,我的"文档自由"之路
上回说到,我用 Unlimited-OCR 把一本 600 多页的技术书啃成了 Markdown------标题层级、表格、公式全部保留,爽得我差点把 PaddleOCR 的 icon 从桌面删了。
但问题来了:书是"复制"出来了,怎么让 AI "读懂"它?
直接塞给大模型?600 页的 Markdown 就算切碎了也塞不进去,就算硬塞进去了,AI 也会像金鱼一样------聊到第三章忘了第一章。更别提每次问答都要传大量文本,延迟高、费用高,16G 显存也扛不住这么造。
于是我开始折腾 RAG(检索增强生成) ------给大模型装一个"外挂大脑",让它随时翻书、按需回答。今天就把这套本地向量数据库 RAG 方案完整拆出来,顺便聊聊怎么把上回的 OCR 成果丝滑接入。
选型:为什么 FAISS 是我最终的归宿?
在动手之前,我把市面上的向量数据库扫了一遍:
| 方案 | 优点 | 缺点 | 适合谁 |
|---|---|---|---|
| FAISS | Meta 出品,GPU 加速猛,10 亿向量随便造 | 纯单机,分布式要自己搭 | 我(以及和我一样不想折腾的人) |
| Milvus | 分布式成熟,云原生 | 部署复杂,吃内存 | 企业级生产环境 |
| Chroma | 轻量简单,5 分钟上手 | 性能一般,大规模拉胯 | 快速验证想法 |
| Qdrant | 检索效果好 | Rust 写的,二次开发费劲 | 追求极致效果 |
最后选了 FAISS,原因很简单------它把我这张 5060 Ti 16G 的价值榨到了极致。
① GPU 加速是真的香
python
# CPU 检索
index = faiss.IndexFlatL2(dimension)
# GPU 加速------就多了两行
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
就这么两行代码,检索速度直接起飞。实测在 5060 Ti 上,GPU 比 CPU 快 10-50 倍。Meta 官方数据说 FAISS 在 GPU 上处理 10 亿级向量,单次查询延迟仅 0.1 秒------虽然我的数据量没到那个级别,但"快"的感觉是一样的。
② 索引类型多到选择困难
IndexFlatL2--- 暴力检索,精确但慢,适合小数据集(几万条)IndexIVFFlat--- 倒排索引,先聚类再搜索,大数据集首选IndexIVFPQ--- 乘积量化,把高维向量压缩到 1/4 甚至 1/10 的内存占用IndexIDMap--- 支持自定义 ID,不用数组下标,方便和原文档映射
③ 单机能扛十亿级
FAISS 本来就是为大规模向量检索生的。单机几十亿向量不是梦,我这 600 页书才多少向量?杀鸡用牛刀了属于是。
整体架构:把 600 页书变成 AI 的"外挂大脑"
markdown
你的资料(上回 OCR 出来的 Markdown)
↓
文本分割成小块(chunk)------防止 AI 一次性吃太多
↓
Embedding 模型把文字变成"数字指纹"(向量)
↓
FAISS 向量数据库(建索引)------相当于给每段文字拍了一张"语义身份证"
↓
用户提问 → 找最相关的 N 个 chunk → 塞给 LLM → 生成回答
说人话:把书切成小段,每段配一个"指纹"存起来。 用户提问时,把问题也变成指纹,在数据库里找最相似的指纹对应的段落,再让 LLM 看着这些段落回答问题。
整个过程就像:你不是让 AI 把整本书背下来,而是给它配了一个随时翻书查资料的小助手。
核心代码:从零搭建本地 RAG
1. 文本分割------别让 AI 噎着
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", ".", " ", ""],
chunk_size=1000, # 每块 1000 字符------经验值,别乱改
chunk_overlap=200, # 相邻块重叠 200 字符
)
chunks = text_splitter.split_text(text)
chunk_overlap=200 很重要。 不然"我爱中华人民共和"和"共和国人民"这种就可能被一刀切开,语义断裂。重叠 200 字符相当于给每段文字留了一个"记忆缓冲带",保证上下文连贯。
2. Embedding 向量化------"语义指纹"生成器
python
import requests
import numpy as np
def get_embedding(text: str) -> np.ndarray:
resp = requests.post(
"http://localhost:1234/v1/embeddings",
json={
"model": "text-embedding-qwen3-embedding-8b",
"input": text
},
timeout=60
)
return np.array(resp.json()["data"][0]["embedding"], dtype='float32')
Embedding 模型我用的 Qwen3-Embedding-8B。这玩意儿有多猛?在 MTEB 多语言排行榜上排名第一(截至 2025 年 6 月,得分 70.58),中文专属基准 CMTEB 上更是以 73.84 分稳居第一。支持 100+ 种语言,上下文长度 32K------中文语义理解能力直接拉满。
不想自己跑模型的,可以用 LM Studio 部署,一键启动本地服务,端口默认 1234,代码直接跑。
3. 建 FAISS 索引------给"语义指纹"上户口
python
import faiss
vectors = [get_embedding(chunk) for chunk in chunks]
vectors_np = np.array(vectors).astype('float32')
dimension = vectors_np.shape[1]
index = faiss.IndexFlatL2(dimension) # L2 距离,简单粗暴效果好
index = faiss.IndexIDMap(index) # 支持自定义 ID
index.add_with_ids(vectors_np, np.arange(len(chunks)))
# 保存索引------以后不用重新向量化,省时间
faiss.write_index(index, "vector_index.faiss")
进阶玩法 :如果数据量上了百万级,别用 IndexFlatL2,换 IndexIVFPQ:
python
quantizer = faiss.IndexFlatL2(dimension)
index = faiss.IndexIVFPQ(quantizer, dimension, nlist=100, m=8, bits=8)
index.train(vectors_np)
index.add(vectors_np)
PQ(乘积量化)可以把内存占用降到原来的 1/4 甚至 1/10,检索精度还能保持在 95% 以上。16G 显存跑几百万向量毫无压力。
4. 检索 + 生成------让 AI 看着答案说话
python
def search(query, k=5):
query_vec = get_embedding(query).reshape(1, -1)
distances, indices = index.search(query_vec, k)
results = []
for i in range(k):
if indices[0][i] == -1:
continue
results.append(chunks[indices[0][i]])
return results
# 用户提问
context_chunks = search("learning 这个单词在托业里面考什么意思")
# 拼成 prompt 给 LLM------关键:让 AI "看着资料回答",而不是"凭记忆瞎编"
prompt = f"""你是托业词汇老师。根据以下资料回答问题:
{chr(10).join(context_chunks)}
问题:learning 在托业里考什么意思"""
answer = chat_local(prompt)
print(answer)
这一步的核心是 RAG 的"增强生成" ------不是让 AI 凭空回答,而是先检索相关段落,再让 AI 基于这些段落生成答案。这样既保证了答案的准确性,又避免了 AI 胡编乱造(俗称"幻觉")。
上回 OCR 的成果,怎么接进来?
还记得上回我用 Unlimited-OCR 生成的 600 页 Markdown 吗?直接喂给上面的文本分割器就行。
整个流水线变成了:
scss
扫描版 PDF
↓ (Unlimited-OCR)
结构化的 Markdown(标题、表格、公式全保留)
↓ (文本分割 + Embedding)
向量数据库(FAISS)
↓ (用户提问 + LLM)
精准回答
600 页书 → 5MB Markdown → 向量数据库 → AI 外挂大脑
一套组合拳下来,我的技术书不仅被"复制"了,还被 AI "理解"了。现在问它任何书里的知识点,它都能精准定位到相关段落,给出带引用来源的回答------比我自己翻书快 100 倍。
5060 Ti 16G 跑起来怎么样?
| 指标 | 数据 |
|---|---|
| Embedding 模型 | Qwen3-Embedding-8B(8B 参数) |
| LLM 模型 | Gemma-4-26b-A4B-QAT(量化版) |
| 显存占用 | ~14-15GB(Embedding + LLM 同时跑) |
| Embedding 速度 | ~20-30 tokens/s |
| LLM 生成速度 | ~15-25 tokens/s |
| 检索延迟 | < 50ms(600 页级别数据,GPU 加速) |
16GB 显存跑 8B 的 Embedding 模型 + 26B 的量化 LLM,刚刚好,不爆不卡 。Embedding 和 LLM 全部本地,零 API 费用,数据不外传。
几个我踩过的坑
1. Embedding 模型别选错
中文场景千万别用纯英文训练的模型。Qwen3-Embedding 对中文有专门优化,效果显著优于通用模型。在中文专属基准 CMTEB 上,Qwen3-Embedding-8B 以 73.84 分稳居第一------选它就对了。
2. Chunk 大小不是越大越好
1000 字符是经验值。太大了,相关上下文被稀释;太小了,语义可能不完整。我试过 500、1000、2000,最后 1000 最稳。复杂文档(比如带公式的)可以适当调小。
3. 向量数据库不会自动更新
文档变了要重新建索引。我的做法是写了个脚本,检测到 Markdown 文件更新就自动重新跑一遍 Embedding + 建索引。或者手动触发也行,反正 600 页全量重建也就几分钟。
4. 别在 Embedding 这一步贪多
Qwen3-Embedding 系列从 0.6B 到 8B 全都有。0.6B 版本单次嵌入平均 120ms,8B 版本质量更高但更慢。我的建议:先拿 0.6B 跑通流程,确认效果后再升级到 8B。
总结:从"复制文档"到"理解文档"
上回我用 Unlimited-OCR 解决了 "怎么把纸上的字变成电子版" 的问题。这次我用 FAISS + RAG 解决了 "怎么让 AI 读懂电子版" 的问题。
两套方案加起来,就是一条完整的本地文档处理流水线:
| 阶段 | 工具 | 产出 |
|---|---|---|
| 文档解析 | Unlimited-OCR | 结构化 Markdown |
| 向量化 | Qwen3-Embedding + FAISS | 可检索的向量数据库 |
| 智能问答 | 本地 LLM + RAG | 精准的上下文回答 |
全部本地运行,零 API 费用,数据不出机箱。
| 场景 | 推荐方案 |
|---|---|
| 扫描版 PDF 转 Markdown | Unlimited-OCR(逐页循环) |
| 本地文档智能问答 | FAISS + Qwen3-Embedding + 本地 LLM |
| 海量数据检索 | FAISS GPU 版 + IVF-PQ 索引 |
| 快速验证想法 | LM Studio + Chroma |
| 生产环境 | Milvus / Qdrant |
5060 Ti 16G 跑这套本地 RAG 完全没问题------Embedding + LLM 同时跑,显存稳定在 14-15GB,不爆不卡,温度都没超过 70°C。还要什么自行车?