Milvus HNSW 量化索引选型指南:从 SQ、PQ、PRQ 到 Refine 实测
Milvus 的 HNSW 系列索引包括 HNSW、HNSW_SQ、HNSW_PQ 和 HNSW_PRQ。四者采用同一套分层图构建与搜索框架,但使用不同精度的向量表示参与建图和距离计算;量化索引还可以使用更高精度的向量表示对候选结果进行重排。
本文基于 VectorDBBench Cohere 1M 数据集,对四类索引及其相关量化与搜索参数通过实验进行系统比较,重点考察 Recall@100、峰值 QPS、Latency、运行时容器内存、索引构建产物大小和索引构建时间之间的取舍,并据此给出面向生产环境的选型与调参路径。本文基于 Milvus 2.6.17 和 PyMilvus 2.6.15 完成测试。索引能力、参数范围和性能表现可能随版本演进而变化。
核心结论:
- 使用 HNSW FP32 建立基准后,优先从 SQ8(未启用 refinement)开始评估量化压缩收益;在本次 Cohere 1M 测试中,它在召回率、吞吐和资源占用之间表现最均衡。
- 使用
refine_type=FP32的 refinement 可以显著恢复召回率,但运行时内存可能接近或超过 HNSW FP32。 ef主要解决图搜索宽度不足;refine_k主要修正量化距离造成的候选排序误差。
理解 HNSW 系列索引的关键,是区分图搜索框架、向量表示和候选重排三个概念。HNSW 负责在分层图上导航;SQ、PQ 和 PRQ 决定向量如何压缩以及如何计算近似距离;refinement 则决定是否使用更高精度的表示对候选结果重新计算距离并排序。

标准 HNSW 直接使用 FP32 向量,是本文的全精度参考。HNSW_SQ 采用标量量化;HNSW_PQ 将向量划分为多个子向量,并将每个子向量编码为码本索引;HNSW_PRQ 则在 PQ 粗量化的基础上继续编码残差。三种量化索引均支持 refinement。采用更高精度的refinement通常能提升召回率,但也会增加计算、存储和运行时内存开销。
索引原理
HNSW 将向量组织成多层小世界图。查询从稀疏的高层入口开始,通过贪心遍历快速接近目标区域,再逐层下降到最底层扩展候选。M、efConstruction 和 ef 分别控制图的连接密度、建图阶段的候选宽度以及查询阶段的搜索宽度:
M:每个节点允许建立的连接数上限。增大M通常能改善图的连通性并提高召回率,但也会增加索引内存、构建时间和查询成本。efConstruction:插入节点时参与邻居选择的候选宽度。值越大,建图阶段评估的潜在连接越多,通常能提高图质量,但也会延长构建时间。ef:查询时在底层保留和评估的候选宽度。值越大,召回率通常越高,但查询延迟和 CPU 开销也会随之上升。

量化索引沿用相同的 HNSW 分层建图与搜索流程,但向量压缩表示会参与邻居选择和距离计算。因此,“算法框架相同”并不意味着四种索引会生成完全相同的邻接边或搜索路径。量化误差既可能改变建图阶段的邻接关系,也可能影响查询阶段的候选排序。
2.1 SQ、PQ 与 PRQ 影响什么
HNSW_SQ 使用 Scalar Quantization。SQ8 和 SQ6 分别以 8 bit 和 6 bit 表示每个维度,并为各维度独立估计量化范围。SQ4U 从 Milvus 2.6.8 开始提供,它使用全局共享的均匀量化参数,将每个值编码为 4 bit 无符号整数。SQ4U 更适合已经归一化或各维数值分布较一致的数据;其性能收益也更依赖内存带宽、缓存效率和 SIMD 能力。
HNSW_PQ 使用 Product Quantization。PQ 将 D 维向量划分为 m 个子向量,每个子向量使用一个 nbits 位的码本索引表示。本次 Cohere 向量为 768 维,配置为 m=96, nbits=8,即每个子向量包含 8 个维度,理论编码长度为 96 bytes/vector。相比之下,SQ8 的理论编码长度为 768 bytes/vector。
HNSW_PRQ 使用 Product Residual Quantization。它首先使用 PQ 对原始向量进行粗量化,再使用额外码本对 PQ 近似留下的残差进行迭代量化;参数 nrq 控制残差量化的迭代次数。本次实验设置为 m=96, nbits=8, nrq=2。按该配置,理论编码长度约为 192 bytes/vector,为本次 PQ 配置的两倍。残差编码能够保留更多信息,但也会增加码本训练、索引构建和距离计算成本。

