Skip to content

Embedding 基础 ​

什么是 embedding ​

Embedding(嵌入向量)把文字、图片、音频等对象表示成向量。在 RAG 里常用的单向量文本 embedding,会把一句话、一段文档、一个标题或一个用户问题转成固定长度的数字序列。

用于语义检索的模型会学习让相关内容的向量更接近。比如“猫”和“猫咪”通常比“猫”和“股票”更接近;问题与能够回答它的文档,也应有较高的匹配分数。

可以把向量理解为语义空间里的坐标:

text
文本                    向量
“猫”          -> [0.12, -0.08, 0.31, ...]
“猫咪”        -> [0.11, -0.07, 0.30, ...]
“股票”        -> [-0.42, 0.56, 0.02, ...]

文本向量通常有几百到几千维,每一维一般没有“动物性”“宠物性”这样的固定含义。检索程序比较的是整个向量,把“按意思搜索”转成距离或相似度计算。

这个能力不只用于 RAG,还常用于:

  • 语义搜索:用户说“自动猫粮机”,也能找到写着“宠物喂食器”的文档。
  • 聚类:把一批客服问题自动分成“退款”“物流”“账号登录”等主题。
  • 推荐:找和当前商品、文章、代码片段相似的内容。
  • 长期记忆检索:从历史对话、用户偏好和任务记录中找回相关内容。
  • 去重:发现两段标题不同但意思几乎一样的资料。
  • 异常检测:找出一批文本里语义上特别离群的记录。
  • 分类:把输入和几个类别描述做相似度比较,选最接近的类别。

embedding 是怎么来的 ​

典型的文本 embedding 模型按以下过程生成向量:

  1. 切 token:先把输入文本拆成 token。
  2. 编码上下文:模型计算每个 token 在当前上下文中的表示。
  3. 汇总成向量:通过池化或特定 token 的表示,得到固定长度的文本向量。
  4. 可选归一化:有些模型或框架会把向量长度归一化,方便用 cosine / dot product 比较。

几个容易混淆的点:

  • embedding 不是关键词列表。它不是简单记录“出现过哪些词”,而是学习到的语义表示。
  • embedding 不是可逆压缩。拿到一串向量,通常不能还原原文。
  • embedding 有信息损失。短句、长文、代码、表格、专有名词的压缩难度不同。
  • 模型大小不能单独决定检索质量,还要看训练数据、语言覆盖和目标任务。

向量相似度:怎么算“近” ​

检索时会把用户问题也转成向量,然后和库里的文档向量比较,找最接近的几个。

常见距离 / 相似度:

名称越大越相似?适合记法说明
Cosine similarity是看方向最常见,关心两个向量方向是否接近
Cosine distance否距离越小越近很多向量库返回的是 distance,不是 similarity
Dot product / inner product是点积越大越近两个向量均经过 L2 归一化时,点积等于余弦相似度
L2 / Euclidean distance否直线距离图像、某些传统向量场景常见;文本检索也可用但要看模型建议

设两个非零向量为 q 和 d,余弦相似度为:

text
cosine(q, d) = dot(q, d) / (norm(q) × norm(d))

L2 归一化会把向量长度变成 1。归一化后,dot(q, d) = cosine(q, d),且 L2(q, d)² = 2 - 2 × cosine(q, d),因此精确搜索时这三种方式的排序一致。是否归一化、使用哪种距离函数,应遵循模型的检索配置。

例如,语义检索希望得到这样的匹配关系:

text
"怎么给猫买喂食器"  vs  "自动猫粮机推荐"      -> 相似度高,应该命中
"怎么给猫买喂食器"  vs  "今天股市怎么样"       -> 相似度低,应该过滤

注意两点:

  • 不同模型的分数不可直接比较。模型 A 的 0.82 和模型 B 的 0.82 不是同一个含义,分数也不表示相关概率。
  • “最相似”不等于“能回答”。检索结果还要看是否包含足够事实、是否过期、是否有权限、是否被噪声污染。

维度:为什么常见是 384、768、1024、1536、3072 ​

维度就是向量里有多少个数字。比如 1536 维,就是每条文本变成 1536 个浮点数。

384、768、1024、1536、3072 都是常见的输出维度,取决于模型设计;部分模型还支持配置输出维度。这些数字不能直接当作模型的质量等级。维度主要影响:

  • 存储:float32 每维占 4 字节。100 万条 1536 维向量,原始向量约 6.1 GB;3072 维约 12.3 GB,均按十进制计算,还没算索引和元数据。
  • 计算:其他条件相同时,向量越长,一次距离计算的开销越大。
  • 表示容量:同一模型缩短维度可能损失部分检索质量;不同模型之间,不能仅按维度比较效果。

