happy-llm 深度解析:结合 PyTorch 源码拆解 Transformer 的 Encoder-Decoder 架构与注意力机制
2026/9/10 14:03:59 网站建设 项目流程

happy-llm 深度解析:结合 PyTorch 源码拆解 Transformer 的 Encoder-Decoder 架构与注意力机制

【免费下载链接】happy-llm📚 从零开始构建大模型项目地址: https://gitcode.com/GitHub_Trending/ha/happy-llm

本篇技术指南以 happy-llm 仓库中Extra-Chapter/transformer-architecture的扩展内容为主体,围绕"从整体设计到模块细节"的主线,讲解 Transformer 的 Encoder-Decoder 结构、位置编码设计原则、PyTorch 顶层nn.Transformer的模块化实现,以及注意力机制的核心计算。你将掌握如何阅读 PyTorch 的torch/nn/modules/transformer.py源码来理解 Transformer 的整体设计,并将论文公式与仓库中 docs/chapter2/code/transformer.py 的"手搓"实现一一对应,建立起从架构图到可运行代码的完整认知。

从经典架构开始:Encoder-Decoder 结构

让我们先从经典架构图理解 Transformer 的整体设计思路。Transformer 分为两个主要部分:左侧的编码器(Encoder)和右侧的解码器(Decoder)。那么这两块结构的输入和输出分别是什么?

Encoder 的职责是接受完整的源序列输入,将其转换为一个富含语义信息的表示序列。想象一下,如果我们要做机器翻译,Encoder 就像是一个深度理解原文的专家,它需要充分理解整个句子的含义、语法结构和上下文关系。

Decoder 则承担着更复杂的任务:它需要接受目标序列和编码器输出的表示序列,然后输出词汇/字符的概率分布。这就像是一个翻译专家,既要理解原文的含义(通过 Encoder 的输出),又要根据已经翻译的部分来决定下一个词应该是什么。

在仓库主教程 docs/chapter2/第二章 Transformer 架构.md 的 2.2 章节中,这一 Encoder-Decoder 结构被用于解决 Seq2Seq 任务(如机器翻译):输入是自然语言序列,输出是另一个可能不等长的序列。本节扩展内容正是对 2.2 章节的补充,重点从 PyTorch 源码的角度解释 Transformer 各模块的整体设计。

Positional Encoding:位置编码设计

Transformer 本身对位置信息不敏感。比如"我爱你"和"你爱我"这两个句子,在没有位置信息的情况下,模型无法感知到这是语义完全不同的句子。这就像是一个人失去了对词语顺序的感知能力,显然无法正确理解语言。

因此,我们需要一个带有位置信息的向量,将其添加到每个 input embedding 上,来对不同位置得到不同的表征。这个模块就是架构图中的Positional Encoding

位置编码的三条设计原则

在设计编码模块时,有三个重要的前提假设,这些假设直接影响了最终的实现方案:

1. 确定性原则:每个位置的编码应该是确定的数字,不同序列中相同位置的编码应该相同。

为什么这个原则如此重要?让我们考虑一个反例:如果用等分的设计方法,将一个序列从 0~1 之间做均匀划分,那么序列长度不一样时,每个位置上的编码也就不一样。当序列长度为 5 时,位置编码可能是 0、0.2、0.4、0.6、0.8,但如果序列长度为 10,就变成了 0、0.1、0.2……同样对于第二个位置上的字符,在第一个序列中是 0.2,在第二个序列中又是 0.1,这样的编码就失去了确定性。

2. 相对关系一致性:不同句子中,对于任意两个位置之间的相对距离,相对关系应该保持一致。

这个目的是为了学习通用的语言关系,比如:"修饰词在被修饰词前 1 个位置"是通用模式。以下面的长短句举例:

- 长句子(10个词) 位置: 0 1 2 3 4 5 6 7 8 9 词汇: I am learning about transformers today in class now - 短句子(6个词) 位置: 0 1 2 3 4 5 词汇: I like deep learning models

