turbovec 是第一个把 Google 论文里的 TurboQuant 算法落地的向量检索库。Rust + 手写 SIMD,零训练零调参,比 FAISS 快 20%,内存只要 1/8。
读完你会知道:怎么 10 行代码跑起来、TurboQuant 为什么能做到不训练、手写 SIMD 的架构思路、以及什么时候用它什么时候别用。
🎯 这个项目解决什么问题?
你有没有在本地跑过 RAG?
加载一个 embedding 模型,把几千篇文档向量化存进 FAISS,查个 top-10 大概 50ms——还行。然后文档涨到 1 万、10 万、100 万。内存从 300MB 涨到 3GB 再到 30GB,查询延迟从 50ms 爬到 200ms。你开始调 IVF 参数、倒排索引、PQ 训练……这些都不是"本地 RAG"该有的体验。
turbovec 给出的答案是:同一个 1000 万文档的语料库(1536 维 float32),FAISS 需要 31GB RAM,turbovec 只需要 4GB。而且搜索速度比 FAISS IndexPQFastScan 快 12%–20%。
核心武器是 Google Research 的论文TurboQuant(arXiv:2504.19874),2025 年 4 月提交。一种>
🔧 快速上手
先确认环境:需要Rust 1.75+(编译二进制)或直接用 Python bindings。
安装
# Python 用户:直接 pippipinstallturbovec# Rust 用户:在 Cargo.toml 中添加# turbovec = "0.4"10 行代码跑通
importnumpyasnpfromturbovecimportTurboQuantIndex# 创建索引:1536维(OpenAI ada/text-embedding-3-small 的维度)index=TurboQuantIndex(dim=1536,bit_width=4)# 模拟 10 万条向量(实际用你的 embedding 模型生成)vectors=np.random.randn(100_000,1536).astype(np.float32)index.add(vectors)# 查询 top-10query=np.random.randn(1536).astype(np.float32)scores,indices=index.search(query,k=10)print(f"Top-10 indices:{indices}, scores:{scores}")# 持久化到磁盘index.write("my_index.tq")loaded=TurboQuantIndex.load("my_index.tq")如果你需要稳定的外部 ID(而不是add顺序),用IdMapIndex:
fromturbovecimportIdMapIndex idx=IdMapIndex(dim=1536,bit_width=4)external_ids=np.array([1001,1002,1003],dtype=np.uint64)idx.add_with_ids(vectors[:3],external_ids)# 搜索返回的是你的外部 IDscores,ids=idx.search(query,k=2)# ids 会是 [1003, 1001] 而不是 [2, 0]# O(1) 按 ID 删除idx.remove(1002)过滤搜索:把 BM25、SQL、ACL 的结果当白名单
这是 turbovec 最实用的功能之一——你可以把上游系统的召回结果作为 allowlist 传给search,turbovec 只在这些候选里做向量检索:
# 假设你从 PostgreSQL 查出 500 个候选 IDcandidate_ids=np.array([101,205,307,...],dtype=np.uint64)scores,ids=idx.search(query,k=10,allowed=candidate_ids)# 结果只来自 candidate_ids,不会召回其他文档⚠️常见踩坑:
bit_width只支持 2、4、8,传别的值会 panic- 向量维度目前上限约 32768(受内部 SIMD 块布局约束)
search需要&T而非&mut T,天然支持并发读取——多个线程可以同时查同一个索引
⚙️ 技术原理
核心问题:量化器怎么做到不训练的?
FAISS 的 Product Quantization(PQ)需要先拿一批训练数据,用 K-means 学出子空间的聚类中心(codebook)。这个训练过程有四个问题:
- 需要代表性数据:训练集分布必须和实际查询集分布一致
- 训练耗时:K-means 在百万级向量上跑很慢
- 不支持在线添加:加新向量后 codebook 可能过时,需要重建索引
- 调参地狱:子空间数 m、每个子空间的 bits 数、IVF 倒排单元数……
TurboQuant 用了一个巧妙的数学技巧绕过了训练:随机旋转 + 坐标独立性。
三步看懂 TurboQuant 原理
第一步:随机旋转
对每个输入向量x ⃗ ∈ R d \vec{x} \in \mathbb{R}^dx∈Rd,用一个固定的随机正交矩阵R RR做旋转变换:
y ⃗ = R ⋅ x ⃗ \vec{y} = R \cdot \vec{x}y=R⋅x
旋转后,y ⃗ \vec{y}y的每个坐标都服从一个集中化的 Beta 分布。这是高维几何的一个经典现象:高维球面上均匀分布的向量,其单个坐标的分布会向 0 集中。
第二步:坐标间近似独立
在高维空间(d ≥ 256)中,旋转后不同坐标之间的相关性极低。这意味着你可以把每个坐标当作独立的一维信号来量化,不需要对跨坐标的"向量结构"建模。这就是 TurboQuant 不需要训练 codebook 的根本原因——既然坐标之间几乎独立,最优的标量量化器(Lloyd-Max)就够了。
第三步:两步法消除内积偏差
对最近邻搜索,我们需要的是"近似内积",而 MSE 最优的量化器会引入系统性偏差——量化后的内积和原始内积之间存在偏移。TurboQuant 的解法分两步:
- 先用 MSE 最优的标量量化器把每坐标压到 4-bit(或 2-bit)
- 对残差(原始值减量化值)再做一次1-bit Quantized JL 变换——用一个随机
±1向量和残差做内积,得到一个无偏估计
两步结果的和就是内积的无偏估计。
和 FAISS PQ 的核心差异
| 维度 | turbovec (TurboQuant) | FAISS (PQ) |
|---|---|---|
| 训练需求 | 零训练 | 需要代表性数据训练 codebook |
| 在线添加 | 直接 add,即时可用 | 需要重建或增量训练 |
| 检索速度 | 手工 SIMD 内核,AVX-512/NEON | SIMD via MKL/OpenBLAS |
| 内存效率 | 4-bit 模式下 0.5B/维 | PQ 典型 4-8B/维 |
| 过滤搜索 | 原生支持,不损失召回 | 需要后过滤或 IVF 内嵌 |
| 内存开销 | 无需存 codebook | codebook 本身占用内存 |
TurboQuant 的盲区:几十亿向量的场景,需要 IVF 做粗筛——turbovec 不做这个。不过说实话,到这个量级你应该直接上 Milvus 或 Qdrant,本地 RAG 根本碰不到这种规模。
🏗️ 架构分析
turbovec 的代码组织很 Rust——模块边界小且清晰:
turbovec/ ├── src/ │ ├── lib.rs # 入口:TurboQuantIndex, IdMapIndex │ ├── rotation.rs # 随机旋转矩阵(固定 seed=42) │ ├── codebook.rs # Lloyd-Max 最优量化器质心表 │ ├── encode.rs # 编码管道:旋转→量化→打包 │ ├── search.rs # SIMD 搜索内核(NEON/AVX2/AVX-512) │ ├── pack.rs # 打包/解包:4-bit→密集字节布局 │ ├── id_map.rs # ID 映射索引 │ └── io.rs # 序列化/反序列化 ├── turbovec-python/ # Python bindings (PyO3 + maturin) └── benchmarks/ # 性能对比数据几个让我印象深的设计
1. SIMD 双架构:一条#[cfg]搞定
search.rs里根据编译目标自动选路径:aarch64 走 NEON,x86_64 走 AVX2/AVX-512BW。x86 端抄了 FAISS 的"perm0 交错布局"——32 个向量的量化 code 按寄存器宽度交错排,MADD 每个 cycle 都吃满。
4-bit 模式下,一个 AVX-512 寄存器同时处理 128 个维度的查表。ARM 端用顺序布局(M 系列芯片缓存策略不同),实测比交错布局更快。
2. OnceLock 的并发魔法
这段把 Rust 的OnceLock用得很漂亮:
- 旋转矩阵、质心表、SIMD 块布局的初始化,第一个调
search的线程结账 - 后续所有线程直接读缓存,零锁开销
add操作直接换掉OnceLock,invalidate 缓存
你不用手动调prepare(),也不用担心竞态——Rust 的类型系统替你保证了(search要&self,add要&mut self)。
3. 过滤搜索的零开销路径
大多数向量数据库做过滤都是"多取一些 + 后过滤"——多取 k*N 个结果,然后丢掉不在 allowlist 里的。turbovec 直接在 SIMD 内核里跳过整块不包含合法 ID 的向量 block(32 个一组),用BLOCKS_SKIPPED_BY_MASK原子计数器跟踪跳过比例。
不够好的地方
- 没有内存映射(mmap):索引只能全量加载到内存,不能像 FAISS 那样映射磁盘文件。1000 万条 4-bit 向量约 7.5GB,能跑但不能懒加载
- 维度上限:受 SIMD 块布局约束,维度超过 ~32768 就无法编码。虽然 99% 的 embedding 模型远小于这个值,但如果要索引拼接向量就受限
- 社区年轻:LangChain/LlamaIndex/Haystack 的集成已经写了(见
docs/integrations/),但生态深度还远不如 FAISS
✅ 优缺点 & 适用场景
三个优点
- 零门槛向量检索:不需要懂量化理论,不需要挑训练数据,不需要调 IVF 参数。
pip install turbovec之后 3 行代码就能跑 - 内存效率接近理论极限:Shannon 下界只差 2.7 倍常数,实际 4-bit 编码的召回率在 GloVe 100 万数据集上和 brute-force 几乎无差别
- 过滤搜索不牺牲召回:allowlist 路径直接在 SIMD 内核完成,不是后过滤——你拿 k 个就真的拿到 k 个
两个缺点
- 不支持倒排索引:亿级以上数据量还是要去用 Milvus
- 纯本地方案,无分布式支持:没有 server 模式、没有 sharding、没有副本
谁应该立刻试试
- 在本地跑 RAG、用 Ollama/llama.cpp 做 embedding 的人
- 在资源受限设备上做向量检索(树莓派、边缘设备——ARM NEON 内核表现极好)
- 需要隐私保护的场景(数据不离开本机,不需要托管服务)
谁应该再等等
- 向量量级超过 1 亿——turbovec 的纯暴力搜索路线不适合
- 依赖完整的向量数据库生态(监控、备份、分布式查询)
- 需要混合检索(dense + sparse),虽然可以把 sparse 结果作为 allowlist 传入,但这套方案不如原生 BM25+向量 hybrid 式方案直接
总结:turbovec 不是要替代 FAISS 或 Milvus。它的定位非常清晰——给 “本地 RAG” 场景一个不需要学习成本的、内存高效的、速度不输任何人的向量索引方案。如果你在笔记本上跑一个本地知识库,turbovec 应该成为你的默认选择。
项目地址:github.com/RyanCodrai/turbovec | 论文:arXiv:2504.19874