先用一组带正确证据标注的问题评测检索效果,再比较存储和查询成本。如果 768 或 1024 维已经满足要求,就没必要直接上 3072 维。缩短向量也应使用模型支持的配置,不能任意截取前几维。

常见 embedding 模型 ​

下面列出几类常见选择,便于比较 API 服务和本地部署模型:

模型 / 系列常见维度适合场景备注
text-embedding-3-small1536,可用 dimensions 缩短通用 RAG、OpenAI 生态可作为 API 方案的评测起点
text-embedding-3-large3072,可用 dimensions 缩短通用 / 多语言检索与 small 比较检索收益和调用成本
text-embedding-ada-0021536老项目维护老一代模型,新项目通常优先看 text-embedding-3-*
Cohere embed-v4.0默认 1536,可选 256 / 512 / 1024多语言、长上下文、文本+图片混合检索支持可调维度和多种 embedding 类型
Voyage voyage-4 / large / lite默认 1024,可选 256 / 512 / 2048通用、多语言检索voyage-code-* 系列面向代码检索
BAAI bge-m31024中文 / 多语言、本地或私有化部署支持 dense、sparse、multi-vector 等检索形态
BGE small/base/large-en-v1.5384 / 768 / 1024英文检索、本地实验轻量到中等规模开源选择
all-MiniLM-L6-v2384英文语义搜索、聚类、本地原型模型较小,中文任务需另选或单独评测

选模型时,不要只看排行榜。更实用的判断顺序:

  1. 语言:中文、多语言、代码、法律、金融等领域差异很大。
  2. 任务:问答检索、聚类、分类、推荐、代码搜索,不一定同一个模型最强。
  3. 部署方式:API 省心但要考虑数据合规;本地模型可控但要管推理服务。
  4. 成本与延迟:分别测量批量入库、查询编码和向量检索;API 计费不一定随输出维度变化。
  5. 可复现性:模型版本、维度、归一化策略、chunk 规则都要记录,否则换模型后难排查。

查询与文档的编码配置 ​

入库与查询需要使用配套的编码配置,输出向量必须处于同一空间。通常会用同一个模型,但查询和文档可能要求不同的前缀或 input_type;也有采用成对训练编码器的检索模型。

不同模型学出来的是不同“坐标系”。即使输出维度相同,直接比较两个不兼容模型的向量,也不能得到可靠的检索排序。

换模型时通常要做这些事:

  • 重新切分或至少重新确认 chunk 规则。
  • 用新模型把所有文档重新 embedding。
  • 新建 collection / index,避免新旧向量混在一起。
  • 用同一批评测问题比较旧库和新库的召回质量。
  • 记录 embedding_model、dimension、chunk_version、created_at 等元数据。

Dense、Sparse、Multi-vector:三种检索表示 ​

RAG 里常说的 embedding 通常指 dense embedding,也就是一整串稠密浮点数。但实际检索不止这一种。

类型代表优点短板
Dense embeddingOpenAI / Cohere / Voyage / BGE dense擅长语义相似,能跨同义词、改写表达对精确编号、人名、型号、否定条件有时不够稳
Sparse 表示BM25、SPLADE、BGE-M3 sparse保留词项权重,适合术语、编号等匹配BM25 对同义改写较弱;学习式稀疏模型可通过词项扩展改善
Multi-vector / late interactionColBERT、BGE-M3 multi-vectortoken 级匹配更细,质量潜力高存储和计算更贵,系统复杂度更高

BM25 根据词频等统计量打分,SPLADE 等模型则学习稀疏权重,两者的实现不同。工程上可以把 dense 检索和 BM25 结合,再用 reranker 重排,分别处理语义匹配和精确词匹配。

语义搜索与关键词搜索如何配合 ​

关键词搜索匹配字面:搜“汽车”可能找不到只写了“轿车”的文档。 语义搜索匹配意思:能找到“轿车”“新能源车”“家用车”等相关表达。

但语义搜索也有短板。遇到这些内容时,关键词搜索常常更准:

  • 订单号、合同号、SKU、错误码:A1002、ERR_CONN_RESET
  • 人名、公司名、地名、专有名词
  • 精确版本:Python 3.12、Milvus 2.5
  • 表格里的短字段:状态、枚举值、金额、日期

