向量数据库与嵌入模型:Chroma/FAISS/Milvus 选型
RAG 链路里有两个部件值得单独拿出来说:嵌入模型决定"搜得准不准",向量数据库决定"存得下、查得快"。上一篇它们还是配角,这一篇是主角——中文场景的嵌入模型怎么挑,Chroma / FAISS / Milvus 各适合多大规模,以及那条最容易被忽略的硬规则:换了嵌入模型,整个库必须重建。
一、向量与嵌入是什么
大模型处理文字之前,文字必须先变成数字。向量与嵌入就是"文字 → 数字"这一层的标准答案:
嵌入(Embedding)是一个模型(或函数),输入一段文字,输出一串数字(浮点数数组);这串数字叫向量。关键性质是语义近的文字,向量在空间里距离近:"iPhone 15 怎么换电池"和"苹果手机更换电池教程"字面不同,但向量距离很近;而"iPhone 15 怎么换电池"和"红烧肉的做法"向量距离很远。于是"找相关文档"就变成了一个纯数学问题:算问题向量和所有块向量的距离,取最近的几个。
“距离"一般用余弦相似度(衡量方向接近程度)或欧氏距离来算——向量库底层替你做好,你只需要调"取前 k 个”(top-k)。
三个立刻能用的推论:
- 嵌入模型决定了"什么算相关"。同一个问题,换嵌入模型,命中的块可能完全不同——它是 RAG 质量的隐性上限,值得单独选型(第二节);
- 入库和检索必须用同一个嵌入模型。向量是模型"盖章"的产物,A 模型盖的章 B 模型认不出来,距离算出来就是乱序——这就是"换嵌入必须重建库"的数学原因;
- 向量的维度是模型属性。模型输出 1024 维,库里所有向量就必须都是 1024 维,维度对不上直接报错(第六节第 3 条)。
嵌入模型和对话 LLM 是两回事:LLM 负责"生成回答",嵌入模型只负责"把文本变向量、算距离",参数量小、不吃多少显存,纯 CPU 机器也能跑——这也是 AI-19 说"RAG 本身不需要 GPU"的原因。
二、嵌入模型选型
按部署位置分三档,覆盖"完全本地 / 本地为主 / 愿意用云"三类用户:
2.1 本地 Ollama(与 LLM 同源,本地部署首选)
- bge-m3:中文场景的稳选择,对中文文档的语义区分更稳,多语言也支持(模型名以 ollama.com/library 实时为准);
- nomic-embed-text:轻量通用,资源占用小,英文语料表现好,中文语料建议优先试 bge-m3。
两者都走 Ollama 的嵌入接口,和对话 LLM 共用同一个localhost:11434服务,同源部署少一层跨服务协调,是本地 RAG 的默认搭配。
2.2 中文专用(HuggingFace 路线)
对中文检索质量有更高要求时,BGE 系列(如 bge-large-zh-v1.5 等)是中文检索领域长期被验证的选择,从 HuggingFace 下载后用框架加载(国内走HF_ENDPOINT=https://hf-mirror.com或 ModelScope,思路同《AI-06 模型下载全攻略》)。加载方式与参数以各模型仓库 README 为准,本篇不展开代码。
2.3 云端(对照参考,非本地部署主线)
OpenAI 的text-embedding-3-small/text-embedding-3-large是云端嵌入的代表,按量计费、免运维,适合"文档不敏感 + 不想管模型"的场景;具体版本、维度与价格以官方文档为准。本系列主线是本地部署,云端只作为选型参照。
2.4 维度与大小的权衡
经验值(不绑定具体模型,以各模型官方说明为准):
- 维度越高,表达能力越强,细粒度语义区分更好,检索上限更高;
- 代价是每个向量占的内存/磁盘线性增长、距离计算变慢:10 万块文档,从 512 维换到 1024 维,向量存储直接翻倍;
- 所以选型是"够用就好":个人/小团队知识库,本地 bge-m3 或 nomic-embed-text 的维度完全够用,不必追高维大模型;换维度 = 旧库向量全部作废,和换模型是同一条坑。
我的选型顺序:先确定语料语言(中文 → bge-m3 起步)→ 确定部署位置(本地 → Ollama)→ 维度够用即可,把预算留给对话 LLM 的选型。
三、三大向量库对比与选型
| 维度 | Chroma | FAISS | Milvus |
|---|---|---|---|
| 部署方式 | 嵌入式,pip install chromadb | 库,pip install faiss-cpu(faiss-gpu 需 GPU) | Docker 部署,独立服务 |
| 定位 | 个人 / 小数据量,开箱即用 | 内存中检索,数据量大、追求速度 | 生产 / 海量数据,独立运维 |
| 持久化 | 内置持久化(PersistentClient) | 需自己序列化/落盘 | 自带存储与副本机制 |
| 运维成本 | 无(随 Python 进程) | 低(随应用进程) | 较高(独立容器 + 配置) |
| 典型场景 | 个人知识库、RAG 原型 | 百万级向量、纯 CPU 也能跑 | 企业生产、多服务共享 |
选型结论(先记住这三句话,后面都是解释):
- 个人 / 小数据 → Chroma。一个
pip install搞定,没有服务要管,AI-19 的最小 RAG 就用它。文档块数量在几千到几十万这个量级、单机自用,Chroma 完全够; - 数据量大、追求检索速度 → FAISS。它是纯内存索引库,检索性能在三者中表现突出(数据载入内存,CPU 版即可跑得很好),代价是要自己管理"索引从磁盘加载/保存"这一步;
- 生产 / 海量 / 多服务共享 → Milvus。独立服务部署(Docker),有完整的存储、副本与扩展机制,适合"一个知识库给多个应用用"或数据量超出单机内存的场景。Milvus 的 Docker 部署与 GPU 全家桶搭配见《AI-40 Docker-GPU一键全家桶》。
一个常见误区:向量库不是"越高级越好"。Chroma 和 Milvus 回答同一个问题的检索质量差异,远小于嵌入模型差异带来的影响——先选对嵌入模型,再按数据规模选库,顺序别反。
四、验证:Chroma 最小可用(入库 + 查询)
AI-19 演示了完整 RAG,这里把"向量库本体"的最小用法单独摘出来,只看"入库 + 相似度查询"。接口迭代较快,函数名与参数以 chromadb 官方文档当前版本为准,本篇不罗列未核实的字段。
pip install chromadb -i https://pypi.tuna.tsinghua.edu.cn/simpleimportchromadb# 嵌入:AI-19 已定义,这里复用(调 Ollama bge-m3,# 接口字段以 Ollama 官方文档为准)defembed(texts):importrequests r=requests.post("http://localhost:11434/api/embed",json={"model":"bge-m3","input":texts})returnr.json()["embeddings"]# 持久化客户端:向量库数据落在 ./rag_store 目录client=chromadb.PersistentClient(path="./rag_store")col=client.get_or_create_collection("manual")# 入库:向量 + 原文一起存(metadata 可附来源信息)docs=["本产品保修期为一年。","保外维修仅收取工本费。","退换货需在 7 天内提出。"]col.upsert(ids=["1","2","3"],embeddings=embed(docs),documents=docs)# 查询:问题向量化 → 库内找最近的 k 个hits=col.query(query_texts=["保修期多长?"],n_results=3)print(hits["documents"][0])# 应命中"本产品保修期为一年。"验证标准:
- 入库后重启进程,用同一个
path重新建PersistentClient,col.count()仍能读到原有向量数——持久化生效; - 查询命中:上面示例的第一个命中应是"本产品保修期为一年。",若命中顺序不对,先确认嵌入调用成功(打印向量前几个分量看是否全 0 或报错);
- 维度一致:向库里写入不同嵌入模型的向量时报错,说明维度不匹配——按第六节第 3 条处理。
五、进阶技巧
5.1 换嵌入模型 / 换向量库:两条"重建"规则
这两条是 RAG 运维里容易被忽略的硬约束,一次说清:
- 换嵌入模型 = 必须重建库。新旧模型输出的向量不在同一个空间,维度可能不同、即使维度相同方向语义也对不上,直接复用旧库检索结果就是乱序。正确做法:换持久化目录(或新建 collection)→ 全部文档重新切块、嵌入、入库。我一般把"嵌入模型名"写进 collection 名(如
manual_bge-m3),防止误混; - 换向量库 = 也要重建库。Chroma 的格式 FAISS 读不了,反之亦然;迁移只能"把原文档重新走一遍入库流程"。所以向量库在数据规模确定前尽量一次选到位,别频繁换。
对照记忆:换 LLM 不重建库(LLM 只是"答题人",不碰向量),换嵌入才重建。
5.2 持久化与备份
- Chroma:用
PersistentClient指定目录(如./rag_store),向量与元数据都落盘;备份就是整个目录拷贝,恢复就是换回去,简单直接。建议把这个目录放在数据盘(远离 C 盘),纳入日常备份; - FAISS:索引默认在内存,需要自己调用序列化接口把索引写到磁盘文件、启动时再加载回来(方法以 FAISS 官方文档为准)。备份的是那个索引文件 + 你的原文档映射;
- Milvus:Docker 部署时数据在挂载卷里,备份思路是"卷快照 + 重新部署",具体以官方文档当前版本为准。
一个通用习惯:原始文档要自己留一份。向量库可以随时重建,原始 PDF/MD 丢了就真丢了——备份优先级:原文档 > 向量库目录。
5.3 与平台类工具的衔接
如果你在 AnythingLLM(AI-17)里把向量库从默认 LanceDB 换成 Chroma/Milvus,或在 Dify(AI-18)里配外部向量库,底层规则完全一样:嵌入模型与向量库是两个独立配置、换嵌入必须重建、维度必须一致。本篇的选型表可以直接用来填那些设置页——知道每个下拉选项背后是什么,配置时就不会盲选。
六、故障排查(按层定位)
| # | 症状(报错原文) | 层 | 原因 | 解决 |
|---|---|---|---|---|
| 1 | 连嵌入失败:连接localhost:11434被拒 / Connection refused | 服务 | Ollama 没起(嵌入模型也走 Ollama) | ollama list确认本体与嵌入模型都在;ollama pull bge-m3补拉;端口 11434 被占则查占用进程 |
| 2 | 入库报维度不匹配(如Embedding dimension ... does not match类报错) | 框架 | 换过嵌入模型,新旧向量维度/空间不一致 | 换持久化目录或新建 collection,用当前嵌入模型全量重建;同库锁定一个嵌入模型 |
| 3 | 检索不到相关内容 / 命中顺序乱 | 应用 | 嵌入模型与检索侧不一致、或文档没入库成功 | 核对入库与检索用同一嵌入模型;小文档单独重建验证;打印命中块人眼核对(思路同 AI-19 第六节) |
| 4 | ModuleNotFoundError: No module named 'chromadb'/faiss | 环境 | 装包环境不对 / 激活了错误 venv | where python(Win)/which python(Linux)确认解释器;重新激活后重装;Chroma 与 FAISS 不要混装在混乱环境里 |
| 5 | pip install chromadb/faiss-cpu下载卡住 /ConnectionError/ 超时 | 网络 | 国内访问 PyPI 受限 | 走清华镜像-i https://pypi.tuna.tsinghua.edu.cn/simple;Chroma 首跑可能拉默认嵌入组件,HF_ENDPOINT=https://hf-mirror.com兜底 |
| 6 | 进程被杀Killed/ dmesgoom-kill(大量向量入库或 FAISS 载入时) | 内存 | 物理 RAM 不足(非显存):FAISS 全量索引载入内存 / 一次性嵌入文档太多 | WSL2 在.wslconfig加swap=后wsl --shutdown生效;分批入库;FAISS 评估索引是否真能放进内存,放不下换 Chroma 或 Milvus |
| 7 | 数据量变大后检索变慢 | 应用 | Chroma 随数据增长、FAISS 索引类型不当 | 先查嵌入与 top-k 配置;数据量明显超出 Chroma 舒适区时按第三节升级到 FAISS/Milvus |
| 8 | port ... already in use(Milvus/Docker 部署时) | 端口 | 同机已有服务占用映射端口 | netstat -ano | findstr :3000(Win)/lsof -i:3000(Linux)找占用进程,换端口或停掉旧服务 |
排错顺序:先底后顶——Ollama 通不通(curllocalhost:11434/api/tags探针)→ 包与环境对不对 → 文档有没有入库成功 → 维度/嵌入一致性 → 最后才怀疑数据量与性能。多数这类直觉,最后都落在"嵌入模型配错或换过没重建"上。
七、本篇自检清单
- 能说清嵌入与向量的定义,以及"语义近 → 向量近"如何把"找相关"变成"算距离"
- 牢记两条硬约束:入库与检索必须同一嵌入模型;换嵌入模型/换维度 = 重建库
- 会按语料语言与部署位置选嵌入模型:中文 → bge-m3 起步、本地 → Ollama、云端对照 text-embedding-3-small/large
- 知道维度与大小的权衡(越高越准也越慢越吃内存,够用就好,预算留给 LLM)
- 会用第三节对比表在 Chroma / FAISS / Milvus 间选型,并说清"库的差异远小于嵌入模型的差异"
- 已跑通 Chroma 最小可用(PersistentClient 入库 + 查询 + 重启后数据还在)
- 知道持久化与备份:Chroma 整目录拷贝、FAISS 自己序列化、原文档优先于向量库备份
- 维度不匹配 / OOM Kill / 11434 连不上 / 端口占用四类报错能独立排掉