本文关于 PQ 和 PRQ 的结论仅适用于当前 m=96, nbits=8 和 nrq=2 的配置。增大 m、调整 nbits 或改变 nrq,都可能改变召回率、构建成本和内存占用。上述理论编码长度仅用于说明编码预算,并不等同于最终索引文件大小。本文所说的编码预算,是指每条向量用于保存量化编码的理论字节数,不包括 HNSW 图结构、码本、元数据和可选的 refinement 数据。
2.2 Refinement:用更高精度向量表示重排候选结果
量化索引可以通过 refine=true 参数启用候选重排。Milvus 先使用基础量化表示执行 HNSW 搜索,随后使用 refine_type 指定的更高精度向量表示,对扩大后的候选集重新计算距离并排序。refine_type 的精度必须高于基础量化类型,可选值包括 SQ6、SQ8、BF16、FP16 和 FP32;其中只有 FP32 属于全精度重排,其余类型仍会有相应的表示误差。在 Milvus 2.6.x 中,refine_type 必须采用比基础 sq_type 更高精度的表示。常见的精度升级方向如下:
| 基础 sq_type | 可选的更高精度 refine_type |
|---|---|
SQ4U | SQ6 、 SQ8 、 BF16 、 FP16 、 FP32 |
SQ6 | SQ8 、 BF16 、 FP16 、 FP32 |
SQ8 | BF16 、 FP16 、 FP32 |

