Embedding 模型与向量数据库:语义检索的双引擎选型指南

· 约 6 分钟 ·
本文目录(13 节)

语义检索需要同时解决两件事:把内容表示成合适的向量,以及在给定延迟和资源预算内找到候选结果。前者主要由模型和数据决定,后者涉及索引与检索配置。下面分开评估这两部分,再看它们在同一条链路中怎样影响召回。

前置阅读:建议先读 ViT 与 CLIP(双塔思想)、Milvus + Neo4j 实战、RAG 进阶。

Embedding 模型:向量空间的语义质量从哪来

演进三十年,三步看懂

  1. Word2Vec/GloVe(2013-2015):词向量时代。「国王 − 男人 + 女人 ≈ 女王」的算术惊艳了所有人,但静态向量解决不了多义词(「苹果」在句子和水果里是一个向量)。
  2. BERT 句向量(2018-):上下文相关了,但直接拿 BERT 的 [CLS] 或平均池化做检索效果很差——训练目标不是为相似度设计的,向量空间是「塌缩」的(各向异性)。
  3. 对比学习句向量(2021-):SimCSE、SBERT、BGE、E5 用对比学习专门训练「语义相似靠近、不相似远离」的向量空间——这才是现代语义检索的起点。今天的 BGE-M3、E5-multilingual 一句话:检索专用的双塔编码器。

选型看 MTEB,但要会看

MTEB(Massive Text Embedding Benchmark)是 embedding 模型的权威榜单,读法三点:

  • 看你任务的子项分:检索(Retrieval)、重排、聚类、分类是分开评的——总分高不代表检索强。
  • 看语言:C-MTEB 是中文榜。英文榜首模型中文可能拉胯,中文任务认准 BGE 系、multilingual-e5 系。
  • 看尺寸-效果比:0.5B 的 bge-m3 经常够打 7B 模型——线上延迟是钱,别为用不上的 2 个点付 10 倍推理成本。

我的 2026 默认选择:中文/多语言 → bge-m3(多粒度、稠密+稀疏一体);英文为主 → e5-large 或 bge-en;代码 → 专门代码 embedding。

领域微调:什么时候必须做

通用 embedding 在垂直领域(医疗、法律、工业术语)会「词都认识但语义错位」。微调信号:Recall@K 在评估集上明显低于通用文本,且 badcase 集中在术语理解。微调方法是对比学习,几百到几千对「查询-正文档」就够(自监督篇的 InfoNCE 同款):

from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader

model = SentenceTransformer("BAAI/bge-m3")
examples = [InputExample(texts=[q, pos]) for q, pos in pairs]  # 正样本对
loader = DataLoader(examples, batch_size=32, shuffle=True)
loss = losses.MultipleNegativesRankingLoss(model)   # batch 内其他样本当负例
model.fit(train_objectives=[(loader, loss)], epochs=3, warmup_steps=100)
model.save("bge-m3-mydomain")

关键技巧:硬负样本(和查询字面相似但语义无关的文档)——只拿随机负样本,模型学不到细粒度区分。挖掘法:用基础模型检索 top-K 但不命中的当硬负样本。

向量数据库:工程性能从哪来

索引类型:召回率与速度的权衡

索引原理特点
FLAT暴力全扫100% 召回,百万级以上不可用
IVF_FLAT/PQ先聚类分桶,只搜近的几个桶快,nprobe 调召回率
HNSW分层小世界图,图上游走找近邻当前默认:召回 95%+ 且快,内存占用大
DiskANN磁盘友好的图索引亿级数据、内存有限

经验法则:千万级以下一律 HNSW,调参就两个:M(图的连接度,1664,影响内存和召回)和 ef_search(搜索时探索的候选数,调召回率-延迟)。PQ 压缩再省内存但掉 25 个点召回,内存紧张才开。

三大方案对比

维度MilvusQdrantpgvector
定位专业向量数据库专业向量库(Rust,轻)PostgreSQL 插件
规模亿级+,分布式千万级单机舒适百万级以内
标量过滤强(分区+表达式)强(payload 索引)借 PG 全家桶(JOIN/事务)
运维重(依赖 etcd/MinIO)轻(单二进制)零新增(已有 PG 的话)
适用大规模生产中小规模、快速落地小规模、想少养组件

选型决策树:已有 PostgreSQL 且向量 <100 万 → pgvector 零新增组件;中小规模独立服务 → Qdrant;亿级或多租户 → Milvus。我在 Milvus + Neo4j 篇里走的是 Milvus 路线,今天回头看,那个数据量用 Qdrant 会更轻——组件复杂度是永久的运维税。

标量过滤:被低估的刚需

