从零手写一个 RAG 系统(实战学习笔记)
去年写过一篇 RAG 概念科普,这次不一样:在本地从零手写了一个最小 RAG 系统。不引入 LlamaIndex、LangChain 的检索部分,链路里的每一环——分块、embedding 调用、向量存取、相似度检索——都是自己写的。
这么做的原因很简单:目标是学习,而只有手写一遍,才能回答那些框架文档不会告诉你的问题:
- embedding 到底返回了什么?数值长什么样?
- 为什么"苹果"和"汽车"的相似度也有 0.53?
- 检索为什么不能只看 top-k,还要看分数?
- 全量遍历检索在什么时候会撑不住?
这篇文章是完整的过程记录,包含所有实验结果和踩坑经历。
一、环境:老显卡上的 Ollama
选型结论:本地模型 bge-m3(1024 维,中文效果好,离线免费),通过 Ollama 提供 OpenAI 兼容的 /v1/embeddings 接口。
踩了个大坑:本机是 GTX 1060(Pascal 架构,太老),新版 Ollama 的 CUDA 内核一加载就崩:
llama-server process has terminated:
CUDA error: the provided PTX was compiled with an unsupported toolchain排查过程值得记录(每个变量都试过):
| 尝试 | 结果 |
|---|---|
OLLAMA_GPU_LAYERS=0(0 层上 GPU) | ❌ 无效,CUDA 仍初始化 |
OLLAMA_VISIBLE_DEVICES=""(屏蔽 GPU) | ❌ 无效,空值被当成"全部可见" |
OLLAMA_LLM_LIBRARY=cpu(强制 CPU 推理库) | ✅ 生效 |
经验:embedding 是纯推理(无生成),小模型跑 CPU 完全够用。12 个分块建索引总共才 2 秒。以后遇到老显卡 + 新 CUDA 内核的组合,OLLAMA_LLM_LIBRARY=cpu 是第一选择。