在长句子中,位置 1 和位置 4 之间的编码关系,应该与短句子中位置 2 和位置 5 之间的编码关系完全相同,因为模型需要学会的是通用的相对位置关系。

3. 泛化能力:位置序列应该能推广到没见过的更长序列。

假如训练集中序列长度都是 10 以内的,但测试集中可能会有长度为 15 的句子,我们希望即使测试集中句子长度更长、在训练中没有见过,也能通过这样的位置编码推广过去。

三角函数编码

基于这些假设,Transformer 采用了 sin 和 cos 的组合来表征绝对位置信息:

- 向量维度为偶数:PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) - 向量维度为奇数:PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

通过 sin 和 cos 的组合来表征绝对位置的好处是:pe(pos+k)可以写成pe(pos)的线性组合(利用三角函数公式sin(A + B) = sin(A)cos(B) + cos(A)sin(B))。这样做的意义是:即使测试集中出现了 pos+k 这种未见过的位置,我们也可以把它写成训练集中见过的位置的线性组合,而不用担心测试集中遇到更长的句子无法推广。

在仓库的手写实现 PositionalEncoding 中,正是用向量化的方式完成了这一编码:先用torch.arange(0, args.block_size)构造位置序列,再用torch.exp(torch.arange(0, args.n_embd, 2) * -(math.log(10000.0) / args.n_embd))计算不同维度上的频率项,最后通过pe[:, 0::2] = torch.sin(position * div_term)pe[:, 1::2] = torch.cos(position * div_term)分别填充偶数维和奇数维,并注册为模型缓冲区register_buffer("pe", pe),随模型一起保存但不参与梯度更新。

位置信息的残差传递机制

位置编码在最底层添加,会不会在深层网络中丢失?这个担心是多余的。通过残差连接,位置信息能够充分传递到上层网络。

假设有一个 N 层的神经网络,输入为 x₀(包含位置编码),那么:

- 第1层: x₁ = x₀ + F₁(x₀) - 第2层: x₂ = x₁ + F₂(x₁) = x₀ + F₁(x₀) + F₂(x₁) - 第3层: x₃ = x₂ + F₃(x₂) = x₀ + F₁(x₀) + F₂(x₁) + F₃(x₂) - ... - 第N层: xₙ = x₀ + Σᵢ₌₁ⁿ Fᵢ(xᵢ₋₁)

可以看到,初始的位置信息 x₀ 始终存在于每一层的输出中,这确保了位置信息不会随着网络层数的加深而消失。

从 PyTorch 源码 API 理解 Transformer 架构设计

通过查看 PyTorch 的源码,可以了解 Encoder 和 Decoder 中的架构实现。源码位于 PyTorch 的torch/nn/modules/transformer.py(本次解析借助的版本是 v2.5.1;仓库中 docs/chapter2/code/requirements.txt 锁定的 torch 版本为 2.7.0,核心 API 一致)。

顶层 Transformer 类与核心参数

首先,PyTorch 定义了一个顶层的Transformer类,我们可以通过torch.nn.Transformer来调用它:

# 使用示例 transformer_model = nn.Transformer(d_model=512, nhead=8, num_encoder_layers=6)

Transformer__init__函数中,主要有 5 个核心参数:

参数默认值含义与设计考量
d_model512整个 Transformer 的特征维度,原论文中设置的是 512。这个维度需要足够大以承载丰富的语义信息,但太大会导致计算复杂度过高
nhead8Multi-head attention 的头数目。多头设计的目的是让模型可以捕捉到更多位置与位置之间的关系
num_encoder_layers6Encoder 的 block 数目,encoder 的每个 block 包含多头自注意力机制和前馈神经网络,默认 6 个
num_decoder_layers6Decoder 中 block 数目,decoder 的每个 block 包含多头自注意力机制、交叉注意力机制以及前馈神经网络
dim_feedforward2048前馈神经网络层中间的特征维度。Multihead attention 输出时,会首先映射到 2048 这个大的特征空间,然后再映射回 512 这样的空间。必须要保证输出的维度仍然是 512,这样就可以进行残差连接

