
TieredQ 向量量化算法发布:低比特下比 RaBitQ 省 36–52% 内存
来源标题:TieredQ: Space Efficient Vector Quantization (Beating RaBitQ and TurboQuant)
TieredQ 是一种新的向量量化算法,通过按维度动态分配比特在低比特预算下保持同等召回率,同时比 RaBitQ 和 TurboQuant 减少约 36–52% 的内存与磁盘占用。它先将向量中心化并旋转到 PCA 基,再对每维贪心分配 0–8/12 比特以降低量化误差,可配合 IVF、HNSW 等 ANN 索引使用,并已提供 IVF_TieredQ 参考实现。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
TieredQ 是一种新的向量量化算法,它按维度动态分配比特数,从而在高召回率下实现更高的压缩比。TieredQ 先将向量中心化并旋转到 PCA 基上,然后贪心地为每个维度分配 0–8/12 比特,以降低量化误差。在相同的每比特召回率下,与 RaBitQ 和 TurboQuant 相比,TieredQ 在低比特预算(例如每维 1、2、4 比特)下可减少约 36–52% 的内存和磁盘占用。ANN 索引(例如 IVF / HNSW)可以使用该编解码器来节省内存和磁盘空间(提供了参考实现 IVF_TieredQ)。 uv add tieredq
或者:pip install tieredq
Python import gensim.downloader as api from tieredq import TieredQIndex vectors = api.load("glove-wiki-gigaword-100").vectors # (400000, 100), float32 d = vectors.shape[1] index = TieredQIndex(dimensions=d, code_bits=2 * d) # 每维 2 个编码比特 index.add(vectors) # 首次 add 会训练(从较大批次中取一个带种子的 32,768 行样本);后续 add 只做编码 scores, ids = index.search(vectors[:2], k=10) # (2, 10) float32, uint32 index.save("index.tq") index = TieredQIndex.load("index.tq") gensim 仅本示例需要(uv add gensim)。构造函数选项 code_bits 之后的参数为仅关键字参数: 精度与速度请参见 fast-mode 测量结果。 在 fast 模式下,层级宽于 4 比特的模型现在会保留原始码本精度。 这会以吞吐量为代价提升召回率;已有的数值级 fast 索引需要重建才能恢复该精度。它们的 fast 存储按层级宽度保存编码;以早先每坐标一字节布局保存的文件仍可加载,并会被重新打包。详见 high-bit 调查研究。 from tieredq import TieredQIVFIndex, TieredQNestedIVFIndex, TieredQPruning d = vectors.shape[1] queries = vectors[:2] ivf = TieredQIVFIndex(dimensions=d, code_bits=2 * d, lists=1024) # lists=0:每个编解码器质心一个列表 ivf.build(vectors) # 训练编解码器(带种子的 32,768 行样本)和列表,然后建立索引 scores, ids = ivf.search(queries, k=10, nprobe=32)
在内存 IVF 中使用前缀编码;候选在 SSD 上用 4 比特/维的精化层重新打分。
index = TieredQNestedIVFIndex.build( vectors, prefix_bits=d, total_bits=4 * d, lists=256, fine_tiers=True ) index.write_tier("tier.rows", vectors) # 或 index.attach_tier(index.encode_tier(vectors)) 将其保留在内存中 index.attach_tier("tier.rows", reads="direct", queue_depth=32) scores, ids = index.search(queries, k=10, nprobe=64, candidates=50) index.save("nested.tq") # IVF、精化和层级布局;load() 后需再次 attach 层级 TieredQIVFIndex(dimensions, code_bits, *, lists=0, metric="ip", fine_tiers=False) 始终使用 fast 存储。build(rows) 会替换其向量;只有首次 build 或 train_lists(sample) 会训练编解码器。可在 build 之前调用 train_lists 以在单独样本上固定分区。 TieredQNestedIVFIndex.build(vectors, *, prefix_bits, total_bits, lists=0, metric="ip", tier_rows="prefix_and_suffix", sample=None, fine_tiers=False, max_tier_bits=12) 训练一个嵌套索引。 prefix_bits 和 total_bits 是每个向量的预算,其中 prefix 包含 在 total_bits 内。对于内积/余弦,fine_tiers=True 允许后缀训练分配 宽于 8 比特的组合前缀/后缀编码,最高到 max_tier_bits(8–12)。 IVF 前缀保持固定;候选直接用全部后缀比特重新打分。 max_refinement_bits 报告实际实现的最宽编码; total_bits 和 tier_bytes_per_vector 报告块取整后的实际存储。 更宽的后缀模型使用精化格式 2;已有的格式 1 模型仍可加载。 在本地 Gemini-768 基准测试中, 8 比特/维的总预算将全探测 recall@10 从 99.44% 提升到 99.76% (漏检减少 57%),在单 M1 线程、使用内存层的情况下,每查询的中位批处理时间从 2.480 ms 变为 2.501 ms。 4 比特/维的对照组保持不变。 两个 IVF 构造函数都不暴露扁平索引的 mode、allocation_power 或 exact_fast_scores。 IVF 搜索需要关键字参数 nprobe;嵌套搜索还要求 candidates >= k。结果对探测列表行数之外的槽位使用 232 - 1 作为 id、-inf 作为分数。 文件层默认 reads="cached"、queue_depth=1。在 更大队列深度下的并发读取需要带 liburing 的构建;否则读取保持串行。 PyPI wheel 禁用了 liburing。 一次构建从同一个带种子样本训练前缀、列表和精化,因此它们不会 不匹配。可传 sample= 以改用更早的数据训练;其行也会为后续构建固定列表(如同 TieredQIVFIndex.train_lists 所做的)。 tier_rows="suffix" 存储更小的层,并从列表中解码每个候选的前缀(参见 C++ 说明)。 层级行保存在其他存储(数据库、对象存储)中时:ids = index.candidates(queries, 50, nprobe=64), 取回这些 id 的 encode_tier 行(形状为 ids.shape + (index.tier_bytes_per_vector,);id 为 232 - 1 的是填充),然后 scores, ids = index.rescore(queries, ids, rows, k=10)。 pruning=TieredQPruning(confidence=8) 让 IVF 和嵌套扫描启用近似剪枝(见下文)。 C++ 从源码构建需要 CMake 3.18+、C++20 编译器、OpenMP 以及 BLAS/LAPACK。liburing 为可选项,用于并发 SSD 层级读取。 cmake -S . -B build cmake --build build -j OMP_NUM_THREADS=2 ctest --test-dir build --output-on-failure ./build/tieredq_example cmake --install build --prefix /desired/prefix 包含 <tieredq/tieredq.h>,使用 find_package(TieredQ CONFIG REQUIRED),并 链接 TieredQ::TieredQ。FetchContent 也可用。布局、线程、所有权和依赖设置请参见 integration.md。 dv::TieredQ::train(config, sample, rows, seed) 在连续样本上训练。要在大数组的一部分上训练 而不复制它,dv::TieredQ::train_sampled(config, data, count, sample_ids, sample_rows, seed) 会原地读取 指定的行(同一模型)。在 100 万行数据集上,约 8–16K 训练行即可达到完整召回;每个质心少于 约 32 行会损失召回率(#19.2 结果)。
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。