如图4所示,TopK 表示最终返回的结果数量,refine_k 表示重排候选集相对于 TopK 的放大倍数:
进入 refinement 的候选数 = TopK × refine_k
本文固定 TopK=100。当 refine_k=4 时,进入重排阶段的候选数量为 400。系统使用 refine 表示重新计算这些候选结果的距离,最后仍只返回排名最高的 100 个结果。增大 refine_k 有助于将因量化误差而落在 TopK 之外的真实近邻重新排回结果集,但也会增加计算量和延迟。
Refinement 还会改变索引的存储与内存消耗。以 refine_type=FP32 为例,已加载的 segment 需要能够访问 FP32 级别的 refine 表示,因此索引构建产物和运行时内存会显著增加。但是这并不意味着所有 FP32 向量在任何时刻都必须常驻物理内存;实际内存驻留量还会受到 mmap、page cache、加载策略和访问模式的影响。
ef 与 refine_k 面向不同的瓶颈:ef 扩大图搜索阶段的探索范围,主要减少搜索宽度不足造成的候选结果遗漏;refine_k 扩大高精度重排候选集,主要修正量化距离带来的排序偏差。两者不能简单互相替代。
实验与结果
本次实验使用统一的数据集和测试指标,对 HNSW 系列索引进行横向比较。除主测试结果外,还覆盖 SQ 量化位宽、ef、refine_k、refine_type 以及等召回率选点,用于分析不同配置在召回率、吞吐、延迟、运行时内存和索引构建产物大小之间的工程取舍。
3.1 实验设计
实验围绕两个核心问题展开:第一,量化表示能够带来多少内存与吞吐收益;第二,当召回率下降时,应通过增大 ef、启用 refinement,还是更换量化策略来恢复结果质量。第三章按照以下实验矩阵组织结果。
| 项目 | 对应小节 | 验证问题 |
|---|---|---|
| 主索引横向对比 | 3.2 | 固定图搜索参数,比较 HNSW、HNSW_SQ、HNSW_PQ 和 HNSW_PRQ 在召回率、吞吐、延迟、运行时内存、索引构建产物和构建时间上的差异。 |
| SQ 位宽与 refinement 配置 | 3.3 | 比较 SQ8、SQ6、SQ4U,并评估以 FP32 或 SQ8 作为 refine_type 时的精度与资源占用差异。 |
ef 扫描 | 3.4 | 评估不同精度向量表示对搜索宽度 ef 变化的响应是否一致。 |
refine_k 扫描 | 3.5 | 评估候选结果放大倍数 refine_k 对召回率、QPS 和延迟的影响。 |
| 等召回率选点 | 3.6 | 在 Recall@100 接近 0.98 时,比较不同配置的吞吐与运行时内存。 |
| 实验范围与限制 | 3.7 | 明确固定参数、未覆盖的工作负载和硬件边界,限定结论的适用范围。 |
| 实验小结 | 3.8 | 将各组实验归纳为可用于后续选型的工程结论。 |
3.1.1 测试环境与参数
| 项目 | 配置 |
|---|---|
| Milvus | milvusdb/milvus:v2.6.17 (Docker Standalone) |
| 客户端 | PyMilvus 2.6.15;测试客户端与 Milvus 部署在同一台虚拟机上 |
| 计算资源 | KVM 虚拟机;Intel Xeon Gold 6226R @ 2.90 GHz;16 vCPU;64 GB RAM;x86_64;单 NUMA 节点;支持 AVX2 / AVX-512 |
| 操作系统 | Ubuntu 20.04.6 LTS;Linux kernel 5.15.0-139-generic |
| 存储 | QEMU 虚拟块设备,ext4 |
| 容器资源限制 | Docker 26.1.3;未设置额外 CPU、内存或 cpuset 限制 |
| 数据集 | VectorDBBench Cohere 1M:1,000,000 条库向量、1,000 条查询向量,维度为 768 |
| 相似度度量与返回规模 | COSINE , TopK=100 |
| HNSW 参数 | M=16 , efConstruction=200 |
| SQ 参数 | SQ8、SQ6、SQ4U |
| PQ / PRQ 参数 | m=96 , nbits=8 ;PRQ 使用 nrq=2 |
| 搜索参数 | ef=128/256/512 ;启用 refinement 时扫描 refine_k=1/2/4/8 |
| 评价指标 | Recall@100、峰值 QPS、串行查询 P95/P99 延时、峰值 QPS 运行点 P95 延时、加载后容器内存增量、索引构建产物增量(估算)、索引构建时间 |
Recall@100 使用与查询相同的 COSINE 相似度度量。对每个查询按下式计算,再对 1,000 个查询取平均值:
Recall@100 = |ANN Top-100 ∩ Ground-truth Top-100| / 100
Recall@100 与 串行查询 P50/P95/P99 来自 1,000 个查询的顺序执行。峰值 QPS 取并发压测中的最高吞吐:关键配置测试并发度 8、16 和 32,参数扫描测试并发度 8 和 16。峰值 QPS 运行点 P95 与 峰值 QPS 来自同一次并发测试,未与串行查询 P95 混用。
加载后容器内存增量通过 docker stats --no-stream 记录 collection 加载前后的容器内存差值,因此它并不是单个 Milvus 进程的严格 RSS。该指标会受到内存分配器、page cache、segment 元数据和 mmap 行为的影响,本文仅将其用于比较同一环境下各配置的相对运行时内存占用。
索引构建产物增量(估算) 通过统计索引构建期间新增的 Milvus 索引文件体积进行估算,不代表实例的总磁盘占用。索引构建时间仅统计从 create_index 到索引构建完成的时间,不包含数据导入时间。
关键配置重复运行 3 次,主表中的 QPS 与延迟优先采用中位数。由于基准测试客户端与 Milvus 部署在同一台虚拟机上,吞吐和延迟包含双方对 CPU 资源的竞争,但不包含跨主机网络延迟。
3.2 HNSW 系列索引横向比较
主表固定查询阶段的搜索宽度为 ef=256;启用 refinement 的配置统一使用 refine_k=4。固定 ef 只保证图搜索宽度一致,并不代表总计算成本相同,因为 refinement 还会扩大候选集并执行更高精度的重排。整体测试结果如下:
| 索引配置 | Recall@100 | 峰值 QPS | 对应并发度 | 串行查询 P95 | 峰值 QPS 运行点 P95 | 加载后容器内存增量 | 索引构建产物大小 | 索引构建时间 |
|---|---|---|---|---|---|---|---|---|
| HNSW FP32 | 0.9807 | 614.8 | 32 | 8.3 ms | 96.7 ms | 3217 MB | 3071 MB | 284.7 s |
| SQ8(未启用 refinement) | 0.9761 | 793.1 | 16 | 7.4 ms | 76.8 ms | 1006 MB | 874 MB | 161.5 s |
SQ8 + refine_type=FP32 | 0.9881 | 653.6 | 32 | 9.3 ms | 89.7 ms | 3975 MB | 3804 MB | 193.7 s |
| SQ4U(未启用 refinement) | 0.8861 | 1042.9 | 16 | 4.9 ms | 26.1 ms | 608 MB | 508 MB | 170.7 s |
| PQ(m=96,未启用 refinement) | 0.6083 | 1001.4 | 16 | 6.1 ms | 26.5 ms | 446 MB | 236 MB | 363.3 s |
PQ(m=96)+ refine_type=FP32 | 0.9667 | 748.2 | 32 | 8.4 ms | 80.5 ms | 3422 MB | 3166 MB | 393.9 s |
| PRQ(m=96,nrq=2,未启用 refinement) | 0.7869 | 570.4 | 16 | 11.7 ms | 39.7 ms | 516 MB | 337 MB | 5378.4 s |
PRQ(m=96,nrq=2)+ refine_type=FP32 | 0.9869 | 420.4 | 32 | 16.2 ms | 109.5 ms | 3508 MB | 3266 MB | 5337.1 s |
在本次 Cohere 1M 的测试中,SQ8(未启用 refinement) 在召回率、吞吐和资源占用之间呈现出最均衡的结果。与HNSW FP32 相比,其 Recall@100 下降约 0.46 个百分点,峰值 QPS 提升至 1.29 倍,加载后容器内存增量 则从3.22 GB 降至约 1.01 GB。
SQ8 + refine_type=FP32 将 Recall@100 提高到 0.9881,但加载后容器内存增量 上升至约 3.97 GB,索引构建产物约为 3.80 GB。该配置的主要价值是恢复召回率,而不是继续降低运行时内存。
SQ4U(未启用 refinement) 的峰值 QPS 达到 1,042.9,加载后容器内存增量降至 608 MB,但 Recall@100 只有 0.8861。
在本次 m=96, nbits=8 配置下,PQ(未启用 refinement) 的 Recall@100 为 0.6083,说明 96-byte PQ 编码带来的量化误差已经成为主要瓶颈。该结果不能代表所有 HNSW_PQ 配置;增大 m、提高 nbits 或采用更长的编码,都可能形成不同的精度与资源取舍。
本次 PRQ 配置使用 nrq=2,理论编码长度约为 PQ 的两倍。与 PQ(未启用 refinement) 相比,PRQ(未启用 refinement) 将 Recall@100 从 0.6083 提高到 0.7869,但索引构建时间达到 5,378 秒,约 89.6 分钟。这说明残差编码可以用更高的编码预算和构建成本换取更多召回率。
PRQ + refine_type=FP32 在 refine_k=4 时达到 0.9869 Recall@100,但峰值 QPS 为 420.4,峰值 QPS 运行点 P95 为 109.5 ms,构建时间约为 88.9 分钟。评估该配置时,需要同时考虑离线构建时间窗口和在线延迟。
3.3 SQ 系列:SQ8、SQ6 与 SQ4U 的取舍
该组实验比较 SQ8、SQ6 和 SQ4U 在未启用 refinement 时,形成的压缩梯度,并进一步对比以 FP32 和 SQ8 作为 refine_type 时的结果。
| SQ 类型 | 位宽(bit/dim) | Recall@100 | 峰值 QPS | 串行查询 P50 | 串行查询 P95 | 串行查询 P99 | 加载后容器内存增量 | 索引构建产物 | 索引构建时间 |
|---|---|---|---|---|---|---|---|---|---|
| SQ8 | 8 | 0.9761 | 793.1 | 5.4 ms | 7.4 ms | 12.7 ms | 1006 MB | 874 MB | 161.5 s |
| SQ6 | 6 | 0.9563 | 794.9 | 5.6 ms | 7.9 ms | 15.6 ms | 828 MB | 691 MB | 182.1 s |
| SQ4U | 4 | 0.8861 | 1042.9 | 3.7 ms | 4.9 ms | 6.1 ms | 608 MB | 508 MB | 170.7 s |
SQ6 将索引构建产物增量从 SQ8 的 874 MB 降至 691 MB,将加载后容器内存增量 从 1,006 MB 降至 828 MB,但 Recall@100 也从 0.9761 降至 0.9563。在 Cohere 1M 上,SQ6(未启用 refinement) 体现的是明确的空间—精度取舍,而不是近乎无损的 SQ8 替代方案。SQ6 的召回率损失能否通过增大 ef 弥补,将在后面的ef参数扫描实验中进一步验证。
SQ4U(未启用 refinement) 的加载后容器内存增量为 608 MB,峰值 QPS 为 1,042.9,但 Recall@100 仅为 0.8861。对于直接依赖向量检索结果质量的应用,需要结合更大的候选集、refinement 或下游 reranker,进一步验证该配置是否满足业务要求。
3.3.1 SQ + refine_type=FP32:低位宽量化的 Recall 恢复能力
在固定 ef=256 的条件下,分别测试 refine_k=1/2/4/8的Recall和QPS,结果如下图所示:


SQ4U + refine_type=FP32 在 refine_k=4 时达到 0.9830 Recall@100,在 refine_k=8 时达到 0.9899。这表明,低位宽量化可以通过扩大高精度重排候选集来恢复排序质量;但加载后容器内存增量会回升到约 3.56 GB,SQ4U 原有的运行时内存优势也会大幅减弱。
SQ6 与 SQ8 在采用 refine_type=FP32 后的结果非常接近:refine_k=4 时,Recall@100 分别为 0.9875 和 0.9881;refine_k=8 时,分别为 0.9936 和 0.9940。此时,基础量化位宽对最终召回率的影响已经显著缩小,主要差异转向索引构建产物、查询吞吐和重排开销。
吞吐表现呈现出相反趋势:随着 refine_k 增大,进入高精度重排的候选数量按比例增加,峰值 QPS 整体下降。在单次参数扫描中,SQ8 的峰值 QPS 从 768.6 降至 443.0;SQ4U 则从 827.4 降至 613.5。SQ6 也表现出相同的下降趋势。
因此,refine_k 并不是越大越好。它提高 Recall@100 的同时,会增加候选重排计算和内存访问。在本次测试中,refine_k=4 对三种 SQ 类型都已经把 Recall@100 提升到约 0.983~0.988;继续提高到 refine_k=8 主要用于追求接近 0.99 的召回率,但需要接受更明显的吞吐损失。
3.3.2 refine_type=SQ8:以部分 Recall 上限换取更低资源占用
在固定 ef=256、refine_k=4 的条件下,对比 refine_type=FP32 与 refine_type=SQ8。
| 配置 | Recall@100 | 峰值 QPS | 串行查询 P95 | 加载后容器内存增量 | 索引构建产物增量 | 索引构建时间 |
|---|---|---|---|---|---|---|
SQ6 + refine_type=FP32 | 0.9875 | 656.2 | 9.1 ms | 3764 MB | 3621 MB | 216.2 s |
SQ6 + refine_type=SQ8 | 0.9818 | 661.9 | 9.6 ms | 1555 MB | 1423 MB | 192.5 s |
SQ4U + refine_type=FP32 | 0.9830 | 810.3 | 7.5 ms | 3564 MB | 3438 MB | 204.3 s |
SQ4U + refine_type=SQ8 | 0.9779 | 839.2 | 7.3 ms | 1381 MB | 1240 MB | 176.3 s |
使用 SQ8 作为 refine_type 时,召回率低于 refine_type=FP32,但 refinement 数据的资源占用显著下降。以 SQ6 为例,refine_type=SQ8 的 Recall@100 为 0.9818,加载后容器内存增量 约为 1.55 GB;refine_type=FP32 的 Recall@100 为 0.9875,对应的容器内存增量约为 3.76 GB。
因此,refinement 不能仅作为一个布尔开关看待。refine_type 决定重排表示的精度与存储成本,refine_k 决定进入重排阶段的候选规模,两者需要联合调优。
3.3.3 SQ 系列的等召回率对比
为比较相近结果质量下的资源成本,选取以下 Recall@100 接近的实测配置:
| 目标 Recall@100 | 配置 | sq_type | refine_type | ef | refine_k | Recall@100 | 峰值 QPS | 加载后容器内存增量 | 索引构建产物增量 |
|---|---|---|---|---|---|---|---|---|---|
| ~0.98 | SQ8 + refine_type=FP32 | SQ8 | FP32 | 256 | 1 | 0.9807 | 768.6 | 3975 MB | 3804 MB |
| ~0.98 | SQ6 + refine_type=SQ8 | SQ6 | SQ8 | 256 | 4 | 0.9818 | 661.9 | 1555 MB | 1423 MB |
| ~0.98 | SQ4U + refine_type=FP32 | SQ4U | FP32 | 256 | 4 | 0.9830 | 827.4 | 3564 MB | 3438 MB |
| ~0.99 | SQ8 + refine_type=FP32 | SQ8 | FP32 | 256 | 8 | 0.9940 | 443.0 | 3975 MB | 3804 MB |
| ~0.99 | SQ6 + refine_type=FP32 | SQ6 | FP32 | 256 | 8 | 0.9936 | 460.9 | 3764 MB | 3621 MB |
| ~0.99 | SQ4U + refine_type=FP32 | SQ4U | FP32 | 256 | 8 | 0.9899 | 613.5 | 3564 MB | 3438 MB |
当目标 Recall@100 约为 0.98 时,SQ6 + refine_type=SQ8 的加载后容器内存增量明显低于采用 refine_type=FP32 的配置。若目标接近 0.99,三种 SQ 位宽都需要 refine_type=FP32 和更大的 refine_k,基础量化带来的内存优势会被 refine_type=FP32 数据显著抵消。
3.4 ef 扫描:扩大搜索宽度无法直接消除量化误差

