优化向量数据库,增强 RAG 驱动的生成式人工智能
这篇文章最初发表在英特尔的 Medium 频道上,现经授权在此转贴。
使用 RAG 时优化向量数据库的两种方法
照片由Ilya Pavlov在Unsplash上拍摄
作者:Cathy Zhang 和 Malini Bhandaru 博士 撰稿人:Cathy Zhang 和 Malini Bhandaru 博士杨林、刘长艳
生成式人工智能(GenAI)模型在我们的日常生活中得到了指数级的应用,它正在通过检索增强生成(RAG)得到改进,RAG 是一种通过从外部来源获取事实来提高响应准确性和可靠性的技术。RAG 可帮助常规大型语言模型(LLM)理解上下文,并通过利用以向量形式存储的非结构化数据巨型数据库来减少幻觉--向量是一种数学表现形式,有助于捕捉数据之间的上下文和关系。
RAG 有助于检索更多的上下文信息,从而生成更好的响应,但它们所依赖的向量数据库却越来越大,以提供丰富的内容供人们借鉴。就像万亿参数的 LLMs 即将出现一样,数十亿向量的向量数据库也不远了。作为优化工程师,我们很想知道能否让向量数据库的性能更强、加载数据的速度更快、创建索引的速度更快,以确保在增加新数据时仍能保持检索速度。这样做不仅能减少用户等待时间,还能使基于 RAG 的人工智能解决方案更具可持续性。
在本文中,您将了解更多关于向量数据库及其基准测试框架、解决不同方面问题的数据集以及用于性能分析的工具--您开始优化向量数据库所需的一切。我们还将分享我们在两个流行的向量数据库解决方案上取得的优化成果,为您的性能和可持续性影响优化之旅提供启发。
了解向量数据库
与以结构化方式存储数据的传统关系数据库或非关系数据库不同,向量数据库包含使用嵌入或转换函数构建的单个数据项(称为向量)的数学表示。向量通常表示特征或语义,可长可短。向量数据库通过使用距离度量(距离越近表示结果越相似),如欧几里得、点积或余弦相似度进行相似性搜索来完成向量检索。
为了加速检索过程,向量数据使用索引机制进行组织。这些组织方法的例子包括平面结构、倒置文件(IVF)、 层次化可导航小世界(HNSW)和位置敏感散列(LSH)等等。这些方法中的每一种都有助于在需要时提高检索相似向量的效率和效果。
让我们来看看如何在 GenAI 系统中使用向量数据库。图 1 展示了向向量数据库加载数据以及在 GenAI 应用程序中使用数据的过程。当您输入提示时,它将经历一个与在数据库中生成向量相同的转换过程。转换后的向量提示将用于从向量数据库中检索类似的向量。这些检索到的项目实质上就是会话记忆,为提示提供了上下文历史,类似于 LLMs 的操作符。在自然语言处理、计算机视觉、推荐系统和其他需要语义理解和数据匹配的领域,这一功能尤其具有优势。您的初始提示随后会与检索到的元素 "合并",从而提供上下文,并帮助 LLM 根据所提供的上下文制定响应,而不是完全依赖其原始训练数据。
图 1.RAG 应用架构。
向量的存储和索引用于快速检索。向量数据库主要有两种类型,一种是扩展用于存储向量的传统数据库,另一种是专门构建的向量数据库。提供向量支持的传统数据库包括Redis、pgvector、Elasticsearch 和OpenSearch。专用向量数据库的例子包括专有解决方案Zilliz和Pinecone,以及开源项目Milvus、Weaviate、Qdrant、Faiss 和Chroma。您可以通过LangChain 和OpenAI Cookbook 在 GitHub 上了解有关向量数据库的更多信息。
我们将从 Milvus 和 Redis 两类数据库中各选一个进行详细介绍。
提高性能
在深入了解优化之前,让我们先回顾一下向量数据库的评估方式、一些评估框架以及可用的性能分析工具。
性能指标
让我们来看看可以帮助你衡量向量数据库性能的关键指标。
- 加载延迟衡量的是将数据加载到向量数据库内存和建立索引所需的时间。索引是一种数据结构,用于根据相似度或距离有效地组织和检索向量数据。内存索引的类型包括平面索引、IVF_FLAT、IVF_PQ、HNSW、 可扩展近邻(ScaNN)和DiskANN。
- 召回率是指在搜索算法检索到的前 K 个结果中找到的真实匹配项或相关项所占的比例。召回值越高,表明检索到的相关项目越多。
- 每秒查询次数(QPS)是指向量数据库处理传入查询的速度。QPS 值越高,说明查询处理能力和系统吞吐量越好。
基准框架
图 2.向量数据库基准测试框架。
矢量数据库基准测试需要矢量数据库服务器和客户端。在性能测试中,我们使用了两种流行的开源工具。
- VectorDBBench:VectorDBBench 由 Zilliz 开发并开源,可帮助测试具有不同索引类型的不同向量数据库,并提供方便的 Web 界面。
- vector-db-benchmark:vector-db-benchmark由Qdrant开发并开放源代码,有助于测试几种典型的HNSW索引类型的向量数据库。它通过命令行运行测试,并提供一个Docker Compose__ 文件,以简化服务器组件的启动。
图 3.用于运行基准测试的 vector-db-benchmark 命令示例。
但基准框架只是其中的一部分。我们需要能锻炼向量数据库解决方案本身不同方面的数据,比如它处理大量数据、各种向量大小和检索速度的能力。有了这些,让我们来看看一些可用的公开数据集。
锻炼向量数据库的开放数据集
大型数据集是测试负载延迟和资源分配的理想选择。有些数据集具有高维数据,适合测试计算相似性的速度。
数据集的维度从 25 到 2048 不等。LAION数据集是一个开放的图像 Collections,已被用于训练非常大的视觉和语言深度神经模型,如稳定扩散生成模型。OpenAI 的数据集包含 5M 个向量,每个向量的维度为 1536,由 VectorDBBench 通过在原始数据上运行 OpenAI 创建。鉴于每个向量元素的类型都是 FLOAT,因此仅保存向量就需要大约 29 GB(5M * 1536 * 4)的内存,再加上用于保存索引和其他元数据的类似额外内存,总共需要 58 GB 的内存用于测试。使用 vector-db-benchmark 工具时,要确保有足够的磁盘存储来保存结果。
为了测试负载延迟,我们需要大量的向量 Collections,而deep-image-96-angular可以提供。为了测试索引生成和相似性计算的性能,高维向量能提供更大的压力。为此,我们选择了包含 1536 维向量的 500K 数据集。
性能工具
我们已经介绍了对系统施加压力以确定相关指标的方法,下面我们来看看更低层次的情况:计算单元的繁忙程度、内存消耗、锁等待时间等。这些都提供了数据库行为的线索,对识别问题区域特别有用。
Linuxtop工具提供系统性能信息。不过,Linux 中的perf工具能提供更深入的见解。要了解更多信息,我们还建议阅读Linux perf 示例和英特尔自顶向下微体系结构分析方法。还有一个工具是英特尔® vTune™ Profiler,它不仅可以优化应用程序,还可以优化系统性能和配置,适用于 HPC、云计算、物联网、媒体、存储等各种工作负载。
Milvus 向量数据库优化
让我们举例说明我们是如何尝试提高 Milvus 向量数据库的性能的。
减少数据节点缓冲区写入时的内存移动开销
Milvus 的写路径代理通过MsgStream 将数据写入日志代理。然后,数据节点消耗数据,将其转换并存储为段。分段将合并新插入的数据。合并逻辑会分配一个新的缓冲区,用于保存/移动旧数据和要插入的新数据,然后将新缓冲区作为旧数据返回,用于下一次数据合并。这导致旧数据逐渐变大,进而使数据移动速度变慢。性能曲线显示,这种逻辑的开销很大。
图 4.在向量数据库中合并和移动数据会产生很高的性能开销。
我们改变了合并缓冲区逻辑,直接将要插入的新数据追加到旧数据中,避免了分配新缓冲区和移动大型旧数据。Perf 配置文件证实,这一逻辑没有任何开销。微代码指标metric_CPU 操作频率和metric_CPU 利用率表明,这种改善与系统无需再等待长时间的内存移动是一致的。负载延迟改善了 60% 以上。GitHub 上记录了这一改进。
图 5.减少复制后,我们发现负载延迟的性能提高了 50%。
减少内存分配开销的反转索引构建
Milvus 搜索引擎Knowhere 采用Elkan k-means 算法训练集群数据,用于创建反转文件(IVF)索引。每一轮数据训练都会定义一个迭代次数。迭代次数越大,训练结果越好。不过,这也意味着 Elkan 算法将被更频繁地调用。
Elkan 算法每次执行时都要处理内存的分配和删除。具体来说,它会分配内存来存储一半大小的对称矩阵数据,但不包括对角线元素。在 Knowhere 中,Elkan 算法使用的对称矩阵维数设置为 1024,因此内存大小约为 2 MB。这意味着在每一轮训练中,Elkan 会重复分配和取消分配 2 MB 内存。
Perf 分析数据显示,频繁的大内存分配活动。事实上,它触发了虚拟内存区(VMA)分配、物理页分配、页面映射设置以及内核中内存 cgroup 统计数据的更新。这种大内存分配/去分配活动模式在某些情况下还会加剧内存碎片。这是一项重大税收。
IndexFlatElkan结构是为支持 Elkan 算法而专门设计和构建的。每个数据训练过程都会初始化一个IndexFlatElkan实例。为了减轻 Elkan 算法中频繁的内存分配和取消分配对性能的影响,我们重构了代码逻辑,将 Elkan 算法函数之外的内存管理转移到IndexFlatElkan 的构建过程中。这使得内存分配只需在初始化阶段进行一次,同时还能为当前数据训练过程中所有后续的 Elkan 算法函数调用提供服务,并有助于将负载延迟提高约 3%。点击此处查找Knowhere 补丁。
通过软件预取加速 Redis 向量搜索
Redis 是一种流行的传统内存键值数据存储,最近开始支持向量搜索。为了超越典型的键值存储,它提供了可扩展模块;RediSearch模块便于直接在 Redis 中存储和搜索向量。
对于向量相似性搜索,Redis 支持两种算法,即蛮力算法和 HNSW 算法。HNSW 算法专门用于在高维空间中高效定位近似近邻。它使用名为candidate_set的优先级队列来管理用于距离计算的所有候选向量。
除向量数据外,每个候选向量还包含大量元数据。因此,当从内存加载候选向量时,可能会导致数据缓存丢失,从而造成处理延迟。我们的优化引入了软件预取功能,在处理当前候选数据的同时主动加载下一个候选数据。这一改进使单例 Redis 设置中向量相似性搜索的吞吐量提高了 2% 到 3%。该补丁正在向上游发布。
更改 GCC 默认行为以防止混合汇编代码处罚
为了最大限度地提高性能,经常使用的代码段通常以汇编形式手写。但是,当不同的代码段由不同的人或在不同的时间点编写时,所使用的指令可能来自不兼容的汇编指令集,如Intel® Advanced Vector Extensions 512 (Intel® AVX-512)和Streaming SIMD Extensions (SSE)。如果编译不当,混合代码会导致性能下降。了解有关混合使用英特尔 AVX 和 SSE 指令的更多信息,请点击此处。
你可以很容易地判断自己是否在使用混合模式汇编代码,以及是否没有使用VZEROUPPER 编译代码,从而导致性能下降。可以通过 perf 命令来观察,如sudo perf stat -e 'assists.sse_avx_mix/event/event=0xc1,umask=0x10/' <workload>。如果操作系统不支持该事件,请使用cpu/event=0xc1,umask=0x10,name=assists_sse_avx_mix/。
Clang 编译器默认插入VZEROUPPER,从而避免了任何混合模式惩罚。但 GCC 编译器只有在指定 -O2 或 -O3 编译器标志时才插入VZEROUPPER。我们联系了 GCC 团队并解释了这个问题,现在他们默认会正确处理混合模式汇编代码。
开始优化您的向量数据库
向量数据库在 GenAI 中发挥着不可或缺的作用,而且为了生成更高质量的响应,向量数据库的规模也在不断扩大。在优化方面,人工智能应用程序与其他软件应用程序没有什么不同,只要使用标准性能分析工具以及基准框架和压力输入,它们就会暴露自己的秘密。
利用这些工具,我们发现了与不必要的内存分配、未能预取指令以及使用不正确的编译器选项有关的性能陷阱。基于我们的发现,我们对 Milvus、Knowhere、Redis 和 GCC 编译器进行了上游增强,以帮助人工智能提高性能和可持续性。向量数据库是一类重要的应用,值得您进行优化。希望这篇文章能对您的工作有所帮助。
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word