模型架构 / 整体架构 Technique入门 11 分钟阅读 查看源文件 ↗

Transformer:从 token 向量到下一词预测#

TL;DR#

本页内容8 个部分

原始 Transformer:编码完整源句,解码器读取输出前缀和编码结果,预测下一个 token

1. 从一句话生成另一句话#

以翻译为例,输入是“小明 开始 工作”,输出是“Xiaoming starts working”。为方便说明,假设这里每个词恰好对应一个 token;实际分词不一定如此。

循环神经网络 RNN 按位置递推:计算当前位置的状态,需要前一位置已经算出的状态。因此,同一句话中各位置的这段计算不能同时完成。Transformer 改用 Self-Attention,中文称自注意力:每个位置直接从允许读取的位置汇集信息,不再沿输入顺序递推隐藏状态。[1]

原论文的模型由 Encoder(编码器)和 Decoder(解码器)组成。编码器把源句变成一组含上下文信息的向量;解码器读取这些向量,以及已经知道的输出前缀,预测下一个输出 token。原始 Transformer 是 Encoder–Decoder 架构;常见的 decoder-only 语言模型是另一种组织方式。

论文标题中的“All You Need”强调序列建模不需要循环或卷积结构。完整模型仍包含 embedding、前馈网络、残差连接、归一化与输出层,并非只有 Attention。[1]

2. 一层的输入与输出是什么#

2.1 token ID 先转换为可训练的向量#

token ID 是词表中的整数编号。模型用它查找 embedding 表中的一行。设词表大小为 VV,向量维度为 dd,embedding 参数矩阵为 E∈RV×dE\in\mathbb R^{V\times d}。token ID 为 ii 时,查表结果为:

token 向量=E[i,:]∈Rd.\text{token 向量}=E[i,:]\in\mathbb R^d.

这也等价于用 one-hot 向量选择 EE 的一行,但实际实现通常直接查表,不必构造长度为 VV 的 one-hot 向量。EE 的元素是模型参数,训练时接收梯度并由优化器更新;整数 ID 本身不参与优化。

向量有 dd 个数,但通常不能把某个坐标固定解释成“人物”或“动作”。含义来自整个向量及后续计算,不是人为给每一维指定一个语义标签。

2.2 序列是一张矩阵,位置编码说明词序#

设源句有 TT 个 token。每个 token 对应一行,序列对应 T×dT\times d 的矩阵。原始 Transformer 将缩放后的 embedding 与位置编码相加。位置 jj 从 0 开始,输入向量定义为:

Hj,:(0)=d E[tokenj,:]+PEj,:.H^{(0)}_{j,:}=\sqrt d\,E[\mathrm{token}_j,:]+\mathrm{PE}_{j,:}.

这里 H(0)H^{(0)} 是进入第一层编码器前的整段输入,PEj,:\mathrm{PE}_{j,:} 是同样长为 dd 的位置向量。位置编码让计算能够区分“小明先出现”和“工作先出现”,同时保持矩阵形状不变。原论文使用正弦与余弦构造位置编码;具体定义见位置编码。[1]

2.3 层是一次完整的向量变换#

一层编码器包含 Attention、前馈网络及其残差与归一化操作。输入整段序列的向量,输出仍是同样多行、同样维度的向量。

用 FℓF_\ell 表示第 ℓ\ell 层的完整计算,H(ℓ)H^{(\ell)} 表示该层输出:

H(ℓ)=Fℓ(H(ℓ−1))∈RT×d.H^{(\ell)}=F_\ell\bigl(H^{(\ell-1)}\bigr)\in\mathbb R^{T\times d}.

“第一层输出”不是一个新词,也不是词表概率,而是整段序列的新向量。例如“小明/开始/工作”输入 3 行,第一层仍输出 3 行;第二层接收这 3 行,继续计算。每一层有自己的参数,同一层则在不同位置使用同一组参数。

