跳到正文

目录

turbovec 深度解析:基于 Google TurboQuant 的 Rust 向量索引,10M 文档从 31 GB 砍到 4 GB

turbovec 深度解析:基于 Google TurboQuant 的 Rust 向量索引,10M 文档从 31 GB 砍到 4 GB


一、turbovec 是什么

turbovec(GitHub: RyanCodrai/turbovec)是 Ryan Codrai 开源的面向 Python 用户的 Rust 向量索引库,底层算法是 Google Research 等的 TurboQuant。TurboQuant 于 2025-04-28 发布在 arXiv(arXiv:2504.19874,作者 Amir Zandieh / Majid Daliri / Majid Hadian / Vahab Mirrokni),2026 年 1 月被 ICLR 2026 接收。turbovec 在 2026 年上线 PyPI 与 crates.io,2026-08-18 发布 1.0.0。

项目的核心卖点,README 写得很直白:

A 10 million document corpus takes 31 GB of RAM as float32. turbovec fits it in 4 GB - and searches it faster than FAISS.

三个主要优势:

  1. 极小内存:10M × 1536 维 float32 向量约 31 GB,用量化压到约 4 GB(2-bit)。
  2. 比 FAISS 快:手写 NEON(ARM)与 AVX-512(x86)内核,项目自报在 4-bit 下平均比 FAISS IndexPQFastScan 快约 3.4 倍。
  3. 零训练 / 零码本:TurboQuant 是 data-oblivious 量化器,没有 codebook 训练步骤,新向量加进来立刻可搜,不需要 rebuild。

它不是要取代 FAISS 在云端做亿级召回,而是瞄准了「中规模 + 私有 / 离线 + Python 友好」这个窗口——10M 量级的数据在 FAISS 里要走 IndexIVFPQ + train,对中小团队偏重。


二、TurboQuant 是什么:为什么能让内存砍到 1/8

2.1 传统乘积量化的痛点

FAISS 的 IndexPQIndexIVFPQ 用乘积量化(Product Quantization):

  • 把 1536 维向量切成子向量
  • 每个子向量在码本(codebook)里找最近的中心点
  • 用中心的索引代替原向量

代价有三个:

  • 必须 train:要在语料上跑 k-means 才能得到像样的码本
  • 冷启动差:新文档进来,码本要重跑
  • 过滤搜索别扭:带 allowlist 的搜索往往要先取大 top-k 再过滤,recall 不稳

2.2 TurboQuant 的不同思路

TurboQuant 是一族在线(online)向量量化算法,目标是在不依赖数据分布的前提下逼近信息论给出的失真率下界(实测差距约常数因子 2.7)。它由两个变体组成:

  • TurboQuant_mse:优化均方误差(MSE)。
  • TurboQuant_prod:优化无偏内积估计,在 MSE 量化器之后,对残差再加一个 1-bit 的 Quantized JL(QJL)变换。

它的基本步骤是:

  1. 归一化:把向量长度(范数)单独存成一个 float,向量变成单位方向。
  2. 随机旋转:用一个固定种子的随机正交矩阵打乱坐标,旋转后每个坐标服从已知的 Beta 分布(高维下渐近取向正态)。
  3. 逐坐标标量量化:既然坐标分布是已知的,就能按分布算好最优边界(Lloyd-Max),对每个坐标只做查表式的标量量化——不需要看数据。
  4. (prod 变体)QJL 残差校准:对量化残差做 1-bit 变换,消除标量量化带来的内积偏置。

关键结论:在多数维度下,turboquant 的 4-bit 召回可以达到甚至超过 PQ 8-bit,但不需要 train。维度偏低(如 GloVe 200 维)时 Beta 渐近假设变松,表现会打折扣——这是它的适用边界,后面会展开。

2.3 turbovec 的工程化

turbovec 在论文的基础上做了四件工程化的事:

  • Rust 实现 + PyO3/maturin 绑定:关键计算全在 Rust,Python 侧只是胶水。
  • SIMD 内核:ARM 上用手写的 NEON SDOT/SMMLA 点积内核,x86 上用 AVX-512 VNNI 与 vpermb,并保留 AVX2 与标量兜底。
  • 过滤感知搜索:把 allowlist / bitmask 直接编进 kernel,按 32 向量一块的粒度短路跳过,不在 Python 层后过滤。
  • 增量落盘sync(path) 只写自上次同步以来变化的部分,支持大规模索引下的毫秒级删除与追加。

需要区分的是,TurboQuant 是算法论文,它解决的问题是"给固定分布做最优量化";turbovec 是把它做成了可用的索引。两者不是一回事——看论文能理解它为什么快,看 turbovec 才知道怎么用。


三、安装与基础使用

3.1 安装

pip install turbovec        # Python
# 或者
cargo add turbovec          # Rust

wheel 已发布到 PyPI 与 crates.io,覆盖 Linux / macOS / Windows,Python 要求 >= 3.9。

3.2 最简 RAG 例子