否定和例外条件需要另外检查。例如“不支持退款”和“支持退款”可能同时出现在候选中,单靠词项命中或向量分数都不足以判断适用规则。

例如,客服知识库里有“错误码 TS-999 的处理步骤”和“常见连接错误排查”两条记录。用户问“TS-999 怎么办”时,向量检索可能同时召回两条;关键词匹配可以保留错误码这个精确条件。混合检索(hybrid search)通常按以下流程组织:

text
用户问题
  -> 两路召回:dense 向量检索找语义相关,BM25 / 全文检索找精确词
  -> 合并候选
  -> reranker 重排
  -> 选择证据片段,交给 LLM 生成答案

向量索引:为什么能在百万数据里快速找最近 ​

如果有 1000 万条向量,每次查询都和所有向量逐一算距离,成本会很高。 向量库通常使用 ANN(Approximate Nearest Neighbor,近似最近邻)索引,在召回率、延迟和内存占用之间做取舍。

常见索引思路:

索引核心直觉适合场景代价
Flat / brute-force全量逐个比较小数据、评测基线、最高召回慢,数据大后不可接受
HNSW建一张“近邻图”,搜索时沿图跳转低延迟、高召回、通用生产场景内存占用高,构建成本高
IVF先把向量分桶,查询只搜相近桶大规模、可调速度和召回需要训练/建桶,参数不当会漏召回
PQ / Quantization把向量压缩成更小表示超大规模、内存受限有精度损失,调参复杂
Disk-based index把部分向量或索引放磁盘数据大到内存放不下依赖 SSD / NVMe,延迟更敏感

调参时主要看三个指标:

  • ANN Recall@k:相对于相同距离函数和过滤范围下的精确 top-k,近似搜索找回了多少结果。
  • Latency:一次查询多久返回。
  • Memory / storage:索引和向量占多少资源。

ANN 召回率衡量索引是否漏掉近邻;业务召回率衡量是否找到了能回答问题的证据。前者很高时,仍可能因为模型或分块不合适而漏掉关键内容。

向量数据库 ​

向量数据库专门存 embedding,并支持“快速找最近邻”。一般不只存向量,还会存文档内容和元数据。

一条 RAG chunk 记录通常包含原文、向量和元数据,下面只列出向量的前三个分量:

json
{
  "id": "handbook:p12:chunk-03",
  "text": "报销单需要在发票开具后 30 天内提交...",
  "embedding": [0.012, -0.034, 0.087],
  "metadata": {
    "source": "employee-handbook.pdf",
    "page": 12,
    "section": "财务制度",
    "updated_at": "2026-08-07",
    "tenant_id": "company-a",
    "embedding_model": "text-embedding-3-small",
    "dimension": 1536,
    "chunk_version": "v1"
  }
}

常见选择:

工具更准确的定位适合场景备注
Chroma轻量向量库本地 demo、小项目、教学上手快,适合先把 RAG 跑通
FAISS高性能向量检索库,不是完整数据库本地批量检索、研究、需要 GPU/高性能索引需要自己处理元数据、服务化、权限等
Qdrant专用向量数据库自托管 / 云服务、过滤条件多、RAG 生产payload 过滤、HNSW、dense/sparse/named vectors 生态成熟
Milvus / Zilliz分布式向量数据库更大规模、多集合、高吞吐功能完整,部署和运维比轻量方案重
Weaviate向量数据库 + 搜索能力想要 schema、模块化向量化、hybrid search对混合检索和对象 schema 支持友好
Pinecone托管向量数据库快速上线、少运维、云上生产省心,但要接受供应商成本和能力边界
pgvectorPostgreSQL 扩展已经有 Postgres、数据量中等、强事务/SQL/权限需求业务表和向量放一起很方便,大规模要认真调索引
Elasticsearch / OpenSearch搜索引擎 + 向量能力已有全文检索体系,想做 hybrid search适合关键词搜索强、向量只是增强的系统
Redis Vector Search内存数据库 + 向量检索低延迟、缓存型语义检索内存成本高,持久化和复杂检索要评估

简单选型:

  • 学习 / 原型:Chroma 或 FAISS。
  • 已经有 PostgreSQL:先试 pgvector,别急着引入新数据库。
  • 过滤条件很多:Qdrant、Weaviate、Milvus 这类专用向量库更顺手。
  • 不想运维:Pinecone、Weaviate Cloud、Zilliz Cloud、Qdrant Cloud。
  • 超大规模 / 多租户 / 高吞吐:优先评估 Milvus、Qdrant、Pinecone 等生产型方案。

RAG 里 embedding 的完整位置 ​

