给 500 万条 1024 维向量建 HNSW 索引,M 设 64 之后进程被 OOM 杀掉,机器 32G 内存,向量本身占 20G 左右。

一开始怀疑是「ef_construction 过高会拖慢构建并抬高峰值内存」,查了一圈,方向不对。

这类报错背后常见的原因有几处,逐个排除就行。M 越大每个节点的邻居表越占内存,线性放大。少数情况下是构图阶段还要额外内存放候选队列。

少数情况下是向量用 float32 存,切换 float16 能省一半。

估一遍索引本身的体积

python -c "n=5_000_000;d=1024;M=32;print(round((n*(d*4+M*2*4))/1024**3,2),'GB')"

再往下把向量降到 float16 再入库

python -c "import numpy as np;v=np.load('v.npy').astype('float16');np.save('v16.npy',v)"

降低 M 与 ef_construction 重建索引

python build_hnsw.py --M 16 --ef-construction 100 --metric ip

等前面都确认过,再观察构建过程的内存峰值

python -c "import resource;print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)"

手上没有现成的复现脚本,就照着线上请求手工拼了一个。

别只看报错消失,验证要多做一步。索引构建完成且进程未被 OOM,内存峰值低于机器上限,召回率评测下降在可接受范围内。

容易忽略的是,HNSW 的内存大头是邻居表不是向量,M 翻倍内存近似翻倍,先按公式估再动手,别用试错换时间。报错文案容易误导,真正的原因往往在相邻那一层的日志里。

作者 9823189