对应到仓库的手写实现 Transformer 中,这些参数被封装进ModelArgs数据类(n_embdn_headsdimdropoutmax_seq_lenvocab_sizeblock_sizen_layer),模型通过nn.ModuleDict统一注册了词嵌入wte、位置编码wpe、Dropout、Encoder 和 Decoder,最后通过lm_head线性层将隐藏维度映射回词表大小。

模块化设计:四个核心 class

init函数的作用是实例化模块,第一个要实例化的模块就是 encoder。encoder 通过TransformerEncoder的 class 去实现实例,在这个 class 中需要传入encoder_layer;而在TransformerEncoderLayer的 class 中实现了 Multihead self attention 的调用、残差连接、层归一化、全连接层网络,这些共同构成一个 encoder_layer。

对于 decoder 部分也是一样,传入decoder_layer参数,这个 layer 包含了自注意力机制、交叉注意力机制以及前馈神经网络。

总体上 Transformer 源码就是由四个 class 所构成:

  • TransformerEncoderLayer:每一个编码层的实现
  • TransformerEncoder:负责把这些编码层串起来
  • TransformerDecoderLayer:每一个解码层的实现
  • TransformerDecoder:把这些解码层串起来

这种模块化的设计体现了软件工程的最佳实践:单一职责原则和组合优于继承的思想。在仓库手写实现中同样遵循了这一结构:EncoderLayer 与 DecoderLayer 负责单层内部的计算,而 Encoder 与 Decoder 通过nn.ModuleList把 N 层串联起来,并在末尾追加一个 Layer Norm。

Forward 函数:编排计算流程

forward函数中,Transformer 的计算流程非常清晰。

首先 encoder 输入是 source 句子以及 padding_mask。encoder 中的注意力机制不需要掩码,因此 mask 及 is_causal 参数不需要传入掩码;但需要对样本长度做掩盖,即padding_mask。这个 mask 表示每一个样本的长度。当我们做训练时,序列长度是不一样的,有些短的样本,在后面的一些位置上就是无效的,通过在 softmax 中把无效位置上的值转成负无穷,这样经过归一化后概率就变成 0,使得这些没有值的位置变得无效。

decoder 输入有四个要素:

  • target:目标句子;
  • memory:encoder 输出,因为 memory 会输送到交叉注意力中;
  • target mask:一个考虑因果的 mask,在数学上是一个上三角矩阵;
  • memory mask:和 source sentence 的长度有关,在 batch 训练中 source sentence 每一个样本都不太一样,memory mask 就是每个输入源序列样本的长度。

因果掩码 mask

每次预测时,decoder 都会有一个输入句子,即 output embedding。但这个 output embedding 不能全部给它——如果全部给它的话,那就变成了 identity 映射,相当于"从 x 到 x"的关系直接给出,那预测出来的答案肯定是从 x 到 x。

我们需要保证 output 每次只根据当前要预测单词的左边所有单词,去预测这个单词;这个单词本身和它右边的单词都不要输送到 output embedding。这样的操作需要通过 mask 来实现,随着预测的字符往右进行,我们给到 decoder 中的 output 会越来越多,所以它就是一个上三角矩阵

在仓库手写实现的 MultiHeadAttention 中,因果掩码的生成逻辑是:

# 创建一个上三角矩阵,用于遮蔽未来信息 # 注意,因为是多头注意力,Mask 矩阵比单头实现多一个维度 if is_causal: mask = torch.full((1, 1, args.max_seq_len, args.max_seq_len), float("-inf")) mask = torch.triu(mask, diagonal=1) self.register_buffer("mask", mask)

torch.triu(mask, diagonal=1)使得上三角位置(包含对角线右上方)全部为-inf、其余位置为 0;在注意力分数计算后执行scores = scores + self.mask[:, :, :seqlen, :seqlen],再经过 Softmax 归一化,-inf位置的概率自然变为 0,从而遮蔽未来信息。注意到这里对 mask 做了:seqlen的截取,因为有些序列可能比max_seq_len短。在forward中,只有is_causal=True(即 decoder 的掩码自注意力)才应用该 mask。