层间数据流:三个位置的向量共同进入一层,Attention 汇集位置信息,FFN 逐位置变换,完整输出作为下一层输入

Attention 中,不同位置都读取上一层的向量。第一层“工作”的计算不需要等待第一层“小明”先算完;第二层则必须使用第一层已经算出的结果。序列位置之间可以并行计算,层与层仍按深度依次计算。

3. Attention 怎样汇集其他位置的信息#

3.1 同一个输入生成 Q、K、V#

Self-Attention 先把输入矩阵 XX 做线性投影,得到 Query、Key、Value,记为 Q,K,VQ,K,V。对单个注意力头,定义为:

Q=XWQ,K=XWK,V=XWV.Q=XW_Q,\qquad K=XW_K,\qquad V=XW_V.

WQ,WK∈Rd×dkW_Q,W_K\in\mathbb R^{d\times d_k},WV∈Rd×dvW_V\in\mathbb R^{d\times d_v} 是可训练参数;dk,dvd_k,d_v 是该头的向量维度。它们是对同一组输入做不同变换,不是三份不同的句子。

对位置 ii,qiq_i 用来与各位置的 kjk_j 计算匹配分数;这些分数决定各个 vjv_j 在汇集结果中占多大权重。Attention 权重不是下一个词的概率,而是当前头在位置之间分配的权重。

分开生成 Q、K、V,是为了让匹配方式与传递内容可以分别学习。 Q、K 用来决定从哪些位置读取信息、各占多大权重;V 则是按这些权重实际汇集的内容。Q 与 K 也使用不同投影,使一个位置作为查询时使用的特征,不必与它被其他位置匹配时使用的特征相同。

例如,计算“工作”的新向量时,用“工作”的 Q 与“小明/开始/工作”各自的 K 比较,再按得到的权重对各位置的 V 加权求和。计算“小明”的新向量时,则换成“小明”的 Q;此时“工作”的 K 参与匹配,“工作”的 V 提供内容。因此,每个位置都需要生成 Q、K、V,才能同时参与读取与被读取。

这不要求 Q、K、V 的数值一定不同,而是允许模型通过不同参数学习各自的投影。将它们全部设为原向量也能计算注意力,但会限制匹配和内容表示的选择。

3.2 算出“工作”位置的汇集结果#

下面是专门用于手算的二维例子,不是论文训练得到的 embedding。假设输入已经包含位置信息,且只有一个头,各个投影矩阵均取二维单位矩阵。因此,本例的 Q,K,VQ,K,V 都等于输入矩阵:

X=H(0)=[100112]←小明←开始←工作.X=H^{(0)}= \begin{bmatrix} 1&0\\ 0&1\\ 1&2 \end{bmatrix} \quad \begin{matrix} \leftarrow\text{小明}\\ \leftarrow\text{开始}\\ \leftarrow\text{工作} \end{matrix}.

“工作”在第三行,查询向量为 q3=[1,2]q_3=[1,2]。这里是编码器,没有因果遮罩,可以读取源句的全部位置。用点积衡量匹配,再除以 dk\sqrt{d_k};本例 dk=2d_k=2,三个匹配分数为:

工作位置的匹配分数=[ 1×1+2×0,  1×0+2×1,  1×1+2×2 ]2≈[0.7071,1.4142,3.5355].\text{工作位置的匹配分数} =\frac{[\,1\times1+2\times0,\;1\times0+2\times1,\;1\times1+2\times2\,]}{\sqrt2} \approx[0.7071,1.4142,3.5355].

softmax 将分数转换为非负、总和为 1 的权重。设匹配分数为 sjs_j,对应权重为 αj=exp⁡(sj)/∑rexp⁡(sr)\alpha_j=\exp(s_j)/\sum_r\exp(s_r)。代入得到:

工作位置的注意力权重=softmax⁡([0.7071,1.4142,3.5355])≈[0.0501,0.1017,0.8482].\text{工作位置的注意力权重} =\operatorname{softmax}([0.7071,1.4142,3.5355]) \approx[0.0501,0.1017,0.8482].

