已关闭
faiss/npu FlatIndex 分页搜索缺跨页 Top-K 归并,库规模超 pageSize 时召回率灾难性下降 #40
崇理战队创建于  8月13日关闭于  8月17日
崇理战队
8月13日 创建

在 Ascend NPU 上用 faiss 的 Flat / Int8Flat 做大数据集检索(向量数 > 约 419 万)时,返回的最近邻召回率严重不对,但单测全过。读 FlatIndex::query 才发现分页搜索根本没有跨页归并,只把最后一页的局部 Top-K 拷回去了。

位置:faiss/npu/impl/FlatIndex.cpp

  • :357-360 topk 输出缓冲(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.19MFlatIndex.h:172IndexUtils.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_ 的回归测试把这个问题钉死。

likedislike
xiangjie10成员
8月13日 评论:

👋 您好,感谢向 faiss 提交 Issue!
🎉 我们已收到您的反馈,感谢你对开源社区的支持!

📅 处理时效 维护团队将在工作日 24 小时内查看并回复您的问题。
🔍 自助排查(推荐优先查看) 在等待回复期间,您可以先查阅仓库README以及历史 Issue 中相似问题的解决方案,多数问题可快速解决。
💡 为了更快定位问题,请您确保 Issue 包含:

  • 清晰的问题描述
  • 可复现的操作步骤
  • 相关日志、截图或环境信息
    我们会尽快跟进,感谢您的理解与配合!
likedislike
xiangjie10成员
8月13日 评论:

/label add triaged

likedislike
ascend-robotascend-robot成员
8月13日 添加了label:triaged
Qanly
Qanly成员
8月17日 评论:

感谢 review。经过实际测试验证,TopK 算子内部已实现跨页归并, topkDistBuf / topkLabelsBuf 作为 in-out 参数复用是正确的设计。

实测数据(5M 向量,触发 2 页分页,10 个 query × top-10):

image.png

如果真如分析所说"只保留最后一页局部 Top-K",召回率理论上限仅 ~16%(第 2 页仅 80 万向量 / 总量 500 万),实际 97% 的召回率说明跨页归并正常工作。

少量 ID 不匹配的原因是 NPU 内部全程 fp16 计算,与 CPU fp32 参考存在精度差异,距离相同的向量在排序时可能有少量交换,属于正常现象。

likedislike
QanlyQanly成员
8月17日 issue状态由 TODO 改变为 DONE
QanlyQanly成员
8月17日 关闭了 issue
ascend-robotascend-robot成员
8月17日 添加了label:resolved