IVF、PQ、ScaNN 索引对比:大规模数据下的性能与内存博弈 ⚖️

🔥 本文是《向量数据库实战:选型、调优与落地》专栏第 05 篇

⏱️ 阅读时间:约 13 分钟


🎯 开篇:HNSW 搞不定的场景

上一篇我们夸了 HNSW 半天,但它也有致命弱点 👇

  • 内存占用大:1 亿条 1024 维向量,HNSW 需要 ~500GB 内存 💸
  • 构建速度慢:数据量越大,构建索引越慢
  • 不适合超大规模:千万级以上数据,单机基本扛不住

怎么办? 这时候就需要 IVF、PQ、ScaNN 这些"大规模数据专用索引"出场了!


📐 IVF:倒排文件索引

核心思想

IVF(Inverted File Index)的思路非常直觉:

类比:图书馆找书

暴力搜索:从第一本书开始,一本一本翻(O(N))
IVF:先把书按类别分到不同书架,先找到对的书架,再在书架里找

步骤:
1. 训练阶段:用 K-Means 把向量空间分成 nlist 个"簇"(Voronoi cells)
2. 索引阶段:每个向量分配到最近的簇中心
3. 查询阶段:只搜索查询向量最近的 nprobe 个簇

结构图解

┌─────────────────────────────────────────────────────────┐
│                    IVF 索引结构                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  训练后的簇中心(nlist=4):                                │
│                                                         │
│        C1 ●              C2 ●                            │
│       ╱    ╲            ╱    ╲                           │
│      ╱  ●   ╲    ●    ╱  ●   ╲                          │
│     ╱   ●    ╲       ╱   ●    ╲                         │
│    ●    ●     ●     ●    ●     ●                        │
│                                                         │
│        C3 ●              C4 ●                            │
│       ╱    ╲            ╱    ╲                           │
│      ╱  ●   ╲    ●    ╱  ●   ╲                          │
│     ╱   ●    ╲       ╱   ●    ╲                         │
│    ●    ●     ●     ●    ●     ●                        │
│                                                         │
│  查询:★ 落在 C1 和 C2 的边界                              │
│  → 只搜索 nprobe=2 个最近的簇(C1 和 C2)                  │
│  → 计算量从 N 降到 N/2                                    │
│                                                         │
└─────────────────────────────────────────────────────────┘

关键参数

参数 含义 推荐值 影响
nlist 簇的数量 sqrt(N) ~ 4*sqrt(N) 越大 → 精度↑ 但每个簇数据越少
nprobe 查询时搜索的簇数 nlist 的 1%~10% 越大 → 召回率↑ 速度↓

内存估算

IVF 内存 ≈ 向量数据本身 + 簇中心
         = N × 维度 × 4 bytes + nlist × 维度 × 4 bytes

示例:1 亿条 1024 维,nlist=10000
= 100,000,000 × 1024 × 4 + 10,000 × 1024 × 4
≈ 400 GB + 40 MB
≈ 400 GB(簇中心几乎不占内存)

⚠️ 注意:纯 IVF 的内存和暴力搜索差不多!它主要优化的是查询速度,不是内存。要省内存,需要搭配 PQ。


📦 PQ:乘积量化

核心思想

PQ(Product Quantization)的核心是压缩——把高维向量压缩成短编码,大幅降低内存。

类比:用邮编代替完整地址

原始地址:北京市海淀区中关村大街 1 号(20 个字)
邮编:100080(6 个数字)

虽然精度降低了,但大部分场景够用,而且存储成本降了 90%!

PQ 的原理:
1. 把 1024 维向量切成 M 个子向量(如 64 段,每段 16 维)
2. 每段独立做 K-Means 聚类(通常 K=256)
3. 每个子向量用 1 个 byte 的聚类编号代替
4. 1024 维 → 64 bytes(压缩 64 倍!)

压缩效果

┌─────────────────────────────────────────────────────────┐
│                    PQ 压缩示意                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  原始向量(1024 维 float32):                              │
│  [0.12, -0.34, 0.56, ..., 0.23]                         │
│  大小:1024 × 4 = 4096 bytes                             │
│                                                         │
│  ↓ PQ 编码(M=64 子空间,每子空间 256 个聚类中心)           │
│                                                         │
│  编码向量:                                                │
│  [42, 128, 7, 201, 55, ..., 89]                         │
│  大小:64 × 1 = 64 bytes                                 │
│                                                         │
│  压缩比:4096 / 64 = 64 倍!                              │
│                                                         │
│  内存对比(1 亿条向量):                                    │
│  原始:400 GB                                            │
│  PQ:  6.4 GB ← 省了 98.4%!                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

关键参数

参数 含义 推荐值 约束
M 子空间数量 64~256 必须能整除向量维度
nbits 每个子空间的编码位数 8(默认) 2^nbits = 每段聚类数