| 索引配置 | ef=128 | ef=256 | ef=512 |
|---|---|---|---|
| HNSW FP32 | 0.9599 | 0.9812 | 0.9903 |
| SQ8 | 0.9567 | 0.9762 | 0.9844 |
SQ8 + refine_type=FP32 , refine_k=4 | 0.9874 | 0.9874 | 0.9903 |
| SQ4U | 0.8755 | 0.8861 | 0.8911 |
| PQ m=96 | 0.6068 | 0.6083 | 0.6088 |
| PQ m=96 + refine, refine_k=4 | 0.9668 | 0.9668 | 0.9697 |
| PRQ m=96, nrq=2 | 0.7816 | 0.7869 | 0.7896 |
| PRQ m=96, nrq=2 + refine, refine_k=4 | 0.9873 | 0.9873 | 0.9899 |
ef 扫描用于区分瓶颈来自图搜索宽度(ef)不足,还是来自向量压缩表示本身的距离误差。HNSW FP32 和 SQ8(未启用 refinement) 的召回率都会随着 ef 增大而持续上升,说明扩大底层候选搜索范围仍然有效。
在本次 m=96, nbits=8 配置下,PQ(未启用 refinement) 的 Recall@100 从 ef=128 到 ef=512 几乎没有变化;PRQ(未启用 refinement) 的增幅也很有限。这表明主要限制不再是图搜索宽度,而是量化表示中的距离排序误差。
因此,当 PQ/PRQ(未启用 refinement) 的召回率进入平台期后,继续提高 ef 通常只会带来有限的召回率增益,同时降低吞吐。更有效的方向是提高编码预算,例如增大 m,或启用 refinement 并扫描 refine_k。
3.5 refine_k 扫描:收益取决于量化误差规模