Transformer 整体框架

这就是 Transformer class 总体上的构成框架:init函数去实例化 encoder 和 decoder;在forward函数中基于 source、target、source mask、target mask,分别入参到 encoder 和 decoder 中,最终得到 output,即要预测字符的概率。

Transformer 本质上是一个自回归的解码过程,不是并行的预测输出,而是每次只会预测一个、输出一个,然后不断进行解码去预测出整体的 target sentence。仓库手写实现中这一过程也体现在Transformer.forward的推理分支:logits = self.lm_head(x[:, [-1], :]),即只取序列中最后一个位置的输出作为下一步预测。

Encoder 编码器层

接下来分别看四个 class 的具体实现。首先是单个编码器:在TransformerEncoderLayer__init__函数中,需要实例化四个关键组件。先看 init 函数中的参数,和 Transformer 中传入的一致:d_model是整个 Transformer 的特征维度(512),nhead是 Multi-head attention 中多头自注意力机制中头的数目。

多头注意力的设计

为什么是多头?目的是让模型可以捕捉到更多的位置与位置之间的关系。多头会分为多组的 query、key 和 value,每一组会单独地计算 attention 的上下文向量,最后把这个向量拼起来,再通过 FFN 得到最终的一个向量。

这样做的话,embedding 特征向量的维度会降低:比如说原来的特征向量维度是 512,如果分为 8 个头,这时每个头向量的维度就会变成 64(这里不是通过压缩,而是线性变换重组)。每个头独立计算注意力后,会得到 8 个 64 维的输出向量;然后通过拼接操作(concatenation),将这 8 个 64 维向量首尾相接,重新组合成一个 512 维的向量;最后再通过一个输出线性变换层,得到最终的 512 维输出。

仓库实现 MultiHeadAttention.forward 展示了这一过程的工程技巧:通过self.wq / self.wk / self.wv三个nn.Linear组合矩阵一次性完成所有头的线性投影,再通过view(bsz, seqlen, self.n_heads, self.head_dim)transpose(1, 2)把张量重排为(B, n_head, T, head_dim)从而并行计算多头注意力——其核心逻辑在于"矩阵内积再拼接"等同于"拼接矩阵再内积"。计算完注意力后,再transpose(1, 2).contiguous().view(bsz, seqlen, -1)恢复时间维并合并头,最后经wo投影回残差流。源码注释特别提醒:由于transpose并不改变底层存储,必须先contiguous()view,否则 PyTorch 会报错。

前馈神经网络

dimension feed forward 是前馈神经网络 FFN 的维度,因为需要先从 512 到 2048,再从 2048 到 512,所以设定了两个全连接层。前馈神经网络对每个单独位置进行建模,并且不同位置的参数是共享的。类比 1×1 的 pointwise 卷积——对图像中每个像素位置的特征向量独立进行变换;参数共享就是为序列中的每个位置都设计相同的参数,目的是希望模型学会"如何处理特征"的通用能力,而不是"如何处理第 x 个位置的特征"的特定能力。

FFN 实际上做的是 embedding 相同位置不同维度间的融合,在每个位置内部对该位置的不同特征维度进行融合,注意力机制则负责位置间的信息交流。仓库中的 MLP 实现正是"两个线性层 + ReLU + Dropout"的结构:

class MLP(nn.Module): '''前馈神经网络''' def __init__(self, dim: int, hidden_dim: int, dropout: float): super().__init__() self.w1 = nn.Linear(dim, hidden_dim, bias=False) self.w2 = nn.Linear(hidden_dim, dim, bias=False) self.dropout = nn.Dropout(dropout) def forward(self, x): return self.dropout(self.w2(F.relu(self.w1(x))))

注意,Dropout 层只在训练时开启、推理阶段关闭,因此许多 Transformer 结构示意图中不会画出该层。

Encoder 层的组件实例化

