Note
本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。
English · 原始项目 · 上游 README
原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。
1000 万文档语料库以 float32 存储需要 31 GB RAM。turbovec 将其压缩到 4 GB——且搜索速度比 FAISS 更快。
turbovec 是一个带 Python 绑定的 Rust 向量索引,基于 Google Research 的 TurboQuant) 算法构建——一种数据无关(data-oblivious)的量化器,具有接近最优的失真且无需单独的训练阶段。
- 在线摄入(Online ingest)。 添加向量即可建立索引——无需训练步骤、参数调优,语料库增长时也无需重建。
- 快速 SIMD 搜索。 手写 NEON(ARM)和 AVX-512BW(x86)内核在 ARM 上比 FAISS IndexPQFastScan 快 10–19%;在 x86 上,4-bit 配置领先,2-bit 配置仅落后几个百分点。
- 搜索时过滤。 向
search()传入 id 白名单(或 slot 位掩码),内核会直接遵守。你始终从允许的集合中获得最多k条结果——无过度抓取,选择性过滤也不会影响召回。 - 纯本地。 无托管服务,数据不会离开你的机器或 VPC。搭配任意开源嵌入模型,即可构建完全气隙隔离(air-gapped)的 RAG 栈。
在构建 RAG 时,隐私、内存或延迟很重要?你来对地方了。
Python
pip install turbovec
from turbovec import TurboQuantIndex
index = TurboQuantIndex(dim=1536, bit_width=4)
index.add(vectors)
index.add(more_vectors)
scores, indices = index.search(query, k=10)
index.write("my_index.tv")
loaded = TurboQuantIndex.load("my_index.tv")
需要能在删除后仍然保留的稳定 id?使用 IdMapIndex:
import numpy as np
from turbovec import IdMapIndex
index = IdMapIndex(dim=1536, bit_width=4)
index.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64))
scores, ids = index.search(query, k=10) # ids are your uint64 external ids
index.remove(1002) # O(1) by id
index.write("my_index.tvim")
loaded = IdMapIndex.load("my_index.tvim")
混合检索(过滤搜索)
将结果限制为另一系统(SQL、BM25、ACL、时间窗口等)产生的候选集:
import numpy as np
from turbovec import IdMapIndex
idx = IdMapIndex(dim=1536, bit_width=4)
idx.add_with_ids(vectors, ids)
# Stage 1: external system narrows to candidate ids.
allowed = np.array(db.execute("SELECT id FROM docs WHERE tenant=?", (t,)).fetchall(),
dtype=np.uint64)
# Stage 2: dense rerank within the candidate set.
scores, ids = idx.search(query, k=10, allowlist=allowed)
过滤在 SIMD 内核内部以 32 向量块粒度进行:没有允许 slot 的块会在任何 LUT 查找或评分工作之前被短路跳过,已评分块内个别不允许的 slot 会在堆插入时被丢弃。因此,选择性白名单(仅允许索引的一小部分)能避免大部分 SIMD 开销,而不是先付出计算成本再丢弃结果。
输出长度为 min(k, len(allowed))——当白名单小于 k 时,你会得到恰好 len(allowed) 条结果,而非填充的回退值。
完整参考见 docs/api.md。
框架集成
可作为各框架内置参考向量/文档存储的即插即用替代。相同的公开接口、相同的持久化语义、相同的检索器与流水线接线——只需更换 import,流水线其余部分保持不变。
- LangChain —
pip install turbovec[langchain]· 替代langchain_core.vectorstores.InMemoryVectorStore - LlamaIndex —
pip install turbovec[llama-index]· 替代llama_index.core.vector_stores.SimpleVectorStore - Haystack —
pip install turbovec[haystack]· 替代haystack.document_stores.in_memory.InMemoryDocumentStore - Agno —
pip install turbovec[agno]· 替代agno.vectordb.lancedb.LanceDb
Rust
cargo add turbovec
use turbovec::TurboQuantIndex;
let mut index = TurboQuantIndex::new(1536, 4).unwrap();
index.add(&vectors);
let results = index.search(&queries, 10);
index.write("index.tv").unwrap();
let loaded = TurboQuantIndex::load("index.tv").unwrap();
需要能在删除后仍然保留的稳定外部 id:
use turbovec::IdMapIndex;
let mut index = IdMapIndex::new(1536, 4).unwrap();
index.add_with_ids(&vectors, &[1001, 1002, 1003]).unwrap();
let (scores, ids) = index.search(&queries, 10);
index.remove(1002);
index.write("index.tvim").unwrap();
let loaded = IdMapIndex::load("index.tvim").unwrap();
召回率(Recall)
TurboQuant 对比 FAISS IndexPQ(LUT256,nbits=8)——论文第 4.4 节的基线。100K 向量,k=64。FAISS PQ 子量化器数量按 TurboQuant 的比特率匹配(2-bit 时 m=d/4,4-bit 时 m=d/2)。
在 OpenAI d=1536 和 d=3072 上,TurboQuant 在 2-bit 和 4-bit 的 R@1 上比 FAISS 高 0.2–1.9 个百分点,且两者在 k=8 时均达到 1.0(k=4 时已 ≥0.997)。GloVe d=200 是更难的场景——低维时渐近 Beta 假设更宽松。TurboQuant 在 4-bit 的 R@1 上比 FAISS 高 0.9 个百分点,2-bit 时基本持平(差距在 0.1 个百分点以内),到 k≈16 时两者都紧密跟踪 FAISS。
关于基线的说明。 我们与 FAISS IndexPQ(LUT256,nbits=8,float32 LUT)对比,因为它是大多数用户会选用的默认生产级 PQ。这比 TurboQuant 论文) 中的自定义 u8-LUT PQ 是更强的基线——FAISS 在评分时使用更高精度的 LUT,并用 k-means++ 训练码本。我们在 OpenAI d=1536 / d=3072 上复现了论文的 TurboQuant 数值,在低维嵌入上也达到了与其他社区参考实现相近的数值(见 turboquant-py) 在 d=384 的结果)。在 GloVe(d=200)上——渐近 Beta 假设最宽松的低维场景——TurboQuant 在 2-bit 与 FAISS 持平,4-bit 领先;TQ+ 校准弥补了基础算法在低维上留下的差距。
完整结果:d=1536 2-bit、d=1536 4-bit、d=3072 2-bit、d=3072 4-bit、GloVe 2-bit、GloVe 4-bit。
压缩
搜索速度
所有基准测试:100K 向量,1K 查询,k=64,5 次运行的中位数。
ARM(Apple M3 Max)
在 ARM 上,TurboQuant 在所有配置下均比 FAISS FastScan 快 10–19%。
x86(Intel Xeon Platinum 8481C / Sapphire Rapids,8 vCPUs)
在 x86 上,TurboQuant 在 4-bit 配置下领先最多约 5%(d=3072 多线程持平),在 2-bit 上略落后于 FAISS——最明显的是 d=1536 单线程(约 8%),其余配置差距在几个百分点以内——FAISS 的 AVX-512 VBMI 路径在短 2-bit 累加循环上更有优势。
工作原理
每个向量都是高维超球面上的一个方向。TurboQuant 用一条简单洞见压缩这些方向:施加随机旋转后,每个坐标都遵循已知分布——与输入数据无关。
1. 归一化(Normalize)。 从每个向量中剥离长度(norm)并将其存储为单个浮点数。现在每个向量都是超球面上的单位方向。
2. 随机旋转(Random rotation)。 将所有向量乘以同一个随机正交矩阵。旋转后,每个坐标独立服从 Beta 分布,在高维下收敛于高斯分布 N(0, 1/d)。这对任意输入数据都成立——旋转使坐标分布变得可预测。
3. 逐坐标校准(TQ+)。 步骤 2 中的 Beta 分布是渐近的——在有限维度下,单个坐标会偏离标准形状(尤其是低位宽和词向量风格的嵌入)。TQ+ 在首次 add 时为每个坐标拟合两个标量——一个平移和一个缩放——将每个坐标的经验 5/95% 分位数映射到标准 Beta 边缘分布上。随后 Lloyd-Max 码本针对其设计目标分布进行量化。校准在首次 add 后冻结,供后续 add 复用——无需重训练、无需重建、无需单独的训练阶段。召回增益:在漂移最严重的单元上 @1 最高可达 +1.4pp(例如 2-bit 的 GloVe)。
4. Lloyd-Max 标量量化。 由于分布已知,我们可以预先计算每个坐标的最优分桶方式。2-bit 为 4 个桶;4-bit 为 16 个桶。Lloyd-Max 算法 找出使均方误差最小的桶边界和质心。这些由数学一次性计算得出,而非从数据中学习。
5. 位打包(Bit-pack)。 每个坐标现在是一个小整数(2-bit 为 0-3,4-bit 为 0-15)。将这些整数紧密打包进字节。1536 维向量从 6,144 字节(FP32)压缩到 384 字节(2-bit),即 16 倍压缩。
6. 长度重归一化评分。 标量量化会系统性低估内积——重建的单位方向比原始向量略短。我们在编码时为每个向量计算一个标量——旋转后的单位向量与其自身质心重建的内积——并将 ||v|| / ⟨u, x̂⟩ 与每个压缩向量一并存储。搜索内核在堆插入前将该标量乘以每个候选分数,使内积估计器从向下偏置变为无偏,且搜索时零额外开销、零额外存储。召回增益在低位宽下最为明显,因为此时量化收缩最大。
编码开销:每个向量额外一次 d 维点积以计算 ⟨u, x̂⟩。在 d=1536 的 100 万向量上,额外编码时间不到一秒——这是在摄入时一次性付出的成本,而非查询时。
搜索。 无需解压每个数据库向量,只需将查询一次旋转到同一域,并直接针对码本值评分。评分内核使用 SIMD 内建函数(ARM 上为 NEON,现代 x86 上为 AVX-512BW,并提供 AVX2 回退),配合半字节拆分查找表以实现最大吞吐。
Lloyd-Max 码本的失真在信息论下界(Shannon 失真-速率极限)的 2.7 倍以内;长度重归一化步骤消除了 Lloyd-Max 码本在内积估计器本身上引入的残余偏置。
构建
Python(通过 maturin)
pip install maturin
cd turbovec-python
maturin build --release
pip install target/wheels/*.whl
Rust
cargo build --release
所有 x86_64 构建通过 .cargo/config.toml 以 x86-64-v3(AVX2 基线,Haswell 2013+)为目标。任何能运行 AVX2 回退内核的 CPU 都能运行整个 crate——AVX-512 内核通过 is_x86_feature_detected! 在运行时门控,仅在支持该指令集的硬件上启用。
运行基准测试
下载数据集:
python3 benchmarks/download_data.py all # all datasets
python3 benchmarks/download_data.py glove # GloVe d=200
python3 benchmarks/download_data.py openai-1536 # OpenAI DBpedia d=1536
python3 benchmarks/download_data.py openai-3072 # OpenAI DBpedia d=3072
每个基准测试是 benchmarks/suite/ 中的独立脚本。可单独运行任意一个:
python3 benchmarks/suite/speed_d1536_2bit_arm_mt.py
python3 benchmarks/suite/recall_d1536_2bit.py
python3 benchmarks/suite/compression.py
运行某一类别的全部基准测试:
for f in benchmarks/suite/speed_*arm*.py; do python3 "$f"; done # all ARM speed
for f in benchmarks/suite/speed_*x86*.py; do python3 "$f"; done # all x86 speed
for f in benchmarks/suite/recall_*.py; do python3 "$f"; done # all recall
python3 benchmarks/suite/compression.py # compression
结果以 JSON 保存到 benchmarks/results/。重新生成图表:
python3 benchmarks/create_diagrams.py
参考文献
- TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate (ICLR 2026) -- 本文实现的论文
- RaBitQ: Quantizing High-Dimensional Vectors with a Theoretical Error Bound for Approximate Nearest Neighbor Search (SIGMOD 2024) -- 步骤 5 中采用的逐向量长度重归一化修正的来源
- FAISS Fast accumulation of PQ and AQ codes -- turbovec 的 x86 SIMD 内核借鉴了 FastScan 的打包布局、半字节 LUT 评分和 u16 累加器策略