| 索引配置 | refine_k=1 | refine_k=2 | refine_k=4 | refine_k=8 |
|---|---|---|---|---|
SQ8 + refine_type=FP32 | 0.9807 | 0.9807 | 0.9881 | 0.9940 |
| PQ m=96 + refine | 0.8629 | 0.9322 | 0.9667 | 0.9848 |
| PRQ m=96, nrq=2 + refine | 0.9704 | 0.9779 | 0.9869 | 0.9931 |
固定 ef=256 后,refine_k 的收益与基础量化误差密切相关。SQ8 的量化误差较小,refine_k=1 和 refine_k=2 时 Recall@100 均为 0.9807;继续提高到 4 和 8,召回率仍会上升,但 峰值 QPS 明显下降。
PQ 对 refine_k 的响应最明显:Recall@100 从 0.8629 提高到 0.9848,说明扩大候选集后,FP32 重排能够显著修正量化距离造成的排序偏差。该结论仍仅适用于本次 m=96, nbits=8 配置。
PRQ + refine_type=FP32 在 refine_k=1 时已经达到 0.9704 Recall@100;提高到 4 和 8 后,分别达到 0.9869 和 0.9931。与此同时,峰值 QPS 从 560.1 降至 270.9,串行查询 P95 从 12.3 ms 上升至 24.5 ms。生产评估还应同时观察峰值吞吐对应并发度下的 P95/P99、过滤条件以及真实查询分布。
3.6 相近 Recall@100(约 0.98)下的内存与吞吐取舍
固定 ef=256,选取各类索引中 Recall@100 最接近 0.98 的实测配置。
| 目标 Recall@100 | 索引配置 | ef | refine_k | Recall@100 | 峰值 QPS | 加载后容器内存增量 | 索引构建产物增量 |
|---|---|---|---|---|---|---|---|
| ~0.98 | HNSW FP32 | 256 | - | 0.9807 | 614.8 | 3217 MB | 3071 MB |
| ~0.98 | SQ8 | 256 | - | 0.9761 | 793.1 | 1006 MB | 874 MB |
| ~0.98 | SQ8 + refine_type=FP32 | 256 | 1 | 0.9807 | 768.6 | 3975 MB | 3804 MB |
| ~0.98 | PQ m=96 + refine | 256 | 8 | 0.9848 | 553.3 | 3422 MB | 3166 MB |
| ~0.98 | PRQ m=96, nrq=2 + refine | 256 | 2 | 0.9779 | 533.1 | 3508 MB | 3266 MB |
如果业务可以接受约 0.976 的 Recall@100,SQ8(未启用 refinement) 在本次测试中同时提供了更高的峰值 QPS 和更低的运行时容器内存。若必须达到 0.98 以上,量化索引通常需要启用 refinement;此时 refine_type=FP32 可能使运行时内存占用接近或超过 HNSW FP32。
3.7 实验范围与限制
- 数据集仅覆盖 Cohere 1M、768 维向量、COSINE 相似度度量和
TopK=100。不同的向量分布、维度、相似度度量和 TopK 可能产生不同结果。 - 所有索引均固定使用
M=16和efConstruction=200,未针对不同向量表示分别调优建图参数。 - PQ 仅测试
m=96, nbits=8;PRQ 仅测试m=96, nbits=8, nrq=2。本文未对m、nbits和nrq进行完整参数扫描。 - SQ 仅覆盖 SQ8、SQ6 和 SQ4U,未测试 BF16 与 FP16。
- 实验未覆盖标量过滤、混合检索、动态写入与删除、compaction、集群部署以及多租户资源竞争。
- 测试在单台 16 vCPU、64 GB RAM 的 KVM 虚拟机上完成,客户端与 Milvus 共享 CPU 资源。测试环境向虚拟机暴露 QEMU 虚拟块设备,底层物理存储类型及宿主机 I/O 资源竞争情况不可观测。因此,本文中的 QPS、延迟和索引构建时间主要用于比较同一环境下不同索引配置的相对差异,不应直接作为其他硬件环境中的绝对性能预期。
3.8 实验小结
| 实验阶段 | 主要结论 |
|---|---|
| 主索引横向对比 | HNSW FP32 提供全精度参考;SQ8(未启用 refinement) 在本次工作负载中兼顾了召回率、吞吐和资源占用;当前 PQ/PRQ 参数采用更高的压缩强度,也表现出更明显的量化误差。 |
| SQ 位宽与 refinement 配置 | 降低 SQ 位宽会缩小量化编码体积; refine_type=FP32 能恢复更多召回率,但会显著增加存储和运行时内存, refine_type=SQ8 则提供介于二者之间的取舍。 |
ef 与 refine_k | ef 主要弥补图搜索宽度不足, refine_k 主要修正量化排序误差;应根据召回率曲线判断瓶颈所在。 |
| 等召回率对比 | 选型应围绕目标 Recall@K,综合比较 QPS、延迟、容器内存、索引构建产物大小和构建时间,而不是只比较单个固定参数点。 |
选型指南
以下表格配置仅作为本次 Cohere 1M 测试的评估参考,不代表适用于所有数据集和硬件环境的固定默认值。实际决策仍需结合目标向量分布、TopK、过滤条件、并发模型和硬件重新验证。
| 目标 | 建议起点 | 后续验证方向 | 主要代价 |
|---|---|---|---|
| 建立无量化索引参考标准 | HNSW FP32 | 扫描 ef ,确定目标工作负载的召回率上限 | 原始向量数据与运行时内存占用最高 |
| 默认压缩起点 | SQ8(未启用 refinement) | 扫描 ef ,验证召回率差距是否可以接受 | 存在小幅量化误差 |
| Recall@100 为 0.98,同时控制资源占用 | SQ6 + refine_type=SQ8 (实测候选之一) | 联合扫描 ef 与 refine_k | 重排计算增加,召回率上限低于 refine_type=FP32 |
| 极限压缩或粗召回 | SQ4U 或 PQ(未启用 refinement) | 扩大候选集,并接入下游 reranker | 基础召回率可能明显下降 |
| PQ/PRQ 需要高召回率 | 启用 refinement | 扫描 refine_k ,同时检查峰值 QPS 对应并发度下的 P95/P99 | refinement 数据会增加内存与存储占用 |
| 频繁重建索引 | 优先评估 SQ,或采用编码成本较低的 PQ 配置 | 将构建时间纳入发布窗口 | PRQ 的残差码本训练可能成为主要成本 |
4.1 高召回率要求:先从 HNSW FP32 开始
当内存允许,并且目标是建立无量化索引参考线,应先使用 HNSW FP32 扫描 ef。这能够给出当前 M、efConstruction 和数据分布下的召回率—延迟基线。
在固定 ef 下,量化索引配合较大的 refine_k 可能得到高于 HNSW 基线点的 Recall@100,因为它扩大了候选集,并使用更高精度进行重排。比较时应同时考虑总计算量、峰值QPS对应并发度下的 P95/P99,以及 refinement 数据带来的资源占用。
4.2 平衡召回率、吞吐与内存:优先评估 SQ8(未启用 refinement)
在本次 Cohere 1M 测试中,SQ8(未启用 refinement) 的 Recall@100 为 0.9761,峰值 QPS 为 793.1,加载后容器内存增量约为 HNSW FP32 的 31.3%。因此,它可以作为评估量化索引时的首选基线配置。
如果需要进一步降低资源占用,可以测试 SQ6;但本次 SQ6(未启用 refinement) 的 Recall@100 比 SQ8 低约 2.0 个百分点。是否采用 SQ6,应以业务目标 Recall@K 为依据,而不能只比较内存下降比例。
4.3 PQ/PRQ 未启用 refinement 更适合作为粗召回
本次 PQ 的理论编码长度为 96 bytes/vector,PRQ 约为 192 bytes/vector,二者的压缩强度都显著高于 SQ8。对应的加载后容器内存增量 分别约为 446 MB 和 516 MB,但 Recall@100 分别为 0.6083 和 0.7869。
在当前参数下,这类未启用 refinement 的配置更适合以下检索场景:
- 第一阶段只负责生成规模较大的候选集;
- 后续链路配有业务排序模型或更高精度的 reranker;
- 容量成本优先于第一阶段召回率,例如离线预筛或召回兜底场景。
该结论仅适用于本次 m=96, nbits=8, nrq=2 的参数组合。增大 m、调整 nbits 或采用其他编码预算,都可能改变实验结果。
4.4 PQ/PRQ 需要高召回率时,将 refine_k 纳入主参数空间
当 PQ/PRQ(未启用 refinement) 的召回率受到量化误差限制时,继续提高 ef 的收益通常较为有限。此时应启用 refinement,并将 refine_type 与 refine_k 作为主要参数联合测试。
本次 PQ + refine_type=FP32 的 Recall@100 从 refine_k=1 时的 0.8629 提高到 refine_k=8 时的 0.9848;PRQ + refine_type=FP32 则从 0.9704 提高到 0.9931。代价是运行时容器内存回升至 3.4 GB 以上,并伴随更高的并发延迟。
4.5 SQ 系列选型建议
| 选型目标 | 建议起点 | 重点验证 |
|---|---|---|
| 平衡召回率、吞吐与资源占用 | SQ8,未启用 refinement | Recall@K 是否满足业务要求,以及提高 ef 后的收益 |
| 在 SQ8 基础上进一步降低资源占用 | SQ6,未启用 refinement | 量化带来的召回率损失是否可以接受 |
| 追求更高压缩率 | SQ4U,未启用 refinement | 数据分布是否适合全局均匀量化,以及下游 reranker 能否补偿召回损失 |
| 恢复量化后的结果质量 | 启用 refinement,并逐步提高 refine_k | Recall 增益、峰值 QPS、尾延迟和 refinement 数据带来的资源开销 |
| 目标接近 0.99 Recall@100 | SQ8 / SQ6 / SQ4U + refine_type=FP32 | 较大 refine_k 下的峰值 QPS、峰值 QPS 对应并发度下的 P95/P99 以及运行时内存 |
| 需要降低 refinement 数据的资源占用 | refine_type=SQ8 | 资源节省是否足以抵消召回率上限的下降 |
refinement 的精度应按业务目标选择:refine_type=FP32 通常能恢复更多召回率,但资源开销更高;refine_type=SQ8 可以降低 refinement 数据占用,但召回率上限也相对较低。
4.6 调参流程建议
- 先定义约束。明确 TopK、目标 Recall@K、峰值 QPS、峰值吞吐对应并发度下的 P95/P99、可用内存、索引构建产物预算以及构建窗口。
- 用 HNSW FP32 建立参考。固定
M与efConstruction,扫描ef,确认未量化条件下可达到的 Recall@K。 - 从 SQ8(未启用 refinement) 开始压缩。若召回率差距可以接受,它通常是最简单的落地路径;若不满足要求,再比较更大的
ef与 refinement。 - 区分图搜索瓶颈和量化瓶颈。提高
ef后召回率仍停滞,说明应增加编码预算、改变量化类型或启用 refinement。 - 联合调优
refine_type与refine_k。FP32 提供最高重排精度,但资源占用最大;SQ6、SQ8、BF16 和 FP16 可作为中间精度选项。 - 在目标环境重新运行基准测试。SIMD 能力、内存带宽、底层存储、过滤条件、动态更新以及客户端与服务端的部署方式,都会影响最终结果。
总结
HNSW、HNSW_SQ、HNSW_PQ 和 HNSW_PRQ 共享同一套 HNSW 算法框架,但向量压缩表示会同时影响建图、距离计算和候选排序。选型时,应分别评估图搜索参数、编码预算和 refinement。
本次 Cohere 1M 实验表明,HNSW FP32 适合建立无量化参考基线,SQ8(未启用 refinement) 是均衡的压缩起点;更激进的 SQ、PQ 和 PRQ 配置能够继续降低资源占用,但需要通过更高的编码预算、refinement 或下游 reranker 来恢复结果质量。
最终决策应围绕目标 Recall@K、峰值 QPS、并发延迟、运行时容器内存和索引构建时间窗口展开,并在目标硬件与真实查询分布上重新运行基准测试,而不是寻找脱离具体工作负载的最佳索引。