作者:来自 Elastic Blake Holden
在 Elasticsearch 中 部署 NVIDIA cuVS GPU 加速 HNSW 索引的两种模式:适用于小型集群的构建与服务一体化节点,以及适用于大规模生产环境、通过 ILM 切换到 CPU 的专用 GPU 摄取层。
从向量搜索到强大的 REST API, Elasticsearch 为开发者提供了最全面的搜索工具集。深入体验 Elasticsearch Labs 仓库中的示例 notebooks,尝试一些新内容。你也可以立即开始免费试用,或者今天就在本地运行 Elasticsearch。
NVIDIA cuVS 在 GPU 上构建 HNSW 图,在 Elasticsearch 中实现最高快 12 倍的 向量索引 速度。本文介绍了两种生产部署模式:模式 A 在同一个 GPU 节点上同时执行构建和服务,适用于少于五个数据节点的集群。模式 B 使用三个精简 GPU 摄取节点(每个 64 GB RAM),并通过 ILM rollover 切换到七个 CPU 热服务节点(每个 192 GB)—— 这是大规模生产环境下默认的正确选择。文中还包含完整的索引模板、 ILM 策略,以及针对 3 亿向量语料库的容量计算。
Elasticsearch cuVS GPU 插件 实际上做了什么?
Elasticsearch cuVS GPU 插件会在两个操作期间接管 HNSW 图构建:segment flush 和 force merge。该插件要求数据节点连接受支持的 NVIDIA GPU、安装 cuVS 插件,并在 elasticsearch.yml 中设置 vectors.indexing.use_gpu: true。
- Segment flush。向量会累积在 JVM 写缓冲区中。在 flush 时,插件会对这些向量进行批处理,通过 PCIe 将它们复制到 GPU VRAM,在 VRAM 中构建 CAGRA 图,将其转换为 HNSW,然后再通过 PCIe 将结果复制回系统 RAM,随后 Lucene 将 segment 文件写入本地 NVMe。之后,该 segment 会被 memory-map 到操作系统页缓存中,用于查询服务。
- Force merge。当 segment 被合并时,相同的 GPU 路径会加速图重建。根据已发布的基准测试, force merge 时间大约可提升 7 倍。
其他所有内容(HTTP 请求处理、查询服务、集群状态管理、ILM 执行)都运行在 CPU 上,并使用系统 RAM。GPU 是两个写路径操作的协处理器,而不是主机计算环境的替代品。
将 GPU 构建与 CPU 查询服务分离这一点非常重要,因为连接 GPU 的节点不需要承担查询服务层角色。它们可以专门用于写路径,并将构建完成的 shard 转交给成本更低的 CPU 热服务节点。这正是模式 B 背后的核心思想。
模式 A:在单个 Elasticsearch 节点中同时进行 GPU 构建与服务
模式 A 是更简单的部署方式。每个 GPU 节点既负责构建 shard,也负责对这些 shard 提供查询服务。这也是运行 cuVS 基准测试时采用的方式:单个 g6.4xlarge(64 GB RAM、1 个 NVIDIA L4)在单个 Elasticsearch 进程上执行索引。该节点能够同时处理索引和搜索,不过已发布的基准测试主要测量的是索引吞吐量。
何时使用组合式 GPU 节点(模式 A)
-
小型集群(少于 5 个数据节点)
-
概念验证或边缘部署,在这些场景下,运维简洁性比成本优化更重要
-
语料库规模足够小,以至于完整 HNSW 图能够与正常运行负载一起放入每个节点的页缓存中
节点配置(模式 A)
每个数据节点都配备 GPU,并使用相同的 elasticsearch.yml 配置:
`
1. # elasticsearch.yml: Pattern A (combined GPU + serving node)
2. node.roles: [data_hot, data_content]
4. # auto (default): use GPU when available. true: fail to start if GPU unavailable.
5. # Use true on dedicated GPU nodes to catch misconfiguration at boot.
6. vectors.indexing.use_gpu: true
8. # JVM heap: ~32 GB max (compressed OOPs boundary)
9. # Remaining system RAM goes to OS page cache for HNSW graph
`AI写代码
不需要任何 shard 路由技巧,也不需要 ILM 分层隔离。Shard 会按照 Elasticsearch 默认分配器的规则进行分配,并且每个节点都可以同时进行构建与服务。
注意:还有一个索引级别设置 index.vectors.indexing.use_gpu,它可以按索引覆盖节点级默认配置。有效值包括:
-
auto(默认值,在 GPU 可用时使用 GPU) -
true(要求必须有 GPU,如果不可用则失败) -
false(为该索引禁用 GPU)
容量规划影响
由于每个节点都持有长期存在的 shard,因此它需要足够的系统 RAM 来支持:
-
JVM heap(约 32 GB)
-
用于保存其对应语料库 HNSW 图的操作系统页缓存
-
操作系统和 CUDA 开销(约 10–15 GB)
对于一个包含 3 亿向量、启用 HA(两份副本)的 int8 语料库,10 个数据节点中的每个节点大约需要在页缓存中保存 65 GB 的 HNSW 数据。再加上 JVM 和其他开销后,每个节点大约需要 128–192 GB 系统 RAM。这远高于 64 GB 的参考基准配置,因为该基准只是在单节点上保存了 260 万个向量。
每个节点的系统 RAM 是随着每节点 shard 数量增长的,而不是随着整个语料库大小增长。你可以选择更多节点配更少 RAM,或者更少节点配更多 RAM。两者之间的权衡在于运维复杂度与硬件成本。
关于已发布基准测试的说明
约 12 倍的索引吞吐提升以及约 7 倍的 force merge 提升,是在单个 g6.4xlarge 节点上,使用 260 万个、1536 维的 float32 hnsw 向量测得的。本文中的示例则使用了 1024 维的 int8_hnsw。不同维度数量和量化级别会影响性能特征,因此在生产环境容量规划之前,请先针对你自己的语料库进行基准测试。
模式 B:通过 ILM shard 切换到 CPU 的专用 GPU 摄取节点
模式 B 将集群拆分为两个数据层,每个层具有不同的硬件配置和职责:
- GPU 摄取层。少量配备 NVIDIA GPU 和适度系统 RAM(64 GB)的节点。这些节点负责接收写入、通过 cuVS 在 GPU 上构建 HNSW segment,并持有当前活跃写 shard。
- CPU 热服务层。更多不带 GPU、但拥有更大系统 RAM(192 GB)的节点。这些节点通过 ILM rollover 从 GPU 摄取层接收迁移过来的 shard,并承担所有查询流量。
一旦 shard rollover 并迁移到 CPU 热服务层后,GPU 摄取节点将不再持有该 shard。由于 GPU 摄取节点始终只保存当前正在写入的索引,因此它的页缓存占用会保持较小。
何时使用 GPU 摄取 + CPU 服务(模式 B)
-
生产集群拥有 5 个以上数据节点,并且已经将 ILM rollover 作为日常运维流程的一部分
-
GPU 硬件成本较高,并且你希望尽可能减少需要 GPU 的节点数量
-
查询服务工作负载需要专用 CPU 和 RAM,而不希望与 GPU 构建操作共享资源
-
ILM 已经是现有运维模型的一部分(对于任何时序型或生命周期管理的向量语料库来说,本就应该如此)
节点配置(模式 B)
GPU 摄取节点(elasticsearch.yml):
`
1. # GPU ingest node: builds shards, does not serve queries long-term
2. node.roles: [data_hot]
3. node.attr.tier_function: gpu_ingest
5. vectors.indexing.use_gpu: true
`AI写代码
CPU 热服务节点(elasticsearch.yml):
`
1. # CPU hot-serving node: receives migrated shards, serves queries
2. node.roles: [data_hot]
3. node.attr.tier_function: cpu_serve
`AI写代码
两种节点类型都共享 data_hot 角色,因为它们属于同一个逻辑层。自定义属性 node.attr.tier_function 让我们能够控制哪些节点接收新的写入,以及哪些节点接收迁移后的 shard。
索引模板:将新的写入路由到 GPU 节点
`
1. PUT _index_template/vectors-template
2. {
3. "index_patterns": ["vectors-*"],
4. "template": {
5. "settings": {
6. "index.routing.allocation.require.tier_function": "gpu_ingest",
7. "index.lifecycle.name": "vectors-lifecycle",
8. "index.lifecycle.rollover_alias": "vectors-active"
9. },
10. "mappings": {
11. "properties": {
12. "embedding": {
13. "type": "dense_vector",
14. "dims": 1024,
15. "index": true,
16. "index_options": {
17. "type": "int8_hnsw"
18. }
19. }
20. }
21. }
22. }
23. }
`AI写代码
匹配 vectors-* 的新索引会被专门分配到具有 tier_function: gpu_ingest 的节点上。写入会流向 GPU 摄取节点,并由 cuVS 在 GPU 上构建 HNSW 图。
关键点:你必须显式设置 index_options.type 为 int8_hnsw(或 hnsw)才能启用 GPU 加速。在 Elasticsearch 9.1 及以上版本中,维度为 384 或更高的 [dense_vector](https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector "dense_vector") 字段默认使用 bbq_hnsw,而 cuVS 不支持该类型。如果你在模板之外创建向量字段但没有指定 int8_hnsw,GPU 插件将在索引构建时回退到 CPU。上面的模板已经正确设置,但集群中所有希望使用 cuVS 加速的向量字段都必须显式配置这一项。
ILM 策略:滚动更新并迁移到 CPU 服务节点
`
1. PUT _ilm/policy/vectors-lifecycle
2. {
3. "policy": {
4. "phases": {
5. "hot": {
6. "min_age": "0ms",
7. "actions": {
8. "rollover": {
9. "max_age": "7d",
10. "max_primary_shard_size": "50gb"
11. },
12. "set_priority": {
13. "priority": 100
14. }
15. }
16. },
17. "warm": {
18. "min_age": "0ms",
19. "actions": {
20. "migrate": {
21. "enabled": false
22. },
23. "allocate": {
24. "require": {
25. "tier_function": "cpu_serve"
26. }
27. },
28. "set_priority": {
29. "priority": 50
30. }
31. }
32. },
33. "cold": {
34. "min_age": "180d",
35. "actions": {
36. "migrate": {
37. "enabled": false
38. },
39. "allocate": {
40. "require": {
41. "tier_function": "cpu_serve"
42. }
43. }
44. }
45. },
46. "delete": {
47. "min_age": "730d",
48. "actions": {
49. "delete": {}
50. }
51. }
52. }
53. }
54. }
`AI写代码收起代码块
这个策略里有几个需要注意的点。
在 warm 和 cold 阶段里,"**migrate": {"enabled": false}** 很关键。warm 阶段的 migrate 操作通常会尝试把 _tier_preference 设置为 data_warm,但在这个架构中没有任何节点具备 data_warm 角色,如果启用 migrate,就可能导致 shard 无法分配。禁用 migrate,并使用基于 tier_function 自定义属性的显式 allocate 动作,可以精确控制 shard 的落点。
warm 阶段里的 min_age: "0ms" 表示在 rollover 之后立即迁移,而不是等待一段时间。这是有意设计的:我们希望 shard 在 rollover 后几分钟内就离开 GPU 摄取节点,从而保持 GPU 摄取层的轻量状态。
替代方案:如果 “warm”(ILM 阶段名)和 “hot serving”(CPU 服务节点实际角色)之间的命名混淆对团队来说是个问题,也可以反过来设置 GPU 摄取节点为 node.roles: [data_hot],CPU 热服务节点为 node.roles: [data_warm],然后让 ILM 使用标准的 tier 迁移机制完成移动,而不需要自定义属性。这种方式的优点是 ILM 更简单,但语义可能有点反直觉:承担最重查询负载的节点会被标记为 “warm”。
为什么 GPU 摄取节点 64 GB 系统 RAM 就足够
在 Pattern B 中,GPU 摄取节点只需要承载以下内容(大致基于 Elasticsearch JVM 建议和 CUDA 运行时开销经验值):
| 消耗项 | 大致 RAM 占用 |
|---|---|
| JVM heap | ~32 GB |
| CUDA driver + pinned PCIe buffers | ~10 GB |
| 用于活跃写入 shard 的页缓存 | ~10–15 GB |
| 操作系统与容器开销 | ~5 GB |
| 总计 | ~60 GB |
这与已发布的 cuVS benchmark 硬件一致:AWS g6.4xlarge(64 GB RAM)。GPU 摄取节点不需要保存整个语料库累积的 HNSW 图,只需要保存当前正在写入的 index,该 index 会以较短周期进行 rollover。
相比之下,CPU 热服务节点需要在 page cache 中保存累积语料库,并且根据 vectors-per-node 的不同需要 128–192 GB。
容量规划总结(Pattern B,3 亿 vectors,int8_hnsw,HA)
| 节点类型 | 数量 | 系统 RAM | GPU | 角色 |
|---|---|---|---|---|
| GPU 摄取 | 3 | 64 GB | 1x L40S (48 GB VRAM) | 构建 shards, cuVS |
| CPU 热服务 | 7 | 192 GB | 无 | 提供查询服务,在 page cache 中保存 HNSW |
| Warm/cold( BBQ ) | 5 | 64 GB | 无 | 老化数据,约 8x 压缩 |
| Masters | 3 | 16 GB | 无 | 集群仲裁 |
| Coord + Kibana | 4 | 32–64 GB | 无 | 查询路由, Kibana UI |
在 ECK 下的总 ERU(Elastic Resource Unit):约 ~32。
Pattern B 中的数据流(端到端)
在 Pattern B 中,向量从客户端进入 GPU 摄取节点,在该节点上由 cuVS 在 GPU 上构建 HNSW segment,然后通过 ILM 将完成的 shard 迁移到 CPU 热服务节点。下面的时序图展示了一批向量的完整往返流程,以及将已完成 shard 迁移到服务层的 rollover 过程:
步骤如下:
-
客户端发送
_bulk请求,向量以 base64 字符串编码(推荐用于提升吞吐)。 -
协调节点将请求路由到拥有当前写入 shard 的 GPU 摄取节点。
-
GPU 摄取节点的 JVM 解析请求,并将向量排入写入缓冲区。
-
达到 flush 阈值时,cuVS 插件对向量进行批处理,并通过 PCIe 将其从系统内存复制到 GPU VRAM。
-
GPU 在 VRAM 中构建 CAGRA 图,并将其转换为 HNSW 格式。
-
HNSW 数据通过 PCIe 从 GPU VRAM 复制回系统内存。
-
Lucene 将 segment 文件从系统内存写入本地 NVMe。
-
该 segment 被 memory-map 到操作系统页缓存(系统内存中),并变为可查询状态。
-
达到配置的 rollover 阈值(按时间或大小)后,ILM 执行 rollover,在 GPU 摄取层创建新的写入 index。
-
已完成的 index 的分配条件从
gpu_ingest变为cpu_serve,Elasticsearch 的 shard allocator 将这些 shard 通过网络迁移到 CPU 热服务节点。 -
CPU 热服务节点将接收到的 segment memory-map 到其页缓存中,并开始提供查询服务。
-
GPU 摄取节点的页缓存随着 shard 的迁出而释放,为下一轮写入周期腾出空间。
-
在整个过程中,查询流量不会经过 GPU 摄取节点;GPU 也不会在 VRAM 中同时持有超过一个 batch 的向量。
-
GPU 仅在步骤 4–6 处于忙碌状态,其余时间处于空闲。
-
系统内存在 GPU 前后都作为关键路径:一方面作为 PCIe 传输的 staging 区域,另一方面作为查询服务的持久 page cache。
添加 BBQ 冷层(带重量化)
Pattern B 还扩展出了用于长期存储语料的 BBQ 冷层。BBQ 相比 int8 大约提供 8 倍压缩,显著降低冷层的内存与磁盘占用。
ILM 策略会增加一个阶段,将 int8_hnsw 数据重新索引为使用 bbq_hnsw 配置的新 index:
`
1. "cold": {
2. "min_age": "180d",
3. "actions": {
4. "allocate": {
5. "require": {
6. "tier_function": "cpu_serve"
7. }
8. }
9. }
10. }
`AI写代码
实际上,重新量化是通过在冷阶段转换时触发的独立 reindex 作业来完成的。BBQ 相比 int8 大约提供 8 倍压缩(相比 float32 约 32 倍压缩),因此冷层的 RAM 和磁盘占用会大幅下降。
需要注意的是,BBQ 是纯 CPU 的量化路径。截至 Elasticsearch 9.5,cuVS 并不支持 bbq_hnsw。因此 GPU 摄取层使用 int8_hnsw 来构建,而 BBQ 的转换发生在之后的 CPU warm 或 cold 节点上。冷路径不依赖 GPU。
应该选择哪种模式?
对于大多数拥有 5 个或更多数据节点的生产集群,Pattern B 是更好的默认选择。Pattern A 则更适合概念验证部署和小型集群,在这些场景中,运维简单性比成本优化更重要。
| 因素 | Pattern A(组合式) | Pattern B(专用 GPU 摄取) |
|---|---|---|
| 集群规模 | 少于 ~5 个数据节点 | 5+ 数据节点 |
| 运维复杂度 | 更低 | 更高(ILM + 分配路由) |
| GPU 节点系统 RAM | 128–192 GB | 64 GB |
| GPU 利用率 | GPU 在查询期间处于空闲(无负载) | GPU 在 flush 间隙空闲,但节点不承载查询 |
| 硬件成本 | 更高(每个 GPU 节点都需要查询级 RAM) | 更低(GPU 摄取节点更精简) |
| ERU 影响 | 更高 | 更低 |
| 最佳适用场景 | POC 或边缘部署 | 5+ 数据节点的生产环境 |
对于大多数生产部署来说,Pattern B 是更好的默认选择。运维复杂度是适中的(本文中展示的 ILM 策略和分配属性就是全部实现),并且 GPU 节点 RAM 和 ERU 的节省是显著的。
开始使用 NVIDIA cuVS GPU 向量索引
GPU 加速向量索引在 Elasticsearch 9.3 中作为技术预览提供(自管 Enterprise 版本),并计划在 Elasticsearch 9.5 中进入 GA(正式可用)。
要求:
- Elasticsearch 9.3+ 且具备 Enterprise 订阅
- NVIDIA Ampere 架构或更新 GPU(计算能力 ≥ 8.0),最少 8 GB VRAM
- CUDA 12.x 和 cuVS 运行时库(具体支持版本参考 Elastic 支持矩阵)
- GPU 节点上 Java 22 或更高版本
- Linux x86_64
- 高速本地 NVMe(不推荐使用网络存储)
对于在 FAISS 或专用向量数据库(如 Milvus 或 Pinecone)之间做评估的团队来说,其核心运维理念是相同的:同一平台、同一 ATO、同一运维模型,在已有 Elasticsearch 集群上叠加 GPU 加速的 ingest 能力。更广泛的 Elastic 与 NVIDIA 合作背景可参考 Elastic 和 NVIDIA 一起开启下一代企业 AI 搜索。
从 Pattern A 开始,在单个 GPU 节点上验证你的语料库吞吐性能。当你对结果有信心后,再切换到 Pattern B,并使用上面的 ILM 策略,按查询 SLA 扩展 CPU 热服务层,并让 GPU ingest 层专注做它唯一擅长的事情:以比 CPU 快 12 倍的速度构建 HNSW 图。
关于 cuVS benchmark 方法与结果,参考 在 Elasticsearch 中使用 NVIDIA cuVS 实现向量索引最高可提升 12 倍速度。关于 Elastic 与 NVIDIA 更广泛的合作,参考 在大规模企业 AI 场景中提供动力:Elastic 与 NVIDIA cuVS 的集成。
常见问题
为什么 Elasticsearch 在大规模语料上向量索引很慢?
HNSW 图构建是 CPU 受限的,并且在规模化时占据主要索引时间。加入 NVIDIA cuVS 插件后,图构建被迁移到 GPU,在 benchmark 硬件上可以提升最高 12 倍速度。对于数亿级向量语料,索引时间可以从“周级”下降到“天级”。
如何在不让 GPU 节点承担查询的情况下部署 Elasticsearch GPU 向量索引?
使用双层架构:GPU ingest 节点安装 cuVS 插件并配备 64 GB RAM,负责构建 HNSW segment 并持有活跃写 shard。随后通过 ILM rollover 将完成的 shard 迁移到 CPU 热服务层(192 GB RAM),该层负责所有查询。自定义节点属性 tier_function 用于控制新写入与迁移 shard 的分配。
Elasticsearch 的 dense_vector 字段是否可以使用 NVIDIA cuVS 进行 GPU 加速?
可以,在自管 Enterprise Elasticsearch 9.3 及以上版本中,且节点安装 cuVS 插件并配备支持的 NVIDIA GPU 时可以使用。但必须在 dense_vector mapping 中显式设置 index_options.type 为 int8_hnsw 或 hnsw,因为 9.1 及以上默认的 bbq_hnsw 不支持 GPU 加速。
GPU 向量索引节点在 Elasticsearch 中需要多少 RAM?
GPU ingest 节点只需要保存当前正在写入的 shard,因此总计约 60 GB 即可(32 GB JVM heap、10 GB CUDA 运行时与 pinned PCIe buffers、10–15 GB page cache、5 GB OS),与 AWS g6.4xlarge benchmark 一致。CPU 热服务节点由于需要保存累积 HNSW 语料,因此需要 128–192 GB RAM(取决于每节点 shard 数)。
为什么即使安装了 cuVS,我的 Elasticsearch GPU 插件仍然没有加速向量索引?
最常见原因是使用了默认的 bbq_hnsw 索引选项,而该选项不被 cuVS 插件支持。需要在 dense_vector mapping 中设置 index_options.type 为 int8_hnsw 或 hnsw。同时需要确认 GPU compute capability ≥ 8.0,CUDA 12.x,并且 vectors.indexing.use_gpu 设置为 true。
如何配置 ILM,使 shard 从 GPU 构建层迁移到 CPU 服务层?
在 warm 阶段设置 "migrate": {"enabled": false},避免 ILM 尝试迁移到不存在 data_warm 角色的节点,然后使用显式 allocate 动作将 shard 路由到 node.attr.tier_function: cpu_serve 的节点。完整策略结构和 index template 在文中给出。
我应该选择 Elasticsearch + NVIDIA cuVS 还是专用 向量数据库 ?
如果你已经在使用 Elasticsearch 做搜索、可观测性或安全能力,那么加入 cuVS 可以在同一平台上支持 agentic AI 工作负载,而无需额外的数据存储或运维体系。同时 cuVS 完全可以在本地硬件运行,这对于无法使用托管向量数据库的 air-gapped、主权云或本地部署环境非常重要。