内存不够怎么办:VexDB-Lite PQ 量化索引教程,把向量存储压缩到几分之一
【免费下载链接】VexDB-LiteA cross-platform vector database, which can be integrated into existing databases as a plugin.项目地址: https://gitcode.com/gh_mirrors/ve/VexDB-Lite
VexDB-Lite 是一个可插入 PostgreSQL、DuckDB、SQLite 的跨平台向量数据库插件,内置PQ(Product Quantization,乘积量化)量化索引:建索引时把每个向量用训练好的码本编码成几十字节的短码,索引侧存储直接缩小到原始向量的几分之一,特别适合内存紧张但又要跑向量相似度检索的场景。本文面向新手,讲清楚 PQ 是什么、能省多少内存,以及三种数据库里怎么写出一条 PQ 索引。
一、为什么向量检索总"吃内存"
一条 768 维的 embedding(常见于文本/图像向量)按 float32 存储就是 768 × 4 =3072 字节。100 万条向量的裸向量文件约3 GB——这还没算图索引的拓扑结构和查询时的缓存。机器只有 8 GB 内存时,索引直接加载进来基本就"OOM 预演"了。
VexDB-Lite 的图索引默认会把向量留在索引侧参与距离计算;开启 PQ 后,索引侧只保留量化码,原始向量留在业务表里,查询时再按需取回精确重排。官方压测报告中,100 万条 768 维向量的物理存储从 3.4 GB 级别压缩到 1.2 GB 级别(见 Cohere-1M 复测报告),量级思路与 PQ 一致:索引越瘦,内存越宽裕。
二、PQ 量化原理:把 768 维"切成小段"记编码
PQ 的思路非常直观,三步走:
- 切段:把向量切成
pq_m段(子向量)。例如 128 维向量、pq_m = 32,每段 4 维。 - 训练码本:对每段用 K-means 聚成 256 个簇心(代码见 common/quantizer/product_quantizer.h 与 common/quantizer/annkmeans.h),形成码本。
- 编码:每个向量的每段只记录"落在哪个簇心",1 个字节即可表示。
于是压缩比一目了然:
| 向量维度 | 原始大小(float32) | PQ 码大小(pq_m=32) | 压缩比 |
|---|---|---|---|
| 128 | 512 B | 32 B | 约 1/16 |
| 768 | 3072 B | 32 B | 约 1/96 |
| 1536(多模态 embedding) | 6144 B | 64 B(pq_m=64) | 约 1/96 |
怎么选pq_m?唯一硬性要求是pq_m必须能整除向量维度。经验值是每段 4~8 维效果最好(源码里也有同样的AutoSelectM逻辑,见 product_quantizer.h),即pq_m ≈ dim / 4,768 维用 32~192 都合理,128 维用 16~32。段数越多,压缩越狠,但召回损失也越大——这是本文后面"调召回"一节要解决的。
三、PostgreSQL:一条 WITH 子句开启 PQ 索引
完整语法示例在 README.md 里,核心就两步:
SET maintenance_work_mem = '2GB'; -- 训练需要内存,内存不足会回退成普通图索引 CREATE INDEX idx_pq ON items USING vexdb_graph (vec floatvector_l2_ops) WITH (quantizer = 'pq', pq_m = 4);几个新手最容易踩的点:
- 训练样本要够:训练样本少于 256 条、或内存预算太低时,PQ 会回退成普通图索引并给出 NOTICE,不会报错——建完记得检查。
- 训练在主进程跑:
parallel_workers > 0时,并行 worker 只参与磁盘构建阶段,码本训练仍在 leader 进程完成,所以maintenance_work_mem给主进程留足。 - 建完仍可增删改:PQ 索引支持建后
INSERT/UPDATE/DELETE,新向量直接用已训练好的码本编码,不用重建。 - compact 模式是强约束:
memory_mode='compact'下量化器必须激活,样本或内存不足时直接报错而不是静默回退,适合生产上"我就要压缩"的严格场景。
四、DuckDB 与 SQLite:compact 模式极限压缩
DuckDB(语法见 documentation/features.md):
CREATE INDEX idx_pq ON items USING GRAPH_INDEX (vec) WITH (quantizer = 'pq', pq_m = 32); -- 紧凑模式:索引侧不再保留原始向量镜像 CREATE INDEX idx_compact ON items USING GRAPH_INDEX (vec) WITH (quantizer = 'pq', pq_m = 32, memory_mode = 'compact');SQLite直接在建虚拟表时指定(完整示例见 vexdb_sqlite/README.md):
CREATE VIRTUAL TABLE idx USING GRAPH_INDEX( embedding FLOAT[128], metric=cosine, quantizer=pq, pq_m=32, memory_mode=compact );SQLite 侧的细节:PQ 按ef_search × 1.25扩展候选,再用%_vectors里的原向量做精确重排,所以 compact 下业务数据仍然完整,只是索引 blob 不再写原始向量镜像。
五、PQ 之后怎么调召回和速度
PQ 码算出的距离是"近似距离",VexDB-Lite 用精确重排找回召回:先按 PQ 距离筛出一批候选,再取原始向量精算距离排序。三个关键旋钮(DuckDB 侧):
| 参数 | 默认 | 作用 |
|---|---|---|
vexdb_ef_search | 64 | 图搜索宽度,调大提升召回 |
vexdb_pq_refine_k_factor | 1.0 | 重排倍率,1.0 = 不额外扩展;设 4.0 会按 top k×4 用 PQ 筛候选再精排 |
vexdb_pq_search_mode | 'off' | 设'pq_only'跳过精确重排,速度最快但召回略低 |
实用策略:先默认跑一遍看 recall,不够再调大ef_search;仍不够且延迟可接受,把pq_refine_k_factor提到 2.0~4.0。对应行为有完整测试用例覆盖,可参考 graph_index_pq.yaml 与 graph_index_pq_refine.yaml。
六、建完索引怎么确认"真的量化了"
三个动作养成习惯:
- 查状态:PG 用
SELECT indexname, use_pq, pq_m FROM vexdb_index_info();,DuckDB 用SELECT * FROM vexdb_index_info();。 - 看内存模式:PG 的 compact 模式下,
ALTER INDEX ... SET不会重写已有索引,必须REINDEX后用vexdb_index_info()与index_inspect()(关注Working Quantizer、Vector Storage两个属性)确认实际状态。 - 跑一遍 ANN 查询:
ORDER BY vec <-> '...' LIMIT 10,优化器会自动改写成索引扫描,确认走的是量化路径而不是全表扫。
小结:内存不够时的决策清单
- 向量维度高(≥768)+ 内存紧张 → 直接 PQ + compact,收益最大(约 1/96 压缩);
pq_m取dim/4左右且整除维度,兼顾压缩比与召回;- PG 记得给
maintenance_work_mem留训练空间,小表(<256 样本)别开 PQ; - 建完用
vexdb_index_info()验证,再用ef_search/ refine 倍率两档调召回。
更多参数与三种引擎的差异对照,推荐通读 功能文档;想从源码理解量化内核(K-means 训练、ADC 距离表、SIMD 分发),入口在 common/quantizer/ 目录。
【免费下载链接】VexDB-LiteA cross-platform vector database, which can be integrated into existing databases as a plugin.项目地址: https://gitcode.com/gh_mirrors/ve/VexDB-Lite
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考