Table of Contents
如何在 12 GB 内存的电脑上本地检索机密 PDF,而不过度搭建 RAG
假设你有每天记录的纯文本日志,还有一大批 PDF;电脑只有 12 GB 内存,没有独立显卡;最重要的要求是资料不能离开本机。
这时很容易一上来就选择本地大模型、向量数据库、RAG 编排框架和聊天界面。可是,如果真正的需求只是“输入一个大概意思,找到对应文件和段落”,那么更可靠的起点,是先做一个关闭大模型后仍然能用的检索系统。
先区分你所说的“搜索”
下面其实是四种不同的产品:
- 找文件:返回最可能相关的文件和匹配片段。
- 找段落:返回最相关的页码或章节,并保留来源路径。
- 问答:根据检索出的段落组织一段回答。
- 文档分析:比较、分类或从大量资料中提取结构化事实。
如果只需要前两项,完全可以不做文本生成。对于小型、仅 CPU 的电脑,确定性的检索结果更快、更容易审计,也更容易发现错误在哪里。
安装任何工具之前,先写下 20–30 个真实问题,并记录每个问题应当找到的文件或页码。这就是你的检索测试集。没有它,新增组件只会让演示更复杂,却无法证明搜索真的变好了。
第一步:盘点资料
至少记录文件路径、格式、语言、是文本 PDF 还是扫描件、使用权和敏感级别。
原始资料应保持只读。提取后的文本、OCR 结果、索引和缓存写入单独的派生数据目录。搜索索引本身也可能包含绝大部分原文,因此同样是敏感数据,不能因为它不是 PDF 就放松保护。
第二步:先提取文本,再谈 AI
如果 PDF 已经有可选择的文字,Poppler 的 pdftotext 是一个小而可预测的起点:
pdftotext -layout input.pdf output.txt
-layout 会尝试保留页面的物理布局,但有些索引更适合简单阅读顺序。不要假设一种设置适合全部资料,先在代表性样本上比较。可以参考 Poppler 项目和 `pdftotext` 手册。
扫描页需要单独的 OCR 阶段。OCRmyPDF 可以为扫描 PDF 增加可检索文字层。保留原件,输出新文件:
ocrmypdf --skip-text input.pdf searchable.pdf
pdftotext -layout searchable.pdf searchable.txt
对于文字页和扫描页混合的 PDF,--skip-text 很方便。但人名、日期、表格、生僻字和多语言页面仍然需要抽样检查。自定义 OCR 很容易成为整个项目中最大的工作,所以应先测量到底有多少页需要 OCR。
第三步:先试普通全文检索
如果想直接使用桌面界面,Recoll 可以索引文档内容并返回相关文件;支持的搜索方式和 PDF 等格式需要的外部工具都写在用户手册中。
如果在做一个小型自用工具,SQLite FTS5 往往已经足够。FTS5 支持短语、前缀、邻近、布尔组合、排序、高亮和摘要片段。
CREATE VIRTUAL TABLE docs USING fts5(
path UNINDEXED,
page UNINDEXED,
text,
tokenize = 'unicode61'
);
SELECT
path,
page,
snippet(docs, 2, '[', ']', ' … ', 24) AS excerpt,
bm25(docs) AS rank
FROM docs
WHERE docs MATCH ?
ORDER BY rank
LIMIT 20;
这不是完整的导入程序,但它确定了以后不能丢失的契约:每条结果都应包含文件、页码或章节、片段以及可重复的查询。
现在运行那 20–30 个问题,记录正确文件是否进入前五名。如果普通搜索已经解决问题,就停在这里。一个完成真实任务的小型搜索系统,并不是“没有做完的 RAG”。
第四步:只为测量到的失败加入语义检索
如果查询和原文使用不同词语表达同一个意思,全文检索可能失败。只有当测试集确实暴露这个问题时,才把向量检索作为第二路信号加入,而不是替换精确检索。
一个实用的混合流程是:
- 检索精确词语和全文匹配;
- 对较短且有重叠的段落做语义检索;
- 合并两组结果;
- 每条结果都显示来源路径和原文;
- 保留“仅精确匹配”开关,便于调试。
在小型电脑上,应批量计算嵌入并持久化索引,不要每次启动都重算全部资料。如果模型只需下载一次就能离线使用,还应验证后续检索不会发出外部请求。
语言也会影响结果。英语专用模型未必适合中文、日文或混合资料。要用读者真正会输入的语言、人名、缩写和专业词语来测试。
把上下文窗口当成预算,而不是资料仓库
离线模型不需要把整套资料塞进提示词。资料继续留在检索索引中,每次只把与当前问题有关的少量段落放入上下文。这正是检索增强生成(RAG)的核心结构:先从明确的外部记忆中检索,再让模型处理检索到的证据。
上下文越长不等于效果越好。*Lost in the Middle* 发现,当相关信息位于长输入的中间时,语言模型可能更难可靠地利用它。因此,模型标称的上下文很大,并不是把所有命中结果都塞进去的理由。
可以把下面流程作为实验起点,而不是固定答案:
- 先取大约 20 条全文和语义候选;
- 去重,并限制每个文档最多贡献多少段落;
- 重排后,只发送能轻松放入预算的 4–8 个短段落;
- 给段落加上稳定的来源编号,例如
P1、P2、P3; - 要求回答只能依据这些段落,并允许明确返回“证据不足”。
在前面的 20–30 个测试问题上,分别测量检索召回、引用是否支持结论,以及证据不足时能否停止回答。只有预期证据被漏掉时才增加段落数量;无关内容开始干扰答案时就减少。真正有用的上下文大小,是能稳定容纳所需证据的最小值,而不是模型允许的最大 token 数。
小型语言模型适合放在哪里
小型语言模型可以承担三种不同工作,应该分别测试:
| 角色 | 何时使用 | 必须测量的主要失败 |
|---|---|---|
| 查询改写 | 短问题、缩写或词汇错位导致检索遗漏时。 | 改写偏离了用户真正的问题。 |
| 重排 | 已找到正确段落,但它在结果列表中过于靠后时。 | 精确相关结果被降级,延迟增加。 |
| 答案生成 | 读者需要综合或比较少量段落时。 | 出现无证据结论,或引用并不支持对应句子。 |
rewrite–retrieve–read 研究展示了用小型可训练模型做查询改写的方法。对私有本地系统,应保留原始问题,同时用原始和改写后的问题检索;改写记录只保存在本地,并比较两组结果。不要让一句流畅的改写悄悄取代用户原话。如果改写不能改善测试集上的检索结果,就删掉这一层。
这样,即使上下文窗口不大也足够使用:检索负责把整套资料缩减为证据,小型模型只完成一个有边界的任务。
第五步:只有生成确实有工作时才加入大模型
当你需要摘要、比较或根据多个片段组合答案时,大模型才真正有用。但它不应成为查看证据的唯一入口。
- 没有大模型时,检索仍然可用。
- 每条生成结论都能显示对应文件和页码。
- 用户可以打开原始段落。
- “没有找到”必须是允许的结果。
- 生成文字不能悄悄写回原始资料。
在仅 CPU 的机器上,小型量化模型可能足以整理语言。但第一问题不是模型有多大,而是检索是否准确、来源能否追踪。
隐私检查必须包括派生数据
“本地运行”本身还不是完整的隐私设计。
- 按资料敏感级别保护原件和派生索引。
- 除非远程访问已经有明确保护,否则浏览器界面只绑定到
127.0.0.1。 - 确认提取、嵌入、重排和生成不会调用云端 API。
- 把日志、缩略图、OCR sidecar、向量索引和备份都视为敏感数据。
- 威胁模型要求时,对备份加密。
- 记录生成索引时使用的模型和提取设置,保证能够重建。
- 原始资料需要删除时,同时删除派生索引。
安装完成后,可以断开网络,用一个小样本重新构建。它不能证明系统绝对安全,但能发现很多意外的外部依赖。
最小而有用的架构
只读原始资料
↓
文本提取 / 只对需要的页面 OCR
↓
带路径和页码的段落
↓
Recoll 或 SQLite FTS5
↓
能打开原文的排序片段
只有测试证明存在词汇错位时才加入语义索引,只有用户确实需要综合回答时才加入生成。这样,每一层复杂度都有可测量的理由。
什么时候适合做一次资料适配检查
真正困难的往往不是选择流行的 RAG 技术栈,而是判断资料能否提取、哪些内容必须保持私密、目标语言和读者是谁、引用应指向哪里,以及现有电脑实际上能支持什么。
在分享任何资料之前,你可以先查看一份基于 LKT 自有、已有文档记录的参考资料制作的完整适配示例报告。其中展示了数据与隐私地图、代表性的浏览器验证,以及 go/no-go 边界。这是项目自有的验证资料,不是客户成果或推荐评价。
我维护 Local Knowledge Terminal,并提供一次免费、隐私优先的资料适配检查来帮助做这个决定。页面只在浏览器中准备一封邮件草稿,不会自动上传、保存或发送你的回答。
如果资料适合,而且双方都接受范围,可选的创始阶段 sprint 价格是 250 美元。它只覆盖客户提供的一套资料、一个语言目标和一台现有电脑。交付内容包括书面的数据、隐私和引用地图;在样本可用时提供一个小型浏览器证明;以及是否值得继续扩展的 go/no-go 建议。
硬件、运输、自定义 OCR 和生产部署不在范围内。适配检查发生在付款之前,而且你必须有权使用所提供的资料。
无论是否使用这项服务,原则都一样:先让文件能够被搜索,测量遗漏,再只在真正需要的地方加入智能。
