Elasticsearch:加快 HNSW 圖的合并速度

作者:來自 Elastic?Thomas Veasey?及?Mayya Sharipova

過去,我們曾討論過搜索多個 HNSW 圖時所面臨的一些挑戰,以及我們是如何緩解這些問題的。當時,我們也提到了一些計劃中的改進措施。本文正是這項工作的成果匯總。

你可能會問,為什么要使用多個圖?這是 Lucene 中架構選擇的一個副作用:不可變段(immutable segments)。正如大多數架構決策一樣,它既有優點也有缺點。例如,我們最近正式發布了無服務器版本的 Elasticsearch。在這個背景下,我們從不可變段中獲得了非常顯著的優勢,包括高效的索引復制、索引計算與查詢計算的解耦,以及它們各自的自動擴展能力。對于向量量化,段合并讓我們有機會更新參數,以更好地適應數據特性。在這個方向上,我們認為通過測量數據特性并重新評估索引選擇,還可以帶來其他優勢。

本文將探討我們為顯著減少構建多個 HNSW 圖的開銷,特別是為了降低圖合并成本所做的工作。

背景

為了保持可管理的段(segment)數量,Lucene 會定期檢查是否需要合并段。這基本上是檢查當前段數量是否超過了一個由基礎段大小和合并策略決定的目標段數。如果超過了這個限制,Lucene 就會將若干段合并,直到約束條件不再被違反。這個過程在其他文檔中已有詳細描述。

Lucene 傾向于合并相似大小的段,因為這樣可以實現寫放大的對數增長。對于向量索引來說,寫放大指的是同一個向量被插入圖的次數。Lucene 通常嘗試將大約 10 個段合并成一組。因此,向量大約會被插入圖 1 + 9/10 × log??(n/n?) 次,其中 n 是索引中的總向量數量,n? 是每個基礎段的預期向量數量。由于這種對數增長,即便在非常大的索引中,寫放大仍能保持在個位數。然而,合并圖所花費的總時間與寫放大呈線性關系。

在合并 HNSW 圖時,我們已經采用了一個小的優化:保留最大段的圖,并將其他段的向量插入這個圖。這就是上述公式中 9/10 系數的原因。接下來我們將展示,如何通過利用所有參與合并的圖中的信息,顯著提高這一過程的效率。

圖合并

之前,我們保留了最大的圖,并插入了其他圖中的向量,忽略了包含它們的圖。我們在下面利用的關鍵見解是,每個我們丟棄的圖包含了它所包含的向量的相似性信息。我們希望利用這些信息來加速插入至少一部分向量。

我們專注于將一個較小的圖 G_s = (V_s, E_s) 插入到一個較大的圖?G_l = (V_l, E_l)? 的問題,因為這是一個原子操作,我們可以用它來構建任何合并策略。

該策略是找到一個小圖中頂點的子集?J \subset V_s 來插入到大圖中。然后,我們利用這些頂點在小圖中的連接性來加速插入剩余的頂點 V_s \setminus J?。以下我們用 N_s(u)N_l(u)?分別表示小圖和大圖中頂點 u 的鄰居。該過程的示意如下。

我們使用下文將討論的一個過程(第1行)來計算集合 J。然后,使用標準的 HNSW 插入過程(第2行)將?J 中的每個頂點插入到大圖中。對于尚未插入的每個頂點,我們會找到其已插入的鄰居及這些鄰居在大圖中的鄰居(第 4 和第 5 行)。接著,使用以這個集合為種子的 FAST-SEARCH-LAYER 過程(第 6 行),從 HNSW 論文中的 SELECT-NEIGHBORS-HEURISTIC 中找到候選集合(第 7 行)。實際上,我們是在替換 INSERT 方法(論文中的算法1)中的 SEARCH-LAYER 過程,其他部分保持不變。最后,將剛插入的頂點加入集合 J(第8行)。

顯然,為了使這個過程有效,每個 V_s \setminus J?中的頂點必須至少有一個鄰居在 J 中。實際上,我們要求對于每個 u \in V_s \setminus J,都有 |J \cap N_s(u)| \geq k_u?,其中 k_u < M,即最大層連接數。我們觀察到,在實際的 HNSW 圖中,頂點度數的分布差異很大。下圖展示了 Lucene HNSW 圖底層中頂點度數的典型累計密度函數。

