先把当时的情况摆出来。把集合的度量方式从 L2 改成内积,重建索引后检索分数全是负数,原来按 0.8 设的相似度阈值把所有结果都过滤掉了,接口返回空列表,日志里却没有任何报错。

把日志和配置对完,能解释这个现象的基本就这几处。内积的分数范围与 L2 不同,旧阈值完全不适用。另一种情况是向量没有归一化,内积值随模长放大甚至变负。

少数情况下是重建后集合的度量方式与查询参数不一致。不容易想到的是,业务侧把距离当相似度,方向理解反了。

先打印几条命中的原始分数

python -c "print([(h.id, round(h.distance,4)) for h in client.search('kb',[q],'embedding',limit=5)[0]])"

接着确认集合的度量方式与索引参数

python -c "from pymilvus import Collection;print(Collection('kb').index().params)"

然后写入前统一做 L2 归一化

python -c "import numpy as np;v=np.random.rand(1,1024);v=v/np.linalg.norm(v,axis=1,keepdims=True);print(np.linalg.norm(v))"

剩下的就是按新分布重设阈值再验证

python tune_threshold.py --scores ip_scores.jsonl --kind ip --target-recall 0.9

恢复的判定其实很直接。归一化后内积分数落在零到一之间,阈值改成 0.55 后检索结果非空,十条已知问答对全部命中。

这里还有个坑。L2 的分数越小越相似,内积正好相反,换度量方式等于换了一套语义,阈值和归一化要一起改。报错文案容易误导,真正的原因往往在相邻那一层的日志里。

作者 9823189