turbovec API参考(上):TurboQuantIndex向量索引完整方法手册
【免费下载链接】turbovecA vector index built on TurboQuant, written in Rust with Python bindings项目地址: https://gitcode.com/GitHub_Trending/tu/turbovec
turbovec是一个基于 GoogleTurboQuant算法的向量索引(Vector Index):Rust 内核 + Python 绑定,把高维向量压缩到每维 2–4 bit,1000 万条文档约 31 GB 的 float32 存储可以直接装进 4 GB 内存。它是数据无关(data-oblivious)的量化器——无需训练、无需调参,add即可用。本文是 API 参考手册的上篇,完整梳理核心类TurboQuantIndex的每一个方法。
两个索引类:先选对再上手 🎯
turbovec 提供两个索引类,官方完整文档见 docs/api.md:
| 索引类 | 特点 | 适用场景 |
|---|---|---|
TurboQuantIndex | 位置索引,向量以插入槽位0..n标识 | 只增不删、或接受位置 ID |
IdMapIndex | 在TurboQuantIndex之上加一层稳定外部u64ID 映射,remove(id)为 O(1) | 需要可删除、ID 稳定的生产库(LangChain/LlamaIndex/Haystack 集成内部均使用它) |
本篇聚焦
TurboQuantIndex;IdMapIndex的方法与它高度对称,将在下篇详解。
两类索引的 Rust 实现位于 turbovec/src/lib.rs,Python 绑定位于 turbovec-python/src/lib.rs,Python 端封装与持久化辅助在 turbovec-python/python/turbovec/_persist.py。
构造参数:30秒创建向量索引
from turbovec import TurboQuantIndex idx = TurboQuantIndex(dim=1536, bit_width=4) # 也可省略 dim,首次 add 时自动推断构造参数一览:
| 参数 | 取值 | 说明 |
|---|---|---|
dim | 8 的正整数倍,且≤ 16384(MAX_DIM),可选 | 向量维度;省略则为"惰性索引",第一次add时锁定 |
bit_width | {2, 3, 4},默认 4 | 每维压缩位数:2-bit 最省内存,4-bit 精度更高 |
惰性索引(lazy index)的行为:在首次add之前,idx.dim为None、len(idx)为0、search()返回空结果;空批次(0 行)的add是 no-op,索引保持惰性。
💡 典型 embedding 模型:OpenAI d=1536 / d=3072、GloVe d=200 都能直接建库。
核心工作流:add 入库、search 检索
import numpy as np idx.add(vectors) # float32 二维数组,形状 (n, dim) scores, indices = idx.search(query, k=10) # 返回内积分数与槽位下标add(vectors)要点:
- 输入必须是C 连续的 float32 数组,其他 dtype 会被直接拒绝(而不是静默转换),必要时先
np.asarray(x, dtype=np.float32) - 出现 NaN / Inf 或
|值| ≥ 1e16会抛ValueError,且报错信息精确到第几条向量第几维 - L2 范数
≤ 1e-10的向量没有可表示方向,会以 scale 0 存储,对任何查询得分恒为 0(记录在len(idx)中,但排在所有正常向量之后)
search(queries, k, *, mask=None)要点:
- 返回
(scores, indices),形状均为(nq, effective_k),indices是int64槽位下标 - 分数是内积,因此与查询向量同向缩放;ID 排序对查询乘以任意正数不变
effective_k = min(k, len(idx)):库里向量不足 k 条时返回实际条数,不做 NaN 填充mask:布尔数组(长度len(idx)),只允许mask[i] == True的槽位参与,详见下文"过滤检索"
过滤检索:在 SIMD 内核里直接屏蔽槽位
turbovec 的过滤不是"先搜后扔"(post-filter),而是在内核内直接跳过不允许的向量——总能从允许集合中返回最多 k 条,不会被宽松过滤"掏空"结果:
mask = np.ones(len(idx), dtype=bool) mask[disabled_slots] = False scores, slots = idx.search(query, k=10, mask=mask)⚠️一个重要陷阱:mask引用的是槽位,而任何变更(哪怕len不变)都可能让槽位重排——所以每次变更后必须重建 mask,长度校验救不了你。这一点在 docs/api.md 中有专节说明。
删除与持久化:swap_remove、write、sync 三件套
swap_remove(i):O(1) 位置删除
把最后一个向量换到槽位i并截断一条。它不是移位——不保序,未删除向量的槽位下标可能已指向别的向量。命名对齐 Rust 的Vec::swap_remove,语义可预测。
write(path)/load(path):整文件快照
write以 fsync + 原子重命名产出.tv文件,load读取。write(path, durable=False)可跳过重命名前的 fsync(更快,但断电可能丢文件);Rust 侧对应write_with_durability(path, io::Durability::Fast | Durable)。
sync(path):增量保存
sync只写入自上次 sync 以来的变更:追加写入新的 32 行块 + 提交头;删除完全不写数据块,只在提交头里记一条 redo 操作。每次sync返回即持久(sync_all级 fsync),任意字节处崩溃都保留上一次完整提交。load()自动识别快照与增量两种容器格式,且加载后的索引仍绑定原路径,继续增量同步。
内存序列化与拷贝:to_bytes、from_bytes、pickle
idx.to_bytes():返回与write(path)字节完全一致的内存载荷(.tv格式)TurboQuantIndex.from_bytes(data):接受bytes/bytearray,校验规则与load一致,损坏载荷抛ValueErrorpickle.dumps/copy.copy/copy.deepcopy全部支持,底层都归约为from_bytes(to_bytes()),拷贝与原对象完全独立,可跨越multiprocessing的 spawn 边界
这条路径是缓存与数据库列存储的推荐方式,框架集成库的持久化也构建在它之上。两个小坑值得记住:空索引在布尔上下文中为 falsy(用idx is None判断索引、len(idx)判断内容);索引不允许挂用户属性(idx.tag = "x"会抛AttributeError)。
TQ+ 校准:calibrate 与 calibration_state
默认情况下索引是"纯 TurboQuant";调用一次calibrate(sample)后开启TQ+——对每个坐标拟合(shift, scale)校准,平均可提升约 +2.5 个 R@10 召回点,最各向异性的数据上提升近 8.7。要点:
- 时机:能在校准后再
add就尽量先校准;大批量入库后补校准相当于二次量化,会损失几个召回点 - 样本:随机且有代表性即可,约 1024 行就接近全库拟合效果(2048 行在所有测量语料上达标);排序/聚类前缀会破坏召回
- 可重调:
calibrate可随时多次调用,会用存储的码重编码全部存量行,无需原始向量;但被严重偏差校准"剪坏"的数据无法修复,只能从源向量重建 idx.calibration_state报告当前状态:"uncalibrated"或"calibrated"- 校准状态可完整往返于
write/load、to_bytes/from_bytes、pickle 与拷贝
Rust 侧对应calibrate(&mut self, sample)(2D 批次为calibrate_2d),测试参考 turbovec/tests/tqplus_calibration.rs。
附:prepare 预热与内省属性
prepare():可选。提前构建旋转矩阵、Lloyd-Max 质心与 SIMD 分块布局,让第一次search不必支付一次性初始化成本(惰性索引上调用为空操作)- 内省:
len(idx)(向量数)、idx.dim(已提交维度或None)、idx.bit_width、idx.calibration_state - 线程安全:
search只读锁,多线程并发搜索互不阻塞;变更走写锁,add/swap_remove期间读者等待
TurboQuantIndex 方法速查表
| 方法 / 属性 | 说明 |
|---|---|
TurboQuantIndex(dim=None, bit_width=4) | 构造;bit_width ∈ {2,3,4},dim可选 |
add(vectors) | 批量入库;float32 二维数组(n, dim) |
search(queries, k, *, mask=None) | 返回(scores, indices);支持布尔 mask 过滤 |
swap_remove(i) | O(1) 位置删除,末位向量换入i,返回被移动向量的原位置 |
prepare() | 预热缓存,消除首次搜索的一次性开销 |
calibrate(sample)/calibration_state | TQ+ 校准提交与状态查询 |
write(path, *, durable=True)/load(path) | .tv整文件快照读写 |
sync(path) | 增量持久化,崩溃安全 |
to_bytes()/from_bytes(data) | 内存字节序列化 |
len(idx)/idx.dim/idx.bit_width | 内省 |
📚延伸阅读:完整 API 文档 docs/api.md;Rust 入口与并发约定见 turbovec/src/lib.rs;Python 绑定实现见 turbovec-python/src/lib.rs;召回与速度基准见 benchmarks/results/ 下的 JSON 结果。
下篇预告:
IdMapIndex完整方法手册——add_with_ids、remove(id)、allowlist过滤与.tvim文件格式。
【免费下载链接】turbovecA vector index built on TurboQuant, written in Rust with Python bindings项目地址: https://gitcode.com/GitHub_Trending/tu/turbovec
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考