向量数据库实战:选型、调优与落地~系列文章05:IVF、PQ、ScaNN 索引对比:大规模数据下的性能与内存博弈
·
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 入门实战:从安装到第一次相似度查询 🚀》
💬 有问题欢迎评论区讨论,觉得有用请点赞收藏 👍
作者:高炉炼铁智能化技术研究者,专注钢铁冶金与人工智能 交叉领域。
👍 如果觉得有帮助,请点赞、收藏、转发!
版权归作者所有,未经许可请勿抄袭,套用,商用(或其它具有利益性行为)。
🔔 关注专栏,不错过后续精彩内容
更多推荐



所有评论(0)