我正在尝试确定一个函数
f(x, n)
,其中x
是向量的数量,n
是向量维度的数量,这样对于任何x
,n
,f(x, n)
都大于通过具有 HNSW 索引的 pgvector 数据库为这些向量提供所需的 RAM 量。
我做了一些谷歌搜索,但是
这篇文章说:
HNSW 索引的第三个权衡是它们很大——100 万行 AI 嵌入的索引可以是 8GB 或更大。出于性能原因,您需要将所有这些索引都存储在内存中。
尽管在本文中,据说使用相同 HNSW 索引算法的 Qdrant 仅需要约 1GB RAM 即可服务 100 万个向量(无内存映射)。为什么会有这样的差异?
根据 FAISS 文档,HNSW 索引的内存消耗(以每个向量字节为单位)为
(d * 4 + M * 2 * 4)
,其中 d
是索引向量的维数,M
是构造的每个节点的边数图(通常使用默认值 32)。
您链接的Qdrant文章使用维度为100的glove100_angular数据集,需要
100 * 4 + 32 * 2 * 4 = 656 bytes/vector
使用HNSW索引进行索引。
这将需要
656_000_000 bytes
内存来索引 1M 向量,或者 0.656GB
,它跟踪 Qdrant 文章估计的 1GB RAM。
查看 PGVector 文档,看起来整个索引不需要来适应 RAM,但是当索引不再适合内存时,索引构建时间将花费更长的时间(我认为这是由于 Postgres 构建造成的)磁盘上的索引)。
注意:在FAISS提供的内存消耗公式(d * 4 + M * 2 * 4)
中,第一部分
(d * 4)
似乎对应于用于存储向量本身的字节数,而
M * 2 * 4
则对应于该数字存储对其
M
邻居的引用所需的字节数。由于 HNSW 索引构建的图的层数逐渐变得稀疏,其中基础层每个节点使用
M * 2
连接(默认情况下),并且每个后续层每个节点使用
M
连接,我的理解是上面公式only占基础层。 每个节点都
根据概率随机添加到后续层,该概率由值M_L
标准化。
M_L
的典型默认值为
ln(M)
。使用默认值
M=32
,对于索引 1M 向量的图,每层的节点数将如下所示:
L0: 1000000
L1: 1000000 / ln(32) ~= 288539
L2: 288539 / ln(32) ~= 83254
L3: 83254 / ln(32) ~= 24022
L4: 24022 / ln(32) ~= 6931
L5: 6931 / ln(32) ~= 1999
L6: 1999 / ln(32) ~= 577
L7: 577 / ln(32) ~= 166
L8: 166 / ln(32) ~= 48
L9: 48 / ln(32) ~= 13 --> this value is less than M=32, so it becomes the last layer of the graph (first layer to be searched)
L_i > L0
层中所有节点总和的上限为
N
,因此
N
向量的边总数受以下限制:
M * 2 * N (edges for L0) + M * N (edges for layers L>0) = 3 * M * N
。这将使每个向量的内存消耗为:
(d * 4 + M * 3 * 4)
,因此 Qdrant 文章中的数据集将需要:
((100 * 4) + (32 * 3 * 4)) * 1_000_000 = 784000000 bytes (0.784GB)
。这与 FAISS 文档中提供的公式描述的内存消耗非常接近,但在某些情况下差异可能很有意义