目录 · 10 节
1这次发布改了什么?2BM25 为什么要从稀疏向量搬到全文索引?3代码分词器解决什么问题?4嵌套过滤为什么以前慢?5turbo16:同样 16 位,为什么误差更小?6turbo8:再省一半要付出什么?7升级时要注意什么?8我认为它的局限9一个检索团队可以怎么试10结论与评价用 Qdrant 做检索、或者在评估向量数据库的团队值得读这篇 1.20 版本发布说明。它不是论文,但有三处改动会直接影响检索效果和机器成本。核心一句话:关键词检索不再需要先把文本转成稀疏向量,向量存储则多了一种“同样字节数、误差更小”的压缩格式。几个关键数字:带嵌套条件的过滤,在 10 万条数据上快了 7 到 38 倍;新的 16 位格式 turbo16 的重建误差是 float16 的 1/7.5;8 位格式 turbo8 只占 float16 一半空间,搜索快 1.1 到 2.4 倍。
这次发布改了什么?
按对检索团队的影响排个序:
| 改动 | 以前的做法 | 1.20 的做法 | 谁该关心 |
|---|---|---|---|
| 全文索引上直接算 BM25 | 文本先转稀疏向量,自己维护平均文档长度 | 索引自动统计,直接查询 | 做关键词检索、混合检索的团队 |
| 代码分词器 | getUserName 被当成一个词 | 按下划线和大小写拆开 | 做代码搜索的团队 |
| 嵌套过滤的逐元素索引 | 索引只能缩小候选,再逐条读原始数据核对 | 只靠索引就能回答 | 数据里有对象数组的团队 |
| turbo16、turbo8 两种存储格式 | float16 或自己量化成 uint8 | 先旋转再量化,自动完成 | 向量多、内存紧的团队 |
| 内存分级扩展、内部接口默认鉴权 | ID 映射表常驻内存,内部端口和公网同址 | 可放磁盘,可单独绑定内网地址 | 运维和升级负责人 |
下面挑前四项讲清楚原理,最后讲升级时容易踩的坑。
BM25 为什么要从稀疏向量搬到全文索引?
BM25(关键词检索最常用的打分公式)给一篇文档打分时,要看三样东西:查询词在这篇文档里出现几次、这个词在整个库里有多稀有、这篇文档比平均长度长多少。以前在 Qdrant 里用 BM25,要先把每篇文档和每个查询都转成稀疏向量,平均文档长度要自己算好写进去。
问题出在平均长度会过时。用一个手算例子说明(示意):BM25 的长度项是 k×(1−b+b×文档长度÷平均长度),取常用值 k=1.2、b=0.75。一篇 10 个词的文档,查询词出现 2 次。
| 平均长度 | 长度项 | 词频得分 2×2.2÷(2+长度项) |
|---|---|---|
| 5(当前真实值) | 1.2×(0.25+0.75×2)=2.1 | 4.4÷4.1≈1.07 |
| 10(建库时的旧值) | 1.2×(0.25+0.75×1)=1.2 | 4.4÷3.2≈1.38 |
同一篇文档,只因平均长度没更新,分数就高了约 28%。这说明 旧做法下数据一变,排序就悄悄偏了,而修正它要把所有文档重新转一遍。1.20 让全文索引自己统计这些量,文本写进去建了索引就能按 BM25 排序,不需要额外的模型推理。k 和 b 可以在每次查询时指定,调参不用重建索引。
原来用稀疏向量的团队可以平滑迁移:只要文本本来就存在数据里,建一个开启打分的全文索引,配置和原来一样的分词处理,再把稀疏向量查询换成文本查询即可,不用重新上传数据。
代码分词器解决什么问题?
默认分词器按空格和标点切词,对代码不友好。getUserName 会被当成一个整词,搜“user name”或“get_user_name”都找不到它。新的代码分词器按下划线和大小写变化切分,连缩写也能识别,于是 getUserName、get_user_name 和 user name 能互相匹配。它同时保留完整标识符,所以搜 hashmap 仍能找到 HashMap。配合上一节的 BM25,代码搜索可以不依赖embedding 模型就做出排序。
嵌套过滤为什么以前慢?
数据里常有对象数组。比如一件商品有多个报价,每个报价有自己的店铺和价格。嵌套过滤要找的是“同一个报价同时满足所有条件”的商品。
手算例子(示意):商品 X 有两个报价,A 店 50 元、B 店 200 元。过滤条件是“B 店且低于 100 元”。
- 旧做法:店铺索引说 X 有 B 店报价,价格索引说 X 有低于 100 元的报价,两者都命中,X 成为候选。但两个条件来自不同的报价,必须读出 X 的原始数据逐个报价核对,才能排除它。
- 新做法:逐元素索引把每个报价单独登记。店铺索引命中的是“X 的第 2 个报价”,价格索引命中的是“X 的第 1 个报价”,编号对不上,直接排除,不用读原始数据。
原始数据通常在磁盘上,候选一多,逐条读取就是瓶颈。官方在 10 万条数据上测了三种嵌套过滤,即使原始数据全在内存里(对旧做法最有利的情况),逐元素索引也快 7 到 38 倍。这意味着数据在磁盘上时,差距只会更大。搜索接口不变,建好索引后已有的嵌套过滤自动变快。
turbo16:同样 16 位,为什么误差更小?
float16 用浮点数表示每一维,精度是“相对”的:数值越大,能表示的最小间隔越大。用一个数说明(示意):float16 有 10 位尾数,在 16 到 32 之间相邻两个可表示的数相差 2^4÷2^10≈0.016,而在 0.01 附近间隔只有约 0.00001。有些embedding 模型的向量里会出现少数特别大的坐标,博客点名了 text-embedding-ada-002、CLIP 和 Cohere,这几个大坐标的舍入误差就会拖累整体精度。
turbo16 先对向量做一次 TurboQuant 旋转。按 Qdrant 文档的说法,这是一次快速的随机旋转:把向量乘上一个随机的正交矩阵,向量长度和向量之间的距离都不变,但每个新坐标都是所有旧坐标的混合,于是一个特别大的坐标会被摊到很多维上。二维的手算例子(示意):向量 (4, 0) 旋转 45 度后变成约 (2.83, 2.83),最大坐标从 4 降到 2.83,长度仍是 4。
旋转之后,每个坐标都四舍五入到同一套均匀刻度上,再用一个 16 位整数记下刻度编号,16 位整数能表示 65,536 个刻度。假设刻度覆盖 −1 到 1(示意),相邻刻度相差 2÷65,536≈0.00003,而且不论坐标大小,误差都不超过半个刻度。和 float16 对比就清楚了:float16 对大坐标给粗刻度,turbo16 先把大坐标消掉,再给所有坐标同样细的刻度。
效果有两组数字。在 13 个数据集上比较重建误差,turbo16 是 float16 的 1/7.5。在 12 个数据集上做精确搜索,turbo16 的 recall@10(前 10 个结果里找回真实近邻的比例)和 float32 的差距都在 0.001 以内,而 float16 在 ada-002 和 CLIP 数据集上掉到 0.995。用 HNSW(一种基于图的近似向量检索索引)搜索时,turbo16 的中位延迟是 float16 的 0.81 倍(x86)和 0.83 倍(ARM)。也就是说,同样的字节数,turbo16 在精度和速度上都比 float16 好,没有取舍。
turbo8:再省一半要付出什么?

