Decoder-only架构做嵌入模型:原理、训练与面试要点解析
发布时间:2026/10/20 7:47:26 锦皓数字建站

如果你最近在准备大模型算法方向的面试八成会在题单里撞见这样一句话“分析一下 OnlyDecoder 架构怎么做嵌入模型”。我第一次看到这道题脑子里的第一反应是嵌入不是应该用双向编码器吗自回归模型不是只配做生成吗把这两个东西绑在一起怎么看都像是一场大乱斗。后来我在本地把一个中等规模的 decoder-only 模型微调成嵌入模型又对着注意力矩阵反复推了好几遍才终于理清这不是一个“能不能”的问题而是一道关于表征出口、训练目标、部署成本的综合题。这篇文章不打算复述教科书只讲实操和面试视角的拆解适合正在准备算法岗面试的候选人也想帮想自己动手验证这个思路的工程师少走几个弯路。1. 面试现场为什么要把Decoder-only与嵌入模型绑在一起面试官不会无缘无故问一个偏门组合。把“Decoder-only”和“嵌入模型”放在同一道题里背后考察的不是记忆而是你对模型架构、表征学习和工程取舍的综合理解。1.1 嵌入模型到底在求什么嵌入模型的核心任务是把一段变长的文本映射成一个定长向量并且让语义相近的文本在向量空间里距离更近。这个定义说起来轻巧但有两个隐含要求很关键第一向量要能承载足够丰富的语义信息第二向量空间的结构要对下游任务友好比如检索时能按距离排序、聚类时能形成自然的分群。传统做法是训练一个双向编码器。双向编码器在编码某个词时能看到它左边和右边的全部上下文因此产生的词向量天然带有“全局视野”。句子层面则通过取[CLS]位置输出、或者对词向量做池化来获得。这种方法在分类、检索、语义相似度等任务上表现稳定所以很长一段时间内大家默认嵌入模型就该长这样。但嵌入模型的本质并不是“必须用某个架构”。它要的只是一个好的向量出口。谁能提供更好的向量出口谁就能当嵌入模型的底座。这正是Decoder-only有机会进场的原因。1.2 Decoder-only和Encoder在信息获取上的根本差异Decoder-only 使用的是因果注意力也叫自回归注意力。它限制每个位置只能看到当前和之前的token不能看未来的token。这种设计是为了生成任务服务的预测下一个词时绝对不能让模型偷看答案。而双向编码器用的是双向注意力每个位置可以同时看左右两侧的信息。对表征学习来说双向上下文通常更有利——理解“苹果好吃”和“苹果手机好用”里的“苹果”需要结合后文的“好吃”或“手机”才能准确判断含义。这个差异导致一个直觉上的矛盾Decoder-only 架构本身是“残缺”的编码器为什么还要用它做嵌入面试时这个问题如果答不好后面基本就垮了。正确思路是承认单向注意力确实损失了一部分上下文信息但要指出这种损失可以通过训练目标、池化策略和数据构造来弥补并且Decoder-only在大模型时代有自己的独特优势。1.3 为什么这个偏门组合开始成为面试热点过去两年检索增强生成、向量数据库、语义缓存这些方向越来越火嵌入模型的上游基座也越来越多元化。很多从大规模预训练模型时代走过来的团队发现与其单独维护一个双向编码器嵌入模型不如让现有的自回归大模型兼任嵌入模型。这种做法的好处很直接第一部署上只保留一套模型权重第二嵌入模型能复用大模型训练积累的语言知识和世界知识第三支持更长的上下文窗口长文档处理能力比传统嵌入模型强很多。这些理由综合在一起让“Decoder-only做嵌入”从一个偏门思路变成了有真实落地场景的方案面试官自然也会拿它来考察候选人的架构迁移能力。2. 拆解架构Decoder-only做嵌入的几个核心决策真正动手做的时候你会发现最纠结的部分不是“能不能训练”而是“向量从哪里取、注意力方向要不要改、和传统方案比到底值不值”。这三个问题决定了整个方案的方向。2.1 表示从哪里取最后token、归纳token与多层池化Decoder-only 没有[CLS]这样的专用出口最常见做法是取序列最后一个token的隐藏状态。原因很简单因果注意力下最后一个token能“看到”序列里所有前序token所以它天然携带了整个序列的上下文汇总信息。如果你觉得只取最后一个token太粗暴可以考虑加一个可学习的归纳token放在句子末尾。归纳token不参与原始文本的语义但它可以通过训练学会“汇总前面所有内容”。这比直接取最后一个词的隐状态更干净——因为你不会让“苹果”这个具体词的表征承担整句的语义概括。另一种常见做法是多层池化。大模型的底层更偏向语法和词汇知识中间层偏语义顶层因为贴近生成任务反而容易携带下一词预测的偏置。你可以对多个中间层做加权平均也可以只取中间某几层拼接。实操中我发现取靠近中间的两到四层做平均有时比只取最后一层稳定尤其是当基座模型训练得很充分时。不管用哪种方式输出向量通常要做 L2 归一化。这样做一方面让向量模长统一余弦相似度计算更稳定另一方面也能把向量限制在单位超球面上后续做ANN检索时方便使用内积索引。2.2 因果注意力带来的“单向视野”怎么破很多人卡在一个点上Decoder-only不能看后文做句子相似度会不会吃亏我的回答是会但可以补救而且没必要强行改变架构。如果你坚持原始的单向注意力核心补救手段是“把后文信息塞到前面”。比如在句子后面追加一个特殊后缀像是“这句话的语义载体是 [EOS] ”让模型知道自己需要在末尾输出一个汇总向量。这种做法本质上是把“双向依赖”转化为“单向的长程汇总”实践起来不需要改注意力矩阵成本很低。更激进的处理是把某些层的因果掩码改成双向掩码让模型变成部分双向。这种办法在效果上会接近传统编码器但代价是推理速度下降也失去了严格的自回归约束。如果面试官问“能不能把因果注意力改成双向”建议先反问一句“改完之后这个模型还能正常生成文本吗”。如果不能统一模型的价值就没了。所以我的建议是不要轻易改注意力结构而是通过训练数据、池化方式和训练目标来弥补单向信息损失。2.3 与Encoder嵌入模型相比的收益与代价拿传统的双向编码嵌入模型和Decoder-only嵌入模型摆在一起时可以做一个直接对比。对比维度双向编码器嵌入模型Decoder-only嵌入模型注意力视野双向能看完整上下文单向只能看左侧预训练目标掩码语言建模适合表征下一词预测偏向生成上下文长度通常较短几千token以内可支持更长文本模型规模与知识量通常较小知识有限通常较大知识更丰富训练成本较低较高多任务统一只能做嵌入不能生成既能嵌入又能生成推理延迟相对较低相对较高KV缓存占用大这张表基本概括了面试时会遇到的价值判断Decoder-only更贵、更慢但它换来了长上下文和大模型知识双向编码器更轻、更快但天花板在前置。实际选型没有绝对的对错只看你到底需要什么。3. 实操流程把一个小型Decoder-only模型训练成嵌入模型理论聊得再漂亮面试时被追问“你跑过吗”就露馅了。接下来我给出一个可以直接复现的最小流程从选基座、构造数据、定义损失到微调和评估全程不需要特别大的显存。3.1 选择基座与数据准备先选一个参数量适中的开源decoder-only模型作为基座。为什么要选中型而不是巨型因为对比学习训练对batch size敏感模型太大容易显存爆炸而且调试一次的时间成本太高。中小型模型足够你验证“Decoder-only能不能训练出有区分度的嵌入向量”这件事。数据准备是最容易被低估的一步。最简单可用的数据格式是成对的文本对比如“标题-正文”“问题-答案”“同义句对”。正样本是语义相关的一对文本负样本是语义不相关的其他文本。单纯用同义句对还不够最好是构造难负样本——比如同一类目下的两个不同商品描述或者同一个问题的不同答案。难负样本能逼着模型去学细粒度语义差异普通随机负样本学出来的是空泛的类别区分。如果手里没有现成的标注数据可以用自监督方式构造对同一句话做随机词屏蔽、词序打乱、同义词替换形成“原句与增强句”作为正样本。这种方式虽然不如人工标注精准但胜在规模大、成本低适合跑通流程。3.2 损失函数与训练参数对比学习是最主流的训练目标。以InfoNCE为例对于batch里的每个样本它有一个正样本其余样本都是负样本。模型要学到的是一件事——分配给正样本的相似度概率尽可能接近1同时把负样本的相似度压下去。损失函数的核心公式是[ L -\log \frac{\exp(\mathrm{sim}(q, p)/\tau)}{\sum_{i1}^{K} \exp(\mathrm{sim}(q, n_i)/\tau)} ]其中 (\mathrm{sim}) 通常用余弦相似度(\tau) 是温度系数。温度系数特别值得花时间调。温度高时所有负样本的权重都被拉平模型学得粗糙温度低时模型会集中火力把最难的负样本推开但温度过低会导致训练不稳定甚至梯度爆炸。我常用的初始范围是0.02到0.05调到0.05附近成功率最高。优化器一般用AdamW学习率要比训练生成模型时大一点但不要大到破坏预训练参数。我通常设1e-5到5e-5之间batch size越大越好至少32起步。batch太小负样本数量太少模型容易塌。如果显卡放不下大batch可以考虑梯度累积或者用队列缓存更多负样本这在MoCo这类方法里很常见。3.3 代码骨架编码、池化与训练循环这里给出一个精简可运行的PyTorch风格代码骨架。核心只有两块编码函数和对比损失函数。import torch import torch.nn.functional as F # 取序列最后一个有效token的隐藏状态作为句向量 def encode(model, input_ids, attention_mask): outputs model( input_idsinput_ids, attention_maskattention_mask, output_hidden_statesTrue ) hidden outputs.hidden_states[-1] # [batch, seq_len, hidden_dim] # 每个样本最后一个有效位置的索引 lengths attention_mask.sum(dim1) - 1 batch_idx torch.arange(input_ids.size(0)).to(input_ids.device) pooled hidden[batch_idx, lengths, :] return F.normalize(pooled, p2, dim1)训练循环里把query和doc都过同一个编码器然后算对比损失。def contrastive_loss(query_emb, doc_emb, temperature0.05): # query_emb、doc_emb 已经L2归一化 logits torch.matmul(query_emb, doc_emb.T) / temperature labels torch.arange(query_emb.size(0)).to(query_emb.device) # 同一batch内第i个query与第i个doc是正样本 loss F.cross_entropy(logits, labels) return loss注意这个对称操作假设了第 (i) 个 query 只与第 (i) 个 doc 为正样本。实际数据里如果一个正样本句对出现两次或者batch内有重复样本就要小心标签对齐。轻则会拖慢收敛重则会让模型学到错误的相似度分布。训练一段时间后可以顺手观察query和doc编码器的输出是否都还在同一个向量空间。如果发现query向量空间和doc向量空间发生偏移可以在损失开关加一个对称项比如同时计算doc query方向的对齐这样能更稳。3.4 评估与上线评估嵌入模型的黄金标准是“下游检索效果”。你可以在一个带标签的验证集上算RecallK比如对于每个query看正确文档是否出现在最相似的top 10里。也可以算NDCG10这样能同时考虑排序位置。上线的时候嵌入模型的推理不是单个向量计算而是要处理海量文本。路线通常是先对全量文档做一次性离线向量化建好向量索引再让每个query上线时只走一次编码器去索引里做近似近邻检索。索引工具可以选择普通的内积索引也可以用量化索引压缩内存。Decoder-only模型推理时比双向编码器多一个KV cache如果服务内存紧张建议做量化推理精度损失通常可以接受。4. 面试官的高频追问与答题思路只要把项目经历里写了“Decoder-only嵌入模型”面试官一定会往细节里问。下面几个问题是我自己面试和模拟面试时被问烂的提前准备好现场就不会慌。4.1 为什么不用双向注意力这样到底损失了什么先说损失了什么后文信息。有些任务高度依赖后文比如判断“苹果”是指水果还是手机牌子时看到后文组合“苹果发布会”才能确定。在纯单向注意力下模型读到“苹果”这个词时还没有看到“发布会”只能靠前面已经读到的词语和世界知识来推断精度会打折扣。但仔细拆一下很多检索场景里的query本身并不长用户输入的意图往往已经在句首就明确了。这时单向注意力的影响没有想象中大。更重要的是Decoder-only可以通过大规模对比学习和难负样本挖掘把“语言先验”注入到表示中从效果上弥补部分双向信息缺失。所以面试时不需要否认损失而是要说清楚“有损失但我用什么样的训练策略去弥补了”。4.2 微调后模型还能正常生成吗会不会遗忘这个问题非常关键。你如果用对比学习去微调一个decoder-only模型最直接的结果就是模型的生成能力受到破坏。因为对比学习的目标是拉近正样本、推远负样本这个梯度信号和“预测下一个词”的目标是冲突的。如果产品需求是同一个模型既做生成又做嵌入那就不能用简单的单任务对比学习微调。必须采用多任务混合训练让模型同时看到对比学习样本和语言建模样本用两个损失的加权来更新参数。也可以用LoRA这类参数高效微调方法把可训练参数限制在低秩子空间里这样对生成能力的影响更可控。如果完全不在意统一模型那最省事的做法就是微调之后把生成能力放在一边只把它当嵌入模型用。4.3 没有标注数据怎么训练嵌入模型纯无监督也是可行的。一种方案是“同句增强”对原始文本做随机词删除、随机词重排、同义词替换等扰动把原句和扰动后的句子当成正样本对。另一种方案是和生成目标结合比如让模型根据标题生成正文然后把标题和正文embedding拉近这等于把生成任务变成弱监督信号。负样本方面可以用batch内负采样加随机负样本还可以用BM25或少量启发式规则挖掘“表面看起来像、语义上不同”的难负样本。没有标注数据时难负样本质量直接决定模型上限。数据宁可少而精也不要多而脏。4.4 和传统双塔模型、交叉编码器相比谁更优这里需要区分清楚双塔模型是一种“查询端和文档端分别编码”的工程架构cross-attention编码器是一种“拼接后联合编码”的精细排序器。Decoder-only做嵌入本质上是在“共享参数的双塔”路上走query和doc用同一个模型编码只是输出向量位置不同。相比独立双塔共享参数能减少一半模型参数但换来的是query和doc必须适应同样一套表示空间灵活性会差一点。相比交叉编码器Decoder-only嵌入方案牺牲了精度换来了可上线性。如果面试官问线上怎么做就说清楚粗排阶段用Decoder-only嵌入模型做向量召回精排阶段再上交叉编码器或者更复杂的重排序模型这套路在工业界已经很成熟了。5. 复现过程中踩过的坑与排查清单纸上得来终觉浅。真正跑这个方案时我遇到了好几个“看似正常但效果崩盘”的问题。下面全部记下来希望能帮你省几天调试时间。5.1 训练指标不降反升先去检查你的对齐第一次训练时loss一直在震荡验证集指标不升反降。我以为是温度系数没调好折腾了一晚上。最后打印了每个batch里query和doc的ID才发现数据加载器在随机打乱时把正样本对打散了第 (i) 个query和第 (i) 个doc根本不是一个pair。这类对齐问题在分布式训练、多卡数据并行、引入难负样本挖掘后特别容易发生。排查方法很简单训练脚本里固定随机种子单独打印一个batch的input_id和label肉眼核对一遍。5.2 向量全部挤成一团可能遇到了表示坍缩训练到某一轮之后我把所有query向量画到二维分布里发现几乎都挤在一起检索结果也变得毫无区分度。这是对比学习最经典的“表示坍缩”问题。常见原因有三个温度系数太高、负样本数量太少、两个encoder之间没有做正则化。解决思路分三步。第一步把温度系数从0.1降到0.03附近重新跑几个step观察。第二步想办法扩大负样本规模比如用梯度累积或维护一个负样本队列。第三步如果是完全对称的双塔结构可以在其中一个视角上做stop-gradient操作这是很多自监督方法避免坍缩的通用手段。5.3 在线检索延迟压不住给部署方案的取舍Decoder-only做嵌入模型最大的痛点不是效果而是推理开销。同样一个batch双向编码器的计算量通常明显更小。我跑过一次线上延时压测模型原始精度下的P99延迟比预期的贵很多。最后通过三件事把延迟降下来了对模型权重做8bit量化把向量维度从高维降到512维并给长文档做了一个“首尾截断关键段抽取”的前置逻辑避免全部文本进入模型。效果虽然掉了零点几个点但延迟指标完全可接受。这个取舍我觉得值得分享追求极致精度就保留全量模型和全维度向量追求吞吐就要接受量化与裁剪带来的小幅损失。没有哪个方案是免费的关键是给面试官讲清楚你的决策依据。5.4 快速问题排查表症状可能原因排查思路与解决办法Loss不降或震荡Batch内正样本对齐错误固定种子人工检查一个batch的标签向量表示集中温度过高、负样本太少调低温度引入队列或难负样本训练后生成能力明显下降单任务对比学习破坏生成改多任务混合训练或使用LoRA收敛速度极慢学习率过低、数据噪声大适当调大学习率清洗数据重复项检索结果质量差池化只用了顶层、未做归一化尝试中间层池化输出做L2归一化部署显存超限KV cache占用过高量化模型、裁剪序列长度、批量推理6. 写在最后我的一点体会这个方案真正让我觉得有价值的不是“Decoder-only能不用来做嵌入”这个结论而是面试准备倒逼自己去理解一个更底层的问题模型的架构和任务之间到底应该匹配在哪里过去我习惯把“双向编码器负责理解、自回归模型负责生成”当成铁律但实际跑过之后发现模型的能力边界并不完全由注意力方向决定训练目标和数据配置同样有巨大的塑造力。如果你正在准备面试我的建议是不要只停留在“能说出方案”的层面最好亲手跑一个几小时的小实验。当你真正看到loss下降、向量检索结果一点点变准的时候面试官追问题时你心里的底气是完全不一样的。这个方向后续还可以扩展成“多任务统一模型”“长文本检索增强生成”等更综合的题目值得你把基础打牢一点。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。