TransformerEncoderLayerinit函数中需要实例化以下实例:

  1. 首先是 Multi-head attention(本节着重整体框架,其细节在下一章注意力机制部分展开);
  2. 实例化 FFN 前馈神经网络中的两个 Linear 层,第一个 Linear 比较大(512→2048),第二个 Linear 重新投射回 d_model 的尺度(2048→512);
  3. 实例化 layer norm,在 self attention 之后会经过层归一化,以及在前馈神经网络之后也会经过一个层归一化;
  4. 实例化两个 dropout。dropout 是为了使得这个网络具备集成学习的特点,即使我们在训练多个模型时也能提高泛化能力。

Encoder 前向传播:编排计算流程

forward函数中,encoder 层的调用很简单。Transformer encoder layer 的第一部分:通过 self attention block 得到一个表征(self._sa_block),然后再加上一个残差连接(就是和 x 加起来),最后再经过一个层归一化。self attention 的输入是序列 x 和 padding mask,这里的序列 x 既充当了 query,又充当了 key 和 value(即自注意力的 Q=K=V 同源)。

第二部分是feed forward block:把第一部分输出经过每个位置独立的一个全连接层,再进行一个残差连接,输送到层归一化中就得到 x。这个就是 Transformer encoder 中每个 layer 的输出。

需要特别说明的是层归一化的位置:原始论文(Post-Norm)的设计是层归一化在后(先子层、后 Norm),而 PyTorch 源码中TransformerEncoderLayer默认走的是norm_first=False分支之外的另一条路径(即先 Norm 再子层的 Pre-Norm 风格)。考虑到目前 LLM 一般采用 Pre-Norm 结构(可以使训练 loss 更稳定),仓库的手写实现也采用 Pre-Norm 风格,见 EncoderLayer.forward:

def forward(self, x): # Layer Norm x = self.attention_norm(x) # 自注意力 h = x + self.attention.forward(x, x, x) # 经过前馈神经网络 out = h + self.feed_forward.forward(self.fnn_norm(h)) return out

Encoder 层的串联

TransformerEncoderclass 的作用是将多个编码器层串联起来:将上一层的输出作为下一层的输入,经过多层处理得到最终的编码器输出。init 主要传入两个参数:

  • encoder_layerTransformerEncoderLayerclass 的一个实例;
  • num_layers:transformer encoder 有多少层,层的含义就是 block。encoder 中"自注意力机制 + 前馈神经网络"这两块是一个 block,也就是一层。

仓库中的 Encoder 通过nn.ModuleList([EncoderLayer(args) for _ in range(args.n_layer)])实现多层串联,并在全部层之后追加一个最终的LayerNorm

Decoder 解码器

解码器的实现比编码器更复杂,因为它包含三个子模块,需要处理更多的交互。在TransformerDecoderLayer中,我们需要实例化三套组件(自注意力 + 交叉注意力 + 前馈神经网络)。

在 init 参数中:

  • d_model:Transformer 模型特征大小,默认 512;
  • nhead:Transformer decoder 的多头自注意力机制的头数;
  • dim_feedforward:decoder 中 FFN 前馈神经网络的维度。

Decoder 的两种注意力机制

init 参数中,decoder 和 encoder 不同的地方,就是需要实例化两个Multihead attention:

  • 第一个 Multi-head attention 是自注意力机制,它针对 decoder 的输入序列,即 target sentence embedding 作为输入序列的自身表征,Q、K、V 全部来自目标序列本身;
  • 第二个 Multi-head attention 是交叉注意力机制。我们想知道 decoder multihead attention 的输出和 encoder 输出状态的关联性,用该注意力机制跨越了 encoder 和 decoder 两个不同序列——不是 decoder 内部的自我关注,而是让 decoder 去关注 encoder 的信息。于是我们通过用 decoder MHA(多头注意力)的一个输出作为 query,然后用 encoder 的输出作为 key 和 value 来算出一个上下文表征。

同样 Decoder 要实现两个 Linear 层:第一个 Linear 层比较大,把交叉自注意力机制的输出投射到更高维的空间(2048);然后再投射回低维空间(2048→512)。由于 Decoder 有三个模块(自注意力 + 交叉注意力 + 前馈神经网络),所以这里要实现 3 个 norm 和 3 个 dropout。