turbovec 只支持 2-bit 和 4-bit 两种位宽。向量必须是 2 维的 float32 数组,其他 dtype 会被拒绝而不会悄悄转换。

import numpy as np
from turbovec import TurboQuantIndex

# 1536 维(OpenAI text-embedding-3-small 维度)
index = TurboQuantIndex(dim=1536, bit_width=4)

# 直接 add,不需要 train
vecs = np.random.randn(1_000_000, 1536).astype(np.float32)
index.add(vecs)

# 新向量来了再 add
more = np.random.randn(50_000, 1536).astype(np.float32)
index.add(more)

# 搜索
query = np.random.randn(1536).astype(np.float32)
scores, indices = index.search(query, k=10)

# 整库快照(全量)
index.write("my_index.tv")
loaded = TurboQuantIndex.load("my_index.tv")

# 之后增量落盘:只写自上次以来的变化
index.sync("my_index.tv")

3.3 带稳定 ID 的版本

import numpy as np
from turbovec import IdMapIndex

index = IdMapIndex(dim=1536, bit_width=4)
ids = np.array([1001, 1002, 1003], dtype=np.uint64)  # 你的业务 ID
index.add_with_ids(vectors, ids)

# 查询返回你的业务 ID
scores, ids = index.search(query, k=10)

# 按 ID 删除:O(1)
index.remove(1002)

# IdMapIndex 的落盘扩展名是 .tvim
index.write("index.tvim")

3.4 搜索时过滤

过滤搜索是 turbovec 常用的用法。典型做法是混合检索:先用外部系统(SQL / BM25 / 权限过滤 / 时间窗)把候选 ID 收窄,再把候选集喂给 search,让 SIMD 内核只在这些候选里打分:

import numpy as np
from turbovec import IdMapIndex

idx = IdMapIndex(dim=1536, bit_width=4)
idx.add_with_ids(vectors, ids)

# Stage 1: 外部系统收窄为候选 ID
allowed = np.array(db.execute(
    "SELECT id FROM docs WHERE tenant=?", (t,)
).fetchall(), dtype=np.uint64)

# Stage 2: 在候选集内稠密重排
scores, ids = idx.search(query, k=10, allowlist=allowed)

过滤在 SIMD 内核内、以 32 向量为块进行:完全没有允许槽位的块在查表打分前就被短路,块内不被允许的槽位在堆插入时丢弃。它的返回长度是 min(k, n_allowed),当允许的向量少于 k 时就返回实际数量,不会补 null 兜底。

3.5 框架集成

turbovec 提供了四个主流 RAG 框架的即插即用替代,换 import 即可保留原有 pipeline:

框架安装替换的默认存储
LangChainpip install turbovec[langchain]InMemoryVectorStore
LlamaIndexpip install turbovec[llama-index]SimpleVectorStore
Haystackpip install turbovec[haystack]InMemoryDocumentStore
Agnopip install turbovec[agno]LanceDb

四、性能:测的是什么,不能推出什么

性能数据全部来自项目自报(README 的 benchmark,v1.0),不是第三方独立复现。看清它的测试口径再决定怎么用:

  • 数据:100K 向量、1K 个 query、k=64、5 次取中位数。
  • 对比基准:FAISS IndexPQFastScan。turbovec README 强调这是比论文更强的一个基线(FAISS 用的是 float32 LUT 与 k-means++ 训练码本)。

搜索速度(相对 FAISS IndexPQFastScan):

架构4-bit2-bit
ARM(Google Axion,GCP c4a-standard-8)平均约 3.5×(3.4–3.7×)平均约 26%(22–29%)
x86(Intel Sapphire Rapids)平均约 3.4×(3.2–3.5×)平均约 20%(5–32%)

在线增删(每操作,含 Python 调用开销):

  • 单向量 add():6.3–19.7 µs,比 FAISS 快 7.6–13.9×。
  • 100 向量批量 add():摊到 4.6–16.3 µs/向量。
  • IdMapIndex.remove(id):0.44–1.37 µs/次;FAISS 的 remove_idsIndexIDMap over IndexPQFastScan)每次要重排存储编码,100K 规模下单次删除 0.19–1.02 秒。

召回率(校准后的 TQ+):

  • OpenAI d=1536 / d=3072:在 k≤4 时召回即达 ≥0.997,k=8 时两边都到 1.0。
  • GloVe d=200(低维,Beta 渐近假设更松):4-bit 在 R@1 领先 FAISS 约 1.9 个点,2-bit 在深 k 处 FAISS 仍保持微弱优势。

两个必须说清的边界:

  1. 速度优势主要来自 4-bit + 点积内核。2-bit 的短累加循环没法完全吃到 AVX-512 的展开收益,优势会明显收窄。想冲速度,首选 4-bit。
  2. 删除差距不等于" FAISS 慢"。这个差距来自 IndexIDMap 的重排成本,是 FAISS「用重排换直立 ID」的设计代价,不是单纯实现不济。对查询多、删除少的场景,这点不足以构成替换理由。