我們嘗試了使用固定值的 k_u? 和將其設為頂點度數的函數這兩種方法。第二種選擇帶來了更大的加速,并對召回率的影響較小,因此我們選擇了以下公式:

請注意,|N_s(u)| 是頂點 u 在小圖中的度數。設定下限為 2 意味著我們將插入所有度數小于 2 的頂點。

一個簡單的計數論證表明,如果我們仔細選擇 J,我們只需要直接將約 \frac{1}{5}|V_s|的頂點插入到大圖?G_l 中。具體而言,我們對圖的邊進行著色,如果我們將它的一個端點插入到 J?中。然后我們知道,為了確保 V_s \setminus J 中的每個頂點都有至少 k_u? 個鄰居在 J 中,我們需要著色至少 \sum_{u \in V_s \setminus J} k_u 條邊。此外,我們預期:

這里,E_U[|N_s(U)|] 是小圖中頂點的平均度數。對于每個頂點 u \in J,我們最多會著色|N_s(u)| 條邊。因此,我們預期著色的總邊數最多為|J| \, E_U[|N_s(U)|]。我們希望通過仔細選擇 J,能夠著色接近這個數量的邊。因此,為了覆蓋所有的頂點,\left| J \right| 需要滿足:

這意味著:

提供 SEARCH-LAYER 支配了運行時間,這表明我們可以在合并時間上實現最多 5 倍的加速。考慮到寫入放大效應的對數增長,這意味著即使對于非常大的索引,我們的構建時間通常也僅僅是構建一個圖時的兩倍。

這種策略的風險在于我們可能會損害圖的質量。最初,我們嘗試了一個無操作的 FAST-SEARCH-LAYER。我們發現這會導致圖質量下降,特別是在合并到單個段時,召回率作為延遲的函數會降低。然后我們探索了使用有限圖搜索的各種替代方案。最終,最有效的選擇是最簡單的。使用 SEARCH-LAYER,但設置一個較低的 ef_construction。通過這種參數設置,我們能夠在保持優良圖質量的同時,減少約一半的合并時間。

計算連接集

找到一個好的連接集可以表述為一個圖覆蓋問題。貪心啟發式算法是一種簡單有效的啟發式方法,用于逼近最優的圖覆蓋。我們采用的方法是使用貪心啟發式算法,一次選擇一個頂點將其加入到 J 中,使用以下定義的每個候選頂點的增益:

在這里,c(v) 表示向量 vJ 中的鄰居數量,1_{\{\cdot\}} 是指示函數。增益包括我們加入到 J 中的頂點的鄰居數量變化,即 \max(k_v - c(v), 0),因為通過加入一個覆蓋較少的頂點,我們可以獲得更多的增益。增益計算在下圖中對于中央橙色節點進行了說明。

我們為每個頂點 v 維護以下狀態:

  • 是否過時(stale)

  • 它的增益 \text{Gain}(v)

  • J 中相鄰頂點的計數,記作?c(v)

  • 一個在 [0, 1] 范圍內的隨機數,用于打破平局。

計算加入集合的偽代碼如下。

我們首先在第 1-5 行初始化狀態。

在主循環的每次迭代中,我們首先提取最大增益的頂點(第 8 行),并在存在平局時隨機打破平局。在做任何修改之前,我們需要檢查該頂點的增益是否過時。特別地,每次我們將一個頂點添加到 J 中時,會影響其他頂點的增益:

  1. 由于它的所有鄰居在 J 中有了額外的鄰居,它們的增益可能會發生變化(第14行)。

  2. 如果它的任何鄰居現在已經完全覆蓋,那么它們所有鄰居的增益都可能發生變化(第14-16行)。

我們以懶惰的方式重新計算增益,因此只有在我們要將一個頂點插入到 J 中時,才會重新計算該頂點的增益(第18-20行)。

請注意,我們只需要跟蹤我們已添加到 J 中的頂點的總增益,以決定何時退出。此外,盡管 \text{Gain}_{\text{tot}} < \text{Gain}_{\text{exit}},至少會有一個頂點具有非零增益,因此我們始終會取得進展。

結果

