search
纯Go嵌入式向量语义搜索库,purego无cgo调用llama.cpp GGUF模型,支持SIMD加速和GPU推理
加载项目详情…
本应用为开源项目,仅供学习研究,请遵守其开源协议。
纯Go嵌入式向量语义搜索库,purego无cgo调用llama.cpp GGUF模型,支持SIMD加速和GPU推理
加载项目详情…
本应用为开源项目,仅供学习研究,请遵守其开源协议。

kelindar/search 是一个用纯 Go 编写的嵌入式向量搜索与语义嵌入库,它借助 llama.cpp 的 GGUF 格式模型(尤其是 BERT 系列)实现文本语义搜索能力。无需 Python 环境、无需 cgo 绑定,通过 purego 直接调用底层 C 库,兼顾高性能与跨平台部署便利性。目前在 GitHub 拥有 552 颗 Stars,是中小规模语义搜索场景中的轻量级优质选择。
在传统关键词搜索时代,检索"安装 Go 环境"的文档,系统只能精确匹配包含"安装"和"Go"的词条,无法理解"配置 Golang 运行环境"其实表达的是同一个意思。这种语义鸿沟导致搜索结果要么漏掉大量相关内容,要么噪音过多。
嵌入式向量搜索(Embedding-based Semantic Search) 正是解决这一问题的关键:它将文本转换为高维稠密向量——语义相近的句子在向量空间中距离更近,语义相反的句子距离更远。这样,搜索时就变成了在向量空间中寻找与查询向量最相似的 Top-K 结果,突破了词面匹配的局限。
然而,传统方案往往依赖 Python + NumPy + PyTorch 的重型组合,或者需要引入 Milvus、Pinecone 等外部向量数据库服务。对于只想在现有 Go 后端中快速集成语义搜索能力的开发者而言,这些方案都显得过于笨重。kelindar/search 正是为这一细分需求而生。
本库最核心的技术创新,在于借助 purego 绕过 cgo 直接调用 llama.cpp 编译出的 C 共享库。
cgo 在 Go 项目中有几个显著缺点:
kelindar/search 选择了完全不同的路线:预编译共享库 + purego 动态绑定。
作者在 dist/ 目录下为 Linux(AVX / Vulkan)和 Windows(AVX / Vulkan)分别提供了预编译的 libllama_go.so 和 llama_go.dll。运行时通过 purego(一个纯 Go FFI 库)直接加载这些 .so/.dll 文件并调用其导出函数,无需任何 cgo 代码。整个调用链路为:
Go 代码 (llama.go)
→ purego 动态调用
→ libllama_go.so / llama_go.dll( llama.cpp 编译产物)
→ GGUF 格式 BERT 模型(MiniLM-L6-v2.Q8_0.gguf)
这种设计使得项目天然支持跨平台交叉编译:在 macOS 上也能直接 go build 出一个 Linux 二进制,全程无需配置 C 交叉编译工具链。
向量检索中最耗时的操作是余弦相似度 / 点积计算——遍历 N 个向量,对每个向量做数百次浮点乘加运算。kelindar/search 在 internal/cosine/ 目录下提供了多套 SIMD 实现:
| 文件 | 目标架构 | 优化手段 |
|---|---|---|
cosine_avx.c | x86-64 AVX2 | #pragma clang loop vectorize(enable) interleave_count(2) |
cosine_neon.c | ARM NEON | #pragma clang loop vectorize(enable) interleave_count(4) |
cosine_apple.c | Apple Silicon | 同上,配合苹果编译器优化 |
通过 gen.sh 脚本调用 gocc 编译器(Go 风格的 C 代码生成工具),将这些 .c 文件编译为平台特定的 SIMD 代码。搜索时,Go 层的 simd.DotProduct 函数被路由到经过 SIMD 优化的底层汇编实现,吞吐量相比纯 Go 循环有数倍的提升。
通过 search.NewVectorizer(modelPath, gpuLayers) 加载 GGUF 模型,即可将任意文本转换为 float32 向量:
m, _ := search.NewVectorizer("MiniLM-L6-v2.Q8_0.gguf", 0) // CPU模式
defer m.Close()
embedding, _ := m.EmbedText("A boy is studying a calendar")
// embedding 是一个 []float32,长度取决于模型维度(如 MiniLM-L6-v2 为 384)
支持加载 GGUF 格式的任意 BERT 变体模型——只要 llama.cpp 支持的 BERT 模型导出为 GGUF 即可。gpuLayers 参数控制加载到 GPU 的层数,设为 0 则纯 CPU 运行。
内部实现了对象池模式(pool[*Context])来复用推理上下文,避免每次 EmbedText 都重新分配内存。并发调用时性能更加稳定。
search.Index[T] 是一个纯内存 Brute-Force 精确搜索索引,泛型设计,可以存储任意类型的 Value:
index := search.NewIndex[string]()
// Add(item Value, vector Vector)
// 自动对向量做 L2 归一化,后续只需点积即为余弦相似度
index.Add(embedding, "A boy is studying a calendar")
index.Add(embedding2, "A boy is staring at a calendar")
// Search 返回 Top-K 最相似结果
results := index.Search(queryEmbedding, 10)
搜索使用最小堆(Min-Heap) 实现 Top-K 优先队列,时间复杂度 O(N log K),在 10 万条以内数据量上表现优异。Index 还支持 ReadFile / WriteFile 将索引序列化到磁盘,无需每次启动重新生成。
README 原文明确指出了三类不适用场景:
这种坦诚的局限性说明,体现了作者对技术边界的清醒认知,值得肯定。
作为 Go 库,最简单的方式是通过 go get 安装:
go get github.com/kelindar/search
然后下载 GGUF 模型文件(dist/MiniLM-L6-v2.Q8_0.gguf),即可在项目中使用。
如果不自己编译 llama.cpp C 库,可以直接下载作者提供的预编译产物:
dist/linux-x64-avx/libllama_go.so:Linux AVX2 版本(纯 CPU)dist/linux-x64-vulkan/libllama_go.so:Linux Vulkan GPU 加速版本dist/win-x64-vulkan/llama_go.dll:Windows Vulkan GPU 加速版本将 .so 放入 /usr/lib 或 .dll 放入 PATH,即可被 purego 自动发现加载。
| 资源 | 需求 |
|---|---|
| CPU | x86-64(AVX2)或 ARM64(NEON) |
| GPU | 可选(Vulkan 支持,用于加速推理) |
| 内存 | 约 2GB(包含 Go runtime + 模型加载) |
| 磁盘 | 500MB(库本身)+ 模型文件(MiniLM 约 90MB Q8 量化版) |
纯 CPU 模式下也能正常运行,适合在无 GPU 的服务器环境中部署。
本库是纯 Go 语言库,没有 Web 界面,不提供独立可执行服务,也无 Docker 镜像。需要由开发者自行将库集成到业务代码中,作为模块被调用。这不是缺陷,而是设计选择——它的定位是嵌入式组件而非独立服务。
search/
├── index.go # 向量索引核心:Index[T]、minheap、normalize
├── llama.go # 模型加载与推理:Vectorizer、Context、对象池
├── loader.go # purego 动态加载 .so/.dll 的核心逻辑
├── llama-go.cpp # llama.cpp 的 C++ 包装层(生成 C 接口)
├── llama.cpp # 作为 submodule 引入的 llama.cpp 源码
├── index_codec.go # 索引序列化/反序列化(磁盘读写)
├── index_test.go # 索引功能测试
├── loader_test.go # 加载器测试
├── internal/
│ └── cosine/
│ ├── cosine_avx.c # AVX2 SIMD 实现
│ ├── cosine_neon.c # ARM NEON SIMD 实现
│ ├── cosine_apple.c # Apple Silicon SIMD 实现
│ ├── gen.sh # SIMD 代码生成脚本
│ └── simd/ # 编译产物(平台特定 .s 汇编)
├── example/
│ └── main.go # 端到端示例:加载模型→嵌入→检索
└── dist/ # 预编译二进制(Linux/Windows,AVX/Vulkan)
| 层次 | 技术选型 |
|---|---|
| 主语言 | Go 1.23(泛型支持) |
| FFI 层 | purego(纯 Go,无 cgo) |
| 模型推理 | llama.cpp(GGUF 格式) |
| SIMD 加速 | AVX2 / NEON / Apple Silicon |
| 对象池 | 自实现泛型 pool[T] |
| 序列化 | 自定义二进制格式(Index.ReadFile/WriteFile) |
go.mod 仅引入 3 个直接依赖:purego(FFI)、iostream(高效 IO)和 cpuid(CPU 特性检测)。相比 PyTorch 动辄几十个依赖,kelindar/search 的依赖树几乎可以"一眼望穿"。
当前向量搜索生态大致分为三个层级:
重型云服务层:Pinecone / Weaviate / Qdrant(托管/自托管向量数据库)
中等工具层: FAISS / Milvus(成熟但偏重,Python-first)
轻量嵌入层: kelindar/search(Go 语言,嵌入式,无服务依赖)
kelindar/search 填补了 Go 生态嵌入式语义搜索 的空白。在它出现之前,Go 开发者想在应用内集成语义搜索能力,要么引入外部服务(增加运维复杂度),要么自行绑定 Python 推理服务(增加网络开销和部署复杂度)。kelindar/search 让这一切在纯 Go 进程内完成。
552 Stars 相比热门 AI 基础设施项目仍有差距,但考虑到它是一个定位精准的嵌入式工具库而非通用平台,这个数量已说明它在特定圈层(Go 开发者 + 小规模语义搜索需求)中有稳定的受众。MIT 许可证、文档完善、API 简洁等优点为后续增长奠定了基础。
总结:kelindar/search 是一个设计思路清晰、技术选型大胆的 Go 语义搜索库。它通过 purego + llama.cpp 的组合,绕过了 cgo 的跨编译噩梦,实现了在纯 Go 进程内直接加载 GGUF 模型做推理。对中小规模语义搜索场景而言,这是一个值得纳入技术选型视野的轻量级方案。