Embedding 为检索提供向量表示,检索结果中的原文再交给生成模型。两者可以独立选型:更换生成模型通常不需要重算文档向量,更换 embedding 模型则通常需要重建向量索引。

text
离线入库:
原始文档 -> 清洗 -> 切 chunk -> 生成 embedding -> 写入向量库

在线查询:
用户问题 -> 生成 query embedding -> 在权限及 metadata 约束下检索
       -> reranker 重排 -> 取证据片段
       -> 拼进 prompt -> LLM 生成答案 -> 返回引用来源

几个关键参数:

参数常见起点影响
chunk size300-800 token 起步太小缺上下文,太大噪声多
overlap10%-20% 起步防止答案被切断,但会增加成本
top_k5-20 个候选太少漏资料,太多塞爆上下文
rerank top_n3-8 条最终给 LLM 的证据数量
score threshold需要用评测集调防止低相关内容混进答案

用评测集调整这些参数,观察正确证据是否进入候选、最终上下文是否保留了回答所需条件。分块和上下文组装的具体流程见 RAG 完整链路。

聚类:embedding 不只用来搜索 ​

聚类(clustering)就是:不预先告诉模型“应该分成哪几类”,让算法自己按相似度把一批样本分成若干组。 你可以把它理解成“自动分堆”。比如一堆客服消息里,算法自己把“退款”“物流”“登录失败”“发票问题”分开。

它和分类不一样:

  • 分类:你先告诉模型类别是什么,比如“退款 / 物流 / 登录”。
  • 聚类:你先不给类别标签,让算法自己发现哪些东西像一类。

聚类不是 RAG 必需环节,但在这些任务里很有用:

  • 给文档、工单、评论自动发现主题。
  • 做语义去重,把重复 FAQ 合并。
  • 从大量资料里抽样,保证覆盖不同主题。
  • 给长期记忆分区,减少每次检索的候选范围。
  • 做异常检测,找和常规数据不一样的输入。

常见聚类算法:

算法是否要先给簇数 K适合场景注意点
K-Means要数据量大、希望分成固定 K 个主题假设簇比较“圆”,对 K 和初始化敏感
MiniBatch K-Means要更大数据量、速度优先质量可能略低,但很适合百万级粗分组
Agglomerative 层次聚类可给 K,也可给距离阈值小中规模、想看层次结构数据大时慢,内存压力大
DBSCAN不要想自动发现密集区域和噪声点eps 很难调,高维文本向量上不一定稳定
HDBSCAN不要密度不均、希望识别离群点仍需调 min_cluster_size 等参数,高维距离也可能难以区分样本
Spectral Clustering通常要非凸形状、小中规模图聚类计算更贵,不适合直接上大规模
Gaussian Mixture要想要“软分类概率”假设数据近似高斯,高维文本上要谨慎

做文本 embedding 聚类时的实用建议:

  • 按模型建议决定是否做 L2 归一化,并选用与距离度量匹配的聚类算法。
  • 数据很多时,先用 MiniBatch K-Means 做粗分组,再人工看每组代表样本。
  • 做可视化可以用 UMAP / t-SNE 降到 2D,但不要把 2D 可视化结果当最终真相。
  • 聚类结果要人工抽检。silhouette_score 这类指标只能辅助,不能替代业务判断。
  • 文本太短时聚类会漂。比如只有“好”“不行”“已处理”,缺上下文就很难分。

常见易忽略的细节 ​

  • 只存向量,不存原文和来源:最后 LLM 无法引用证据,也无法排查检索错在哪里。
  • 不兼容模型的向量混在同一个 collection:维度可能相同,但距离无法反映内容的相关性。
  • 维度和索引不匹配:例如库建的是 1536 维,查询传 1024 维,会直接报错或无法检索。
  • 忽略权限过滤:先检索再过滤可能泄露候选信息;企业场景要把 tenant_id、ACL 等作为强过滤条件。
  • 只用语义检索查编号:订单号、错误码、合同号优先走关键词或 metadata 精确过滤。
  • chunk 切得太粗:一个 chunk 里混了多个主题,检索命中后 LLM 仍然找不到答案。
  • chunk 切得太碎:召回的是半句话,缺少解释条件和上下文。
  • 混淆召回与重排:reranker 可以改善候选顺序,但找不回未进入候选集的片段。
  • 不记录版本:换模型、换切分策略后,很难解释线上效果为什么变化。
  • 只看向量库返回分数:分数高不代表事实足够,必须看文本本身。

持续学习,持续实践。