方法和引擎
方法定义了用于在索引时组织向量数据并在搜索时对其进行搜索的算法,应用于近似 k-NN 搜索。
OpenSearch 支持以下方法
- 分层可导航小世界 (HNSW) 创建向量之间连接的分层图结构。有关该算法的更多信息,请参阅使用分层可导航小世界图进行高效稳健的近似最近邻搜索。
- 倒排文件索引 (IVF) 根据聚类将向量组织成桶,并在搜索期间仅搜索桶的子集。
引擎是实现这些方法的库。不同的引擎可以实现相同的方法,有时具有不同的优化或特性。例如,所有受支持的引擎都实现了 HNSW,每个引擎都有其自身的优势。
OpenSearch 支持以下引擎
- Lucene:原生搜索库,提供 HNSW 实现,具有高效的过滤功能
- Faiss(Facebook AI 相似性搜索):一个全面的库,实现了 HNSW 和 IVF 方法,并提供额外的向量压缩选项
- NMSLIB(非度量空间库):HNSW 的旧版实现(现已弃用)
方法定义示例
方法定义包含以下组件
- 方法的
name(例如,hnsw或ivf) - 构建方法的
space_type(例如,l2或cosinesimil) - 将实现该方法的
engine(例如,faiss或lucene) - 特定于该实现的
parameters映射
以下示例使用l2空间类型、faiss引擎和特定于方法的参数配置了hnsw方法
PUT test-index
{
"settings": {
"index": {
"knn": true,
"knn.algo_param.ef_search": 100
}
},
"mappings": {
"properties": {
"my_vector1": {
"type": "knn_vector",
"dimension": 1024,
"method": {
"name": "hnsw",
"space_type": "l2",
"engine": "faiss",
"parameters": {
"ef_construction": 128,
"m": 24
}
}
}
}
}
}
并非所有方法/引擎组合都支持所有空间。有关支持的空间列表,请参阅特定引擎的部分。
通用参数
以下参数适用于所有方法定义。
| 映射参数 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
名称 | 是 | 不适用 | 否 | 最近邻方法。有效值为hnsw和ivf。并非所有引擎组合都支持所有方法。有关支持的方法列表,请参阅特定引擎的部分。 |
space_type | 否 | l2 | 否 | 用于计算向量之间距离的向量空间。有效值为l1、l2、linf、cosinesimil、innerproduct、hamming和hammingbit。并非所有方法/引擎组合都支持所有空间。有关支持的空间列表,请参阅特定引擎的部分。注意:此值也可以在映射的顶层指定。有关更多信息,请参阅空间。 |
engine | 否 | faiss | 否 | 用于索引和搜索的近似 k-NN 库。有效值为faiss、lucene和nmslib(已弃用)。 |
parameters | 否 | null | 否 | 用于最近邻方法的参数。有关更多信息,请参阅特定引擎的部分。 |
Lucene 引擎
Lucene 引擎直接在 Lucene 中提供了向量搜索的原生实现。它提供了高效的过滤功能,非常适合小型部署。
支持的方法
Lucene 引擎支持以下方法。
| 方法名称 | 需要训练 | 支持的空间 |
|---|---|---|
hnsw | 否 | l2、cosinesimil、innerproduct(OpenSearch 2.13 及更高版本支持) |
HNSW 参数
HNSW 方法支持以下参数。
| 参数名称 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
ef_construction | 否 | 100 | 否 | k-NN 图创建期间使用的动态列表的大小。值越大,图的准确性越高,但索引速度越慢。 注意:Lucene 内部使用术语 beam_width,但 OpenSearch 文档为保持一致性使用ef_construction。 |
m | 否 | 16 | 否 | 为每个新元素创建的双向链接的数量。显著影响内存消耗。保持在2到100之间。注意:Lucene 内部使用术语 max_connections,但 OpenSearch 文档为保持一致性使用m。 |
Lucene HNSW 实现会忽略ef_search,并将其动态设置为搜索请求中“k”的值。因此,在使用 Lucene 引擎时,无需为ef_search配置设置。
在 OpenSearch 2.11 或更早版本中创建的索引仍将使用之前的ef_construction值(512)。
示例配置
"method": {
"name": "hnsw",
"engine": "lucene",
"parameters": {
"m": 2048,
"ef_construction": 245
}
}
Faiss 引擎
Faiss 引擎提供高级向量索引功能,支持多种方法和编码选项,以优化内存使用和搜索性能。
支持的方法
Faiss 引擎支持以下方法。
| 方法名称 | 需要训练 | 支持的空间 |
|---|---|---|
hnsw | 否 | l2、innerproduct(当使用PQ时不可用)、hamming和cosinesimil(OpenSearch 2.19 及更高版本支持)。 |
ivf | 是 | l2、innerproduct、hamming(OpenSearch 2.16 及更高版本支持二进制向量。有关更多信息,请参阅二进制 k-NN 向量),cosinesimil(OpenSearch 2.19 及更高版本支持)。 |
HNSW 参数
该hnsw方法支持以下参数。
| 参数名称 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
ef_search | 否 | 100 | 否 | k-NN 搜索期间使用的动态列表的大小。值越高,搜索越准确但速度越慢。对于二进制索引,默认值为256。 |
ef_construction | 否 | 100 | 否 | k-NN 图创建期间使用的动态列表的大小。值越高,图越准确但索引速度越慢。对于二进制索引,默认值为256。 |
m | 否 | 16 | 否 | 插件为每个新元素创建的双向链接数量。增加和减少此值可能对内存消耗产生很大影响。将此值保持在2到100之间。 |
编码器 | 否 | flat | 否 | 用于编码向量的编码器定义。编码器可以减少索引的内存占用,但会牺牲搜索准确性。 |
在 OpenSearch 2.11 或更早版本中创建的索引仍将使用之前的ef_construction值(512)。
IVF 参数
IVF 方法支持以下参数。
| 参数名称 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
nlist | 否 | 4 | 否 | 用于划分向量的桶的数量。值越高可能会提高准确性,但也会增加内存和训练延迟。 |
nprobes | 否 | 1 | 否 | 查询期间搜索的桶的数量。值越高,搜索越准确但速度越慢。 |
编码器 | 否 | flat | 否 | 用于编码向量的编码器定义。 |
有关这些参数的更多信息,请参阅 Faiss 文档。
IVF 训练要求
IVF 算法需要训练步骤。要创建使用 IVF 的索引,您需要使用训练 API训练模型,并传入 IVF 方法定义。IVF 最低要求有nlist个训练数据点,但我们建议您使用更多。训练数据可以与您计划索引的数据相同,也可以来自单独的数据集。
支持的编码器
您可以使用编码器来减少向量索引的内存占用,但会牺牲搜索准确性。
OpenSearch 目前支持 Faiss 库中的以下编码器。
| 编码器名称 | 需要训练 | 描述 |
|---|---|---|
flat(默认) | 否 | 将向量编码为浮点数组。此编码不减少内存占用。 |
pq | 是 | 乘积量化的缩写,PQ 是一种有损压缩技术,它使用聚类将向量编码为固定字节大小,目标是最大限度地减少 k-NN 搜索准确性的下降。从高层次来看,向量被分成m个子向量,然后每个子向量由在训练期间生成的码本中获得的code_size码表示。有关乘积量化的更多信息,请参阅这篇博客文章。 |
sq | 否 | 标量量化的缩写。从 OpenSearch 2.13 版本开始,您可以使用sq编码器将 32 位浮点向量量化为 16 位浮点数。在 2.13 版本中,内置的sq编码器是 SQFP16 Faiss 编码器。该编码器以最小的精度损失减少内存占用,并通过使用 SIMD 优化(在 x86 架构上使用 AVX2,在 ARM64 架构上使用 Neon)提高性能。有关更多信息,请参阅Faiss 标量量化。 |
PQ 参数
pq编码器支持以下参数。
| 参数名称 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
m | 否 | 1 | 否 | 确定将向量分成多少个子向量。子向量彼此独立编码。此向量维度必须可被m整除。最大值为 1,024。 |
code_size | 否 | 8 | 否 | 确定将子向量编码为多少位。最大值为8。对于ivf,此值必须小于或等于8。对于hnsw,此值必须为8。 |
hnsw方法支持 OpenSearch 2.10 及更高版本中的pq编码器。hnsw方法的pq编码器的code_size参数必须为 8。
SQ 参数
sq编码器支持以下参数。
| 参数名称 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
类型 | 否 | fp16 | 否 | 用于将 32 位浮点向量编码为相应类型的标量量化类型。截至 OpenSearch 2.13,仅支持fp16编码器类型。对于fp16编码器,向量值必须在 [-65504.0, 65504.0] 范围内。 |
clip | 否 | false | 否 | 如果为true,则任何超出指定向量类型支持范围的向量值都将被四舍五入到该范围内。如果为false,则如果任何向量值超出支持范围,请求将被拒绝。将clip设置为true可能会降低召回率。 |
有关更多信息和示例,请参阅使用 Faiss 标量量化。
SIMD 优化
从 2.13 版本开始,如果底层硬件支持 SIMD 指令(x64 架构上的 AVX2 和 ARM64 架构上的 Neon),OpenSearch 将支持单指令多数据(SIMD)处理。SIMD 默认仅在 Linux 机器上为 Faiss 引擎支持。SIMD 架构通过提高索引吞吐量和降低搜索延迟来帮助提升整体性能。从 2.18 版本开始,OpenSearch 在 x64 架构上支持 AVX-512 SIMD 指令。从 2.19 版本开始,OpenSearch 在 x64 架构上支持 Intel Sapphire Rapids 或更新一代处理器的更高级 AVX-512 SIMD 指令,从而提高汉明距离计算的性能。
SIMD 优化仅适用于向量维度是 8 的倍数的情况。
x64 架构
对于 x64 架构,以下版本的 Faiss 库与工件一起构建和发布
libopensearchknn_faiss_avx512_spr.so:包含用于新一代处理器的更高级 AVX-512 SIMD 指令的 Faiss 库,可在 AWS 等公共云上的 c/m/r 7i 或更新实例上使用。libopensearchknn_faiss_avx512.so:包含 AVX-512 SIMD 指令的 Faiss 库。libopensearchknn_faiss_avx2.so:包含 AVX2 SIMD 指令的 Faiss 库。libopensearchknn_faiss.so:未经优化的 Faiss 库,不含 SIMD 指令。
使用 Faiss 库时,性能排名如下:高级 AVX-512 > AVX-512 > AVX2 > 无优化。
如果您的硬件支持高级 AVX-512(spr),OpenSearch 会在运行时加载libopensearchknn_faiss_avx512_spr.so库。
如果您的硬件支持 AVX-512,OpenSearch 会在运行时加载libopensearchknn_faiss_avx512.so库。
如果您的硬件支持 AVX2 但不支持 AVX-512,OpenSearch 会在运行时加载libopensearchknn_faiss_avx2.so库。
要禁用高级 AVX-512(适用于 Sapphire Rapids 或更新一代处理器)、AVX-512 和 AVX2 SIMD 指令并加载非优化 Faiss 库(libopensearchknn_faiss.so),请在opensearch.yml中将knn.faiss.avx512_spr.disabled、knn.faiss.avx512.disabled和knn.faiss.avx2.disabled静态设置指定为true(默认情况下,所有这些都设置为false)。
请注意,要更新静态设置,您必须停止集群,更改设置,然后重新启动集群。有关更多信息,请参阅静态设置。
ARM64 架构
对于 ARM64 架构,仅构建并发布了一个性能提升的 Faiss 库(libopensearchknn_faiss.so)。该库包含 Neon SIMD 指令,无法禁用。
配置示例
以下示例使用ivf方法,未指定编码器(默认情况下,OpenSearch 使用flat编码器)
"method": {
"name":"ivf",
"engine":"faiss",
"parameters":{
"nlist": 4,
"nprobes": 2
}
}
以下示例使用带有pq编码器的ivf方法
"method": {
"name":"ivf",
"engine":"faiss",
"parameters":{
"encoder":{
"name":"pq",
"parameters":{
"code_size": 8,
"m": 8
}
}
}
}
以下示例使用hnsw方法,未指定编码器(默认情况下,OpenSearch 使用flat编码器)
"method": {
"name":"hnsw",
"engine":"faiss",
"parameters":{
"ef_construction": 256,
"m": 8
}
}
以下示例使用带有类型为fp16的sq编码器的ivf方法
"method": {
"name":"ivf",
"engine":"faiss",
"parameters":{
"encoder": {
"name": "sq",
"parameters": {
"type": "fp16",
"clip": false
}
},
"nprobes": 2
}
}
以下示例使用带有类型为fp16并启用clip的sq编码器的hnsw方法
"method": {
"name":"hnsw",
"engine":"faiss",
"parameters":{
"encoder": {
"name": "sq",
"parameters": {
"type": "fp16",
"clip": true
}
},
"ef_construction": 256,
"m": 8
}
}
NMSLIB 引擎(已弃用)
非度量空间库(NMSLIB)引擎是 OpenSearch 中最早的向量搜索实现之一。虽然仍受支持,但已被弃用,取而代之的是 Faiss 和 Lucene 引擎。
支持的方法
NMSLIB 引擎支持以下方法。
| 方法名称 | 需要训练 | 支持的空间 |
|---|---|---|
hnsw | 否 | l2、innerproduct、cosinesimil、l1、linf |
HNSW 参数
HNSW 方法支持以下参数。
| 参数名称 | 必需 | 默认值 | 可更新 | 描述 |
|---|---|---|---|---|
ef_construction | 否 | 100 | 否 | k-NN 图创建期间使用的动态列表的大小。值越大,图的准确性越高,但索引速度越慢。 |
m | 否 | 16 | 否 | 为每个新元素创建的双向链接的数量。显著影响内存消耗。保持在2到100之间。 |
对于 NMSLIB(已弃用),ef_search 在索引设置中设置。
在 OpenSearch 2.11 或更早版本中创建的索引仍将使用之前的ef_construction值(512)。
示例配置
"method": {
"name": "hnsw",
"engine": "nmslib",
"space_type": "l2",
"parameters": {
"ef_construction": 100,
"m": 16
}
}
选择正确的方法
构建knn_vector字段时有多种选项可供选择。要选择正确的方法和参数,您应首先了解工作负载的要求以及您愿意做出的权衡。需要考虑的因素是 (1) 查询延迟,(2) 查询质量,(3) 内存限制,以及 (4) 索引延迟。
如果内存不是问题,HNSW 提供了强大的查询延迟/查询质量权衡。
如果您希望与 HNSW 相比使用更少的内存并提高索引速度,同时保持相似的查询质量,则应评估 IVF。
如果内存是问题,请考虑为 HNSW 或 IVF 索引添加 PQ 编码器。由于 PQ 是一种有损编码,查询质量会下降。
通过使用fp_16编码器,您可以将内存占用减少 2 倍,同时搜索质量损失最小。如果您的向量维度在 [-128, 127] 字节范围内,我们建议使用字节量化器将内存占用减少 4 倍。要了解有关向量量化选项的更多信息,请参阅k-NN 向量量化。
引擎推荐
通常,大型用例选择 Faiss。Lucene 是小型部署的不错选择,并提供智能过滤等优势,可根据情况自动应用最佳过滤策略——预过滤、后过滤或精确 k-NN。下表总结了每个选项之间的差异。
| Faiss/HNSW | Faiss/IVF | Lucene/HNSW | |
|---|---|---|---|
| 最大维度 | 16,000 | 16,000 | 16,000 |
| 过滤器 | 后过滤 | 后过滤 | 搜索时过滤 |
| 需要训练 | 否(PQ 需要) | 是 | 否 |
| 相似度指标 | l2、innerproduct、cosinesimil | l2、innerproduct、cosinesimil | l2、cosinesimil |
| 向量数量 | 数百亿 | 数百亿 | 小于 1000 万 |
| 索引延迟 | 低 | 最低 | 低 |
| 查询延迟和质量 | 低延迟和高质量 | 低延迟和低质量 | 高延迟和高质量 |
| 向量压缩 | Flat PQ | Flat PQ | Flat |
| 内存消耗 | 高 使用 PQ 时低 | 中 使用 PQ 时低 | 高 |
内存估算
在典型的 OpenSearch 集群中,部分 RAM 会保留给 JVM 堆。OpenSearch 将本地库索引分配给剩余 RAM 的一部分。此部分的大小由circuit_breaker_limit集群设置决定。默认情况下,限制设置为 50%。
使用副本会使向量总数翻倍。
有关使用向量量化进行内存估算的信息,请参阅向量量化。
HNSW 内存估算
HNSW 所需内存估算为1.1 * (4 * dimension + 8 * m)字节/向量。
例如,假设您有 100 万个向量,dimension为 256,m为 16。内存需求可估算如下
1.1 * (4 * 256 + 8 * 16) * 1,000,000 ~= 1.267 GB
IVF 内存估算
IVF 所需内存估算为1.1 * (((4 * dimension) * num_vectors) + (4 * nlist * d))字节。
例如,假设您有 100 万个向量,dimension为256,nlist为128。内存需求可估算如下
1.1 * (((4 * 256) * 1,000,000) + (4 * 128 * 256)) ~= 1.126 GB