我們在4個數據集上進行了實驗,涵蓋了我們支持的三種距離度量(歐幾里得、余弦和內積),并采用了兩種類型的量化方法:1)int8 —— 每個維度使用1字節的整數;2)BBQ —— 每個維度使用一個比特。

實驗 1:int8 量化

從基準到候選方案(提出的改進)的平均加速比為:

  • 索引時間加速:1.28×

  • 強制合并加速:1.72×

這對應于以下運行時間的劃分:

為了完整性,以下是各項時間數據:

  • quora-E5-small; 522931 文檔;384 維度;余弦度量
    基準:索引時間:112.41s,強制合并:113.81s
    候選:索引時間:81.55s,強制合并:70.87s

  • cohere-wikipedia-v2; 100萬文檔;768 維度;余弦度量
    基準:索引時間:158.1s,強制合并:425.20s
    候選:索引時間:122.95s,強制合并:239.28s

  • gist; 960 維度,100萬文檔;歐幾里得度量
    基準:索引時間:141.82s,強制合并:536.07s
    候選:索引時間:119.26s,強制合并:279.05s

  • cohere-wikipedia-v3; 100萬文檔;1024 維度;內積度量
    基準:索引時間:211.86s,強制合并:654.97s
    候選:索引時間:168.22s,強制合并:414.12s

下面是與基準進行對比的召回率與延遲圖,比較了候選方案(虛線)與基準在兩種檢索深度下的表現:recall@10 和 recall@100,分別針對具有多個分段的索引(即索引所有向量后默認合并策略的最終結果)以及強制合并為單個分段的索引。曲線越高且越靠左表示性能越好,這意味著在較低的延遲下獲得更高的召回率。

從下面的圖表可以看出,在多個分段索引上,候選方案在 Cohere v3 數據集上的表現更好,對于其他所有數據集,曲線稍差但幾乎可比。當合并為單個分段后,所有數據集的召回曲線幾乎相同。

實驗 2:BBQ 量化

從基準到候選方案的平均加速比為:

  • 索引時間加速:1.33×

  • 強制合并加速:1.34×

以下是具體的細分:

為完整性考慮,以下是時間數據:

  • quora-E5-small; 522931 個文檔; 384 維度; 余弦度量
    基準:索引時間:70.71秒,強制合并:59.38秒
    候選方案:索引時間:58.25秒,強制合并:40.15秒

  • cohere-wikipedia-v2; 100 萬個文檔; 768 維度; 余弦度量
    基準:索引時間:203.08秒,強制合并:107.27秒
    候選方案:索引時間:142.27秒,強制合并:85.68秒

  • gist; 960 維度,100 萬個文檔; 歐氏度量
    基準:索引時間:110.35秒,強制合并:323.66秒
    候選方案:索引時間:105.52秒,強制合并:202.20秒

  • cohere-wikipedia-v3; 100 萬個文檔; 1024 維度; 內積度量
    基準:索引時間:313.43秒,強制合并:165.98秒
    候選方案:索引時間:190.63秒,強制合并:159.95秒

從多個段索引的圖表可以看到,候選方案在幾乎所有數據集上表現更好,除了 cohere v2 數據集,基準略優。對于單個段索引,所有數據集的召回曲線幾乎相同。

總的來說,我們的實驗涵蓋了不同的數據集特征、索引設置和檢索場景。實驗結果表明,我們能夠在保持強大的圖質量和搜索性能的同時,實現顯著的索引和合并加速,這些結果在各種測試場景中表現一致。

結論

本文討論的算法將在即將發布的 Lucene 10.2 版本中提供,并將在基于該版本的 Elasticsearch 發布中可用。用戶將在這些新版本中受益于改進的合并性能和縮短的索引構建時間。這個變化是我們不斷努力使 Lucene 和 Elasticsearch 成為一個快速高效的向量搜索數據庫的一部分。

自己親自體驗向量搜索,通過這款自定進度的搜索 AI 實踐學習。你可以開始免費的云試用或立即在本地機器上嘗試 Elastic。

原文:Speeding up merging of HNSW graphs - Elasticsearch Labs

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/77394.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/77394.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/77394.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

人事|人事管理系統|基于Springboot+vue的人事管理系統設計與實現(源碼+數據庫+文檔)