按这些权重对 Value 向量加权求和。用 a3a_3 表示“工作”位置的注意力输出:

a3=0.0501[1,0]+0.1017[0,1]+0.8482[1,2]≈[0.8983,1.7981].a_3=0.0501[1,0]+0.1017[0,1]+0.8482[1,2] \approx[0.8983,1.7981].

结果中已经包含“小明”和“开始”的向量贡献。“工作”无需先经过多个相邻位置,便能在这一层直接读取“小明”。这些权重只是本例参数产生的数值,不代表一个训练完成的模型应当怎样理解这句话。

3.3 一次计算所有位置,再组合多个头#

把各位置的点积排成矩阵,便得到缩放点积注意力的一般表达式:

Attention⁡(Q,K,V)=softmax⁡ ⁣(QK⊤dk+M)V.\operatorname{Attention}(Q,K,V) =\operatorname{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}+M\right)V.

MM 是遮罩矩阵:允许读取的位置取 0,禁止读取的位置取 −∞-\infty,经过 softmax 后对应权重为 0。softmax 对每一行单独计算;每一行对应一个查询位置。忽略 padding 时,编码器 self-attention 的 MM 全为 0。

QK⊤QK^\top 中,第 ii 行第 jj 列就是“位置 ii 读取位置 jj”的匹配分数。除以 dk\sqrt{d_k} 用来控制点积随维度增大而扩大的尺度,减轻 softmax 进入梯度较小区域的情况。[1]

多头注意力为每个头设置不同的投影参数,分别汇集信息,再拼接并投影回 dd 维。设头数为 mm,其定义为:

headr=Attention⁡(XWQ(r),XWK(r),XWV(r)),MHA⁡(X)=Concat⁡(head1,…,headm)WO.\begin{aligned} \mathrm{head}_r&=\operatorname{Attention}(XW_Q^{(r)},XW_K^{(r)},XW_V^{(r)}),\\ \operatorname{MHA}(X)&=\operatorname{Concat}(\mathrm{head}_1,\ldots,\mathrm{head}_m)W_O. \end{aligned}

不同头允许模型使用不同的投影与权重;不保证某个头固定负责语法、另一个固定负责人物关系。拼接不增加序列位置数,最终输出仍是 T×dT\times d。原始 base 模型使用 d=512d=512、8 个头,每个头的 dk=dv=64d_k=d_v=64。[1]

4. Attention 输出怎样成为完整的一层输出#

4.1 残差与归一化保留并调整输入信息#

Attention 之后,原始 Transformer 将注意力输出加回输入,再做 LayerNorm;随后经过 FFN,再做一次残差与 LayerNorm。设 UU 为第一段处理结果,一层编码器定义为:

U=LN⁡1(X+Dropout⁡(MHA⁡(X))),H(1)=LN⁡2(U+Dropout⁡(FFN⁡(U))).\begin{aligned} U&=\operatorname{LN}_1\bigl(X+\operatorname{Dropout}(\operatorname{MHA}(X))\bigr),\\ H^{(1)}&=\operatorname{LN}_2\bigl(U+\operatorname{Dropout}(\operatorname{FFN}(U))\bigr). \end{aligned}

残差相加让当前输入保留一条直接传递的计算路径,不必完全由 Attention 或 FFN 重建。LayerNorm 对每个位置自身的特征坐标计算均值和方差,调整数值尺度;它不在多个 token 之间取平均。

这是原论文的 Post-LN 顺序,即相加后归一化。将 LayerNorm 放在 Attention、FFN 前面属于 Pre-LN 变体,不能与原始结构的顺序混用。Dropout 在训练时随机屏蔽部分分量,推理时关闭。[1] [2]

4.2 FFN 分别变换每个位置#

FFN 是前馈网络,也常称 MLP。它对每行向量做相同的非线性变换,不读取其他位置。原论文使用两次线性变换和 ReLU:

