1000万文档塞进4GB内存,还比FAISS搜得快:turbovec 是怎么做到的
一千万条文档向量,用 float32 存要占 31 GB 内存。turbovec 把它压进 4 GB,搜起来还比 FAISS 快。
第一眼像是吹牛。它其实是 Google Research 一篇论文(TurboQuant)的工程落地,作者 Ryan Codrai 用 Rust 实现,配了 Python 绑定。下面先讲清楚它解决什么问题、为什么能做到这个效果,再上手跑一遍。
项目地址:https://github.com/RyanCodrai/turbovec
先说说,向量检索到底卡在哪
做 RAG(检索增强生成)的人都知道,流程大致是:把文档切块、用 embedding 模型转成一堆高维向量、存进向量库,查询的时候把问题也转成向量,去库里找最近的几个。
问题出在「存」这一步。
一个 OpenAI 的 embedding 是 1536 维,每一维是一个 float32,也就是 4 个字节。算一下:
31 GB 全得放进内存才搜得快。要么准备一台大内存机器,要么用托管的向量数据库服务、把数据交出去。对在意隐私、希望本地运行或控制成本的场景,这两条路都有明显代价。
turbovec 的方案是把每条向量从 6 KB 压到 384 字节,16 倍压缩,1000 万条只需 4 GB,而且压缩后搜索不变慢。
通常压缩会牺牲精度和速度,turbovec 两样都几乎没有损失。它是怎么做到的?
核心:一个反直觉的数学洞察
turbovec 实现的是 Google Research 的 TurboQuant 算法,论文已被 ICLR 2026 接收。它最特别的地方是 data-oblivious,「数据无关」。
什么叫数据无关?传统量化方法(比如乘积量化 PQ)要先拿数据跑一遍训练,学出一套码本才能开始压缩。数据变了、加了新向量,往往还得重训。turbovec 不需要这个训练步骤,码本是算出来的,不是学出来的。
它的核心洞察是:
每个向量本质上是高维球面上的一个方向。给所有向量乘上同一个随机正交矩阵(随机旋转)之后,每一个坐标都会服从一个已知的分布,而且与原始数据的形态无关,这个分布都一样。
这是整个算法的关键。坐标分布一旦已知、可预测,「这个坐标分几个桶、桶的边界划在哪」就能用纯数学提前算好(Lloyd-Max 算法求最优分桶),不需要看数据。
具体分这么几步:
- 归一化。把每个向量的长度(模)抽出来单独存成一个 float,剩下的就是球面上的单位方向。
- 随机旋转。所有向量乘同一个随机正交矩阵。旋转之后每个坐标独立服从 Beta 分布,高维下逼近 N(0, 1/d) 的高斯分布,这一步与输入数据无关。
- 逐坐标校准,作者称之为 TQ+。Beta 分布是渐近的,在低维或词向量这类数据上,单个坐标会偏离理想形状。TQ+ 在第一次 add 时为每个坐标拟合两个标量(一个平移、一个缩放),把经验分位数对齐到理想分布。校准完成后冻结,后续 add 直接复用,不再重训。
- Lloyd-Max 标量量化。分布已知,就能预先算出每个坐标的最优分桶。2-bit 是 4 个桶,4-bit 是 16 个桶。
- 位打包。每个坐标变成一个小整数(2-bit 是 0-3,4-bit 是 0-15),紧密打包进字节。1536 维由此从 6144 字节压到 384 字节。
- 长度重归一化打分。标量量化会系统性低估内积,turbovec 在编码时为每条向量多算一个标量做校正,把内积估计从偏低修正为无偏,查询时无额外开销。
整个过程没有训练阶段、不需要参数调优、加新向量也不用重建索引,这就是 Online ingest(在线摄入),加进去就索引好了。
速度方面,turbovec 手写了 SIMD 内核:ARM 上是 NEON,x86 上是 AVX-512BW,带 AVX2 兜底。搜索时不解压数据库里的每条向量,而是把查询旋转到同一个空间,直接对着码本打分。在 ARM(Apple M3 Max)上,它比 FAISS 的 IndexPQFastScan 快 10–19%,x86 上 4-bit 配置也略有领先。
它明确交代了自己的边界能力
turbovec 在文档里把精度数据列得很清楚,没有回避。
OpenAI d=1536 和 d=3072 上,TurboQuant 在 R@1 召回率上比 FAISS 高 0.2–1.9 个百分点,到 k=8 时双方都到 1.0。GloVe d=200 是更难的低维场景,渐近假设在这里最松,TurboQuant 在 4-bit 上领先 0.9 个百分点,2-bit 上基本打平,差距在 0.1 个百分点以内。
它的对照基线选的是 FAISS IndexPQ(LUT256、nbits=8、float32 LUT),这是多数人在生产环境会默认采用的版本,比 TurboQuant 原论文用的自定义 u8-LUT 基线更强。在更强的基线下仍能持平或领先,这一点值得肯定。
实战:上手跑一遍
原理讲清楚了,接下来直接跑。turbovec 的 Python 接口很克制,几行就能用起来。
装它
最小可用例子
注意这里 add 之后直接就能 search,中间没有任何训练或调参。这就是数据无关带来的工程便利。
想要稳定的 ID(能删、能改)
上面那个索引返回的是下标。但实际业务里文档往往有自己的 ID(数据库主键之类),还需要支持删除。这时候用 IdMapIndex:
删除是 O(1) 的,ID 在删除后依然稳定。对于长期运行、文档不断增删的系统,这一点很重要。
最实用的能力:搜索时过滤
实际的 RAG 系统很少是纯向量检索。通常要先用别的系统(SQL、BM25、权限控制 ACL、时间窗口)把候选集缩小,再在候选集里做向量精排,也就是混合检索。
常见做法是过取:多取回一些结果,再在外层过滤掉不符合条件的。这样既慢,过滤比例高时召回也会下降。
turbovec 把过滤直接放进了 SIMD 内核:
它的关键在于过滤发生在 32 向量为一块的粒度上。整块没有任何被允许的 slot,就直接短路跳过,连查表打分都不做;块内单个不被允许的 slot 在堆插入时丢掉。
因此过滤越是有选择性(只允许索引里一小部分),越能省下 SIMD 开销,而不是先全部算一遍再丢弃。返回的始终是允许集合内的 top-k,输出长度为 min(k, len(allowed)),候选比 k 少时返回恰好 len(allowed) 个,不会用无关结果填充。
框架集成:换个 import 就行
如果你已经在用主流 RAG 框架,turbovec 提供了几个 drop-in 替换,公开接口、持久化语义、retriever 接线都一样,换掉 import 就能用:
Rust 用户
底层是 Rust 写的,Rust 项目可以直接用:
一个常见误解:它需要嵌入式模型吗
不需要。前面例子里反复出现的 vectors 从哪来,turbovec 并不关心。
它只负责向量的存储和检索这一层,接收的是一组 float 数组,给它什么向量就存什么、搜什么。embedding 模型只是 RAG 场景里最常见的向量来源,那是场景的需求,而非 turbovec 本身的需求。
所以:
- 任何能产出向量的来源都可以——图像特征(ResNet、CLIP)、音频指纹、推荐系统的物品向量、自定义特征工程的结果,只要维度对得上。
- 不做 RAG 也能用——图片相似搜索、去重、聚类召回,跟文本 embedding 没有关系。
- 甚至不需要真实语义——用
np.random.rand(n, 1536)造一批随机向量灌进去测试也能正常运行。文档里 benchmark 用的 GloVe、OpenAI 数据集,本身也只是现成的向量集。
简单说,turbovec 需要的是向量,而不是模型。向量从哪来,由你决定。
它适合谁
turbovec 反复强调 Pure local(纯本地):没有托管服务,数据不出本机或 VPC。配上任意一个开源 embedding 模型,就能搭一套完全离线、气隙隔离的 RAG。
符合下面任意一条,它就值得一试:数据不能交给第三方向量数据库服务;机器内存有限、希望把大语料放进去;需要本地低延迟检索;要在 SQL、权限、时间窗口的候选集里做向量精排。
需要说明的是,它目前是单机的顺序索引,不是分布式向量数据库。如果体量已经到了需要分片、跨机房、高可用的程度,那是另一类系统的范畴。但对于多数单机容量内的本地 RAG,turbovec 把省内存和搜得快这两件原本矛盾的事同时做到了,已经相当出色。
写在最后
能把一篇硬核论文落地成 pip install 后几行就能跑的工程作品,并不容易。TurboQuant「随机旋转之后坐标分布即可预测」的洞察很漂亮,但要把这套数学变成手写 SIMD 内核、变成支持在线摄入、按 ID 删除、内核内过滤的库,中间是大量扎实的工程工作。
如果你正在搭本地 RAG,或者只是好奇向量量化的实现思路,可以去仓库看看,docs/api.md 和 How it works 一节写得很清楚。觉得有用的话,给作者点个 Star。
项目地址:https://github.com/RyanCodrai/turbovec
论文:https://arxiv.org/abs/2504.19874