人事管理系統 目錄 基于Springboot的人事管理系統設計與實現 一、前言 二、系統功能設計 三、系統實現 1、管理員登錄 2、員工管理 3、公告信息管理 4、公告類型管理 5、培訓管理 6、培訓類型管理 四、數據庫設計 1、實體ER圖 五、核心代碼 六、論文參考 七、最新…

2.4GHz射頻前端噪聲系數優化架構

2.4GHz射頻前端電路架構由信號處理鏈路、硬件模塊及性能規范構成&#xff0c;其系統組成與參數要求如下&#xff1a; 一、信號發射鏈路? 數字基帶信號通過DAC轉換為模擬信號? 調制電路將信號加載至本地振蕩器生成的2.4GHz載波? 功率放大器將信號強度提升至20-25dBm范圍? …

開源 LLM 應用開發平臺 Dify 全棧部署指南(Docker Compose 方案)

開源 LLM 應用開發平臺 Dify 全棧部署指南&#xff08;Docker Compose 方案&#xff09; 一、部署環境要求與前置檢查 1.1 硬件最低配置 組件要求CPU雙核及以上內存4GB 及以上磁盤空間20GB 可用空間 1.2 系統兼容性驗證 ? 官方支持系統&#xff1a; Ubuntu 20.04/22.04 L…

Trae AI 保姆級教程:從安裝到調試全流程指南

Trae AI 保姆級教程&#xff1a;從安裝到調試全流程指南 Trae AI 是字節跳動推出的一款 AI 原生集成開發環境(IDE)&#xff0c;專為中文開發者設計&#xff0c;集成了 Claude 3.5 和 GPT-4o 等先進 AI 模型&#xff0c;支持通過自然語言交互實現代碼生成、項目構建與調試。本教…

博物館小程序怎么做?從0到1打造數字化文化窗口

博物館小程序怎么做&#xff1f;從0到1打造數字化文化窗口 一、行業痛點&#xff1a;傳統博物館的數字化困局 在數字化浪潮下&#xff0c;傳統博物館普遍面臨三大挑戰&#xff1a; ??客流受限??&#xff1a;線下接待能力有限&#xff0c;難以觸達更廣泛人群 ??互動單一…

基于 Netty 框架的 Java TCP 服務器端實現,用于啟動一個 TCP 服務器來處理客戶端的連接和數據傳輸

代碼&#xff1a; package com.example.tpson_tcp;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; imp…

深入解析原生鴻蒙中的 RN 日志系統:從入門到精通!

全文目錄&#xff1a; 開篇語&#x1f4d6; 目錄&#x1f3af; 前言&#xff1a;鴻蒙日志系統究竟有多重要&#xff1f;&#x1f6e0;? 鴻蒙 RN 日志系統的基礎結構&#x1f4dc; 1. 日志的作用?? 2. 日志分類 &#x1f527; 如何在鴻蒙 RN 中使用日志系統&#x1f58b;? 1…

算法訓練營Day01(二分 雙指針)

704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 關于二分查找 最重要的是要處理好邊界問題&#xff0c;每次寫完邊界可以帶入特殊值進行測試確定區間的不變量是什么&#xff1f;比如區間的左閉右閉&#xff0c;和左閉右開&#xff0c;每次二分完的新區間&#xff0c;一…

shadcn 使用步驟與注意點

目錄 一、shadcn ui 二、使用流程 1.安裝 2.顏色與主題 3.引用blocks 三、使用注意點 四、推薦搭配工具 五、總結 一、shadcn ui 官網&#xff1a;Build your component library - shadcn/ui 為何選擇它&#xff1f;因為它是一個基于 Tailwind CSS Radix UI 的組件集…

STM32CubeMX-H7-12-IIC讀寫MPU6050模塊(中)-MPU6050模塊詳解以及軟件IIC驅動

前言 上一篇我們已經完成對IIC代碼基本框架的編寫&#xff0c;以及獲取MPU6050的ID&#xff0c;接下來我們逐一分析這個模塊的功能&#xff0c;并用IIC驅動 建議看完上一篇再來看這篇 MPU6050寄存器介紹 1.電源管理寄存器&#xff08;PWR_MGMT_1&#xff0c;地址&#xff1a;0…