FFN⁡(u)=max⁡(0,uW1+b1)W2+b2.\operatorname{FFN}(u)=\max(0,uW_1+b_1)W_2+b_2.

这里 uu 是一个位置的行向量,max⁡(0,⋅)\max(0,\cdot) 逐坐标保留正数、将负数置零。第一层线性变换把 dd 维映射到内部维度 dffd_{\mathrm{ff}},第二层再映射回 dd 维。base 配置使用 d=512d=512、dff=2,048d_{\mathrm{ff}}=2{,}048。同一层 FFN 在各位置共享参数,各层之间不共享这组参数。[1]

Attention 负责跨位置汇集信息,FFN 负责对每个位置已有的信息做非线性变换。FFN 不单独接触“小明”位置,却可以处理“工作”向量里已经汇集的小明信息。

4.3 算完第一层,再确定第二层输入#

继续二维手算例子。令注意力输出投影为单位矩阵,关闭 dropout。为便于手算,每次 LayerNorm 都取缩放参数 γ=[1,1]\gamma=[1,1]、偏移参数 β=[0,0]\beta=[0,0],暂忽略数值稳定项 ϵ\epsilon;本例各次方差均非零。LayerNorm 的计算定义为:

vˉ=1d∑r=1dvr,σv2=1d∑r=1d(vr−vˉ)2,LN⁡(v)=γ⊙v−vˉσv2+ϵ+β.\begin{aligned} \bar v&=\frac1d\sum_{r=1}^{d}v_r,\qquad \sigma_v^2=\frac1d\sum_{r=1}^{d}(v_r-\bar v)^2,\\ \operatorname{LN}(v)&=\gamma\odot\frac{v-\bar v}{\sqrt{\sigma_v^2+\epsilon}}+\beta. \end{aligned}

⊙\odot 表示逐坐标相乘。真实实现使用正的 ϵ\epsilon;忽略它只是为了本次手算得到简洁数字。

“工作”的输入为 x3=[1,2]x_3=[1,2],注意力输出为 a3≈[0.8983,1.7981]a_3\approx[0.8983,1.7981]。相加并归一化后:

工作位置的残差和=x3+a3≈[1.8983,3.7981],u3=LN⁡([1.8983,3.7981])=[−1,1].\begin{aligned} \text{工作位置的残差和}&=x_3+a_3\approx[1.8983,3.7981],\\ u_3&=\operatorname{LN}([1.8983,3.7981])=[-1,1]. \end{aligned}

再取一个用于演示的 FFN:FFN⁡(u)=0.5max⁡(0,u)\operatorname{FFN}(u)=0.5\max(0,u),可由 W1=I2W_1=I_2、W2=0.5I2W_2=0.5I_2、偏置为零实现,其中 I2I_2 是二维单位矩阵。它的内部维度也取 2,与论文的配置不同。“工作”位置的完整第一层输出为:

FFN⁡(u3)=[0,0.5],h3(1)=LN⁡([−1,1]+[0,0.5])=[−1,1].\begin{aligned} \operatorname{FFN}(u_3)&=[0,0.5],\\ h_3^{(1)}&=\operatorname{LN}([-1,1]+[0,0.5])=[-1,1]. \end{aligned}

其他位置执行同样的计算。按未舍入数值计算,本例的完整第一层输出、也就是第二层输入为:

第二层输入=H(1)=[1−1−11−11].\text{第二层输入}=H^{(1)}= \begin{bmatrix} 1&-1\\ -1&1\\ -1&1 \end{bmatrix}.

第二层接收整个矩阵,不是只接收“工作”的 [−1,1][-1,1]。 第二层使用自己的参数,从这个矩阵重新生成 Q、K、V,并计算新的输出。

二维 LayerNorm 会把许多不同输入映射成相同结果,因此本例中“开始”和“工作”的输出恰好相同。这个简化例子只用于核对数据流和计算,不能作为实际模型表示能力的示例。

