LEANN 归一化嵌入支持:自动距离度量检测与 Cosine/MIPS 正确选择实战指南
【免费下载链接】LEANN[MLsys2026 Best Paper]: https://arxiv.org/abs/2506.08276. RAG on Everything with LEANN. Enjoy 97% storage savings while running a fast, accurate, and 100% private RAG application on your personal device.项目地址: https://gitcode.com/GitHub_Trending/le/LEANN
LEANN 在构建索引时会自动识别 OpenAI、Voyage AI、Cohere 等厂商的归一化嵌入模型(L2 范数为 1 的单位向量),并自动为其选择最优的distance_metric="cosine",无需手动干预。本文以 docs/normalized_embeddings.md 为骨架,结合leann-core与leann-backend-hnsw的源码实现,深入讲解归一化嵌入的原理、自动检测的判定逻辑、支持的模型清单、完整的构建与搜索用法,以及错误度量导致 HNSW 提前终止等问题的底层原因,帮助你为自己的 RAG 应用选对距离度量。
什么是归一化嵌入(Normalized Embeddings)
归一化嵌入是指L2 范数恰好等于 1的向量,即单位向量。这类向量经过 L2 归一化(向量除以自身范数)后,任意两个向量之间的余弦相似度(Cosine Similarity)就等于它们的内积(点积),因此它们天然是为余弦相似度优化,而非为最大内积搜索(MIPS,Maximum Inner Product Search)设计。
在 LEANN 中,归一化嵌入的识别结果直接决定索引使用的距离度量,进而影响整个检索链路的质量。这一点在 api.py 的LeannBuilder.__init__(L469-L536)中有着完整的实现。
自动检测机制:三步帮你做对度量选择
当你用归一化嵌入模型创建LeannBuilder实例时,LEANN 会自动完成三件事(见 api.py):
- 未指定度量时,自动设置
distance_metric="cosine":检测到归一化模型且用户没有显式传入distance_metric时,代码会直接写入backend_kwargs["distance_metric"] = "cosine",并发出UserWarning告知用户已自动切换; - 手动指定了其他度量时给出警告:若用户显式传入非 cosine 度量(如
mips),LEANN 不会静默覆盖,而是发出警告提示该组合可能导致次优的检索结果; - 以正确度量获得最优检索性能:构建端与查询端都会依据
cosine对向量做 L2 归一化,使 HNSW 图的度量与向量空间严格匹配。
检测逻辑的源码实现细节
从源码看,检测分为精确匹配与模式匹配两级(api.py):
- 精确匹配:维护一个
(embedding_mode, model_name)元组集合normalized_embeddings_models,包含 OpenAI、Voyage、Cohere 的全部已知归一化模型;匹配时先将 mode 与 model 转小写,再做全等或子串包含判断; - 模式匹配(兜底):未命中精确集合时,按厂商规则推断:
- OpenAI:mode 或 model 名含
"openai",且模型名含"text-embedding"、"ada"、"3-small"、"3-large"之一; - Voyage:mode 或 model 名含
"voyage"即视为归一化(Voyage 全系模型均归一化); - Cohere:mode 或 model 名含
"cohere",且模型名含"embed"。
- OpenAI:mode 或 model 名含
这意味着即使你使用的具体 OpenAI/Cohere 版本号不在精确清单中(例如未来的text-embedding-4-*),只要命名符合上述模式,同样会被正确识别。
支持的归一化嵌入模型清单
根据 docs/normalized_embeddings.md 及 api.py 中的精确集合,以下模型会被自动识别为归一化嵌入:
| 厂商 | 模型名 | 说明 |
|---|---|---|
| OpenAI | text-embedding-ada-002 | 全系 OpenAI 文本嵌入模型均归一化 |
| OpenAI | text-embedding-3-small | 归一化 |
| OpenAI | text-embedding-3-large | 归一化 |
| Voyage AI | voyage-2 | 归一化 |
| Voyage AI | voyage-3 | 归一化 |
| Voyage AI | voyage-large-2 | 归一化 |
| Voyage AI | voyage-multilingual-2 | 归一化 |
| Voyage AI | voyage-code-2 | 归一化 |
| Cohere | embed-english-v3.0 | 归一化 |
| Cohere | embed-multilingual-v3.0 | 归一化 |
| Cohere | embed-english-light-v3.0 | 归一化 |
| Cohere | embed-multilingual-light-v3.0 | 归一化 |
注意:模式匹配规则(尤其是 Voyage 全系、OpenAItext-embedding系列)会覆盖精确清单之外的同系列新版本,因此上表是"确认清单"而非"封闭清单"。
示例用法:自动检测与手动覆盖
以下代码继承自 docs/normalized_embeddings.md 的示例,并补充了源码层面的行为说明。
方式一:自动检测(推荐)
from leann.api import LeannBuilder # 自动检测 - 将使用 cosine 距离 builder = LeannBuilder( backend_name="hnsw", embedding_model="text-embedding-3-small", embedding_mode="openai" ) # Warning: Detected normalized embeddings model 'text-embedding-3-small'... # Automatically setting distance_metric='cosine'当未传入distance_metric时,api.py 会将distance_metric写入backend_kwargs并随索引一起持久化到<index>.meta.json的backend_kwargs字段中。之后无论是构建端(HNSWBuilder)还是查询端(HNSWSearcher、BaseSearcher),都会从该字段读取度量,保证索引构建与在线检索使用同一套度量约定(参见 searcher_base.py 中搜索时从 meta 读取distance_metric、缺省回退mips的逻辑)。
方式二:手动覆盖(不推荐)
builder = LeannBuilder( backend_name="hnsw", embedding_model="text-embedding-3-small", embedding_mode="openai", distance_metric="mips" # 将显示警告 ) # Warning: Using 'mips' distance metric with normalized embeddings...此时检测到is_normalized=True且用户指定了非 cosine 度量,api.py 会保留用户的设置并发出UserWarning。度量本身仍然生效,但代价是检索质量下降——具体原因见下文"为什么这很重要"一节。
支持的度量取值与底层映射
在 HNSW 后端中,度量字符串会被映射为 Faiss 的度量枚举(hnsw_backend.py):
distance_metric取值 | Faiss 枚举 | 说明 |
|---|---|---|
"mips" | faiss.METRIC_INNER_PRODUCT | 最大内积搜索,非归一化模型的默认最优度量 |
"l2" | faiss.METRIC_L2 | 欧氏距离平方 |
"cosine" | faiss.METRIC_INNER_PRODUCT | 余弦相似度(向量归一化后内积即余弦) |
值得注意的是,cosine在 Faiss 层实际映射为METRIC_INNER_PRODUCT——因为归一化后的单位向量内积等价于余弦相似度,无需 Faiss 内部再做归一化。真正完成归一化的是 LEANN 自己的normalize_l2()函数(hnsw_backend.py):
def normalize_l2(data: np.ndarray) -> np.ndarray: norms = np.linalg.norm(data, axis=1, keepdims=True) norms[norms == 0] = 1 # 避免除零 return data / norms该函数对零向量做了除零保护。它的调用发生在两端:
- 构建端:
HNSWBuilder.build()在distance_metric == "cosine"时对全部入库向量执行normalize_l2(data)(hnsw_backend.py); - 查询端:
HNSWSearcher.search()对查询向量同样执行normalize_l2(query)(hnsw_backend.py)。
只有两端都归一化,内积度量才能严格等价于余弦相似度。
非归一化嵌入:继续使用 MIPS
像facebook/contriever以及其他未经归一化的 sentence-transformers 模型,LEANN不会把它们判定为归一化模型,因此默认仍使用mips度量——这对它们才是最优的(docs/normalized_embeddings.md)。从源码看,这类模型在精确集合与模式匹配两级检测中均不命中(api.py),backend_kwargs中不会出现distance_metric,各后端因此回退到各自默认值(HNSW 为mips,见 hnsw_backend.py)。
这印证了 LEANN 的设计原则:度量与向量空间必须匹配。归一化模型用 cosine,非归一化模型用 MIPS,各取所长。
为什么这很重要:错误度量的三大危害
原文档明确指出,对归一化嵌入使用错误度量会导致三类问题,而源码给出了更精确的机理:
HNSW 提前终止导致检索质量下降:归一化向量全部落在单位球面上,所有内积分数都挤压在很窄的区间内。HNSW 搜索默认启用相对距离检查(
check_relative_distance),当候选分数区间过窄时会被误判为"已收敛"而过早终止搜索,错过真正的高质量邻居。源码中的针对性处理非常直观(hnsw_backend.py):# 对 OpenAI embeddings + cosine 距离,禁用相对距离检查 # 防止所有分数都处于狭窄区间时提前终止 if self.distance_metric == "cosine" and any( openai_model in embedding_model for openai_model in ["text-embedding", "openai"] ): params.check_relative_distance = False else: params.check_relative_distance = True可以看到,LEANN 对"OpenAI 归一化嵌入 + cosine"这一典型组合显式关闭了相对距离检查,从机制上规避提前终止问题;反之若使用 MIPS 度量,则无法触发这一保护,正是文档所述"poor search quality"的根源。
结果排序不正确:MIPS 搜索的是内积最大,而语义相关性更接近余弦相似度。向量模长与方向信息混杂在内积中,会使排序偏离真实的语义距离。
性能次优:度量与向量空间不匹配时,图结构、剪枝(pruning)与距离重算逻辑都基于错误的几何假设,整体检索效果与正确度量相比有明显差距。
此外,重算模式下的距离计算也在嵌入服务端遵循同样的度量约定:l2用平方欧氏距离,其他(含 cosine 与 mips)用负点积(hnsw_embedding_server.py),保证重算得分与索引度量一致。
实践要点小结
- 使用 OpenAI / Voyage / Cohere 等归一化嵌入模型构建
LeannBuilder时,无需手动指定distance_metric,LEANN 会自动设置cosine; - 若坚持手动覆盖为非 cosine 度量,请接受
UserWarning并知晓检索质量风险; - 自定义 embedding 流程时,若你的模型输出已经是单位向量,可参考 api.py 的检测清单自行确认;若输出未归一化,则保持默认 MIPS 即可;
- 度量选择会影响构建与查询两端(归一化、Faiss 度量枚举、相对距离检查、重算距离),任何一环不一致都会破坏检索一致性。
更多相关细节可继续阅读 docs/normalized_embeddings.md 原文,以及索引构建入口 api.py、HNSW 后端实现 hnsw_backend.py 与检索基类 searcher_base.py。
【免费下载链接】LEANN[MLsys2026 Best Paper]: https://arxiv.org/abs/2506.08276. RAG on Everything with LEANN. Enjoy 97% storage savings while running a fast, accurate, and 100% private RAG application on your personal device.项目地址: https://gitcode.com/GitHub_Trending/le/LEANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考