内存口径:项目头条数字「31 GB float32 → 4 GB」对应的是 2-bit 存储(1536 维 float32 单向量 6 KB,2-bit 约 384 B,约 16× 压缩;4-bit 约 768 B,约 8×)。如果你用 4-bit,内存大约是 2-bit 的两倍。不要去追一个固定的「4 GB」数字,按实际位宽和维度算。


五、典型使用场景

5.1 推荐

  • 本地 / 私有部署的中小型 RAG:不想走 FAISS 的 train 仪式,pip install 就能跑。
  • Apple Silicon 或 ARM 单板上的本地检索:M 系列、AWS Graviton、Raspberry Pi 5、RK3588 这类,CPU 端就能跑得动。
  • 多租户 / 按权限过滤的检索:业务上「只在这个用户可见的文档里搜」是常态,过滤感知内核正好。删除频繁、索引经常增删的场景(delete-heavy)收益最明显。
  • 离线 / 完全内网:纯本地,无托管服务,可配合任意开源 embedding 模型组成完全隔离(air-gapped)的 RAG 栈。

5.2 不推荐

  • 亿级以上召回:FAISS GPU / Milvus / Qdrant 集群仍是单机库打不动的主战场。
  • 要 GPU 加速:turbovec 是纯 CPU SIMD,没有 GPU 路径。
  • 要 sparse + dense 混合检索:Qdrant / Weaviate / Vespa 这类更适合。
  • 需要长驻服务、多进程并发写:turbovec 是内存索引 + 文件快照,不是 Qdrant 那种长进程服务,也没有分布式能力。
  • 低维 embedding:几百维以下时 TurboQuant 的分布假设失效,优势消失,直接用 FAISS 更稳。

六、和其他开源向量库的对比

项目训练内核级过滤内存速度服务化适合
turbovec极低Python 嵌入式 / 本地
FAISS✅(多数索引)极高研究 / 大规模
Qdrant生产服务
Milvus极高亿级
Weaviate多模态
LanceDB嵌入式 OLAP
Chroma⚠️⚠️原型

turbovec 的定位是「单机 + Python + 不 train + 过滤 + SIMD 快」。这几条一起满足的开源选项,2026 年年中之前基本是空白。


七、动手实验:30 分钟跑起来

# 1. 安装
pip install turbovec numpy

# 2. 最小 demo
python << 'PY'
import numpy as np, time
from turbovec import TurboQuantIndex

# 造 100K 1536 维数据 —— 注意量级,别一上来就上千万
np.random.seed(42)
N, D = 100_000, 1536
vecs = np.random.randn(N, D).astype(np.float32)
index = TurboQuantIndex(dim=D, bit_width=4)

t = time.time()
index.add(vecs)
print(f"add {N} took {time.time()-t:.2f}s")

# 搜索
query = np.random.randn(D).astype(np.float32)
t = time.time()
for _ in range(100):
    s, i = index.search(query, k=10)
print(f"avg search {(time.time()-t)/100*1000:.2f} ms / query")
PY

# 3. 持久化 & 重载
python << 'PY'
from turbovec import TurboQuantIndex
# 假设上面 index 还存在
index.write("demo.tv")
loaded = TurboQuantIndex.load("demo.tv")
print("reload ok")
PY

第一次跑建议先用 100K 量级验证,再逐步放大;千万级数据生成的随机数组本身就要占几十 GB 内存,demo 阶段没必要。


八、选用顺序与建议

如果你的场景是「本机 / 内网、中规模(百万到千万级)、Python 为主、有过滤或频繁删除需求」,turbovec 值得直接试:

  1. 先验证召回:用自己的 embedding 在 100K 子集上各跑一遍 2-bit 和 4-bit,确认召回达标。
  2. 默认 4-bit:速度与质量平衡最好;内存吃紧再降 2-bit。
  3. 用 IdMapIndex 而不是裸 TurboQuantIndex:只要你有真实业务 ID,就应选它,删除才 O(1)。
  4. 过滤走候选集 + allowlist:别在 Python 层做后过滤,把候选 ID 直接喂给内核。
  5. 落盘用 sync 而非反复全量 write:索引大了以后增量写的成本是毫秒级。

注意一件事,这也是它引用的算法埋的雷:TurboQuant 论文在 ICLR 2026 评审期间引发了公开争议。RaBitQ 论文一作高健扬公开指出:TurboQuant 在方法上和 RaBitQ 高度相关却回避引用、在实验对比中称对方"次优",且用不同条件对比(如以降低条件跑 RaBitQ)。第三方研究者也在 OpenReview 提出其论文与推广博客的基准口径不一致(PyTorch vs JAX、FP32 对比)。turbovec 是实现方,算法争议不影响它的软件可用性,但在引用 TurboQuant 论文学术结论时要意识到这些质疑并未完全澄清。别在技术选型的论证里只依赖论文单方面宣称的收益。


九、参考链接


2026-08-23 · GitHub Trending 收录 · 文本矩阵「技术笔记」专栏

参与讨论

使用 GitHub 登录。欢迎补充事实、异议与实践。