已关闭
faiss/npu FlatIndex 分页搜索缺跨页 Top-K 归并,库规模超 pageSize 时召回率灾难性下降 #40
崇理战队创建于 8月13日关闭于 8月17日
xiangjie10
8月13日 评论:
8月13日 评论:
👋 您好,感谢向 faiss 提交 Issue!
🎉 我们已收到您的反馈,感谢你对开源社区的支持!
📅 处理时效 维护团队将在工作日 24 小时内查看并回复您的问题。
🔍 自助排查(推荐优先查看) 在等待回复期间,您可以先查阅仓库README以及历史 Issue 中相似问题的解决方案,多数问题可快速解决。
💡 为了更快定位问题,请您确保 Issue 包含:
- 清晰的问题描述
- 可复现的操作步骤
- 相关日志、截图或环境信息
我们会尽快跟进,感谢您的理解与配合!


xiangjie10
8月13日 评论:
8月13日 评论:
/label add triaged


8月13日 添加了label:triaged
Qanly
8月17日 评论:
8月17日 评论:
感谢 review。经过实际测试验证,TopK 算子内部已实现跨页归并, topkDistBuf / topkLabelsBuf 作为 in-out 参数复用是正确的设计。
实测数据(5M 向量,触发 2 页分页,10 个 query × top-10):

如果真如分析所说"只保留最后一页局部 Top-K",召回率理论上限仅 ~16%(第 2 页仅 80 万向量 / 总量 500 万),实际 97% 的召回率说明跨页归并正常工作。
少量 ID 不匹配的原因是 NPU 内部全程 fp16 计算,与 CPU fp32 参考存在精度差异,距离相同的向量在排序时可能有少量交换,属于正常现象。


8月17日 issue状态由 TODO 改变为 DONE
8月17日 关闭了 issue
8月17日 添加了label:resolved
在 Ascend NPU 上用 faiss 的 Flat / Int8Flat 做大数据集检索(向量数 > 约 419 万)时,返回的最近邻召回率严重不对,但单测全过。读
FlatIndex::query才发现分页搜索根本没有跨页归并,只把最后一页的局部 Top-K 拷回去了。位置:
faiss/npu/impl/FlatIndex.cpp:357-360topk 输出缓冲(topkDistBuf/topkLabelsBuf)在分页循环之前声明、全程复用:388分页循环for (pageId ...)起点,每页对pageComputeNum个向量做距离+topk,覆盖写入上述 buffer:832-847分页循环结束之后,仅把 buffer 内容拷回调用方outDistances/outLabels关键代码(结果拷贝段):
// Copy results from device memory to host memory std::vector<uint16_t> resultDists(nq * k); std::vector<uint32_t> resultLabels(nq * k); fromDevice(topkDistBuf.data(), resultDists.data(), resultDists.size(), stream); fromDevice(topkLabelsBuf.data(), resultLabels.data(), resultLabels.size(), stream); for (int i = 0; i < nq * k; ++i) { outDistances[i] = aclFloat16ToFloat(resultDists[i]); outLabels[i] = static_cast<idx_t>(resultLabels[i]); // 832-847 }分析:
topkFlatCpu这个 ACLNN 算子是无状态的,每次调用都是针对"本页"的局部 Top-K,并覆盖写入复用的topkDistBuf/topkLabelsBuf。代码里没有任何跨页归并(merge)步骤——既没有把各页结果合并成全局 Top-K,也没有让算子跨页累积。而pageSize_ = DEFAULT_BLOCK_SIZE * 16 = 262144 * 16 ≈ 4.19M(FlatIndex.h:172与IndexUtils.h:33)。触发条件:库中向量数
num_ > pageSize_ ≈ 4.19M(即pageNum > 1)时。实际影响:返回的
outDistances/outLabels只反映最后一个分页(约 4.19M 向量)内的局部 Top-K,前面所有页贡献的更近邻被直接丢弃。在十亿级 Flat 检索下召回率会灾难性下降,而且静默无报错——不会 crash,也不会触发断言。现有单测库通常 < 4.19M(pageNum==1),所以能侥幸通过,掩盖了这个 bug。Int8Flat(Int8FlatIndex.cpp:322-329, 365, 684, 686-699)是完全同构的实现,同样有这个问题。修复建议:引入和
numLists分页类似的归并——每页得到nq × k局部结果后,用一次设备端/主机端 k-way merge(或把各页 Top-K 先暂存,最后在out上做 min-heap)合成全局 Top-K;或者改成一个能接收整库距离的 Top-K 算子。最起码应该补一个num_ > pageSize_的回归测试把这个问题钉死。