5. 编码器与解码器怎样完成翻译#

5.1 编码器输出供解码器读取#

原始 base 模型堆叠 6 层编码器与 6 层解码器。编码器最后一层输出记为 ZZ,源句有 TsrcT_{\mathrm{src}} 个位置,则 Z∈RTsrc×dZ\in\mathbb R^{T_{\mathrm{src}}\times d}。它保留每个源位置的向量,不把整句压成单个向量。

解码器每层先处理输出前缀,再从 ZZ 汇集源句信息,最后经过 FFN。每段操作都带残差与 Post-LN。其中跨注意力 Cross-Attention 的 Query 来自当前解码器的隐藏向量,Key 和 Value 来自编码器最终输出;不同解码器层对同一 ZZ 使用自己的投影参数。[1]

注意力位置 Q 来自哪里 K、V 来自哪里 允许读取哪些位置
编码器 self-attention 上一层源句向量 同一组源句向量 完整源句中的有效位置
解码器 masked self-attention 上一层目标序列向量 同一组目标序列向量 当前及之前的输入位置
解码器 cross-attention masked self-attention 及其残差归一化的输出 编码器最终输出 ZZ 完整源句中的有效位置

源句位置数和目标句位置数可以不同。Cross-Attention 的权重矩阵为 Ttgt×TsrcT_{\mathrm{tgt}}\times T_{\mathrm{src}}:每个目标查询位置分别对源句各位置分配权重。

5.2 解码器输入右移,防止读到答案#

训练时,目标句已知。将目标序列右移一位,以句首符号 BOS 开始作为解码器输入;EOS 是结束符号。对翻译算例,位置对齐如下:

目标位置 1 2 3 4
解码器输入 BOS Xiaoming starts working
该位置的预测目标 Xiaoming starts working EOS

例如位置 2 用 BOS 和 Xiaoming 预测 starts。它不能读取输入位置 3 的 starts,否则训练时已经获得待预测答案。

设解码器查询位置为 ii、被读取的位置为 jj,因果遮罩定义为:

Mij={0,j≤i,−∞,j>i.M_{ij}=\begin{cases}0,&j\le i,\\-\infty,&j>i.\end{cases}

因此,“允许读取当前输入位置”与“禁止读取当前预测目标”并不冲突:右移后的当前输入是目标的前一个 token。训练使用真实目标前缀,这种方式称为 teacher forcing;各位置可以并行计算,但必须遵守遮罩。[1]

5.3 隐藏向量经输出层才成为词表概率#

设解码器最后一层在目标位置 tt 的输出为 gt∈Rdg_t\in\mathbb R^d,目标词表大小为 VtgtV_{\mathrm{tgt}}。输出矩阵 Wvocab∈Rd×VtgtW_{\mathrm{vocab}}\in\mathbb R^{d\times V_{\mathrm{tgt}}} 和偏置 bb 将其映射为词表分数;再用 softmax 得到下一 token 的概率:

logitst=gtWvocab+b,pθ(yt=v∣y<t,x)=softmax⁡(logitst)v.\begin{aligned} \mathrm{logits}_t&=g_tW_{\mathrm{vocab}}+b,\\ p_\theta(y_t=v\mid y_{<t},x)&=\operatorname{softmax}(\mathrm{logits}_t)_v. \end{aligned}

xx 是完整源句,y<ty_{<t} 是目标前缀,θ\theta 表示全部模型参数。这里 softmax 在目标词表上归一化;Attention 中的 softmax 则在允许读取的位置上归一化,两者用途不同。

假设某次前向计算为正确标签依次给出概率 0.6、0.5、0.25、0.8。这些是假设的模型输出,不是从二维编码器算例直接推得。忽略 padding,使用自然对数,按 4 个目标位置平均的负对数似然为:

LNLL=−ln⁡0.6+ln⁡0.5+ln⁡0.25+ln⁡0.84≈0.7034.\mathcal L_{\mathrm{NLL}} =-\frac{\ln0.6+\ln0.5+\ln0.25+\ln0.8}{4} \approx0.7034.

这是不使用 label smoothing 的教学算例。原论文训练还使用系数为 0.1 的 label smoothing,将监督分布从单个正确 token 扩展为平滑分布,实际目标不等于此处的纯 NLL。[1]

对纯 NLL,设正确标签为 yty_t,位置数为 SS,单个 logit 的梯度为:

∂LNLL∂logitst,v=pθ(v∣y<t,x)−1{v=yt}S.\frac{\partial\mathcal L_{\mathrm{NLL}}}{\partial\mathrm{logits}_{t,v}} =\frac{p_\theta(v\mid y_{<t},x)-\mathbf1\{v=y_t\}}{S}.

1{v=yt}\mathbf1\{v=y_t\} 在词表项等于正确标签时取 1,否则取 0。正确标签概率为 0.6、S=4S=4 时,对应 logit 的梯度为 正确 logit 的梯度=(0.6−1)/4=−0.1\text{正确 logit 的梯度}=(0.6-1)/4=-0.1。若独立优化这个 logit,梯度下降会增加它;真实模型的 logits 由共享参数生成,不能据此保证每个训练位置的正确概率都单独提高。

梯度通过输出层向前面的解码器、编码器和 embedding 传播。优化器随后更新参数。前向计算改变隐藏向量,训练更新改变模型参数;它们不是同一种“更新”。

5.4 生成时逐个扩展输出前缀#

推理时没有真实目标句。模型先编码源句得到 ZZ,然后以 BOS 为解码器输入。假设每次选择概率最大的 token:

  1. 输入 BOS,预测并选择 Xiaoming。
  2. 输入 BOS、Xiaoming,预测并选择 starts。
  3. 输入 BOS、Xiaoming、starts,预测并选择 working。
  4. 输入 BOS、Xiaoming、starts、working,预测并选择 EOS,结束生成。

这是演示用的贪心选择,输出不保证正确。论文翻译评测使用 beam search,保留多个候选前缀。源句没有变化时,编码结果 ZZ 可以复用;但下一个目标 token 仍依赖已经选择的输出,不能像 teacher forcing 那样同时获得整句预测所需的真实前缀。[1]

6. 原始结构的局限性与后续变体#

全注意力显式比较所有位置对,长序列会产生二次增长的注意力矩阵。 长度为 TT 的 self-attention 在每个头中产生 T×TT\times T 个匹配分数。这里描述的是原始稠密注意力的计算与存储方式;整层还包含线性投影和 FFN,不能把整层全部开销都归结为注意力矩阵。[1]

Encoder–Decoder 需要独立的源序列和目标序列数据流。 在翻译中,编码器可以读取完整源句,解码器只读取目标前缀;直接把这套原始结构称为“今天语言模型的全部架构”会混淆注意力方向与模块组成。Decoder-only 模型通常只保留因果自注意力堆栈,不另设读取独立编码结果的 cross-attention。

全局注意力本身没有局部窗口约束。 如果将 self-attention 换为 SWA(Sliding Window Attention,滑动窗口注意力),变化在于遮罩限制可读位置,而不是层与层之间只传递一个窗口。下一层仍接收上一层的完整序列向量;每个查询只从其中的局部范围汇集信息。跨多层时,局部范围以外的信息可能通过中间位置传播,但“允许存在传播路径”不保证模型实际保留了该信息。

参考资料#

  1. Vaswani 等,2017, Attention Is All You Need。本文按原始 Encoder–Decoder、Post-LN 与 base 配置解释;二维向量和概率算例为本文构造,不是论文实验。
  2. PyTorch 2.15:Transformer。核对编码器、解码器接口、输入输出形状及 norm_first 约定;完整模型还需在模块外配置 embedding 和词表输出层。

延伸阅读#

相关阅读路线

理解 Transformer 与架构变体 ↗