仓库的 DecoderLayer 忠实呈现了这一结构:三个 LayerNorm(attention_norm_1attention_norm_2ffn_norm)、一个is_causal=True的掩码自注意力、一个is_causal=False的交叉注意力、一个 MLP。

Decoder forward:编排流程

解码器的forward函数体现了三个模块的协同工作:

  1. 第一个模块会把 target sentence(序列 x)和 target mask 输入到self._sa_block中,对 target 句子做自注意力机制的计算,结果放入到残差网络中,并且经过层归一化得到输出;
  2. 第二个模块依赖于第一个模块输出的 x,再和 encoder 输出的 memory 做交叉注意力的计算,得到新的表征后,经过残差网络和归一化的 norm 输出;
  3. 把第二个模块的输出输送到 FFN 前馈神经网络,再次进行残差网络和归一化的 norm,得到 decoder 的输出。

可以看下_sa_block_mha_block各自的调用,它们都调用的是 Multihead attention,只不过它们的 query、key、value 是不一样的:

  • self attention 中:query、key、value 都是目标序列,是"自身对自身"的相关性计算;
  • 交叉注意力机制中:query 是 decoder 的输出,key 和 value 是 encoder 的输出(始终是 memory)。

仓库中的对应实现(DecoderLayer.forward)通过函数参数显式区分了两者:

def forward(self, x, enc_out): # Layer Norm x = self.attention_norm_1(x) # 掩码自注意力 x = x + self.mask_attention.forward(x, x, x) # 多头注意力(交叉注意力) x = self.attention_norm_2(x) h = x + self.attention.forward(x, enc_out, enc_out) # 经过前馈神经网络 out = h + self.feed_forward.forward(self.ffn_norm(h)) return out

可以看到,掩码自注意力中三个入参都是x,而交叉注意力中 query 是x、key 和 value 都是enc_out(Encoder 输出)。通过多个TransformerDecoderLayer构成TransformerDecoder的方式,与TransformerEncoder实现类似。

注意力机制的核心计算

最后看下注意力机制的核心计算。PyTorch 的实际实现更加复杂和优化,但核心思想可以用论文版本来理解。

注意力机制的直观理解

attention 函数就是将一个 query 和一个由 key 和 value 形成的一对元素建立一个连接,最终得到一个输出。比如:我们去搜索引擎搜索一个词条,这个词条就是 query,搜索引擎的数据库里有很多词条信息,每个信息自身都有个 key,value 就是该词条的具体内容。我们通过这个 query,搜索引擎就会返回一个搜索结果。这个结果就可以理解为一个注意力机制——基于 query 和 key+value 计算出来的一个上下文。

注意力机制的计算结果是Value 的加权求和:权重是基于 Query 和 Key 的相似度计算出来的。先算 Query 和每个 Key 的相似度,基于这个相似度进行 Softmax 归一化得到权重,再把这个权重与每个 Key 所对应的 Value 进行加权求和。

Scaled Dot-Product Attention

在 Transformer 模型中,用的是 "Scaled Dot-Product Attention"。公式中 QKᵀ 会除以一个根号 d_k,这个目的就是为了使得 Softmax 的输入分布会更加稳定一点,也就是使得它的方差会更小一点。

这个 Attention 由三部分构成,分别是 Q、K、V,它们都是向量。首先把 query 和 key 进行一个矩阵相乘:单个样本来看就是向量内积,批量来看就是矩阵相乘。

内积过后再除以一个根号 d_K,把每个位置上的内积放到一起去做一个归一化。这样就可以得到每个位置上的一个概率表示,因为 Softmax 出来的结果总和为 1,且每一个值都在 0 到 1 之间。得到这样一个概率后,把概率和每个位置上的 value 进行一个加权求和,最终得到 attention 的一个输出,这就是 scaled dot product attention 的计算逻辑。