turbo8 每维 1 字节,是 float16 的一半。和 uint8 不同,它直接接收普通浮点向量,量化过程由 Qdrant 完成。从图上看,turbo8 的召回和 float16 几乎一样高。它比上一版的 4 位格式 turbo4 提升最大的是低维数据:SIFT(128 维)上 turbo4 约 0.8,turbo8 约 0.97。原因不难理解,维度越低,每一维承载的信息越多,4 位的量化误差越伤召回。
速度上,在全部 11 个数据集上,turbo8 比 float16 快 1.1 到 2.4 倍,大多数数据集上和 turbo4 相差不到 10%。博客还拿它和“float32 原始向量加标量量化、开启重打分”的常见组合比较:11 个数据集里 10 个召回更高、1 个持平,延迟相近,而存储只有约五分之一,因为标量量化还要保留 float32 原件。

上图把账算清楚:100 万条 1536 维向量,按 float32 存约 6.14 GB,换成 turbo8 约 1.54 GB,所以同样的内存能放下约 4 倍数量的向量。同样的 8 位编码也可以作为量化选项(bits8)使用,压缩约 4 倍;默认的 bits4 压缩约 8 倍。用标量量化的团队值得测一下 bits8,因为它压缩倍数相同,而在 turbo8 的测试里这套编码召回更高。
升级时要注意什么?
两处改动可能让升级后的集群出问题,值得单独列出。
先说明内存级别(1.19 引入):pinned 是常驻内存;cached 是放在磁盘上,Qdrant 打开数据段时把它读进操作系统的页缓存;cold 是放在磁盘上,用到时才读。
- 内联存储的内存占用会变。以前 HNSW 索引文件里存一份原始向量,常规向量存储里再存一份;现在只存在索引文件里,省了磁盘,但向量改为跟随索引的内存级别。HNSW 索引默认是 cached,如果原来向量设成 cold 而没有其他设置,升级后原始向量会变成 cached,量化向量会常驻内存。升级前要显式设好 hnsw_config.memory。
- 内部接口默认鉴权。设置了 api_key 的集群,节点之间的内部请求必须带有效的读写密钥。只设置了只读密钥的集群,升级后节点之间会无法通信,要先补上 api_key 或关闭这项强制。
另外,ID 映射表(记录每条数据的编号对应内部位置)以前常驻内存,每条约 52 字节,1 亿条就是约 5 GB。现在可以放到磁盘上按需读取,数据量大的集群能省下可观的内存。
我认为它的局限
第一,所有数字都是厂商自测。嵌套过滤的 7 到 38 倍只在 10 万条数据、三种过滤上测过,和生产里上亿条数据、复杂条件的差距未知,需要自己复测。
第二,turbo16 的优势集中在“少数坐标特别大”的embedding 模型上。博客说 float16 在 ada-002 和 CLIP 上掉到 0.995,其他数据集上 float16 本来就接近 float32,换 turbo16 主要收益是速度那 17% 到 19% 的延迟下降,而不是精度。
第三,全文索引上的 BM25 只解决关键词排序。混合检索里关键词分数和向量分数怎么融合、权重怎么调,仍要自己做实验,这次发布没有改变这部分工作量。
一个检索团队可以怎么试
- 在测试环境升级到 1.20 前,先按上一节检查内联存储的内存级别和密钥配置,避免升级后内存暴涨或节点失联。
- 把一个现有的稀疏向量 BM25 集合迁到全文索引,用同一批查询对比排序结果和延迟,确认分词配置一致。
- 取线上embedding 模型的一批向量,分别存成 float16、turbo16 和 turbo8,对比 recall@10、延迟和内存,模型输出里有大坐标时重点看 turbo16。
- 数据里有对象数组的,给嵌套字段建逐元素索引,用真实过滤条件压测,看收益是否接近官方的数量级。
结论与评价
这一版最值得关注的是 turbo8 用五分之一的存储,达到了比“float32 加标量量化”更高的召回,对向量规模大、内存紧张的检索服务是实打实的成本下降。
| 维度 | 评价 |
|---|---|
| 问题重要性 | 中高。关键词排序准确性、嵌套过滤速度和向量存储成本都是检索服务的日常痛点 |
| 方法创新性 | 中。旋转后量化、逐元素索引都是已有思路,价值在于做进了数据库默认能力 |
| 实验证据 | 中。数据集覆盖较多,但都是厂商自测,嵌套过滤只测了 10 万条 |
| 工程成本 | 低到中。多数功能建索引或改配置即可,升级要处理内存级别和鉴权两处变化 |
| 是否值得尝试 | 值得。已在用 Qdrant 的团队可以优先评估 turbo8 和全文索引 BM25 |
读完这篇,你觉得
夜读鸮记下了,谢谢你的反馈。
点一下即可,无需登录;再点一次撤回。只统计匿名次数,不记录你是谁。
讨论
欢迎补充实验细节、复现经验或不同看法。需 GitHub 登录。
正在加载讨论…