量子計算模擬中的GPU加速:從量子門操作到Shor算法實現

一、量子模擬的算力困境與GPU破局 量子計算模擬面臨?指數級增長的資源需求?&#xff1a;n個量子比特的態向量需要2^n個復數存儲空間。當n>30時&#xff0c;單機內存已無法承載&#xff08;1TB需求&#xff09;。傳統CPU模擬器&#xff08;如Qiskit的Aer&#xff09;在n28…

spring mvc 異常處理中@RestControllerAdvice 和 @ControllerAdvice 對比詳解

RestControllerAdvice 和 ControllerAdvice 對比詳解 1. 基本概念 注解等效組合核心作用ControllerAdviceComponent RequestMapping&#xff08;隱式&#xff09;定義全局控制器增強類&#xff0c;處理跨控制器的異常、數據綁定或全局響應邏輯。RestControllerAdviceControll…

JavaScript的回調函數:異步編程的基石

引言 在JavaScript的世界里&#xff0c;回調函數是一種強大而基礎的編程模式&#xff0c;它是異步編程的核心概念之一。隨著Web應用程序變得越來越復雜&#xff0c;理解和掌握回調函數變得尤為重要。本文將深入探討JavaScript回調函數的概念、應用場景以及最佳實踐。 什么是回…

測試用例 [軟件測試 基礎]

目錄 測試用例 1. 概念 1.1 什么是測試用例 1.2 什么是要素 1.3 為什么需要測試用例 2. 設計測試用例的萬能公式 2.1 常規思維 逆向思維 發散性思維 2.2 萬能公式 3. 設計測試用例的方法 3.1 基于需求的設計方法 3.2 具體的設計方法 3.3 更多用例練習 測試用例 …

Jupyter notebook定制字體

一、生成配置文件 運行Anaconda Powershell Prompt終端&#xff0c;輸入下面一行代碼&#xff1a; jupyter notebook --generate-config 將生成文件“C:\Users\XXX\.jupyter\jupyter_notebook_config.py”&#xff0c;XXX為計算機賬戶名字。 二、修改配置文件 c.NotebookAp…

miniconda安裝R語言圖文教程(詳細步驟)

本篇教程介紹,如何在Windows使用miniconda安裝R語言。 一、創建1個conda 虛擬環境 # 創建虛擬環境 conda create -n r_env # 激活虛擬環境 conda activate r_env二、安裝 R 語言 conda install -c r r-ggplot2三、運行測試 檢查安裝: 輸入 R 進入 R 的交互式命令行,檢查是…

【day1】AI軟件測試學習筆記

以下為整理的 AI軟件測試學習筆記&#xff0c;涵蓋性能測試工具鏈、AI大模型應用及開發實踐&#xff0c;分為四大模塊&#xff1a; 一、性能測試工具鏈與數據分析 1. 工具鏈整合效果 JMeter InfluxDB Grafana JMeter壓測數據存儲至云端InfluxDB&#xff0c;實現分布式壓測和…

WPF 資源加載問題:真是 XAML 的鍋嗎?

你的觀察很敏銳&#xff01;確實&#xff0c;在 WPF 項目中&#xff0c;.cs 文件主要負責邏輯實現&#xff0c;而資源加載的問題通常跟 XAML&#xff08;以及它背后的 .csproj 配置&#xff09;關系更大。我會圍繞這個觀點&#xff0c;用 CSDN 博客風格詳細解釋一下 .cs、XAML …

C++17模板編程與if constexpr深度解析

一、原理深化 1.1 模板編程 1.1.1 編譯器如何處理模板&#xff08;補充&#xff09; 模板的實例化機制存在兩種模式&#xff1a; 隱式實例化&#xff1a;編譯器在遇到模板具體使用時自動生成代碼&#xff0c;可能導致多翻譯單元重復實例化&#xff0c;增加編譯時間。顯式實…

408 計算機網絡 知識點記憶(6)

前言 本文基于王道考研課程與湖科大計算機網絡課程教學內容&#xff0c;系統梳理核心知識記憶點和框架&#xff0c;既為個人復習沉淀思考&#xff0c;亦希望能與同行者互助共進。&#xff08;PS&#xff1a;后續將持續迭代優化細節&#xff09; 往期內容 408 計算機網絡 知識…