2026/4/17 1:15:25
网站建设
项目流程
如何利用网站模板,黄骅市美食,接入备案和增加网站,公司网站开发费用看一遍就懂-大模型架构及encoder-decoder详细训练和推理计算过程 一、特殊Token的意思
不同模型架构的特殊token体系
BERT#xff08;Encoder-only#xff0c;用于理解任务#xff09;#xff1a;
CLS#xff1a;放在句首#xff0c;用于分类任务#xff0c;…看一遍就懂-大模型架构及encoder-decoder详细训练和推理计算过程一、特殊Token的意思不同模型架构的特殊token体系BERTEncoder-only用于理解任务CLS放在句首用于分类任务其输出向量代表整句语义 SEP分隔符用于句对任务如问答、文本蕴含 PAD填充符用于batch内长度对齐 MASK掩码符用于预训练的完形填空任务GPTDecoder-only用于生成任务|endoftext|既是文档结束符也用作句子间分隔符 PAD填充符但GPT很少用因为生成任务不需要严格对齐T5Encoder-Decoder统一框架pad填充 eos句子结束符 unk未知词 没有专门的bos因为T5用任务前缀如translate English to German:现代Encoder-Decoder如BART、mBART、mT5s句子开始符相当于bos /s句子结束符相当于eos pad填充符我们的例子将使用经典的Seq2Seq符号体系为了讲解清晰我采用最经典、最直观的设定bosBegin of Sequence告诉decoder我要开始生成了 eosEnd of Sequence告诉decoder我生成完了该停了 padPadding在batch训练中让所有序列等长二、完整训练流程的剧本从数据准备到参数更新让我们以今天天气很好这个训练样本为主线完整演绎一遍训练的全过程。第一幕数据预处理演员准备上场原始训练样本源语言中文今天天气很好 目标语言中文今天天气很好 # 这是个复述任务的例子Token化切词源序列[今, 天, 天, 气, 很, 好] 目标序列[今, 天, 天, 气, 很, 好]添加特殊token关键步骤Encoder输入源序列加结束符[今, 天, 天, 气, 很, 好, eos] 长度 7为什么Encoder要加eos因为这告诉模型源句子到此结束了没有更多信息了。在翻译任务中这个信号很重要它让模型知道不要再期待更多源语言词汇。Decoder输入目标序列加开始符[bos, 今, 天, 天, 气, 很, 好] 长度 7为什么Decoder输入要加bos这是生成的起始信号就像告诉一个作家请开始写作。第一个词的生成需要一个初始上下文bos就扮演这个角色。训练标签目标序列加结束符[今, 天, 天, 气, 很, 好, eos] 长度 7为什么标签要加eos因为我们要训练模型学会什么时候停止生成。最后一个时间步模型应该预测eos而不是继续生成新词。转换为ID查词表假设我们的词表10个词是词表 { pad: 0, bos: 1, eos: 2, 今: 3, 天: 4, 气: 5, 很: 6, 好: 7, 的: 8, 是: 9 }转换后Encoder输入 ID: [3, 4, 4, 5, 6, 7, 2] Decoder输入 ID: [1, 3, 4, 4, 5, 6, 7] 标签 ID: [3, 4, 4, 5, 6, 7, 2]注意Decoder输入和标签的错位关系这就是Teacher Forcing输入是已知的正确答案让模型在每一步都基于正确历史来预测下一个词。第二幕Encoder的计算旅程理解源句子场景1Embedding层把ID变成向量/* by 01130.hk - online tools website : 01130.hk/zh/pagecode.html */ # 假设embedding维度 d_model 4 Embedding矩阵 W_emb ∈ ℝ^(10×4) # 词表大小10每个词4维向量 # 查表得到向量我简化一下数值 今 (ID3) → [0.8, 0.1, 0.3, 0.2] 天 (ID4) → [0.2, 0.9, 0.1, 0.4] 天 (ID4) → [0.2, 0.9, 0.1, 0.4] # 相同的词embedding相同 气 (ID5) → [0.1, 0.2, 0.9, 0.1] 很 (ID6) → [0.4, 0.3, 0.2, 0.8] 好 (ID7) → [0.5, 0.4, 0.1, 0.6] eos(ID2) → [0.3, 0.5, 0.6, 0.4] Encoder输入矩阵 X_enc^(0) [7×4]为什么要用embedding因为神经网络不能直接处理离散的ID数字必须转换为连续向量才能进行微分计算。Embedding本质是一个可学习的查找表训练过程会让语义相近的词向量也接近。场景2位置编码加入顺序信息/* by 01130.hk - online tools website : 01130.hk/zh/pagecode.html */ # 位置编码告诉模型这是第几个词 Position Encoding PE ∈ ℝ^(7×4) pos0 → [0.00, 1.00, 0.00, 1.00] pos1 → [0.84, 0.54, 0.10, 0.99] pos2 → [0.91, -0.42, 0.20, 0.98] pos3 → [0.14, -0.99, 0.30, 0.95] pos4 → [-0.76, -0.65, 0.39, 0.92] pos5 → [-0.96, 0.28, 0.48, 0.88] pos6 → [-0.28, 0.96, 0.56, 0.83] # 叠加到embedding上 X_enc^(0) X_enc^(0) PE # 逐元素相加为什么需要位置编码因为Self-Attention是置换不变的它只看谁和谁相关不管顺序。但语言是有顺序的今天很好和很好今天意思不同。位置编码用正弦波函数给每个位置一个独特的身份标签。场景3Self-Attention Layer 1词与词互相理解现在进入第一层Self-Attention这里没有mask因为Encoder可以看到整个句子。# 投影矩阵简化为单位阵 W_Q W_K W_V I_4 # 计算Q, K, V Q X_enc^(0) × W_Q X_enc^(0) [7×4] K X_enc^(0) × W_K X_enc^(0) [7×4] V X_enc^(0) × W_V X_enc^(0) [7×4] # 计算注意力分数 Scores Q × K^T / √4 [7×7] # 这里是关键Encoder的attention矩阵是全连接的 # 每个词都能看到所有其他词包括自己 今 天 天 气 很 好 eos 今 [0.82, 0.69, 0.69, 0.58, 0.61, 0.72, 0.64] 天 [0.69, 0.91, 0.91, 0.78, 0.82, 0.79, 0.81] 天 [0.69, 0.91, 0.91, 0.78, 0.82, 0.79, 0.81] # 两个天完全一样 气 [0.58, 0.78, 0.78, 0.88, 0.76, 0.71, 0.75] 很 [0.61, 0.82, 0.82, 0.76, 0.93, 0.84, 0.79] 好 [0.72, 0.79, 0.79, 0.71, 0.84, 0.94, 0.85] eos [0.64, 0.81, 0.81, 0.75, 0.79, 0.85, 0.87] # Softmax每行归一化 Attention_Weights softmax(Scores) [7×7] # 加权求和 Output Attention_Weights × V [7×4]这一步的意义是什么每个词通过关注其他所有词把句子的全局信息融合进来。比如天气这个词会同时关注今天和很好理解这是在描述今天的天气状况。场景4Add Norm残差连接和归一化# 残差连接把输入直接加到输出上 X_residual X_enc^(0) Output # Layer Normalization让每个样本的均值0方差1 X_enc^(1) LayerNorm(X_residual) [7×4]为什么要残差连接防止深层网络的梯度消失让原始信息能够直通到后面的层。就像高速公路的快车道保证重要信息不会在传递过程中丢失。场景5Feed-Forward层独立处理每个位置# 两层全连接网络对每个token独立处理 FFN(x) W2 × ReLU(W1 × x b1) b2 假设 W1: [4×16], W2: [16×4] # 中间扩展到16维 对每个位置计算 Output_FFN FFN(X_enc^(1)) [7×4] # 再次 Add Norm X_enc^(2) LayerNorm(X_enc^(1) Output_FFN)为什么需要FFNSelf-Attention擅长建模全局依赖但是线性的。FFN提供非线性变换能力让模型能学习更复杂的模式。中间维度扩展4→16→4增加了表达能力。场景6堆叠多层加深理解实际的Transformer会堆叠多层如6层每层都重复Self-Attention FFN的结构。假设我们只有2层那么# 经过第2层后得到最终的Encoder输出 H_enc X_enc^(final) [7×4] 具体数值这是最终经过所有层后的结果 dim0 dim1 dim2 dim3 今 [0.8, 0.1, 0.3, 0.2] 天 [0.2, 0.9, 0.1, 0.4] 天 [0.3, 0.8, 0.2, 0.3] 气 [0.1, 0.2, 0.9, 0.1] 很 [0.4, 0.3, 0.2, 0.8] 好 [0.5, 0.4, 0.1, 0.6] eos [0.4, 0.5, 0.5, 0.5]Encoder的最终产出是什么一个7×4的矩阵每一行是一个源句子token的深度语义表示已经融合了全句的上下文信息。这个矩阵将作为知识库提供给Decoder。第三幕Decoder的生成征程基于源句子生成目标场景1Decoder的输入准备Decoder输入序列 ID: [1, 3, 4, 4, 5, 6, 7] 对应token: [bos, 今, 天, 天, 气, 很, 好] # Embedding 位置编码 X_dec^(0) Embedding(Decoder输入) PE [7×4]场景2Masked Self-Attention只看过去这是Decoder的第一个attention层与Encoder的区别就在于加了Causal Mask。# 计算Q, K, V Q_dec X_dec^(0) × W_Q [7×4] K_dec X_dec^(0) × W_K [7×4] V_dec X_dec^(0) × W_V [7×4] # 计算原始分数 Scores Q_dec × K_dec^T / √4 [7×7] # 应用Causal Mask设置上三角为-∞ Mask矩阵下三角对角线为1上三角为0 [[1, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 0], [1, 1, 1, 1, 1, 0, 0], [1, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1]] Masked_Scores 把Mask为0的位置设为-∞ # Softmax后未来位置的权重自动为0 Attention_Weights softmax(Masked_Scores) [7×7] 示例第2行今的权重分布 [0.45, 0.55, 0.00, 0.00, 0.00, 0.00, 0.00] ↑ ↑ ↑后面全是0 bos 今 # 输出 Output_self Attention_Weights × V_dec [7×4]为什么Decoder要mask因为训练时我们是并行处理整个序列的但预测时只能一个一个生成。Mask确保训练和推理的信息流向一致防止作弊。场景3Cross-Attention从Encoder借鉴知识这是Encoder-Decoder架构的核心# Query来自Decoder的当前状态 Q_cross Output_self × W_Q [7×4] # Key和Value来自Encoder的输出 K_cross H_enc × W_K [7×4] ← 这是Encoder的最终输出 V_cross H_enc × W_V [7×4] # 计算注意力这里没有mask可以看Encoder的所有位置 Scores_cross Q_cross × K_cross^T / √4 [7×7] ↑Decoder7个位置 ↑Encoder7个位置 Attention_Weights_cross softmax(Scores_cross) [7×7] # 示例Decoder位置1预测天对Encoder各位置的关注 位置1的权重: [0.15, 0.25, 0.20, 0.10, 0.12, 0.10, 0.08] 今 天 天 气 很 好 eos # 加权求和 Output_cross Attention_Weights_cross × V_cross [7×4]Cross-Attention的本质是什么Decoder用当前生成状态作为问题Query在Encoder的语义矩阵中检索相关信息Key提取出有用的内容Value。就像翻译时生成每个目标词都会回头看源句子的不同部分。场景4Feed-Forward Add Norm# 和Encoder一样的结构 Output_FFN FFN(Output_cross) X_dec^(1) LayerNorm(Output_cross Output_FFN) [7×4] # 这是Decoder第一层的最终输出场景5堆叠多层后的最终输出假设Decoder也是2层最终得到H_dec X_dec^(final) [7×4] # 这个矩阵的每一行代表Decoder在该位置的条件生成状态第四幕输出层与损失计算评判对错场景1投影到词表空间# 线性变换4维 → 10维词表大小 W_vocab ∈ ℝ^(4×10) Logits H_dec × W_vocab [7×10] # 每一行是一个位置对10个词的原始分数 位置0输入bos预测今 logits[0] [0.21, 0.18, 0.15, 0.89, 0.54, 0.32, 0.41, 0.38, 0.25, 0.19] pad bos eos 今↑ 天 气 很 好 的 是场景2Softmax得到概率分布Probs softmax(Logits) [7×10] # 位置0的概率分布 P[0] softmax(logits[0]) [0.067, 0.065, 0.063, 0.131, 0.094, 0.076, 0.083, 0.080, 0.070, 0.065] pad bos eos 今↑ 天 气 很 好 的 是 13.1% # 全部7个位置的预测 位置0预测分布P(今)13.1%, P(天)9.4%, ... 位置1预测分布P(今)8.2%, P(天)15.3%, ... ← 应该预测天 位置2预测分布... ... 位置6预测分布P(eos)18.5%, ... ← 应该预测结束符场景3计算交叉熵损失# 标签真实答案 Labels [3, 4, 4, 5, 6, 7, 2] # 今,天,天,气,很,好,eos # 对每个位置计算损失 Loss_0 -log(P[0, 3]) -log(0.131) 2.03 Loss_1 -log(P[1, 4]) -log(0.153) 1.88 Loss_2 -log(P[2, 4]) -log(0.147) 1.92 Loss_3 -log(P[3, 5]) -log(0.162) 1.82 Loss_4 -log(P[4, 6]) -log(0.159) 1.84 Loss_5 -log(P[5, 7]) -log(0.155) 1.86 Loss_6 -log(P[6, 2]) -log(0.185) 1.69 # 平均损失 Total_Loss (2.03 1.88 1.92 1.82 1.84 1.86 1.69) / 7 1.86为什么用交叉熵它衡量预测分布和真实分布one-hot的距离。预测概率越高损失越小。模型的目标就是最小化这个损失。第五幕反向传播与参数更新学习提升损失对各层参数的梯度# 梯度从输出层反向流动 ∂Loss/∂W_vocab → 更新词表投影矩阵 ↓ ∂Loss/∂H_dec → 流向Decoder最后一层 ↓ ∂Loss/∂(Decoder FFN参数) → 更新FFN ↓ ∂Loss/∂(Cross-Attention参数) → 更新W_Q, W_K, W_V ↓ ↓ ↓ ↓→ ∂Loss/∂H_enc → 流向Encoder ↓ ∂Loss/∂(Masked Self-Attention参数) ↓ ∂Loss/∂X_dec^(0) → 流向Decoder embedding关键洞察Encoder会被Decoder的损失训练虽然Encoder没有直接的监督信号但Cross-Attention建立了桥梁让生成任务的损失能反向传播到Encoder督促它学习对生成有用的表示。参数更新随机梯度下降学习率 η 0.001 W_vocab^(new) W_vocab^(old) - η × ∂Loss/∂W_vocab Encoder参数^(new) Encoder参数^(old) - η × ∂Loss/∂Encoder参数 Decoder参数^(new) Decoder参数^(old) - η × ∂Loss/∂Decoder参数一次训练步骤完成模型会对成千上万个这样的样本重复这个过程逐渐学会翻译、摘要、对话等任务。三、特殊Token的深层作用机制现在你已经看完了完整流程让我深入解释特殊token为什么必不可少。bos的三重作用第一重作为生成的起点。Decoder的第一个位置必须有一个输入bos提供了一个中性的起始上下文。就像赛跑的发令枪它本身没有语义但给出了开始的信号。第二重在Self-Attention中充当锚点。第一个词如今的Query会同时关注bos和自己bos的embedding会影响第一个词的生成倾向。比如在对话任务中不同的对话类型可能训练出不同的bos表示。第三重在Cross-Attention中引导初始检索。bos位置的Query会去Encoder中检索信息决定从哪里开始翻译/生成。在翻译任务中模型可能学会让bos关注源句子的开头。eos的三重作用第一重Encoder侧标记源句子边界。告诉Encoder句子结束了在Self-Attention中eos能汇聚全句信息成为句子级表示。很多摘要任务会专门提取Encoder的eos向量作为句子摘要。第二重Decoder侧学习何时停止。训练时最后一个位置必须预测eos这教会模型生成到合适长度就该停了。没有这个训练模型可能永远不停地生成。第三重推理时实际停止信号。测试时当模型输出eos解码循环就终止。这是唯一能让模型自主决定生成长度的机制。pad的作用Batch训练专用假设batch中有两个样本样本1: [今, 天, 很, 好, eos] 长度5 样本2: [明, 天, 会, 下, 雨, 吧, eos] 长度7为了并行处理必须对齐到相同长度样本1: [今, 天, 很, 好, eos, pad, pad] 长度7 样本2: [明, 天, 会, 下, 雨, 吧, eos] 长度7在Attention计算时pad位置会被mask掉设为-∞确保它们不参与实际计算只是占位符。在损失计算时pad位置也不计入损失。四、完整流程的理论意义总结Encoder-Decoder为什么如此强大信息的双向流动Encoder通过双向Attention理解源句子Decoder通过Causal Attention保证生成的自回归性Cross-Attention则让两者协同工作。端到端可微从输入token到输出概率的整个链条都是可微的可以用一个统一的损失函数端到端训练不需要分模块训练。Teacher Forcing的妙处训练时给Decoder正确答案作为输入让它学习基于正确历史的预测避免了错误累积大幅加速收敛。但推理时用自己的输出作为下一步输入Autoregressive这种训练-推理的不一致被称为Exposure Bias是当前研究的热点。与Decoder-onlyGPT的本质区别GPT没有Cross-Attention只有Masked Self-Attention。它把输入和输出拼接成一个序列[源句子, sep, 目标句子]全部用Causal Mask处理。这更简单但无法像Encoder-Decoder那样先全面理解再生成而是边看边生成。对于需要深度理解源文本的任务如摘要、翻译Encoder-Decoder理论上更有优势。五、用一个完整的比喻收尾想象你在参加一场即兴翻译大赛Encoder理解阶段你拿到一篇中文文章仔细阅读每个句子在纸上做标注理解上下文把关键信息提取成笔记。这个笔记就是H_enc。bos开始翻译主持人说开始你深吸一口气准备说出第一个英文单词。Decoder生成阶段你一边回看笔记Cross-Attention一边注意自己已经说出的英文Masked Self-Attention然后说出下一个单词。每个单词都基于源文本理解已生成历史做决策。eos结束你翻译完最后一个词说完毕告诉评委你结束了。损失函数评分评委对照标准答案给你的每个词打分算出总分。反向传播复盘你根据评委反馈调整自己的理解方式Encoder和表达策略Decoder下次做得更好。Encoder-Decoder推理过程开篇训练与推理的本质差异当你完全理解了Encoder-Decoder的训练过程后可能会产生一个错觉认为推理只是把训练过程重新跑一遍喂入新数据就能得到结果。但实际上推理过程和训练过程有着本质的不同这种差异如此重要以至于理解它是掌握序列生成模型的关键一跃。在训练阶段我们拥有上帝视角。我们已经知道正确答案是什么所以可以把整个目标序列一次性喂给Decoder让它在每个位置上并行地学习预测下一个词。这种并行化训练被称为Teacher Forcing它就像一个严格的导师站在学生旁边每走一步都告诉学生正确答案应该是这个让学生基于正确的历史去学习下一步。这种方式高效、稳定能让GPU的并行计算能力得到充分利用。但推理阶段是截然不同的故事。现在我们不知道答案Decoder必须真正地一个词一个词地生成每生成一个词都要立即把它作为下一步的输入形成一个自回归的循环。这就像蒙着眼睛走钢丝每一步都基于之前的步伐一旦走错一步后续的所有步骤都可能受到影响。这个过程是串行的、渐进的、不可回退的完全依赖模型自己的判断。让我用一个生动的比喻来说明这个差异。训练就像在驾校练车教练坐在副驾驶上握着一个备用方向盘。每当你要转向时教练会告诉你现在应该向左打30度你只需要学习在看到路况时做出正确的判断。而推理则是你独自开车上路没有教练没有提示你必须自己观察路况、做出决策、执行操作而且每个决策都会影响接下来的路况。现在让我们深入到推理的每一个步骤看看这个独自驾驶的过程到底是如何展开的。推理的起点准备输入和初始化假设我们已经训练好了一个中英翻译模型现在要把中文句子今天天气很好翻译成英文。这是一个经典的Encoder-Decoder任务让我们从头开始看这个推理过程。首先我们需要准备Encoder的输入。和训练时一样我们把源句子进行分词添加结束符然后转换为ID序列。这个过程完全一致因为Encoder的工作方式在训练和推理时没有任何区别。具体来说我们得到的序列是今、天、天、气、很、好、eos转换为ID后是3, 4, 4, 5, 6, 7, 2假设我们的词表中这些词对应的ID就是这样。接下来Encoder开始工作。它把这个ID序列转换为embedding向量每个词变成一个四维的向量。然后加上位置编码让模型知道每个词在句子中的位置。这个加了位置编码的向量矩阵形状是七行四列送入Encoder的第一层。Encoder的每一层都执行相同的操作首先是Self-Attention让每个词都能看到整个句子的所有其他词互相理解彼此的语义和关系。比如第一个天会注意到它前面的今理解这是在说今天而不是天空。然后是Feed-Forward网络对每个位置独立地进行非线性变换提取更复杂的特征。经过残差连接和Layer Normalization后输出被送入下一层。假设我们的模型有六层Encoder那么这个七行四列的矩阵会经过六次这样的处理。每经过一层向量中蕴含的语义就更加丰富和抽象。到最后一层输出时我们得到的矩阵我们称之为H_enc它的每一行都是一个源语言词的深度语义表示已经融合了整个句子的上下文信息。这个矩阵将作为知识库在整个生成过程中被Decoder反复查询。这里有一个关键的认知Encoder的计算在推理时只需要执行一次。无论Decoder后面要生成多少个词Encoder都不需要重新计算。这个H_enc矩阵会被保存在内存中供后续所有时间步使用。这是一个重要的优化点因为Encoder的计算量可能相当大如果每生成一个词都重算一次就太浪费了。现在Encoder已经完成了它的使命把源句子理解透彻了。接下来的重点转向Decoder这才是推理阶段真正惊心动魄的部分。第一步生成从bos到第一个单词Decoder的推理过程从一个特殊token开始这就是bos它代表Begin of Sequence是生成的起点。在训练时我们可以一次性把整个目标序列喂给Decoder但现在我们什么都没有只有这个起始符号。这就像作家面对空白稿纸时的第一个字充满了不确定性。我们把bos假设它的ID是1转换为embedding向量加上位置编码。这时位置索引是0因为它是第一个位置。得到的向量是一个一行四列的矩阵记为X_dec。这个向量送入Decoder的第一层。Decoder的第一个子层是Masked Self-Attention。你可能会问现在只有一个token还需要attention吗答案是需要的虽然这一步的attention计算会很简单。具体来说这个bos向量会和它自己计算attentionQuery是它自己Key也是它自己Value还是它自己。计算出的attention score就是一个标量做softmax后权重是1.0加权求和的结果还是它自己。这看起来像是一个恒等变换但保持这个流程的一致性很重要因为后续步骤会变得复杂。通过Self-Attention后我们得到一个向量它经过Add Norm后送入第二个子层这就是Cross-Attention。这是Decoder真正开始查询Encoder知识库的地方。当前的向量作为Query被投影成一个查询向量。而Encoder的输出H_enc那个七行四列的矩阵被投影成Keys和Values。现在进行的计算是这个Query向量一行四列乘以Keys矩阵的转置四行七列得到一个一行七列的attention score向量。这个向量的每个元素代表Decoder当前状态对Encoder各个位置的关注程度。比如第一个元素表示对源句子第一个词今的关注度第二个元素表示对第二个词天的关注度以此类推。这些分数经过缩放除以维度的平方根然后做softmax变成一个概率分布。假设计算出来的权重分布是0.12, 0.18, 0.15, 0.10, 0.13, 0.20, 0.12这意味着在生成第一个英文单词时模型最关注源句子中的好权重0.20和天权重0.18。这是有道理的因为翻译今天天气很好时英文很可能以Today或The weather开头而这些都和源句子的这些词相关。这个attention权重向量一行七列乘以Values矩阵七行四列得到一个context向量一行四列。这个向量是Encoder所有位置信息的加权混合代表了为了生成当前位置的词从源句子中提取出的最相关信息。Context向量经过Add Norm后送入Feed-Forward网络再经过一次Add Norm完成Decoder第一层的计算。如果模型有六层Decoder那么这个向量会经过六次Masked Self-Attention → Cross-Attention → FFN的循环每一层都进一步精炼这个向量的语义。最终我们得到Decoder最后一层的输出一个一行四列的向量记为h_dec。这个向量包含了基于源句子理解应该生成什么样的第一个词的所有信息。但它还不是一个词而是一个高维语义向量。现在到了关键的一步把这个四维向量投影到词表空间。我们有一个输出投影矩阵W_vocab形状是四行乘以词表大小假设英文词表有10000个词。向量h_dec一行四列乘以这个矩阵得到一个一行10000列的logits向量。这个向量的每个元素对应一个英文单词的原始分数。对这个logits向量做softmax我们得到一个概率分布。假设最高的几个概率是Today对应0.35The对应0.28Weather对应0.12It对应0.08其他词的概率都很低。这个分布告诉我们模型认为最可能的第一个词是Today概率达到35%。在最简单的推理策略中我们选择概率最高的词作为输出这叫做贪心解码Greedy Decoding。所以我们选择Today作为第一个生成的词。这个词的ID假设是8888会被记录下来因为它马上要作为下一步的输入。第一步生成完成了我们用了一个bos符号通过整个Decoder网络的计算从10000个可能的英文单词中选出了Today。这个过程虽然复杂但逻辑是清晰的用起始信号触发生成通过Cross-Attention查询源句子通过Self-Attention整合已有信息虽然现在只有一个token最后输出一个词表概率分布并选择最优的词。第二步生成自回归的开始现在进入推理的核心机制自回归循环。我们已经生成了第一个词Today它不仅是输出的一部分更重要的是它要立即成为Decoder下一步的输入。这就是自回归的含义每一步的输出都被回馈到输入中形成一个闭环。具体来说我们现在的Decoder输入序列是bos, Today长度变成了2。我们把这两个词都转换为embedding加上位置编码bos的位置是0Today的位置是1得到一个二行四列的矩阵。这个矩阵送入Decoder的第一层。在Masked Self-Attention阶段计算方式和训练时完全一样。我们计算Query、Key、Value矩阵现在都是二行四列然后计算Q乘以K的转置得到一个二行二列的attention score矩阵。这个矩阵的四个元素分别代表位置0对位置0的关注、位置0对位置1的关注、位置1对位置0的关注、位置1对位置1的关注。关键的Causal Mask在这里起作用。虽然我们现在只有两个词但mask的逻辑依然适用每个位置只能看到它自己和它之前的位置。所以attention矩阵的模式是bos Today bos [1.0, 0.0 ] Today [0.4, 0.6 ]位置0bos只能100%关注自己而位置1Today可以同时关注bos比如40%和自己60%。这个mask确保了信息的单向流动防止看到未来。Softmax后的attention权重乘以Value矩阵得到输出向量。对于位置1Today这个输出向量融合了bos和Today两个embedding的信息是一个条件化的语义表示。然后进入Cross-Attention。这里有一个非常重要的细节虽然Decoder现在有两个位置但Encoder的输出H_enc还是那个七行四列的矩阵没有任何变化。每个Decoder位置都会独立地和整个Encoder做attention。具体来说位置1Today的Query向量会和Encoder的七个Key向量计算相似度得到七个attention score。假设这次的权重分布是0.08, 0.10, 0.12, 0.15, 0.25, 0.18, 0.12你会发现模型现在更关注源句子中的很0.25和好0.18。这很合理因为已经生成了Today接下来可能要描述天气状况所以关注很好这部分信息。Cross-Attention的输出一个四维向量经过FFN和多层堆叠最终得到位置1的最终表示h_dec。注意我们只需要这个位置1的向量因为我们要预测的是第二个词。位置0的输出我们不关心因为那对应的是已经生成过的bos的后续词已经是历史了。这个h_dec向量再次通过W_vocab投影到词表做softmax得到概率分布。假设这次的最高概率是is对应0.42the对应0.25was对应0.18。模型认为第二个词最可能是is所以我们选择它。现在我们的生成序列变成了Today is这是一个合理的英文开头。但我们还没有结束因为模型没有输出eos结束符。所以循环继续。自回归循环的完整展开从is到eos理解了第二步你就理解了推理的全部核心。后续的每一步都是完全相同的模式把当前已生成的序列作为Decoder输入通过整个网络计算输出下一个词的概率分布选择最优词添加到序列末尾再次循环。让我详细展开第三步帮你巩固这个理解。现在Decoder的输入是bos, Today, is三个词转换为三行四列的embedding矩阵。在Masked Self-Attention中每个位置的attention权重分布遵循causal patternbos Today is bos [1.0, 0.0, 0.0] Today [0.3, 0.7, 0.0] is [0.2, 0.4, 0.4]位置2is能看到所有之前的信息它的Query会同时关注bos20%、Today40%和自己40%。这让is的表示融合了整个已生成序列的信息。在Cross-Attention中位置2的Query和Encoder的七个Key计算attention。假设这次的权重分布聚焦在源句子的天气0.35和很好0.40。模型已经生成了Today is现在要描述具体内容所以关注点自然转向了天气很好这个核心语义。经过所有层的计算后位置2的输出向量投影到词表假设概率分布显示nice是0.38good是0.25sunny是0.20。我们选择nice序列变成Today is nice。第四步输入变成四个词Masked Self-Attention的矩阵变成四行四列下三角都有值上三角都是零。Cross-Attention中新位置的Query可能开始关注源句子的天天气或今今天因为句子结构已经建立现在可能需要补充细节。假设生成的词是weather虽然这让句子语法不完美应该是The weather is nice但这正是自回归的风险一旦前面的词选择不完美后续就可能需要调整来适应。第五步可能生成today序列变成Today is nice weather today。你可能注意到today出现了两次这在人类看来有些冗余但模型在每一步都是基于概率做贪心选择无法回头修改。第六步模型终于输出了eos概率达到0.55超过了所有其他词。这个特殊符号告诉我们生成结束了。最终的翻译结果是Today is nice weather today虽然不完美但表达了源句子的核心意思。整个过程可以总结为一个while循环的伪代码generated_sequence [bos] while True: decoder_input generated_sequence encoder_output H_enc (保持不变) decoder_hidden Decoder(decoder_input, encoder_output) last_position_hidden decoder_hidden[-1] # 只取最后一个位置 logits last_position_hidden W_vocab probs softmax(logits) next_token argmax(probs) # 贪心选择 generated_sequence.append(next_token) if next_token eos or len(generated_sequence) max_length: break return generated_sequence[1:] # 去掉bos返回这个循环的每一次迭代都会让输入序列增长一个词Decoder的计算量也会随之增加。如果最终生成了十个词那么第十步的Masked Self-Attention就要处理一个十行十列的矩阵。这就是为什么推理速度会随着生成长度增加而变慢因为每一步都比上一步多一点计算。推理与训练的深层对比现在你已经看完了完整的推理流程让我们回过头来系统对比推理和训练的差异这些差异不仅仅是技术细节它们反映了序列生成任务的深层挑战。第一个差异是并行性。训练时我们可以把整个目标序列一次性喂给Decoder利用Causal Mask确保信息流向的正确性让GPU的数千个核心并行计算。假设目标序列长度是十那么十个位置的loss可以同时计算梯度可以同时反向传播。但推理时我们必须一个词一个词地串行生成第二个词依赖第一个词的输出第三个词依赖前两个词无法并行。这导致推理速度远慢于训练尤其是生成长文本时可能需要几秒甚至几分钟。第二个差异是输入的来源。训练时Decoder的每一步输入都是ground truth即人工标注的正确答案。即使模型在某一步预测错了下一步依然会用正确答案作为输入这让模型能够重置错误专注于学习每个独立位置的正确预测。但推理时每一步的输入都是模型自己上一步的输出。如果第三步生成了一个不太合适的词第四步就必须基于这个不完美的历史继续生成错误可能累积和放大。这种训练-推理的不一致性被称为Exposure Bias是当前研究的热点问题。第三个差异是确定性。训练时损失函数是确定的给定相同的输入和参数计算出的梯度完全相同。但推理时我们通常有多种解码策略可选。最简单的是贪心解码每步选概率最高的词这是确定性的。但还有随机采样按照概率分布随机选词每次运行可能得到不同结果。还有Beam Search同时保留多个候选序列选择全局概率最高的路径这需要更多计算但通常质量更好。不同的解码策略会导致完全不同的输出而在训练时没有这种选择的自由度。第四个差异是停止条件。训练时序列长度是已知的我们有明确的标签告诉模型这里应该结束。但推理时模型必须自己决定什么时候停下来。它通过预测eos符号来表达我认为已经生成完整了。如果模型训练不够好可能永远不输出eos导致无限循环。所以实际系统中通常会设置一个最大长度限制作为保险比如不管怎样生成100个词后必须停止。第五个差异是计算图的动态性。训练时整个计算图的形状是固定的比如Decoder输入是十行四列输出也是十行一万列词表大小整个前向传播和反向传播的张量形状都是静态的可以高度优化。但推理时每一步的输入长度都不同第一步是一行四列第二步是两行四列第十步是十行四列。这种动态性让优化变得困难也是为什么现代推理引擎如vLLM、TensorRT-LLM花了大量精力优化KV Cache等技术来减少重复计算。KV Cache推理优化的关键技术当你理解了推理的逐步生成过程后你可能会发现一个巨大的浪费每一步我们都要重新计算之前所有位置的Key和Value。具体来说在第三步时我们有三个输入tokenbos, Today, isDecoder会计算三个Query、三个Key、三个Value。但其实bos和Today的Key和Value在第二步时已经算过了它们的值不会改变因为它们只依赖于自己的位置和内容不依赖未来的token。KV Cache的思想就是把已经计算过的Key和Value存起来后续步骤直接复用。具体来说第一步我们计算并缓存一个Key和一个Value都是四维向量第二步计算新的一个Key和一个Value拼接到缓存上变成两个。第三步再计算一个拼接上去变成三个。这样每一步只需要计算新增位置的KV而不需要重算整个历史。这个优化在Cross-Attention中更加显著。Encoder的Key和Value矩阵在整个生成过程中完全不变我们可以在第一步就计算好并缓存后续所有步骤直接使用完全不需要重算。这大幅减少了计算量。在Self-Attention中KV Cache的使用稍微复杂一些。新的Query需要和所有历史的Key计算attention所以第十步的Query要和十个Key做点积。但由于我们缓存了之前九个Key只需要计算新的第十个Key然后把它和缓存拼接起来。这比重新计算十个Key要快得多。KV Cache带来的加速是巨大的尤其是对于长序列生成任务。但它也有代价内存占用。假设模型有六层Decoder每层都要缓存KV序列长度是100那么我们需要存储六层乘以100个位置乘以两个矩阵K和V乘以四维总共4800个浮点数。对于大模型如GPT-3的12288维这个内存开销可能达到几个GB。这就是为什么大模型推理需要大显存很大一部分都被KV Cache占用了。后续提出的MQA,GQA,MLA都是为了缓解KV cache。大家有兴趣可以看这位博主讲的https://www.bilibili.com/video/BV1BYXRYWEMj/?spm_id_from333.1387.favlist.content.clickvd_sourcefc3f7e0bcc8a0bca7d86fc8c60c5db3cBeam Search超越贪心的搜索策略贪心解码虽然简单快速但它有一个明显的缺陷它只看一步不考虑长远影响。假设第一步选Today概率是0.35选The是0.28贪心会选Today。但可能The weather is nice today这个完整句子的联合概率比Today is nice weather更高。贪心因为只看局部最优错过了全局最优。Beam Search是一种折中方案它不是只保留一个候选序列而是同时保留多个比如五个这个数量叫做beam size。具体流程是第一步从词表中选出概率最高的五个词比如Today0.35、The0.28、It0.15、Weather0.12、Good0.10。这五个词各自开启一条生成路径。第二步对每条路径分别往后生成一个词。Today路径可能扩展出Today is、Today the、Today was等The路径可能扩展出The weather、The sky等。现在我们有五条路径各自扩展出若干候选总共可能有几十个。我们计算每个候选的累积概率通常是对数概率的和然后只保留全局最高的五个。比如经过第二步后保留的五个可能是The weather-0.5、Today is-0.6、The sky-0.7、It is-0.8、Today the-0.9。注意Today路径的其他扩展可能被淘汰了因为全局得分不够高。这个过程持续进行每一步都保留全局最优的五条路径直到所有路径都输出了eos或达到最大长度。最后我们选择累积概率最高的那条路径作为最终输出。Beam Search的优势是它能够回头。如果第一步选了Today但后续发展不好它可以在后续步骤中逐渐被The路径赶超并淘汰。这让最终结果更可能是全局较优的。但代价是计算量增加了beam_size倍因为每一步都要并行处理多条路径。在实践中beam size通常取3到10之间。太小则效果接近贪心太大则计算成本过高且容易陷入重复模式模型可能会生成very very very good这样的重复序列因为每个very都让概率稍微提高一点。推理中的其他挑战与技巧除了上面讨论的核心机制推理还有很多细节和技巧值得了解。温度Temperature采样是控制生成多样性的常用手段。在做softmax之前我们可以把logits除以一个温度参数T。当T接近0时概率分布变得非常尖锐几乎所有概率都集中在最高分词上生成变得确定性和保守。当T大于1时概率分布变得平缓低概率词也有机会被选中生成变得更随机和创造性。在创意写作任务中高温度可以产生意想不到的表达而在翻译等需要精确性的任务中低温度更合适。Top-k采样是另一种常用技巧。它在采样时只考虑概率最高的k个词把其他词的概率置零后重新归一化。这避免了极低概率的异常词被偶然选中同时保持一定的随机性。Top-pnucleus采样则更加动态它选择累积概率达到p如0.9的最小词集进行采样词集大小会根据概率分布的形状自适应调整。重复惩罚是对抗生成重复的技术。如果某个词已经在生成序列中出现过我们可以在下一步预测时降低它的logit分数让模型倾向于选择新的词。这在对话和故事生成中很有用可以避免模型陷入I think I think I think这样的循环。长度归一化在Beam Search中很重要。因为我们用对数概率的和作为得分长序列的得分会自然地比短序列低因为每一步都乘以一个小于1的概率。为了公平比较不同长度的序列我们通常会用总对数概率除以长度或者用一个更复杂的长度惩罚公式。