向量檢索提速:cuVS+Elasticsearch GPU索引構(gòu)建實(shí)戰(zhàn))
向量檢索這個(gè)事做到一定規(guī)模之后CPU 就真的扛不住了。我們線上有一批 1.38 億條 384 維的 embedding 召回池之前用 Elasticsearch 原生的 HNSW 建索引一跑就是四五個(gè)小時(shí)而且 JVM 堆內(nèi)存和 GC 壓力大得讓人頭疼每天索引更新的調(diào)度窗口根本排不開。后來把 NVIDIA cuVS 引入檢索鏈路用 GPU 來做向量索引的構(gòu)建和近鄰搜索整批 1.38 億向量從原始數(shù)據(jù)到可查詢的索引文件10 分鐘出頭就能跑完檢索端延遲還比原來低了不少。這篇文章就是這次改造的完整復(fù)盤。里面會(huì)講清楚為什么選 cuVS Elasticsearch 這套組合、索引參數(shù)到底怎么推算才不踩坑、完整搭建流程怎么走以及我在實(shí)際環(huán)境中碰到的各種編譯、顯存、召回率問題。適合正在做推薦召回、多模態(tài)搜索、RAG 向量庫或者被億級(jí)向量索引構(gòu)建折磨得睡不著覺的工程師參考。不管你是第一次聽到 cuVS還是已經(jīng)跑過 GPU 向量檢索應(yīng)該都能從里面找到點(diǎn)能直接抄作業(yè)的東西。1. 方案選型為什么把 GPU 放到 ES 檢索鏈路里1.1 數(shù)據(jù)規(guī)模帶來的第一個(gè)坎先說一個(gè)最直接的感受1.38 億條 384 維 float32 向量光原始數(shù)據(jù)就有 211.9GB這個(gè)數(shù)字很多團(tuán)隊(duì)第一眼看到是沒概念的。放到 Elasticsearch 里L(fēng)ucene 的 HNSW 索引構(gòu)建需要不斷計(jì)算鄰居圖、寫段、合并段不僅慢而且內(nèi)存占用遠(yuǎn)不止原始數(shù)據(jù)的大小。HNSW 的圖結(jié)構(gòu)本身會(huì)有額外開銷加上 JVM 堆、段合并的臨時(shí)空間跑批建索引的時(shí)候一臺(tái) 256GB 內(nèi)存的機(jī)器都會(huì)顯得捉襟見肘。我們當(dāng)時(shí)第一次全量構(gòu)建主節(jié)點(diǎn)直接拉起了 4 個(gè)多小時(shí)的任務(wù)中間還因?yàn)槎褍?nèi)存告警被 kill 過兩次。這時(shí)候就需要重新思考一個(gè)問題ES 在整個(gè)鏈路里的職責(zé)到底是什么。它適合做文檔存儲(chǔ)、過濾、聚合和最終打分但在“億級(jí)向量全量建索引”這件事上純 CPU 的近似最近鄰算法確實(shí)到了瓶頸。GPU 的并行度天然適合這類計(jì)算密集型任務(wù)k-means 聚類、向量距離計(jì)算、PQ 編碼這些操作在 GPU 上可以成量級(jí)地加速。于是方向就很明確了把向量索引的構(gòu)建和搜索從 ES 的 JVM 里拆出來交給 GPU 側(cè)的向量搜索庫ES 只負(fù)責(zé)它最擅長的文檔管理和查詢路由。1.2 cuVS 是什么有哪些算法cuVS 是 NVIDIA 開源的 GPU 向量搜索庫歸屬于 RAPIDS 生態(tài)里面實(shí)現(xiàn)了幾種主流算法IVF-Flat、IVF-PQ、CAGRA也有一部分 HNSW 的支持。你可以把它理解成一個(gè)專門在 GPU 上做最近鄰搜索的工具箱輸入一組向量它幫你建索引然后給你一個(gè)支持近似搜索的接口。算法選型上IVF-PQ 是我們這次的首選原因是它在建索引速度和內(nèi)存占用上最均衡。IVF-PQ 做的事情可以理解成兩步先把向量空間用倒排索引切分成若干個(gè)區(qū)域然后對每個(gè)區(qū)域內(nèi)做乘積量化把高維向量壓縮成一小段碼字。壓縮之后1.38 億條 384 維向量索引文件只有幾個(gè) GBA100 80G 的顯存完全放得下。CAGRA 我也測過一輪它是 cuVS 里性能更極端的圖算法搜索延遲可以做到更低但建索引時(shí)對顯存的要求更高數(shù)據(jù)量上億之后訓(xùn)練階段的中間結(jié)果很容易把顯存撐爆。如果你的數(shù)據(jù)量在幾千萬級(jí)別且對查詢延遲極度敏感CAGRA 值得試但像我這種 1.38 億全量池IVF-PQ 更穩(wěn)。HNSW 在 cuVS 里也有 GPU 實(shí)現(xiàn)但它的構(gòu)建復(fù)雜度擺在那里GPU 加速效果沒有 IVF 系明顯所以我們沒有走那條路。1.3 架構(gòu)設(shè)計(jì)存儲(chǔ)與檢索分層整套架構(gòu)最后落地是這樣分的Elasticsearch 負(fù)責(zé)管理文檔元數(shù)據(jù)、原始向量字段、過濾條件以及最終的精確重排cuVS 負(fù)責(zé)離線構(gòu)建大規(guī)模近鄰索引在線接受查詢向量返回候選集的 doc_id。數(shù)據(jù)流上離線階段先用 GPU 把全量向量構(gòu)建成 IVF-PQ 索引文件同時(shí)把 1.38 億文檔的元數(shù)據(jù)批量導(dǎo)入 ES查詢階段線上請求的 query 向量先送進(jìn) cuVS 搜索接口取回 Top-K 候選 doc_id再去 ES 里做一次 mget 把文檔撈出來最后可以用原始向量做精確距離重排。這樣做的好處是很明顯的。第一ES 的 JVM 堆不再背“全量向量索引”這個(gè)重?fù)?dān)集群穩(wěn)定性直線上升。第二索引構(gòu)建和更新完全獨(dú)立cuVS 重建索引不影響 ES 對外服務(wù)。第三整套鏈路是插拔式的哪天數(shù)據(jù)量再翻一倍我可以只加 GPU 節(jié)點(diǎn)和調(diào)整 IVF-PQ 參數(shù)不用推翻 ES 集群。如果你用的是 OpenSearch它自帶的 k-NN 插件支持 FAISS 思路類似但 ES 這邊沒有原生 GPU 索引插件自己接 cuVS 反而靈活。2. 數(shù)據(jù)準(zhǔn)備與索引參數(shù)推演別拍腦袋選 nlist 和 M2.1 先看清楚你的數(shù)據(jù)形態(tài)任何向量索引方案第一步不是寫代碼而是先搞清楚你的數(shù)據(jù)長什么樣。我們這批數(shù)據(jù)是文本和圖像的混合 embedding統(tǒng)一歸一到 384 維并且做了 L2 歸一化。為什么要?dú)w一化很關(guān)鍵如果你打算用余弦相似度那歸一化之后的內(nèi)積就等價(jià)于余弦距離這樣在 cuVS 里可以直接用內(nèi)積作為距離度量少一層向量長度修正的開銷。不同維度下1.38 億條向量的數(shù)據(jù)體量完全不同直接影響顯存和參數(shù)選擇。下面的表是我當(dāng)時(shí)算的賬向量維度1.38 億條原始數(shù)據(jù)大小IVF-PQ 壓縮后索引大小M24, nbits8128 維約 70.6GB約 1.3GB384 維約 211.9GB約 3.9GB768 維約 423.9GB約 7.7GB所以如果你的 embedding 是 768 維甚至 1536 維壓縮后的索引還能接受但訓(xùn)練和編碼的中間過程對顯存的要求就高很多這時(shí)就要考慮分塊構(gòu)建。我們最終選擇在 A100 80G 上全量跑 384 維數(shù)據(jù)顯存剛好覆蓋。2.2 IVF-PQ 關(guān)鍵參數(shù)nlist、M、nbitsIVF-PQ 有三個(gè)參數(shù)是必須想清楚的nlist 是倒排索引的聚類中心數(shù)量M 是把向量切成多少個(gè)子向量段nbits 是每個(gè)子向量的量化位數(shù)。先說 nlist經(jīng)驗(yàn)法則是取數(shù)據(jù)量的平方根附近的值sqrt(1.38 億) 約等于 11747所以取 16384 這個(gè) 2 的冪比較好。nlist 越大每個(gè)桶里的向量越少搜索時(shí)定位越準(zhǔn)但訓(xùn)練聚類的時(shí)間也更長還可能導(dǎo)致部分桶過小反而影響召回。如果你數(shù)據(jù)是 1 億級(jí)別16384 是一個(gè)很穩(wěn)的起點(diǎn)。M 的選擇相對靈活前提是能被向量維度整除。384 維可以取 M16 或 M24M16 時(shí)每個(gè)子向量是 24 維壓縮后每個(gè)向量 20 字節(jié)M24 時(shí)每個(gè)子向量是 16 維壓縮后 28 字節(jié)。壓縮率越高索引越小但量化誤差越大召回率會(huì)有損失。我們最終在召回率達(dá)標(biāo)的前提下選了 M24單條壓縮 28 字節(jié)總索引約 3.9GB這個(gè)量級(jí)在顯存里非常舒服。nbits 默認(rèn) 8 就夠表示每個(gè)子向量的碼本里有 256 個(gè)中心點(diǎn)再往上提收益很小反而顯著增加訓(xùn)練耗時(shí)。2.3 顯存不夠怎么辦如果你手里的卡不是 80G或者向量維度更高別慌cuVS 支持分批構(gòu)建。關(guān)鍵思路是分離“訓(xùn)練”和“編碼”兩個(gè)過程。訓(xùn)練階段只需要一部分有代表性的樣本比如 200 萬條隨機(jī)采樣就足夠 k-means 收斂200 萬 × 384 維 × 4 字節(jié)也就 3GB 左右編碼階段可以按照 500 萬條一個(gè) chunk 分批進(jìn)行每批算完結(jié)果寫盤最后拼接成完整的索引文件。這樣即便數(shù)據(jù)總量幾百 GB構(gòu)建過程的顯存峰值也能控制在 20GB 左右。還有一個(gè)實(shí)用技巧是索引加載時(shí)用內(nèi)存映射而不是一次性全量塞進(jìn)顯存。cuVS 支持把索引文件映射到內(nèi)存搜索時(shí)按需讀取對應(yīng)的倒排桶這樣單張卡能承載的索引規(guī)??梢赃h(yuǎn)大于顯存容量。代價(jià)是查詢延遲會(huì)有所上升因?yàn)槎嗔艘粚哟疟P/內(nèi)存讀取但對“索引不常駐顯存”的場景來說這是一個(gè)很值得用的降級(jí)方案。實(shí)測下來索引從顯存模式切到映射模式單查詢的 P99 延遲大約從 3ms 漲到 8ms 左右但能支撐更大規(guī)模的數(shù)據(jù)。3. 從零搭建 GPU 加速向量檢索管線的完整實(shí)操3.1 環(huán)境清單與安裝這一步看著簡單坑其實(shí)不少。先說硬件我們用了兩臺(tái) GPU 節(jié)點(diǎn)每臺(tái) 8 張 A100 80G但實(shí)際構(gòu)建單索引只需要一張卡多卡主要用于并行處理不同數(shù)據(jù)分片。軟件層面需要 NVIDIA 驅(qū)動(dòng)支持 CUDA 12.0 以上然后安裝 cuVS 的 Python 包。安裝命令很簡單# 建議用獨(dú)立的 conda 環(huán)境或 docker 鏡像避免污染線上環(huán)境 conda create -n cuvs-env python3.10 -y conda activate cuvs-env pip install cuvs-cu12 nvidia-smi # 確認(rèn)驅(qū)動(dòng)和 GPU 狀態(tài)正常這里特別提醒一句cuVS 的版本和 CUDA 版本必須匹配裝完第一件事是跑一遍官方自帶的 smoke test。另外如果你之前裝過 pytorch 的 GPU 版本不要理所當(dāng)然地認(rèn)為 cuVS 一定能直接用兩個(gè)包的 CUDA runtime 依賴可能不一樣。我們一開始就在一臺(tái)裝過老舊 CUDA 10 環(huán)境的機(jī)器上翻車了最后換到干凈鏡像一次性通過。3.2 用 cuVS 構(gòu)建 IVF-PQ 索引的核心代碼cuVS 的 Python API 非常簡明核心邏輯就三段加載向量集、構(gòu)建索引、保存索引文件。我貼一段我們實(shí)際在用的簡化代碼import numpy as np import cuvs from cuvs.neighbors import ivf_pq # 加載向量假設(shè)是 (N, 384) 的 float32 數(shù)組 vectors np.fromfile(all_vectors.bin, dtypenp.float32).reshape(-1, 384) # 索引配置 nlist 16384 M 24 nbits 8 # 構(gòu)建 IVF-PQ 索引 index ivf_pq.build( vectors, metricinner_product, n_listnlist, mM, n_bitsnbits, kmeans_n_iters20, # k-means 迭代次數(shù)20 次足夠收斂 train_size2000000, # 訓(xùn)練樣本數(shù) ) # 保存索引 ivf_pq.save(index, cuvs_138m.index)這段代碼跑完后磁盤上會(huì)出現(xiàn)一個(gè)索引文件文件名我們自己帶上了版本號(hào)。構(gòu)建過程中你會(huì)發(fā)現(xiàn) GPU 利用率很高而 CPU 幾乎閑置這說明計(jì)算確實(shí)被卸載到顯卡上了。如果你的數(shù)據(jù)量太大一次 build 扛不住可以改用先 train 再分批 add 的寫法cuVS 支持增量添加編碼后的向量訓(xùn)練完成后每個(gè) chunk 走add接口進(jìn)去最后統(tǒng)一 save。需要說明的是不同 cuVS 版本對函數(shù)簽名略有調(diào)整實(shí)際以你安裝版本的官方文檔為準(zhǔn)但整體流程就是這三步。搜索端的代碼同樣很直接from cuvs.neighbors import ivf_pq index ivf_pq.load(cuvs_138m.index) distances, indices ivf_pq.search(index, queries, k200, n_probes64)這里的queries是待檢索的 query 向量 batchn_probes是搜索時(shí)檢查多少個(gè)倒排桶值越大召回越好延遲也越高。返回的indices就是我們需要的 doc_id 候選集下一步拿著它去 ES 撈文檔。3.3 向量數(shù)據(jù)導(dǎo)入 ES雖然主檢索走 cuVS但 ES 里還是要存一份原始向量主要用于兩件事一是精確重排時(shí)計(jì)算真實(shí)距離二是排查線上問題時(shí)能直接撈出來看。因?yàn)檫@批數(shù)據(jù)不會(huì)頻繁更新我們直接把索引的 refresh 關(guān)掉用 bulk 批量寫入導(dǎo)入過程相對可控。Mapping 配置如下PUT /vector_items { mappings: { properties: { doc_id: { type: keyword }, title: { type: text }, embedding: { type: dense_vector, dims: 384, index: true, similarity: cosine, index_options: { type: hnsw, m: 32, ef_construction: 200 } } } } }這里有個(gè)容易糾結(jié)的點(diǎn)ES 里既然已經(jīng)有 HNSW 索引了是不是可以直接用它做檢索如果你數(shù)據(jù)量在百萬級(jí)是的直接查就行但到了億級(jí)ES HNSW 的建索引時(shí)間和內(nèi)存開銷都會(huì)成為瓶頸所以我們在 ES 里保留 dense_vector 字段但不會(huì)把主查詢壓給它。bulk 導(dǎo)入時(shí)把refresh_interval設(shè)為 -1每批 5000 條并發(fā) 8 個(gè)線程1.38 億文檔大約 30 到 40 分鐘寫完。這個(gè)時(shí)間是可以接受的而且和 GPU 建索引并行跑整體調(diào)度窗口并不吃虧。3.4 檢索鏈路GPU 粗排 ES 精確重排查詢鏈路的設(shè)計(jì)決定用戶體驗(yàn)。我們線上的流程是query 向量先進(jìn) cuVS一次取回 Top 200 的候選 doc_id然后帶著這批 doc_id 去 ES 做一次 multi-get把文檔取回來最后用 ES 字段里的原始向量做一次精確的余弦相似度計(jì)算重排出 Top 50 結(jié)果。為什么要做精確重排因?yàn)?IVF-PQ 是近似搜索量化誤差會(huì)導(dǎo)致局部順序不夠準(zhǔn)但它的召回候選通常已經(jīng)包含了真正相關(guān)的文檔精確重排一小批候選的成本很低收益卻很明顯。精確重排可以直接在應(yīng)用層用 numpy 做也可以把候選列表和目標(biāo)向量傳給 ES 的 script_score 查詢讓 ES 順便把分算了。數(shù)據(jù)量在 200 條候選這個(gè)級(jí)別兩種方案延遲差異不大應(yīng)用層做的好處是邏輯更可控。端到端來看如果用戶在 ES 上還有大量 filter 條件比如按類目限制那么更優(yōu)的做法是先把 filter 后的 doc_id 集合傳給 cuVS 搜索接口做限制或者反過來先用 cuVS 粗排再在 ES 里 filter。這個(gè)順序要根據(jù)你的過濾率來測我們的場景過濾率不高所以先 cuVS 再 ES filter延遲表現(xiàn)最好。4. 性能實(shí)測與參數(shù)調(diào)優(yōu)1.38 億向量到底多快4.1 索引構(gòu)建實(shí)測數(shù)據(jù)直接上我們在一張 A100 80G 上跑出來的數(shù)據(jù)。全量 1.38 億條 384 維向量IVF-PQ 參數(shù) nlist16384、M24、nbits8完整構(gòu)建過程分為訓(xùn)練、編碼、寫索引三段總耗時(shí)約 9 分 40 秒。其中 k-means 訓(xùn)練用了 2 分 15 秒分批編碼和寫入用了 7 分出頭。注意這里包含從原始二進(jìn)制文件讀取數(shù)據(jù)的時(shí)間如果數(shù)據(jù)已經(jīng)在顯存里還能再快個(gè)幾十秒。對比之前的 CPU 方案用 Lucene HNSW 在 32 核高主頻機(jī)器上建同樣一批數(shù)據(jù)的索引實(shí)測耗時(shí) 4 小時(shí) 35 分鐘。這已經(jīng)不是“快多少倍”的問題了而是從“每天只能半夜跑一次”變成“隨時(shí)可以全量重建”。對我們這種需要頻繁刷新 embedding 模型的場景來說這個(gè)差異直接決定了能不能做到日更甚至小時(shí)級(jí)更新。如果你用的是 CAGRA 且數(shù)據(jù)量在千萬級(jí)構(gòu)建時(shí)間還能壓到 3 分鐘以內(nèi)但顯存需求會(huì)明顯變高。4.2 檢索延遲與召回率檢索端的表現(xiàn)我用兩個(gè)指標(biāo)說明延遲和召回率。延遲方面GPU 側(cè)的 IVF-PQ 搜索在 n_probes32 時(shí)單查詢平均 1.2msP99 在 3ms 左右加上 ES 的 mget 和精確重排完整端到端 P99 大約在 20 到 25ms。這個(gè)延遲水平和一臺(tái)中等配置的 ES 集群直接查億級(jí) HNSW 差不多但關(guān)鍵在于索引構(gòu)建的瓶頸解決了。召回率方面我們在一個(gè) 10 萬條人工標(biāo)注的驗(yàn)證集上測了 Recall10n_probes64 的時(shí)候可以達(dá)到 94.6%效果非常接近全量精確檢索。不同 n_probes 的權(quán)衡比較明顯n_probesRecall10GPU 單查詢 P99 延遲1688.3%約 1.8ms3292.1%約 3.2ms6494.6%約 5.6ms12896.2%約 9.4ms所以不要迷信單一參數(shù)。如果你的業(yè)務(wù)對召回更敏感比如搜索場景建議 n_probes 開到 64 甚至 128如果是推薦召回這樣的上游環(huán)節(jié)下游還有粗排模型兜底n_probes32 就夠。實(shí)際做法是拿線上真實(shí) query 樣本跑一遍 AB別在驗(yàn)證集上自嗨。4.3 索引不常駐顯存怎么處理前面提到過內(nèi)存映射索引的降級(jí)方案這里再補(bǔ)充一組實(shí)測數(shù)據(jù)。把索引從顯存模式切換成映射模式后同樣的 n_probes64單查詢 P99 從 5.6ms 漲到 11.3msQPS 大概掉了三成。這個(gè)代價(jià)在某些場景能接受但如果你有比較充足的 GPU 資源還是盡量讓索引留在顯存里。映射模式真正適合的是那些查詢量不大、但索引規(guī)模特別大的冷啟動(dòng)場景。顯存不足時(shí)的另一個(gè)實(shí)用思路是縮小 nlist。比如 nlist 從 16384 降到 4096索引文件變化不大但搜索時(shí)要掃描每個(gè)桶的候選數(shù)量會(huì)變多反而可能讓延遲升高。不要為了省顯存盲目調(diào)小 nlist它和 n_probes 是配合使用的。顯存如果真的不夠優(yōu)先考慮換小一點(diǎn)的 M 值比如 M16索引體積能再降三分之一左右。5. 常見問題排查與避坑記錄5.1 CUDA 環(huán)境和 cuVS 版本不匹配這個(gè)坑幾乎每個(gè)人都踩過。cuVS 的 pip 包分為cuvs-cu12和cuvs-cu11等不同版本如果驅(qū)動(dòng)支持的 CUDA 版本和包不一致import 階段不報(bào)錯(cuò)但一執(zhí)行 build 就崩報(bào)錯(cuò)信息往往是CUDA driver version is insufficient或者直接段錯(cuò)誤。排查方法很直接先nvidia-smi看驅(qū)動(dòng)版本再用python -c import cuvs; print(cuvs.__version__)確認(rèn)包版本。還遇到過 GPU 架構(gòu)太老導(dǎo)致的no kernel image is available這個(gè)基本沒法通過換包解決只能換卡。我的建議是直接用官方 docker 鏡像比如nvcr.io/nvidia/rapidsai/base系列省掉一半以上的環(huán)境折騰時(shí)間。5.2 召回率掉得很厲害召回率不達(dá)標(biāo)時(shí)先別急著調(diào) n_probes。我最常見的一個(gè)原因是向量沒有歸一化但用了內(nèi)積度量這會(huì)導(dǎo)致不同長度的向量之間有虛假的“高相似度”召回結(jié)果自然亂掉。確認(rèn)數(shù)據(jù)都做了歸一化之后再看 nlist 和訓(xùn)練樣本數(shù)。訓(xùn)練樣本太少會(huì)導(dǎo)致 k-means 聚類中心沒有代表性經(jīng)驗(yàn)是訓(xùn)練樣本量至少是 nlist 的 100 到 200 倍我們?nèi)×?200 萬對 16384 個(gè)中心來說綽綽有余。最后調(diào)整 n_probes 和 M 的組合如果 M 過大導(dǎo)致壓縮誤差明顯召回率會(huì)卡在一個(gè)平臺(tái)期上不去這時(shí)適當(dāng)減小 M召回率會(huì)有一次明顯回升。5.3 ES 導(dǎo)入慢和 mget 瓶頸ES 導(dǎo)入慢多數(shù)是 refresh 和段合并造成的。導(dǎo)入前把refresh_interval設(shè)為 -1導(dǎo)入結(jié)束恢復(fù)默認(rèn)值這個(gè)方法可以讓寫入吞吐量翻一倍以上。另外 bulk 批量大小和并發(fā)線程數(shù)需要調(diào)5000 條一批、8 到 16 個(gè)并發(fā)線程在我們這邊表現(xiàn)最好再往上加并發(fā)會(huì)導(dǎo)致 ES 端 HTTP 連接池打滿性能反而下降。查詢階段如果發(fā)現(xiàn) mget 慢先看一眼是不是候選 doc_id 沒有走主鍵而是走了其它字段把字段類型設(shè)為 keyword 且只建必要的 doc_values能省不少時(shí)間。如果一次候選 200 條 mget 就要 10ms 以上可以考慮把候選數(shù)先降到 100配合精確重排效果差別不大。5.4 數(shù)據(jù)更新和增量索引怎么做全量重建雖然只要 10 分鐘但也不能每次模型升級(jí)都無腦重建。我們目前的流程是新 embedding 模型上線前先離線抽一批驗(yàn)證集評估召回確認(rèn)指標(biāo)不降再觸發(fā)全量重建。重建過程中ES 的老索引繼續(xù)服務(wù)線上新索引構(gòu)建完成后切換查詢?nèi)肟诨貪L操作也很簡單把配置里的索引版本號(hào)指回去就行。如果以后數(shù)據(jù)規(guī)模達(dá)到十億級(jí)別就需要考慮索引分片把數(shù)據(jù)按 ID hash 分成多個(gè)分片每個(gè)分片單獨(dú)建 IVF-PQ 索引查詢時(shí)并行搜索所有分片再合并結(jié)果。這個(gè)方案我們還沒上線但已經(jīng)在測試環(huán)境驗(yàn)證過流程思路是把一臺(tái) GPU 承載的索引按水平拆到多卡上單卡顯存壓力會(huì)小很多。最后再分享一個(gè)小技巧給每次構(gòu)建的 cuVS 索引文件打上 build_id并把索引路徑放到 ES 的自定義 metadata 里。這樣線上出問題的時(shí)候你能精確知道當(dāng)前查詢跑的是哪一批數(shù)據(jù)構(gòu)建的索引排查“為什么召回結(jié)果和預(yù)期不一致”這種問題會(huì)快很多。當(dāng)你的數(shù)據(jù)更新頻率越來越高版本管理就不再是錦上添花而是必需品。