PQ 的代价

压缩是有代价的——精度会下降!

压缩比 召回率@10 适用场景
8x(M=128) ~92% 内存充足,追求精度
16x(M=64) ~88% 平衡选择
32x(M=32) ~82% 内存紧张
64x(M=16) ~75% 极度内存敏感

🔗 IVF + PQ:最强组合

实际生产中,IVF 和 PQ 几乎总是一起使用!

IVF_PQ = IVF(加速搜索) + PQ(压缩内存)

效果:
- 查询速度:比纯 PQ 快(IVF 先缩小范围)
- 内存占用:比纯 IVF 低(PQ 压缩向量)
- 召回率:略低于纯 IVF,但可接受

内存对比

索引类型 1亿条 1024维 内存 查询延迟 召回率@10
Flat(暴力) ~400 GB ~500ms 100%
IVF ~400 GB ~50ms ~95%
PQ ~6.4 GB ~30ms ~88%
IVF_PQ ~6.4 GB ~15ms ~90%
HNSW ~520 GB ~8ms ~97%

🚀 ScaNN:Google 的黑科技

核心思想

ScaNN(Scalable Nearest Neighbors)是 Google 在 2020 年提出的算法,结合了多种技术的优点。

ScaNN = 各向异性量化(Anisotropic Quantization)
       + 预筛选(Pre-scoring)
       + 重排序(Re-scoring)

核心创新:
传统 PQ 追求"向量重建误差最小"
ScaNN 追求"内积估计误差最小"(直接优化最终目标!)

ScaNN vs PQ

对比维度 PQ ScaNN
量化目标 最小化重建误差 最小化内积误差
同压缩比下召回率 88% 93%+
是否支持重排序 ✅(用原始向量重排 Top-K)
开源 ✅(Google 开源)
生态支持 广泛 较少(主要在 FAISS/ScaNN 库)

📊 四大索引全面对比

维度 HNSW IVF IVF_PQ ScaNN
查询速度 ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
召回率 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
内存效率 ⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
构建速度 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
动态插入
适合数据量 <1000万 >100万 >1000万 >100万
训练需求 无需 需要 需要 需要
磁盘支持 部分

💡 选型决策矩阵

你的数据量是多少?
│
├── < 100 万
│   └── HNSW(简单、效果好、支持动态插入)
│
├── 100 万 ~ 1000 万
│   ├── 内存充足(>50GB) → HNSW
│   └── 内存有限 → IVF_PQ(nlist=1000, M=64)
│
├── 1000 万 ~ 1 亿
│   ├── 需要动态插入 → HNSW(分布式)
│   └── 静态数据 → IVF_PQ(nlist=10000, M=64)
│
└── > 1 亿
    ├── 追求精度 → IVF_PQ + 重排序
    └── 追求极致 → ScaNN + 分布式

💻 代码实战:Milvus 中配置 IVF_PQ

from pymilvus import Collection, DataType

# IVF_PQ 索引配置
index_params = {
    "index_type": "IVF_PQ",
    "metric_type": "COSINE",
    "params": {
        "nlist": 1024,   # 簇数量(约 sqrt(100万) ≈ 1000)
        "m": 64,         # PQ 子空间数(必须整除维度)
        "nbits": 8       # 每个子空间编码位数
    }
}
collection.create_index("embedding", index_params)

# 查询时设置 nprobe
search_params = {
    "metric_type": "COSINE",
    "params": {"nprobe": 64}  # 搜索 64 个最近的簇
}

🔑 本篇核心要点回顾

要点 说明
IVF 分簇加速搜索,但不省内存
PQ 压缩向量省内存,但精度下降
IVF_PQ 两者结合,大规模数据的标配
ScaNN Google 出品,同压缩比下精度更高
选型核心 数据量 + 内存预算 + 精度要求

✍️ 写在最后

没有"最好"的索引算法,只有"最合适"的。

记住这个公式

小数据 + 高精度 → HNSW

大数据 + 内存紧 → IVF_PQ

超大数据 + 极致性能 → ScaNN


📌 下篇预告:《Milvus 2.x 入门实战:从安装到第一次相似度查询 🚀》

💬 有问题欢迎评论区讨论,觉得有用请点赞收藏 👍

作者:高炉炼铁智能化技术研究者,专注钢铁冶金与人工智能 交叉领域。

👍 如果觉得有帮助,请点赞、收藏、转发!
版权归作者所有,未经许可请勿抄袭,套用,商用(或其它具有利益性行为)
🔔 关注专栏,不错过后续精彩内容

Logo

CANN开发者社区旨在汇聚广大开发者,围绕CANN架构重构、算子开发、部署应用优化等核心方向,展开深度交流与思想碰撞,携手共同促进CANN开放生态突破!

更多推荐