真实检索很少是纯向量:「在 2024 年后的文档里语义搜索」「只搜该用户有权限的」。向量 + 标量混合过滤的实现质量是数据库的分水岭:先过滤后检索(pre-filter)可能让图索引退化,先检索后过滤(post-filter)可能过滤完没结果。Qdrant/Milvus 的「filtered ANN」是各自的工程卖点,选型时拿你的过滤比例实测——过滤掉 99% 数据的查询是各家性能的照妖镜。

一个完整的最小检索栈

# bge-m3 + Qdrant 的最小可跑栈
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient, models

model = SentenceTransformer("BAAI/bge-m3")
client = QdrantClient(":memory:")   # 测试用内存模式

client.create_collection("docs",
    vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE))

vecs = model.encode(documents, normalize_embeddings=True)
client.upsert("docs", points=[
    models.PointStruct(id=i, vector=v.tolist(), payload={"text": d, "year": 2024})
    for i, (v, d) in enumerate(zip(vecs, documents))])

q = model.encode([query], normalize_embeddings=True)[0]
hits = client.query_points("docs", query=q.tolist(), limit=5,
    query_filter=models.Filter(must=[
        models.FieldCondition(key="year", match=models.MatchValue(value=2024))]))

踩坑排查

现象原因解法
相似文本检索不到没归一化 + 距离度量错配cosine 距离要 normalize_embeddings=True
中文效果差用了英文优化的 embedding换 bge-m3 / multilingual-e5
检索慢ef_search 过高或用了 FLAT换 HNSW,ef 从 64 起调
加过滤条件后结果为空post-filter 把召回滤没了增大 limit 或用数据库原生 filtered ANN
内存爆了HNSW 的 M 过大 + 未量化降 M 到 16;开 PQ/SQ 量化
微调后通用场景退化过拟合领域负样本混 20% 通用数据一起训

练习

  1. 在 MTEB 中文榜上挑 3 个模型,在你的 50 条领域查询上对比 Recall@5——榜单排名和你实测一致吗?
  2. 用 Qdrant 内存模式建 1 万条文档的索引,扫 ef_search ∈ {16, 64, 256},画「召回率-延迟」曲线。
  3. 构造 100 对领域数据微调 bge-m3,对比微调前后在领域评估集上的表现(别忘了留测试集)。
  4. 设计实验:同一批数据分别用 pgvector 和 Qdrant 索引,对比「无过滤」和「过滤 95%」两种查询的延迟。

面试常问

Q:HNSW 的原理和调参? 分层图:上层稀疏高速(大跳转)、下层稠密精确。搜索从顶层入口点贪心走到最近邻,逐层下降到底层精搜。M(每节点最大连接数)影响图密度——大 M 高召回高内存;ef_construction 影响建图质量;ef_search 影响查询召回(可调在线权衡)。经验:M=16~32、ef_construction=200、ef_search 按 SLA 调。

Q:向量检索为什么用余弦相似度?和归一化什么关系? embedding 的语义主要在方向不在模长(模长受词频等影响)。余弦只比方向。向量 L2 归一化后,余弦相似度 = 内积,欧氏距离也单调等价——所以归一化后选 IP(内积)距离既快又对。忘了归一化又用 IP 是最常见的静默 bug。

Q:双塔 embedding 和 BERT 直接取 [CLS] 的区别? BERT 预训练目标(MLM+NSP)不为相似度设计,[CLS] 向量空间各向异性——所有句向量挤在一个窄锥里,余弦相似度都 0.9+ 无法区分。对比学习微调(SimCSE/BGE 路线)把空间「展开」,语义相似的靠近不相似的拉远。同一个 BERT 底座,效果天壤之别——目标函数决定表示几何。

Q:亿级向量怎么规划架构? 分片(按 ID 或业务键 sharding 到多节点)+ 副本(QPS 扩展)+ 索引选型(DiskANN/IVF_PQ 省内存)+ 分层存储(热数据内存、冷数据磁盘)。Milvus 的云原生架构(查询节点/索引节点分离)就是为这个场景设计。另外问自己:真的需要亿级在线吗?多数场景「按租户/时间分区 + 冷热分离」就把规模降回千万级。

Q:稀疏向量(BM25/SPLADE)和稠密向量的关系? 稠密向量(bge 等)捕获语义,稀疏向量(BM25 天然、SPLADE 学到的词权重)捕获精确词匹配。bge-m3 一体化输出两者(multi-vector 还有 ColBERT 式的晚交互)。工程上混合检索(RAG 进阶篇)通常两路并行 + RRF 融合,是比单纯稠密更稳的方案。

相关阅读

Embedding 决定你的系统「懂不懂」,向量数据库决定它「快不快」。前者看评估集说话,后者看 QPS 说话——两个都别凭感觉选。

💬 评论

这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。

正在连接评论服务,首次访问可能需要几秒…