资讯动态

Embedding计算全流程拆解:从原理到RAG与语义搜索实战

发布时间:2026/9/15 18:26:19 来源:尧图企业网站定制
做RAG和语义搜索这些年几乎每个项目都在跟embedding打交道。但说句实在话真正把“embedding计算过程”从头到尾讲清楚的人不多多数教程一上来就调库、跑模型、算相似度至于中间那几步到底发生了什么、为什么要这样做很多人是迷糊的。这篇文章我就把embedding从输入到输出的完整计算链路拆开揉碎结合这几年实际项目中踩过的坑把核心原理、模型选型、微调方式和排查技巧一次讲透希望能帮刚入门的朋友少走弯路也能给有经验的同行提供一些参考。1. 先从根上理解Embedding到底在算什么1.1 从One-Hot到稠密向量要说清楚embedding必须先回到它要解决的问题。在深度学习普及之前自然语言处理面对的最大难题是计算机不认识文字只认识数字。早期最朴素的做法是One-Hot编码比如词表里有“苹果”“香蕉”“手机”三个词就把每个词表示成[1,0,0]、[0,1,0]、[0,0,1]这样的向量。这种方式简单粗暴但有两个致命问题第一是维度灾难。真实场景的词表动辄几十万每个词都是一个几十万维的向量存储和计算成本都高得离谱。第二是语义鸿沟。[1,0,0]和[0,1,0]之间没有任何几何关系模型无法从向量中感知到“苹果”和“香蕉”都是水果而“手机”和它们无关。Embedding的核心思路就是把这种超高维、离散、无语义信息的稀疏表示压缩成一个低维、连续、携带语义信息的稠密向量。说白了就是用训练数据让模型学会把语义相近的词映射到向量空间中距离更近的位置。这个“映射过程”就是embedding的计算过程。从数学上看embedding本质上是一个从离散符号空间到稠密向量空间的函数。这个函数可以是一个简单的矩阵乘法查表也可以是一个复杂的神经网络编码器比如BERT具体怎么做取决于你用的是哪一代的embedding技术。1.2 计算过程的全貌查表还是训练很多人以为embedding就是拿一个训练好的模型把文本扔进去出来的向量就是结果。这个理解没错但忽略了中间的关键细节。一次完整的embedding计算实际上包含三个环节第一个环节是输入处理。文本要先经过分词器Tokenizer切成token序列每个token对应词表中的唯一ID。第二个环节是向量映射。把token ID序列输入模型得到一个或多个向量。如果是Word2Vec这类静态模型直接用查表方式取向量如果是BERT这类上下文模型还要经过多层Transformer编码每个token都会得到一个上下文相关的向量。第三个环节是向量融合。一个句子通常包含多个token需要把多个token向量合并成一个句子向量这一步叫Pooling池化。这三个环节里最容易出问题的就是第三个。我发现很多新人直接用模型的last_hidden_state第一个token的向量当句子向量或者直接对所有token向量求平均结果相似度计算总是不对劲。后面我会详细说Pooling策略怎么选这里先记住一个结论embedding计算不是“模型输出一个向量”这么简单而是一整套pipeline每一环都影响最终效果。2. Embedding模型家族不同场景怎么选2.1 经典静态EmbeddingWord2Vec、GloVe、FastText先说静态embedding代表是Word2Vec、GloVe和FastText。这类模型的特点是每个词对应一个固定的向量无论上下文怎么变“苹果”的向量永远是同一个。Word2Vec有两种训练方式CBOW用上下文预测中心词Skip-gram用中心词预测上下文。训练完成后模型中间层的权重矩阵就是词向量表。Word2Vec能学到语义关系比如经典的“国王-男人女人女王”这是因为它训练时利用了词的共现统计信息。但静态embedding的短板也很明显一词多义问题无法解决。“苹果”指水果还是公司这两个含义共享一个向量语义上必然存在冲突。GloVe在共现矩阵上做矩阵分解理论上能更好利用全局统计信息但多义词问题依然存在。FastText的核心改进是把词拆成字符n-gram对未登录词OOV更友好因为它可以通过子词组合生成词向量。在今天的RAG和人设问答场景里静态embedding已经很少作为主力方案了但它作为理解embedding原理的入门材料非常合适。而且在某些垂直领域如果数据量小、计算资源有限用FastText训一个自家词表效果可能比大模型还好用。2.2 上下文感知EmbeddingBERT及其变体2018年BERT出现后embedding领域发生了质变。BERT对每个token生成的向量都是上下文相关的。“苹果”在“苹果很好吃”和“苹果发布了新手机”两个句子里得到的向量完全不同。这种动态语义表示能力让文本相似度计算、语义搜索的精度大幅提升。BERT类模型的embedding计算过程和Word2Vec有本质区别。Word2Vec是查表BERT是编码。输入token经过Embedding层后还要经过12层base版或24层large版Transformer编码器每层都在做自注意力计算层层堆叠后最后一层的输出向量就是每个token的上下文表示。这也带来一个问题计算开销比静态embedding大好几个数量级。实际使用中用BERT类模型做embedding通常不会直接用最后一层原始输出而是结合不同层的信息。比如有研究说倒数第二层或中间层的输出在某些任务上效果更好还有一些方案是把最后四层加权求和再池化。这就是为什么会在很多开源embedding模型的说明文档里看到“layer_strategy”或者“pooling_method”这类参数——它们决定了怎么从模型的内部状态中提取最终的句子向量。2.3 专为检索优化的Embedding模型BGE、M3E、OpenAI、Qwen3-VL从2023年开始社区出现了一大批专为检索和RAG优化的embedding模型比如智源的BGE系列、M3EMoka Massive Multilingual Embedding、OpenAI的text-embedding-3-small/large以及Qwen系列的多模态embedding模型。这些模型和通用BERT类模型的关键区别在于训练目标。通用模型训练目标是语言建模预测mask词而检索优化模型训练目标是让相似文本对的向量距离更近、不相似文本对的向量距离更远。这种训练方式叫对比学习Contrastive Learning一般用InfoNCE损失或者Triplet Loss。训练数据是海量的“查询-文档”对和“正例-负例”对。选型的时候我有一个习惯先看目标语言和领域。中文场景优先考虑中文预训练模型比如BGE-large-zh-v1.5、M3E-large英文或跨语言场景再考虑OpenAI或者多语言模型。再看维度需求像OpenAI的text-embedding-3-small支持动态调整维度从3072降到更低BGE系列也有不同量级可选。最后要考虑的是部署成本大规模检索场景中向量维度直接决定存储和计算开销这一点容易被人忽视。Qwen3-VL是比较新的多模态embedding方向支持图片和文本的统一向量表示在图文检索、多模态RAG里很有应用前景。如果项目涉及图搜文、文搜图可以关注它的论文和开源实现核心突破在于用统一的特征空间来对齐视觉和语言的语义。2.4 图EmbeddingSDNE原理简述除了文本embedding也广泛用于图结构数据比如社交网络、知识图谱、分子结构。图embedding的目标是把节点映射成低维向量同时保持图上的结构相似性和语义相似性。思路和文本embedding一脉相承但实现方式差异很大。SDNEStructural Deep Network Embedding是图embedding中的一个经典模型它的核心思想是用深度自编码器同时优化一阶相似度和二阶相似度。一阶相似度指两个节点直接相连向量应该接近二阶相似度指两个节点共享很多共同邻居向量也应该接近。SDNE用邻接矩阵作为输入自编码器中间层的输出就是节点的embedding向量。同时它还借鉴了拉普拉斯特征映射的思路对直接相连的节点加一个惩罚项让它们在低维空间更靠近。对比其他图embedding方法Node2Vec基于随机游走生成节点序列再用Word2Vec训练向量本质上还是静态embedding的路子GraphSAGE则引入了邻居采样和图卷积支持对未见过节点作推理更适合动态图。SDNE的优点是对网络局部结构刻画得细缺点是需要对整个邻接矩阵做矩阵运算扩展性上不如采样类方法。如果项目是知识图谱推理或推荐系统图建模可以按数据规模在图Embedding方法里做取舍。3. Embedding计算过程实操拆解从Tokenizer到向量输出3.1 Tokenizer到底在做什么大部分embedding模型跑起来第一个步骤就是Tokenizer。以BERT为例Tokenizer会先把“我喜欢吃苹果”这句话拆成[我, 喜欢, 吃, 苹果]。注意这不是简单按字切词。中文的tokenizer一般基于词表做匹配英文则用BPEByte Pair Encoding算法把单词拆成子词。Tokenizer完成后每个token会对应一个数字ID这是模型能处理的输入格式。计算过程的第一个关键参数就在这里max_length。设得太短会截断长文本丢失关键信息设得太长会浪费计算资源而且可能引入噪声。我在项目中常用的做法是先统计语料长度分布选择覆盖80%~90%文本的长度作为阈值避免一刀切。Tokenizer还会产生attention_mask标记哪些位置是真实token、哪些是padding补全的部分。后续在Pooling阶段必须用注意力掩码过滤掉padding位置否则会把无效的[PAD]向量混入句子向量拉偏语义表示。这是一个很隐蔽但影响很大的坑。3.2 Embedding层与编码器的分工Token化的ID序列进入模型后先经过Embedding层转换为向量。这个Embedding层本质是一个查表矩阵矩阵的行数是词表大小列数就是模型的hidden size基础BERT一般是768large版本是1024。每个token ID在矩阵中找到对应行得到初始向量。但这一步得到的向量还不携带上下文信息。它只是把“苹果”映射成模型学习到的静态表示后续的Transformer编码层才是真正处理和计算上下文的环节。自注意力机制会让每个token的向量不断吸收其他token的信息比如“苹果”在“苹果好吃”里会更多地融入“好吃”的信息在“苹果手机”里会更多融入“手机”的信息。层数越深这个上下文融合越充分。实操中有个很常见的困惑既然最后一层包含最丰富的上下文信息为什么不直接用最后一层的输出原因在于检索任务需要的句子语义表示不一定完全等价于语言建模任务的最终表示。有些信息比如语法细节对生成任务重要但对相似度计算反而是干扰。所以很多embedding模型采用了“平均倒数几层”的方式提取更平缓、更通用的语义特征。具体选哪几层、怎么加权建议直接参考模型发布方给出的推荐配置。3.3 从Token向量到句子向量Pooling策略全解析这一步是整个计算过程中最容易被忽视的环节也是导致效果“莫名其妙变差”的头号元凶。BERT输出的是每个token的向量维度是[seq_len, hidden_size]但一个句子在语义检索中需要一个固定维度的向量所以必须做池化。常见的池化方式有三种CLS向量、平均池化和最大池化。CLS向量直接取序列第一个[CLS]token的输出作为句子向量BERT预训练时这个位置聚合了整句信息但实际效果并不总是最好。平均池化是对所有非padding token的向量求平均语义更平滑在句子相似度任务上通常表现稳定。最大池化取每个维度上的最大值能保留最显著的特征但受噪声词影响大实际用得不那么多。还有一种方式叫加权池化根据词的权重比如TF-IDF权重对token向量加权平均。这种方式在长文档检索中效果好一些因为可以弱化停用词和无关词的干扰。选择了哪种池化方式决定了检索效果的上限。特别是遇到“语义相似但词面不相似”的情况池化策略不同结果差别会很明显。我的建议是不要只看模型文档里的默认值最好在你自己领域的标注数据集上做一次小规模消融实验实测不同的池化方式从中选出效果最好的。这个步骤花不了多少时间但对最终RAG效果的影响远大于换一个更大参数的模型。3.4 相似度计算的细节归一化与度量方式Embedding计算得到的向量最终通常要用来计算相似度。常见的度量方式有欧氏距离、余弦相似度和点积。大多数检索系统默认用余弦相似度因为对向量长度不敏感只关心方向这与语义相似度的直觉比较一致。但很多人没有注意到在计算余弦相似度之前是否对embedding向量做L2归一化会直接影响点积和余弦相似度的等价性。如果两个向量都已经做了L2归一化那么点积、余弦、欧氏距离三种度量方式在排序效果上是完全等价的。这也是为什么很多向量数据库如Milvus、Faiss在写入时要求开启归一化——归一化之后可以直接用内积代替余弦计算更快。实操中还有一个细节容易被忽略不同模型产出的向量最好不要直接混合计算相似度。因为不同模型的向量空间语义分布不对齐一个模型里的“猫”可能正好落在另一个模型里的“狗”附近强行比较没有意义。所以在多源异构数据场景要么统一模型要么做向量空间对齐比如训练一个映射层把两个空间的向量映射到同一空间否则检索结果会非常离谱。4. 模型微调与部署LoRA微调Embedding模型的实战经验4.1 什么情况下需要微调Embedding模型预训练embedding模型在通用领域表现不错但一落到垂直领域就有点“水土不服”。比如法律条文、医疗报告、金融公告这类文本术语密集、表达方式和通用语料差异很大直接用通用模型做检索经常会召回一些看似相关实则无关的内容或者错过关键文档。这时候就要考虑微调了。但微调embedding模型和微调LLM的路径不太一样LLM微调用交叉熵损失学文本生成embedding微调需要用对比学习损失学向量空间结构。一个很经典的做法是用领域内的“查询-相关文档-不相关文档”三元组微调预训练embedding模型让查询向量和正例更近、和负例更远。判断是否需要微调可以先做一个快速测试抽几十个典型查询用现成模型跑一遍检索看看召回的文档是否命中了你预期的关键信息。如果明显偏离或者排名不分先后那基本可以判定模型没有理解领域语义微调是值得投入的。4.2 LoRA微调Embedding模型中如何操作LoRALow-Rank Adaptation是一种参数高效微调技术。它的核心思想是冻结预训练模型的原始参数在模型的线性层旁边引入两个低秩矩阵微调时只更新这两个低秩矩阵。这样训练参数量可以压缩到全量微调的1%甚至更低显存占用和训练时间都大幅下降。用LoRA微调embedding模型时有几个关键点值得特别注意。第一要明确在哪些层注入LoRA。文本embedding模型中Self-Attention层的Q、K、V、O矩阵是最常见的注入点因为它们对语义表示影响最大。第二LoRA的秩rank直接影响表达能力一般从8到64之间选择过大容易过拟合训练集过小可能学不到领域特征。第三训练目标函数最好用InfoNCE或对比损失搭配in-batch负样本采样策略。我的一个实操心得是微调数据的质量远比数量重要。几百条精心标注的查询-正例-负例三元组往往比几千条自动挖掘的噪声数据更有用。负例的选取很关键——最好用难负例也就是那些表面相似但实际不相关的文档这样模型才能真正学到细粒度语义差异。如果负例太容易区分模型学不到什么有效信息。LoRA微调完成后还有一个容易踩坑的点推断时要把LoRA权重合并回原始模型或者用专门的支持LoRA的推理框架。合并权重后得到的模型大小和原始模型一样部署和常规推理没有区别。如果推理框架不支持LoRA就只能在导出模型时预先合并否则加载权重会出现维度不匹配或结果异常。4.3 部署与推理的常见坑Embedding模型部署看起来简单就是加载模型、输入文本、输出向量但实际生产环境中有不少细节会影响稳定性。第一个坑是并发和的显存控制。Embedding模型虽然比LLM小但多个请求并发时如果batch设置过大显存还是会被打满导致OOM。一般建议推理时动态batch根据输入长度实时计算显存占用避免一次性把所有请求都灌进去。第二个坑是向量维度和向量数据库的匹配。假设模型输出768维但向量数据库集合创建成了1024维写入时会直接报错或静默截断。这种问题一旦上线会造成数据错乱排查起来还特别费劲因为很多数据库的错误提示并不直观。建议在写入前先跑一个维度校验脚本。第三个坑是长文本的处理策略。前面提到tokenizer有max_length限制但真实业务里用户粘贴一篇几千字的文档很常见。如果直接截断标题和开头的信息会保留但中间和结尾的关键内容可能被切掉。更合理的做法是分段embedding把长文本切成多个chunk分别向量化再用均值或加权方式得到文档级向量。分段大小、重叠率如何设置直接关系到RAG的检索精度我一般从256到512个token之间做实验找到当前数据集的最优配置。5. 常见问题与排查技巧实录5.1 指标诡异先查数据再查模型做embedding项目遇到最多的困惑就是“模型没问题指标就是上不去”。我见过很多次团队花大量时间调模型结构、换更大的底座结果问题出在数据上。有一个典型案例某次做电商商品搜索查询是“蓝牙耳机”但商品标题里有大量“蓝牙音箱”“蓝牙适配器”。模型检索出来的结果看着好像都很相关但用户要的其实是“耳机”不是“音箱”。这种场景靠通用模型解决不了必须微调或调整训练数据让模型学会区分品类边界。另一个高频问题是同一句话稍微改几个词向量相似度就暴跌。比如“如何办理签证”和“办理签证需要什么材料”语义很接近但因为词面差异较大相似度可能只有0.7甚至更低。解决方案有多方面尝试不同的pooling策略、换更大的模型、或者用文本增强的方式构造更多语义等价的训练样本。总之指标出问题先回数据别急着换模型。5.2 维度选择的权衡Embedding维度是另一个容易被忽视但影响深远的参数。高维度能捕获更多语义细节但也会带来更高的存储成本和计算开销。一个1000万条数据的集合从768维换成3072维存储会膨胀近4倍而且高维向量在ANN近似最近邻检索中容易出现维度灾难检索效率下降。实践中我的建议是先用中等维度768或1024跑通pipeline验证业务效果再考虑是否需要用大模型和更高维度去精调。如果数据量级在百万以内768维完全够用。如果追求极致效果且资源充足可以考虑更高维模型但务必评估向量数据库的索引参数是否需要调整。很多数据库在高维场景下HNSW的M参数和efConstruction参数需要重新调优否则召回率会有明显下降。5.3 向量检索效果差可能是文本预处理的问题很多embedding检索效果差根因根本不是模型而是数据没有做基本的清洗。比如网页爬下来的文本充满HTML标签、乱码、超长空白这些噪声会让embedding模型的注意力被错误吸引。我建议在embedding之前先做一套标准的文本清洗流程去HTML标签、去空白符、统一大小写、处理特殊字符。这样有时什么都不用改效果就能提升不少。还有一个容易被忽略的问题语言一致性。用户查询是中文但语料库里有大量中英混杂文本模型可能把中文查询和英文文档在某些维度上错误对齐。解决方案有两类要么把语料统一翻译成一种语言要么使用专门的多语言embedding模型并且在query侧和文档侧保持相同的预处理流程。5.4 Embedding模型更新后旧向量怎么办生产环境中模型升级是必然的。但新模型产出的向量空间和旧模型不一致如果直接混用检索质量会受影响。比较常见的做法是双写兼容期。新模型上线时正对线上流量逐步迁移先将新模型和老模型并行运行离线对一批核心查询做AB对比确认效果稳定后再用离线任务把全量旧向量重新算一遍并切换索引。这个成本比较高但可靠。如果切换成本实在太高也可以考虑用映射网络对齐两个模型的空间把旧向量映射到新空间里。但说实话映射方式毕竟有精度损失除非实在没法全量重算否则不建议作为首选方案。后记做embedding项目这几年我最大的体会是不要迷信“换更大的模型就有效果”。很多问题的瓶颈在数据质量、池化策略、预处理流程这些看起来不起眼的细节上。把基础原理吃透再把pipeline的每一环都调校到位往往比盲目堆参数更有效。另外每次上线新模型或调整策略一定记得保存当时的评测样本和评测结果方便后期对比回归。最后再分享一个实用小技巧如果手头没有标注数据可以用大语言模型批量生成“查询-正例-难负例”训练样本再人工抽检清洗成本低且能快速起步。希望这篇文章能给做RAG、语义搜索、向量检索的朋友一些实在的帮助。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价