论文中讲的 Multi-head self attention 其实就是有很多个这样的自注意力机制同时计算,算完之后把每一个得到的结果拼起来,得到 Multi-head self attention 最终的输出。

注意力计算的代码实现

下面 attention 代码是论文实现的简单版本,输入由 query、key 和 value 构成:

'''注意力计算函数(论文简化版本)''' def attention(query, key, value, dropout=None): ''' args: query: 查询值矩阵 key: 键值矩阵 value: 真值矩阵 ''' # 获取键向量的维度,键向量的维度和值向量的维度相同 d_k = query.size(-1) # 计算Q与K的内积并除以根号dk # transpose 相当于转置 scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k) # Softmax p_attn = scores.softmax(dim=-1) if dropout is not None: p_attn = dropout(p_attn) # 根据计算结果对value进行加权求和 return torch.matmul(p_attn, value), p_attn

首先会把 q 跟 k 的转置进行矩阵相乘,得到一个 1×T 的向量,把这个向量做一个 mask。这里的 mask 就是把等于 0 的位置填充一个非常非常小的数(负无穷),因为负无穷的数经过 Softmax 归一化之后就会变成 0 的概率,目的是希望那些不重要位置上的概率赋为 0。这里只有一个 mask,所以你可以理解这是一个自注意力机制的实现;如果有两个 mask,那就是交叉注意力机制的实现。

得到 p_attn 概率分布之后,再把这个 p_attn 跟 value 进行加权求和,得到最终自注意力机制的输出。这是单个自注意力机制的计算逻辑,如果是多头的话,最终把单个的输出拼起来就好。

不同注意力机制的 QKV 来源

在 Transformer 模型中,不同的注意力机制有着不同的 QKV 来源和映射方式:

  1. encoder 层的 query、key 和 value:在编码器中都是由 word embedding 加上 position encoding 后,通过三个独立的线性映射得到 QKV;
  2. decoder 中 self attention 层:同样也是通过 target sentence embedding + position encoding,通过三个独立的线性映射得到 QKV;
  3. 交叉 attention 中:query 是由 decoder 的输出经过一个线性映射得到的,key 和 value 是编码器的输出 memory 分别经过两个映射得到。

仓库手写实现中 MultiHeadAttention.forward 的签名forward(self, q, k, v)正好支持上述三种用法:EncoderLayer 中调用attention.forward(x, x, x)实现自注意力;DecoderLayer 中掩码自注意力调用mask_attention.forward(x, x, x),交叉注意力调用attention.forward(x, enc_out, enc_out),与论文中 QKV 来源的三种情形一一对应。

总结

本节从三个层面完整拆解了 Transformer 的 Encoder-Decoder 架构:

  • 设计层面:Encoder 将源序列编码为语义表示,Decoder 结合目标序列与 memory 输出概率分布;位置编码通过 sin/cos 组合同时满足确定性、相对关系一致性与长度泛化三条设计原则,并依靠残差连接逐层传递。
  • 源码层面:PyTorch 的nn.TransformerTransformerEncoderLayerTransformerEncoderTransformerDecoderLayerTransformerDecoder四个 class 模块化组合而成,其 forward 流程中 padding mask、因果上三角 mask 与 memory mask 各司其职。
  • 计算层面:注意力本质是"基于 Query 与 Key 的相似度对 Value 加权求和",除以根号 d_k 的缩放保证 Softmax 输入分布稳定,多头机制通过分组计算与拼接捕捉更丰富的位置关系。

若希望从零"手搓"一个完整可运行的 Transformer(含 Embedding、位置编码、MultiHeadAttention、LayerNorm、MLP、Encoder/Decoder 组装与训练/推理 forward 分支),可以对照仓库中的 docs/chapter2/code/transformer.py 逐步实现,其运行依赖见 docs/chapter2/code/requirements.txt;完整的主教程讲解可继续阅读 docs/chapter2/第二章 Transformer 架构.md。

【免费下载链接】happy-llm📚 从零开始构建大模型项目地址: https://gitcode.com/GitHub_Trending/ha/happy-llm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询