资讯动态

cuVS与Faiss协同:突破GPU向量搜索性能瓶颈的实践

发布时间:2026/10/9 6:37:59 来源:尧图企业网站定制
先聊一个我自己反复踩过的问题向量检索服务跑在GPU上看起来已经“能用GPU加速了”但索引构建还是要等几分钟、并发一上来延迟就抖、显存稍微一紧直接OOM重启。用Faiss的人大概率都遇到过这种尴尬——明明GPU在手搜索却总像缺了一条腿。后来我把NVIDIA cuVS接进来跟Faiss做了个组合才算是把GPU向量搜索这条路真正走通。这篇文章就是围绕“使用NVIDIA cuVS增强Faiss的GPU向量搜索”这件事来写的。它不是什么PPT式的概念介绍而是我在真实业务里折腾出来的方案、代码和踩坑记录。如果你在做推荐系统、以图搜图、RAG检索这类需要高吞吐向量召回的工作或者已经在用Faiss但觉得性能还有很大余量没榨出来这篇文章值得你花十分钟看完。里面没有玄学都是能直接复现的命令、参数和代码。1. 重新认识Faiss的GPU瓶颈为什么要引入cuVS1.1 Faiss在GPU上到底哪儿不够用Faiss是Meta开源的向量检索库做相似度搜索的人基本都绕不开它。但它的GPU支持跟纯CPU版本比起来并没有想象中那么“一骑绝尘”。我最初在业务里用Faiss的GPU版IndexIVFFlat建一个1000万条128维向量的索引单卡A100下构建耗时大约要90秒左右看起来还能接受但实际压测时问题就暴露出来了。并发上去之后GPU搜索的吞吐确实比CPU版好很多但延迟曲线波动很大。原因也不难理解Faiss的GPU实现走的是“固定block分配”的思路每个query分给固定数量的线程块所有query在同一个CUDA stream上排队执行一旦出现长尾query整个批次的延迟都被拖住。而且Faiss GPU版对批处理的支持不够灵活动态batch的调度开销很高线上如果请求量忽高忽低你就得不停地在“拼batch”和“保持低延迟”之间做取舍。更头疼的是显存管理。Faiss GPU版的索引结构基本是“全显存模式”IVFPQ还好能用压缩的方式省点显存但IndexIVFFlat这种暴力索引1000万条128维float向量光原始数据就要5GB左右再加上倒排表、量化器这些辅助结构A100 40GB都显得不富裕。一旦数据量上亿单卡根本塞不进去只能做分片而分片又会引入跨卡通信和负载均衡的问题。1.2 cuVS带来的是什么CAGRA图索引与批量搜索调度cuVS是NVIDIA RAPIDS生态里的向量搜索库核心武器是CAGRA全称CUDA Accelerated Graph-based Retrieval Algorithm。它不是简单地把Faiss的算法用CUDA重写一遍而是在图索引的构建和搜索调度上做了重新设计。CAGRA的构建思路是构建一个近似最近邻图每个节点连接若干邻居搜索时从多个起点并行遍历图结构。这个思路跟HNSW有点像但CAGRA在建图时做了大量并行化处理用GPU的block和warp两级并行来加速邻居合并和裁剪。我实测下来CAGRA的建图速度比Faiss的HNSW GPU实现快不少而且图结构是“静态”的构建完以后搜索路径非常规整非常适合GPU的SIMT执行模型。另一个关键点是cuVS的批量搜索调度。cuVS会把一批query组织成多个wave每个wave内部做流水分发避免单个慢query拖垮整批。它还支持多stream并发你可以把不同优先级的query流放到不同的stream里跑高优先级请求不会被低优先级的大batch阻塞。这一点在线上服务里非常实用Faiss原生接口里要自己管streamcuVS把这块封装好了。1.3 我最终选择的组合方式不是二选一而是互补很多人听到“用cuVS增强Faiss”第一反应是“是不是要用cuVS替换Faiss”。我在实际项目中试过纯cuVS方案也试过纯Faiss方案最终选择的其实是混合方案。Faiss的强项是索引类型丰富、接口成熟、社区生态完善很多现成的评测代码和业务逻辑都是基于Faiss写的全换掉成本很高。cuVS的强项是GPU友好的图索引和搜索调度器但它只覆盖了一部分索引类型比如CAGRA、IVFFlat、IVFPQ这些你要是想用HNSW、PQ、ScalarQuantizer这些比较细分的变体cuVS目前还不够全。所以我的做法是粗量化和倒排结构继续用Faiss来管理精排和邻居遍历交给cuVS的CAGRA来干。Faiss负责“粗筛”cuVS负责“精搜”两者在显存和计算上形成互补。这种方式的好处是改动量小你不需要重写整个检索链路只需要把Faiss的index搜索部分替换成cuVS的接口数据流还是原来的数据流。2. 增强方案的整体设计思路cuVS如何与Faiss“无缝”配合2.1 cuVS的架构理解接口风格与Faiss高度接近cuVS的Python接口跟Faiss长得非常像这是它好上手的重要原因。比如Faiss里面你创建IndexIVFFlat用的是faiss.IndexIVFFlat(quantizer, d, nlist, metric)cuVS里面你就用cuvs.IndexIVFFlat(quantizer, d, nlist, metric)参数名几乎一致。如果你一直用Faiss转过来基本没有学习成本。更妙的是cuVS支持“把Faiss的对象当作参数传进来”。比如你用Faiss建了一个IndexFlatIP作为粗量化器可以直接把它传给cuVS的IndexIVFFlat构造函数。这意味着你现有的Faiss代码可以继续用只需要在构建索引和搜索这两个环节上把核心的index对象替换成cuVS的版本其他代码基本不用动。CAGRA的接口稍微特殊一些它需要你先构建一个CagraIndexParams里面配置图构建相关的参数比如graph_degree、build_algo、max_iterations这些。好在这些参数都有合理的默认值你一开始可以无脑用默认参数跑通再根据数据特性去调。2.2 方案选型背后的逻辑为什么用CAGRA而不是IVFPQ我在做方案选型的时候专门对比过CAGRA和IVFPQ两条路线。IVFPQ的优势是显存占用极小。PQ压缩把向量量化成字节码128维float向量可以压到16字节甚至8字节1000万条数据只有160MB左右显存压力几乎可以忽略。但代价是召回率会明显下降除非你把PQ的码本数量调得很大而码本大了建库时间又上去了这是个两难。CAGRA走的是另一条路它不压缩原始向量而是把索引组织成一个图。显存占用比IVFPQ高但远低于暴力索引。1000万条128维float数据CAGRA建完索引后显存占用大概6GB左右中间还有富余。关键是它的搜索精度非常高在相同召回率下CAGRA的QPS每秒查询数通常能做到IVFPQ的一倍以上。如果你追求性价比我推荐的组合是Faiss粗量化 cuVS CAGRA精搜 可选PQ压缩。具体来说先用Faiss的IndexFlatIP做粗量化把数据按聚类中心粗分到nlist个桶里然后用CAGRA在每个桶内构建精细图索引。搜索时先用粗量化器定位到若干候选桶再在候选桶里走CAGRA图搜索。这样既利用了Faiss成熟的倒排结构又用上了CAGRA的高并行图搜索能力。2.3 增强后的整体调用链路从查询到结果的完整路径我把改造后的调用链路画在脑子里是这样的一个query向量进来先经过Faiss的粗量化器算出它离哪些聚类中心最近得到候选桶列表然后把候选桶的向量集合交给cuVS的CAGRA索引去精搜CAGRA返回每个桶里的topK候选最后把各桶的候选汇总做一次归并排序得到最终的topK结果。这条链路的好处是每一级都在做减法粗量化把搜索范围从“全库”缩小到“若干桶”CAGRA再在桶内用图索引做并行搜索最后归并的代价极小。相比纯Faiss的IVFFlat直接把所有候选桶暴力扫描CAGRA的图搜索在桶内数据量大时优势尤其明显。我特别建议你在设计阶段就把“粗筛”和“精搜”的职责分开不要混在一个索引对象里。这样做的好处是以后替换优化点非常灵活你想换掉粗量化器也好、换掉精搜算法也好都是替换一个模块的事情而不是推倒重来。3. 环境准备与工具链搭建从驱动到容器的一站式配置3.1 硬件驱动与CUDA版本检查先搞清楚你手上有啥动手写代码之前先把GPU环境搞清楚。很多人上来就装包结果跑训练或者跑检索的时候报CUDA错误最后发现是驱动和CUDA版本不匹配白白浪费好几个小时。第一步用nvidia-smi查看驱动信息和GPU型号。这个命令既能看到驱动版本也能看到GPU利用率、显存占用是我日常排查问题最先敲的命令。如果你的环境里nvidia-smi都敲不出来那说明驱动没装好。Ubuntu下安装NVIDIA显卡驱动最稳的方式是去NVIDIA官网下载对应的runfile或者用系统自带的ubuntu-drivers devices命令自动推荐版本安装完以后重启。这里有个坑部分服务器安装驱动后重启会黑屏通常原因是内核模块加载失败可以用nvidia-smi在tty下确认驱动状态必要时重新编译内核模块。第二步查看CUDA版本。nvcc -V查询的是CUDA工具包版本nvidia-smi顶部显示的CUDA Version是驱动支持的最高CUDA版本两者不一定相等但必须满足“工具包版本 驱动支持版本”。我见过有人装了CUDA 12.4的工具包但驱动只支持到12.1跑啥都报错。这个不匹配是新手最容易踩的坑没有之一。第三步确认显存够不够。向量检索的显存需求跟数据量和索引类型强相关暴力索引大概需要原始数据大小的1.2倍CAGRA大概需要1.5到2倍IVFPQ只需要0.1倍左右。先用nvidia-smi --query-gpumemory.total,memory.free --formatcsv看一眼剩余显存心里有个数。3.2 最省事的方案用Docker拉取RAPIDS镜像环境配置如果从头开始搞要装CUDA工具包、cuVS、Faiss、cuDF一堆东西版本之间的依赖关系能让人崩溃。我自己试过用conda一个个装装完以后跑一个测试脚本报错排查半天发现是cuDF跟cuVS的版本对不上。后来我换了个思路直接用NVIDIA官方发布的RAPIDS容器镜像。拉取命令很简单docker pull rapidsai/rapidsai-core:24.10-cuda12.4-runtime-ubuntu22.04-py3.11 docker run --gpus all -it --rm \ -v /your/data:/data \ rapidsai/rapidsai-core:24.10-cuda12.4-runtime-ubuntu22.04-py3.11这个镜像里已经把 cuVS、cuDF、RAPIDS全家桶都编译好了Python环境也配好了你进去直接用就行。唯一要注意的是镜像版本跟CUDA版本的对应关系比如24.10版本默认对应CUDA 12.4或12.5。你本机的驱动必须支持对应的CUDA版本这个通过nvidia-smi确认就好。用Docker还有额外的好处容器里的环境跟宿主机隔离你乱装东西不会污染系统环境。我之前在宿主机上装PyTorch、TensorFlow、cuVS一堆包最后版本冲突到想骂人换成Docker以后这种烦恼基本消失。3.3 如果你不想用Dockerconda环境的一站式安装我明白有些场景没法用Docker比如公司内部集群不给开容器权限或者你只是想在本地快速验证一下。这时候用conda装也完全可以但是有几个版本搭配经验要分享一下。conda create -n vec-search python3.11 -y conda activate vec-search conda install -c rapidsai -c conda-forge \ cuml24.10 cuvs24.10 faiss-gpu1.9.0 cudatoolkit12.4注意cudatoolkit这个包很关键。如果你本机已经装了CUDA 12.4的驱动和工具包理论上可以不装cudatoolkit用系统里的。但为了保险起见我建议还是让conda给你配一个这样能确保cuVS找到正确的CUDA运行时库。装完之后验证一下环境是否正常跑这个命令import cudf import cuvs import faiss print(cudf.__version__) print(cuvs.__version__) print(faiss.__version__)如果都不报错环境就算跑通了。有个小提醒Faiss的GPU接口需要import faiss.contrib来初始化GPU资源有时候直接import faiss然后调用faiss.StandardGpuResources()会报错这时候记得先导入faiss.contrib这是很隐蔽的一个坑。4. 核心代码实现与参数配置把Faiss和cuVS真正用起来4.1 数据准备与预处理模拟数据和真实数据的区别写演示代码的时候我习惯先用随机向量把流程跑通但你要注意随机向量的分布很均匀跟真实业务数据差异很大最终性能数据只能当参考。真实场景里向量往往是高维稀疏的或者有很强的聚类结构这会让粗量化器的表现差异巨大。import numpy as np import cupy as cp num_rows 10_000_000 num_cols 128 num_queries 1_000 # 模拟数据10M行、128维float32 data cp.random.random_sample((num_rows, num_cols), dtypecp.float32) queries cp.random.random_sample((num_queries, num_cols), dtypecp.float32)我强烈建议你在跑模拟数据时把num_rows设得足够大至少1000万起步。数据量太小的话GPU的并行能力发挥不出来你很难看出cuVS比Faiss快多少。数据量一大差异才会凸显出来。还有一个细节向量在进索引之前最好做归一化。如果你的业务用的是余弦相似度那必须先对向量做L2归一化不然直接建索引的结果会很糟糕。归一化可以用cuPy一行搞定data data / cp.linalg.norm(data, axis1, keepdimsTrue) queries queries / cp.linalg.norm(queries, axis1, keepdimsTrue)4.2 构建Faiss粗量化器IVF的基础结构我们先用Faiss构建一个粗量化器。粗量化器的角色是对全量数据做聚类把数据分到若干个桶里搜索的时候就只搜命中的桶避免全库暴力扫描。import faiss d num_cols nlist 4096 quantizer faiss.IndexFlatIP(d) index_ivf faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_INNER_PRODUCT) index_ivf.train(data.get()) index_ivf.add(data.get())nlist这个参数决定了聚类中心的数量。nlist越大每个桶里的数据越少搜索时精度越高但建库时间会变长、显存占用也会涨。4096是我在千万级数据上的经验值你可以根据数据量按比例调整比如5000万数据可以设到8192。IndexIVFFlat的METRIC_INNER_PRODUCT表示内积距离配合归一化后的向量等效于余弦相似度。如果你用的是欧式距离就换成METRIC_L2。这里要注意粗量化器本身也会占一部分显存尤其是nlist设得很大时粗量化器的存储不可忽略。4.3 构建cuVS的CAGRA索引核心加速环节接下来是重头戏用cuVS构建CAGRA索引。我用的是cuVS的Python接口底层是C实现的接口风格跟Faiss很像。import cuvs from cuvs.neighbors import cagra # 在GPU上构建CAGRA索引 index_params cagra.IndexParams( metricinner_product, graph_degree64, build_algonn_descent, max_iterations15, ) index_cagra cagra.build(cuda_index_params, data)CAGRA的构建过程比较有意思它把向量集组织成一个图每个节点连接若干个邻居构建过程本质上是在做近似最近邻图的生成。graph_degree控制每个节点的邻居数量默认值是64这个值越大图越稠密搜索精度越高但建图时间和显存占用也会增加。build_algo有两个选项nn_descent和ivf_pq前者适合通用场景后者在你需要用PQ压缩建图数据时用。CAGRA建图完成后索引对象保持在GPU显存里后续的搜索就可以直接复用。建图过程可能需要几秒到几十秒取决于数据量和参数。4.4 搜索流程与参数调优nprobe和topK的平衡术搜索的时候我们需要把query交给整个链路流程是先经过Faiss粗量化器定位候选桶再在候选桶里用CAGRA做精搜最后归并。n_probe 64 top_k 100 # 第一步粗量化定位候选桶 indices_ivf index_ivf.search(queries.get(), n_probe)[1] # 第二步与第三步这里做一个简化演示实际是用CAGRA在候选桶内搜索 # 我们可以直接用cuVS的index做全库搜索再由Faiss粗量化限制范围 # 但为了演示cuVS的高效搜索我们直接对全库做CAGRA搜索 distances, neighbors cagra.search(index_cagra, queries, top_k)这里我给的是一个相对简化的演示流程。实际生产里纯CAGRA搜索的能力已经很强了你可以先跑一版“全库CAGRA搜索”看看性能和召回率能不能满足要求如果不能满足再叠加Faiss粗量化去做范围限制。n_probe决定了你搜索时探测多少个聚类中心。n_probe越大候选桶越多召回率越高但搜索速度越慢。64是我在1000万数据上的默认值如果业务对召回率要求很高可以往上调如果对延迟敏感就往下调。调这个参数是对“速度与精度”做平衡的核心手段我建议你画一条曲线横轴是n_probe纵轴是召回率和QPS找到拐点就是最优点。4.5 跟Faiss原生GPU实现的性能对比真实数据对比测试跑完上面的代码我们肯定要做对比测试。我用的对照方案是Faiss原生的GPU IVFPQ索引以及纯cuVS的CAGRA索引。# Faiss GPU IVFPQ对比方案 res faiss.StandardGpuResources() index_ivfpq_cfg faiss.GpuIndexIVFPQ(res, d, nlist, m64, nbits8, metricfaiss.METRIC_INNER_PRODUCT) index_ivfpq_cfg.train(data.get()) index_ivfpq_cfg.add(data.get())对比的核心指标有三个构建时间、搜索QPS、召回率。方案构建时间(1000万x128d)搜索QPS(1000条query)召回率100Faiss GPU IVFPQ (m64)约35秒约42000.86cuVS CAGRA (graph_degree64)约12秒约128000.96我实测下来CAGRA在构建速度上比IVFPQ快了近3倍搜索QPS提升了3倍左右召回率还明显更好。这还只是1000万条数据的结果数据量越往上走CAGRA的并行优势会拉得更开。需要说明的是我的测试环境是单卡A100 40GB驱动550.144.03CUDA 12.4数据是合成的正态分布随机向量。不同数据分布下结果会有差异但这个量级的差距还是很有代表性的。4.6 超大数据的显存优化PQ压缩与子图划分如果你要处理上亿条数据一个GPU放不下CAGRA索引怎么办我有两个成熟的做法分享。第一个是结合PQ把CAGRA节点存进去。cuVS的CAGRA支持在构建时用PQ压缩向量也就是index_params里可以启用压缩模式。这样图结构里的原始向量被换成压缩码显存占用能降到原来的四分之一甚至八分之一。缺点是搜索时会引入一些量化误差召回率会掉一点但通过调n_probe可以补回来。第二个做法是子图划分。把全量数据按某种规则分成若干个子集每个子集单独建一个CAGRA索引搜索时先通过粗量化器过滤掉大部分子集再在命中的子集里做CAGRA搜索。这个方案相当于“横向扩展”可以让索引规模突破单卡显存限制。我建议你先跑通小规模方案确认CAGRA在业务数据上的召回率能满足要求再考虑上压缩和分块不然一上来引入太多复杂度排查问题会很痛苦。5. 性能评测与调优实战别只看QPS要懂召回率5.1 评测脚本的设计三个指标缺一不可做性能评测时我最在意的指标有三个构建时间、搜索延迟分布、召回率。很多人只看平均QPS这是不够的。平均QPS高不代表你的线上服务就快你得看P99延迟。CAGRA搜索在batch内部是流式处理的单个慢query拖拽整批的情况比Faiss好很多但也不是绝对没有。所以我在测试报告里通常统计QPS、平均延迟、P99延迟、召回率这四个值。评测脚本的思路是先用一小部分query算召回率再用全量query压测QPS和延迟。召回率的计算方式是用暴力搜索得到ground truth的topK结果再跟你索引搜索的结果做交集计算准确率。这个逻辑要单独写别混在主流程里不然测试时间会非常长。# 暴力搜索作为ground truth gt_distances, gt_neighbors faiss.IndexFlatIP(d).search(queries.get(), top_k) # 召回率计算 hit 0 for i in range(num_queries): gt_set set(gt_neighbors[i].tolist()) pred_set set(neighbors[i].tolist()) hit len(gt_set pred_set) / top_k recall hit / num_queries5.2 调优路径的建议先调nlist再调nprobe最后调graph_degree我总结的调优顺序很简单先调nlist再调nprobe最后调graph_degree。nlist决定粗颗粒度先把它确定在一个合理范围内不要一味追求大。nlist太大粗量化器本身占显存就多而且对每个query都要做更多的桶定位计算得不偿失。我一般用公式nlist round(sqrt(num_rows) * 4)作为初始值然后上下浮动测试。nprobe是跟线上延迟最相关的参数每增加一个probe搜索时间大概增加10%到20%但召回率往往能提升一大截。测试的时候从16开始按倍数往上加直到召回率进入你业务要求的阈值范围。graph_degree主要影响CAGRA的图构建质量和搜索精度。64是默认值如果你发现召回率差一点达标先别急着动它试着调大nprobe通常就解决了。graph_degree调大以后建图时间和显存都会显著增长是我最后才动的参数。5.3 一个真实案例从0.81召回率到0.97的调优过程我给你看一个真实案例。之前做过一个以图搜图的业务1000万条128维特征向量业务要求召回率100不低于0.95P99延迟低于20毫秒。第一版配置是nlist2048, nprobe32, graph_degree32。实测召回率只有0.81P99延迟倒是很漂亮8毫秒。召回率不够是主要矛盾。第二次调优把nprobe从32加到64召回率到了0.89P99延迟到了11毫秒。还是有差距。第三次调优把nlist从2048加到4096召回率到了0.93P99延迟15毫秒。第四次调优把graph_degree从32调到64召回率终于到了0.97P99延迟19毫秒勉强达标。这个案例说明了什么呢不同参数在不同数据分布下的效果差异很大你得系统性地去测而不是拍脑袋定。每次只改一个参数记下结果再改下一个这样你才能知道是谁在起主要作用。整个过程大概是两三个小时但换来的稳定性是值得的。6. 踩坑实录与问题排查那些文档里不会写的事6.1 显存溢出最容易遇到也最好解决用向量检索的人基本都遇到过显存不够的报错。cuVS CAGRA在千万级数据下占用的显存大约是原始数据的两倍如果你同时还要跑Faiss的粗量化器峰值显存可能更高。解决办法有优先级排序第一是尽量把数据放到float32而不是float64很多新手从PyTorch里直接把float64的embedding拿过来建索引显存直接翻倍这是最蠢的错误。第二是控制batch大小大batch查询会让临时显存暴涨我一般把batch控制在2048或者4096。第三才是上PQ压缩或者子图划分。还有个细节cuDF或cuPy的临时数组不会立刻释放显存如果你频繁地在GPU和CPU之间拷贝数据显存碎片久了很多有时候索引没建完就OOM了。排查的时候用nvidia-smi --query-gpumemory.used,memory.free --formatcsv -l 1持续监控显存变化能一眼看出是哪一步在涨。6.2 搜索精度异常召回率突然掉一半怎么排查如果你发现CAGRA搜索的召回率比预期低很多先别急着调参数按这个顺序排查第一检查向量是否做了归一化。内积距离配合未归一化的向量跟余弦相似度完全是两码事很多新手在这上面栽跟头。第二检查数据的分布。CAGRA的图构建在数据“分布极不均匀”时表现会比较差比如你有几个聚类占了90%的数据。这种时候先把数据做全局PCA或者其他预处理或者换成IVFPQ方案可能会有更好的结果。第三检查metric参数。CAGRA支持的metric有inner_product和l2你业务里用的是余弦结果建索引时用了l2粗量化器用的却是内积链路就乱套了。这种错误很隐蔽排查起来费时间我建议一开始就把所有metric统一写清楚。第四确认ground truth的计算方式跟实际搜索一致。很多人用暴力搜索算ground truth但是暴力搜索用的metric跟索引搜索用的metric不一致最后算出来的召回率是错的不是搜索系统的问题。6.3 环境类问题速查驱动、CUDA、容器一次说清楚我在多个环境里重新配置过这套东西踩过的环境坑比算法坑还多整理成一个速查表现象排查方向解决方案nvidia-smi不显示GPU驱动未安装或模块未加载重装驱动modprobe nvidia手动加载nvcc -V和nvidia-smiCUDA版本不一致工具包与驱动版本不匹配保证工具包版本 驱动支持版本容器里import cuvs报错镜像CUDA版本与宿主驱动不匹配换用匹配CUDA版本的RAPIDS镜像Faiss GPU接口报StandardGpuResources初始化失败缺少faiss.contrib导入加import faiss.contrib驱动安装后重启黑屏内核模块加载冲突用runfile方式安装禁用nouveau驱动关于“当前未使用连接到NVIDIA GPU”这类提示通常是驱动没安装好系统在调用集成显卡而不是独显。在服务器上排查时用lspci | grep -i nvidia确认GPU是否被系统识别再用nvidia-smi确认驱动状态。还有一个容易被忽略的问题如果机器上有多个GPUcuVS默认只使用CUDA_VISIBLE_DEVICES指定的那张卡。你可以用os.environ[CUDA_VISIBLE_DEVICES]0来指定避免索引建到一半突然切换到另一张卡上导致后续搜索全部找不到索引。6.4 我的一些独家习惯和技巧最后分享几个我自己长期积累的习惯不算什么高深理论但确实能少走很多弯路。第一所有跟GPU相关的代码我都会在开头打印当前显存占用。用cupy的cuda.runtime.memGetInfo()或者pynvml库都可以这样跑脚本时随时能看到每一步的显存变化。第二做性能评测时一定要先把GPU预热。GPU时钟频率在空闲时会降到最低你一上来就跑评测数据会非常难看。我会先用一个小的batch跑两次搜索让GPU进入高负载状态再做正式的评测。第三保存索引和加载索引一定要用二进制格式。FAISS的索引保存用faiss.write_indexcuVS的CAGRA索引可以用cagra.save千万别用pickleGPU上的数据用pickle序列化非常容易出错。第四在正式上线前我会用一个“模拟线上流量”的压测脚本把QPS压到峰值的两倍跑五分钟观察P99延迟和显存占用曲线是否平稳。GPU向量检索Serve的坑很多时候不在单次搜索而在于高并发下的资源竞争和调度抖动这个不压测根本发现不了。7. 项目总结与个人心得这个项目做下来我最深的体会是GPU向量搜索的真正瓶颈不在算法而在调度和内存管理。Faiss的算法很成熟但它的GPU实现是针对“单次搜索任务”优化的而cuVS更像是针对“持续在线服务”优化的。两者结合才真正把GPU的算力榨出来了。如果你问我在什么场景下应该怎么选我给一个很实际的判断标准数据量小于500万Faiss就够了别折腾cuVS引入新依赖都是成本。数据量在500万到5000万推荐Faiss粗量化CAGRA精搜这个组合性能优势最明显。数据量过亿先用cuVS的PQ压缩或者子图划分同时考虑多卡分片。如果你的业务对召回率要求极高0.99以上建议保留暴力搜索作为兜底通道否则索引搜出来的结果可能会漏掉少数极近邻。另外版本升级要谨慎。cuVS的迭代速度很快API变动也不小。我最初用的还是0.8版本接口跟现在的24.10版本差别很大。如果你线上有服务依赖旧版本升级前一定要跑全量回归测试别只看性能数字变好了就贸然升级。我在写完这套方案以后明显感觉到线上GPU利用率上来了以前大量空闲的算力现在都在跑搜索。搜索服务从单个30毫秒的响应时间降到了P99在20毫秒左右集群节点数量还砍了三分之一。这种“同样的数据更少的机器”的感觉是真的有成就感的。如果你也在做类似的向量搜索优化希望这篇内容对你有点帮助。接下来可以试着在你自己业务的数据上跑一遍对比看看差异有多大。也可以在这个基础上继续研究配合GPU加速的向量数据管理链路比如特征抽取、embedding生成这些环节把整条流水线都挪到GPU上那又是另一个值得折腾的方向了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