数据截至 (上游 commit 8c51b8dc5408)
两套多向量存储:同一个接口,两条完全不同的路
本章讲什么: MaxSim 精确算太贵,存储层怎么变着法子省。项目给了两个实现,取舍完全相反。
1. 它要解决的小问题
第 02 章留下的账:一页有上千个 patch 向量,一次查询要对每一页算一个 n_query_token × n_patch 的相似度矩阵。一万页文档就是一万次矩阵乘。没法直接上生产。
两个实现分别下注在不同的地方:
慢路 MultiVectorStore | 快路 FastMultiVectorStore | |
|---|---|---|
| 省法 | 把向量压成 1 bit/维,用位运算算 | 先用一个定长向量粗筛,只对候选做精确算 |
| 精度 | 近似(二值化有损),一步到位 | 粗筛近似 + 重排精确 |
| 依赖 | 只要 Postgres | Turbopuffer(外部托管服务)+ 对象存储 |
| 配置 | [multivector_store] provider = "postgres"(默认) | provider = "morphik" + TURBOPUFFER_API_KEY |
| 代码 | core/vector_store/multi_vector_store.py:39 | core/vector_store/fast_multivector_store.py:305 |
两者实现同一个抽象基类 BaseVectorStore,core/services_init.py:141-190 按配置二选一,上层完全无感。
2. 慢路:二值量化 + 自建 SQL 函数
思路
128 维 float32 是 512 字节。但如果只关心"每一维是正是负",128 维就只要 16 字节——压缩 32 倍。两个二值向量的相似度可以用异或加popcount(数 1 的个数)算出来,这在 CPU 上极快。
项目自己在注释里给了出处(core/utils/fast_ops.py:7-10):Qdrant 的二值量化文章、SimSIMD 库。
表结构
CREATE TABLE IF NOT EXISTS multi_vector_embeddings (
id BIGSERIAL PRIMARY KEY,
document_id TEXT NOT NULL,
chunk_number INTEGER NOT NULL,
content TEXT NOT NULL, -- 这里存的是对象存储的 key,不是内容本身
chunk_metadata TEXT,
embeddings BIT(128)[] -- 一页 = 一个位串数组
)
见 core/vector_store/multi_vector_store.py:242-250。BIT(128)[] 是整个设计的核心:一行存一页,数组里每个元素是一个 patch 的 128 位二值向量。