如何用ONNX和FAISS构建RAG语义搜索系统?

摘要:本文从零构建了一个轻量级、高性能的 C++ 语义搜索系统,基于 ONNX 运行 BGE 嵌入模型、FAISS 向量索引与 Markdown 语义分块,完整实现支持增删改查的生产级 RAG 检索后端。
1. 引言 既然是“从零实现”,本文暂不深入探讨繁复的理论背景,而是先聚焦一个核心问题:语义化搜索中的“语义化”到底是什么意思? 传统的关键词搜索依赖字面匹配——比如搜索“如何序列化 JSON”,系统只会返回包含这些精确关键词的文档。这种方式简单直接,但十分“呆板”:它无法理解“JSON 序列化方法”或“把对象转成 JSON 字符串”其实表达了相同的意图。 而语义化搜索的突破在于:它不再比较文字,而是比较含义。其核心思想是将文本(无论是查询还是文档)通过嵌入模型(Embedding Model)转换为高维向量——这个过程称为“向量化”或“嵌入”(Embedding)。在向量空间中,语义相近的句子会被映射到彼此靠近的位置。于是,搜索就变成了一个向量相似度计算问题:找出与查询向量最接近的文档向量。 表面上看,这是数学(向量、内积、距离);本质上,这是智能——因为模型通过海量数据学习到了人类语言的语义结构。而我们要做的,就是用编程语言把这套“用数学理解语言”的能力,变成一个高效、可靠、可部署的生产级系统。 2. 目标 本文的初衷,是为我的个人博客网站 Charlee44 的技术驿站 实现一套真正可用的站内搜索功能。由于博客部署在资源极其有限的云服务器上(比如1核 CPU、1GB 内存),我对系统提出了两个“极致”要求:极致的性能 与 极致的资源效率。 毕竟,每一分算力都来自自己的钱包——这促使我放弃了主流但相对“重”的 Python 生态(如 Sentence Transformers + ChromaDB 的常规组合),转而选择 C++ 作为实现语言。C++ 不仅能提供更低的内存开销和更高的推理吞吐,也让我有机会深入理解 embedding 推理、向量索引、文本分块等模块的底层机制。 当然,需要说明的是:如果是在企业环境中开发,或对迭代速度要求更高,Python 生态仍是更高效、更成熟的选择。而本项目更多是一次“用工程约束驱动深度学习”的实践——在有限资源下,亲手构建一个轻量、可控、可落地的语义搜索系统。 3. 嵌入模型 既然语义化搜索的核心在于将文本转化为富含语义的向量,那么实现这一能力的关键,就在于选择一个合适的嵌入模型。这类模型的作用,是将任意长度的文本映射为固定维度的稠密向量,使得语义相似的文本在向量空间中距离更近。笔者这里使用的是 bge-small-zh-v1.5 。bge-small-zh-v1.5 是由北京智源人工智能研究院(BAAI)推出的 BGE(BAAI General Embedding)系列中的轻量级中文模型。它基于 Transformer 架构,在大规模中英文语料上进行对比学习训练,专为检索任务优化。尽管它并非该系列中最新或最大的版本(如 bge-large),但其在推理速度、内存占用与语义质量之间取得了极佳的平衡,尤其适合资源受限或对延迟敏感的生产环境。 bge-small-zh-v1.5 可以从 Hugging Face 上下载,下载后的数据文件组织结构如下: bge-small-zh-v1.5/ ├── config.json # 模型架构配置(层数、隐藏层维度等) ├── pytorch_model.bin # PyTorch 格式的模型权重(核心文件) ├── tokenizer.json # 分词器定义(WordPiece 词汇表 + 算法参数) ├── tokenizer_config.json # 分词器配置(如是否小写化、特殊 token 等) ├── vocab.txt # WordPiece 词汇表(纯文本,每行一个 token) ├── special_tokens_map.json # 特殊 token 映射([CLS], [SEP], [PAD] 等) ├── modules.json # (可选)模型模块信息 ├── sentence_bert_config.json # (可选)Sentence-BERT 相关配置 ├── README.md # 模型卡片(含使用说明、引用信息等) └── .gitattributes # Git LFS 配置(用于大文件管理) 不过,bge-small-zh-v1.5 原生基于 PyTorch(属于 Python 生态),直接在 C++ 环境中调用并不方便,也难以满足我们对性能和资源占用的要求。
阅读全文