AIGC宇宙 AIGC宇宙

Transformer 模型结构详解及代码实现!

一、Transformer简要发展史以下是Transformer模型发展历史中的关键节点:Transformer架构于2017年6月推出。 原本研究的重点是翻译任务。 随后推出了几个有影响力的模型,包括:时间模型简要说明2017 年 6 月「Transformer」Google 首次提出基于 Attention 的模型,用于机器翻译任务2018 年 6 月「GPT」第一个使用 Transformer 解码器模块进行预训练的语言模型,适用于多种 NLP 任务2018 年 10 月「BERT」使用 Transformer 编码器模块,通过掩码语言建模生成更强大的句子表示2019 年 2 月「GPT-2」更大更强的 GPT 版本,由于潜在风险未立即发布,具备出色的文本生成能力2019 年 10 月「DistilBERT」BERT 的轻量化版本,在保留 97% 性能的同时,速度更快、内存占用更低2019 年 10 月「BART、T5」使用完整的 Encoder-Decoder 架构,在各种 NLP 任务中表现优异2020 年 5 月「GPT-3」超大规模语言模型,支持“零样本学习”,无需微调即可完成新任务这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。

一、Transformer简要发展史

以下是Transformer模型发展历史中的关键节点:

Transformer 模型结构详解及代码实现!

Transformer架构于2017年6月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括:

时间

模型

简要说明

2017 年 6 月

「Transformer」

Google 首次提出基于 Attention 的模型,用于机器翻译任务

2018 年 6 月

「GPT」

第一个使用 Transformer 解码器模块进行预训练的语言模型,适用于多种 NLP 任务

2018 年 10 月

「BERT」

使用 Transformer 编码器模块,通过掩码语言建模生成更强大的句子表示

2019 年 2 月

「GPT-2」

更大更强的 GPT 版本,由于潜在风险未立即发布,具备出色的文本生成能力

2019 年 10 月

「DistilBERT」

BERT 的轻量化版本,在保留 97% 性能的同时,速度更快、内存占用更低

2019 年 10 月

「BART、T5」

使用完整的 Encoder-Decoder 架构,在各种 NLP 任务中表现优异

2020 年 5 月

「GPT-3」

超大规模语言模型,支持“零样本学习”,无需微调即可完成新任务

这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类:

类别

构成

特点

典型模型

「GPT-like」

(自回归 Transformer)

只使用解码器

自回归方式预测下一个词,适合文本生成任务

GPT、GPT-2、GPT-3

「BERT-like」

(自动编码 Transformer)

只使用编码器

掩码机制学习上下文表示,适合理解类任务如问答、情感分析

BERT、RoBERTa、DistilBERT

「BART/T5-like」

(序列到序列 Transformer)

编码器 + 解码器

完整的 encoder-decoder 架构,适合翻译、摘要等生成+理解结合的任务

BART、T5

Transformer 默认都是大模型,除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。其中,GPT-2 是使用「transformer 解码器模块」构建的,而 BERT 则是通过「transformer 编码器」模块构建的。

Transformer 模型结构详解及代码实现!

二、Transformer 整体架构

论文中给出用于中英文翻译任务的 Transformer 整体架构如下图所示:

Transformer 模型结构详解及代码实现!

可以看出Transformer架构由Encoder和Decoder两个部分组成:其中Encoder和Decoder都是由N=6个相同的层堆叠而成。Multi-Head Attention 结构是 Transformer 架构的核心结构,其由多个 Self-Attention 组成的。其中,

部件

结构

层数

主要模块

Encoder

编码器层堆叠

N=6层

Self-Attention+Feed Forward

Decoder

解码器层堆叠

N=6层

Self-Attention+Encoder-Decoder Attention+Feed Forward

Transformer 架构更详细的可视化图如下所示:

Transformer 模型结构详解及代码实现!

1. 输入模块

(1) Tokenizer预处理

在基于Transformer的大模型LLM中,输入通常为字符串文本。由于模型无法直接处理自然语言,因此需要借助Tokenizer对输入进行预处理。具体流程如下:

  • 分词(Tokenization):将输入文本按规则切分为一个个词元(token),如单词、子词或特殊符号。
  • 词表映射(Vocabulary Mapping):每个 token 被映射到一个唯一的整数 ID,该 ID 来自预训练模型所使用的词汇表。
  • 生成 input_ids 向量(矩阵):最终输出是一个由 token ID 构成的向量(或矩阵),作为模型输入。

以下是以 Hugging Face 的 transformers 库为例,展示如何使用 BertTokenizer 和 BertModel 完成输入文本的预处理和编码:

复制
from transformers import BertTokenizer, BertModel
import torch

# 1. 加载预训练的 BERT tokenizer 和模型
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

# 2. 输入文本
text = "A Titan RTX has 24GB of VRAM"

# 3. 分词并映射为 token ID 序列
inputs = tokenizer(text, return_tensors="pt", truncatinotallow=True, padding=True)

# 输出 token IDs
print("Token IDs:", inputs['input_ids'])

# 4. 传入模型,获取输出
outputs = model(**inputs)

# 5. 获取最后一层的隐藏状态表示
last_hidden_states = outputs.last_hidden_state
print("Last hidden states shape:", last_hidden_states.shape)

原始输入文本 "A Titan RTX has 24GB of VRAM" 通过 tokenizer 完成分词和词表映射工作,生成的输入 ID 列表:

复制
[101, 138, 28318, 56898, 12674, 10393, 10233, 32469, 10108, 74727, 36535, 102]

其中,

  • 101 表示 [CLS] 标记;
  • 102 表示 [SEP] 标记;
  • 其余为对应 token 在词表中的索引。

在所有基于 Transformer 的 LLM 中,唯一必须的输入是 input_ids,它是由 Tokenizer 映射后的 token 索引组成的整数向量,代表了输入文本在词表中的位置信息。

(2) Embedding 层

在基于 Transformer 的大型语言模型(LLM)中,嵌入层(Embedding Layer)是将输入 token ID 映射为向量表示的核心组件。其作用是将离散的整数索引转换为连续、稠密的向量空间表示,从而便于后续神经网络进行语义建模。

✅ 万物皆可 Embedding:虽然最常见的是词嵌入(Word Embedding),但图像、语音等也可以通过嵌入层映射为向量形式,实现统一建模。

例如,mnist 数据集中的图片,可以通过嵌入层来表示,如下图所示,每个点代表一个图片(10000*784),通过嵌入层,将图片的像素点转化为稠密的向量,然后通过 t-SNE/pca 降维,可以看到图片的空间分布。

Transformer 模型结构详解及代码实现!

LLM 中,单词 token 需要经过 Embedding 层,Embedding 层的作用是将输入的离散化表示(例如 token ids)转换为连续的低维向量表示,其由单词 Embedding 和位置 Embedding (Positional Encoding)相加得到,通常定义为 TransformerEmbedding 层。

① 单词嵌入(Token Embedding)」

自然语言处理中,输入文本通常是以符号形式存在的词汇,而这些离散符号无法直接被神经网络处理。因此需要一个可学习的嵌入矩阵将每个 token 转换为固定维度的向量。

工作原理:

a. 输入是一个形状为 [batch_size, seq_len] 的整数张量,表示每个 token 在词表中的索引;

b. 输出是一个形状为 [batch_size, seq_len, d_model] 的三维张量,其中:

  • d_model 是嵌入维度(如 512 或 768);
  • 每个 token 对应一个 d_model 维的向量;

c. 嵌入层权重矩阵大小为 [vocab_size, d_model],参数量为:

在 PyTorch 中,词嵌入层通常使用 torch.nn.Embedding 模块实现,其作用是将 token 的索引转换为低维语义向量表示。

✅ 输入与输出说明

类型

描述

输入

一个整数张量,表示每个 token 在词表中的索引

输入形状

(batch_size, sequence_length)其中:- batch_size:批次大小(即一次处理多少条文本)- sequence_length:每条文本包含的 token 数量

输出

每个 token 被映射到 embedding_dim 维度的稠密向量

输出形状

(batch_size, sequence_length, embedding_dim)

  • embedding_dim 是嵌入向量的维度,也称为词向量维度;
  • 它通常被设置为 d_model 或 h,即后续 Transformer 层使用的隐藏层维度(如 512 或 768).

📐 示例代码:构建 Token Embedding 层

复制
from transformers import BertTokenizer
import torch.nn as nn

## 1, 使用 BERT tokenizer 将批量输入的字符串文本序列转化为 input_ids
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased") 
batch_text = ["A Titan RTX has 24GB of VRAM", "I have a dog and cat"]
inputs = tokenizer(batch_text, return_tensors="pt", truncation=True, padding=True)
input_ids = inputs["input_ids"]

# 2. 创建一个 nn.Embedding 层
vocab_size = tokenizer.vocab_size  # 词表大小取决于你加载的具体 tokenizer 模型
embedding_dim = 512  # 嵌入向量的维度,参考 transformer 论文的大小
embedding_layer = nn.Embedding(vocab_size, embedding_dim)

# 3. 通过 nn.Embedding 层,将输入的 IDs 映射到嵌入向量
embedded_output = embedding_layer(input_ids)

# 4. 输出嵌入向量的形状
print("嵌入向量的形状:", embedded_output.shape)  # (batch_size, sequence_length, embedding_dim), torch.Size([2, 12, 512])

# 5. 打印嵌入向量
print(embedded_output)

程序运行后输出结果如下所示:

Transformer 模型结构详解及代码实现!

② 位置嵌入(Positional Encoding)」

由于 Transformer 不依赖于 RNN 的顺序性建模方式,它必须显式地引入位置信息,以保留 token 在序列中的位置特征。

为此,Transformer 使用了 Sinusoidal Positional Encoding(正弦/余弦位置编码):

Transformer 模型结构详解及代码实现!

其中:

  • pos:token 在序列中的位置;
  • i:维度索引;
  • d_model:嵌入维度。

③ TransformerEmbedding 层集成

transformer 输入模块有三个组成部分:文本/提示词、分词器(Tokenizer)和嵌入层(Embeddings)。输入模块的工作流程和代码实现如下所示:

Transformer 模型结构详解及代码实现!

矩阵的每一列表示一个 token 的嵌入向量。

复制
class PositionalEncoding(nn.Module):
    """
    compute sinusoid encoding.
    """
    def __init__(self, d_model, max_len, device):
        """
        constructor of sinusoid encoding class

        :param d_model: dimension of model
        :param max_len: max sequence length
        :param device: hardware device setting
        """
        super(PositionalEncoding, self).__init__()

        # same size with input matrix (for adding with input matrix)
        self.encoding = torch.zeros(max_len, d_model, device=device)
        self.encoding.requires_grad = False  # we don't need to compute gradient

        pos = torch.arange(0, max_len, device=device)
        pos = pos.float().unsqueeze(dim=1)
        # 1D => 2D unsqueeze to represent word's position

        _2i = torch.arange(0, d_model, step=2, device=device).float()
        # 'i' means index of d_model (e.g. embedding size = 50, 'i' = [0,50])
        # "step=2" means 'i' multiplied with two (same with 2 * i)

        self.encoding[:, 0::2] = torch.sin(pos / (10000 ** (_2i / d_model)))
        self.encoding[:, 1::2] = torch.cos(pos / (10000 ** (_2i / d_model)))
        # compute positional encoding to consider positional information of words

    def forward(self, x):
        # self.encoding
        # [max_len = 512, d_model = 512]

        batch_size, seq_len = x.size()
        # [batch_size = 128, seq_len = 30]

        return self.encoding[:seq_len, :]
        # [seq_len = 30, d_model = 512]
        # it will add with tok_emb : [128, 30, 512]         

class TokenEmbedding(nn.Embedding):
    """
    Token Embedding using torch.nn
    they will dense representation of word using weighted matrix
    """

    def __init__(self, vocab_size, d_model):
        """
        class for token embedding that included positional information
        :param vocab_size: size of vocabulary
        :param d_model: dimensions of model
        """
        super(TokenEmbedding, self).__init__(vocab_size, d_model, padding_idx=1)

class TransformerEmbedding(nn.Module):
    """
    token embedding + positional encoding (sinusoid)
    positional encoding can give positional information to network
    """

    def __init__(self, vocab_size, max_len, d_model, drop_prob, device):
        """
        class for word embedding that included positional information
        :param vocab_size: size of vocabulary
        :param d_model: dimensions of model
        """
        super(TransformerEmbedding, self).__init__()
        self.tok_emb = TokenEmbedding(vocab_size, d_model)
        # self.position_embedding = nn.Embedding(max_len, embed_size)
        self.pos_emb = PositionalEncoding(d_model, max_len, device)
        self.drop_out = nn.Dropout(p=drop_prob)

    def forward(self, x):
        tok_emb = self.tok_emb(x)
        pos_emb = self.pos_emb(x)
        return self.drop_out(tok_emb + pos_emb)

2. Multi-Head Attention 结构

Encoder 和 Decoder 结构中公共的 layer 之一是 Multi-Head Attention,其是由多个 Self-Attention 并行组成的。Encoder block 只包含一个 Multi-Head Attention,而 Decoder block 包含两个 Multi-Head Attention (其中有一个用到 Masked)。

Transformer 模型结构详解及代码实现!

(1) Self-Attention 结构

Self-Attention 中文翻译为自注意力机制,论文中叫作 Scale Dot Product Attention,它是 Transformer 架构的核心,使得每个 token 能够关注整个序列中的其他 token,从而建立全局依赖关系。其结构如下图所示:

Transformer 模型结构详解及代码实现!

(2) Self-Attention 实现

✅ 在本文中,Self-Attention 层与论文中的 ScaleDotProductAttention 层意义一致,实现方式完全相同。

🧮 数学定义

Self-Attention 的计算过程可以表示为:

Transformer 模型结构详解及代码实现!

其中:

  • Transformer 模型结构详解及代码实现! :Query 向量;
  • Transformer 模型结构详解及代码实现!:Key 向量;
  • Transformer 模型结构详解及代码实现!:Value 向量;
  • Transformer 模型结构详解及代码实现!:Query 和 Key 的维度;
  • Softmax 对注意力分数按最后一个维度归一化;
  • Transformer 模型结构详解及代码实现!:用于缩放点积,防止 softmax 梯度消失;

输入来源:

  • 输入词向量经过 Embedding 层后,进入位置编码层;
  • 再通过线性变换(Linear 层),分别生成 Query、Key 和 Value 向量;
  • 这三个向量的形状通常为 [batch_size, seq_len, d_k] 或 [seq_len, d_k]。

计算步骤如下:

① 计算注意力分数矩阵

Transformer 模型结构详解及代码实现!

  • 其中 Transformer 模型结构详解及代码实现!是 Key 张量的转置;
  • 点积结果是一个 [seq_len, seq_len] 的注意力得分矩阵;
  • 使用 softmax 归一化,得到注意力权重。

② 应用掩码(可选)

  • 在 Decoder 中使用 Masked Self-Attention,防止未来信息泄露;
  • 若传入 mask,将对应位置设为极小值(如 -1e9)以抑制其影响。

③ 加权聚合 Value 向量

  • 将 softmax 后的注意力权重与 Value 相乘,得到上下文感知的输出张量;
  • 输出维度保持与输入一致:[batch_size, seq_len, d_v]。

🧱 代码实现:

复制
import torch
import math
import torch.nn as nn

class ScaleDotProductAttention(nn.Module):
    def __init__(self):
        """
        初始化 Self-Attention 层,仅包含一个 softmax 操作。
        """
        super(ScaleDotProductAttention, self).__init__()
        self.softmax = nn.Softmax(dim=-1)

    def forward(self, Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor, mask: torch.Tensor = None):
        """
        Self-Attention 前向传播函数

        :param Q: Query 向量,形状为 [batch_size, seq_len, d_k]
        :param K: Key 向量,形状为 [batch_size, seq_len, d_k]
        :param V: Value 向量,形状为 [batch_size, seq_len, d_v]
        :param mask: 掩码张量,形状为 [batch_size, seq_len, seq_len]
        :return: 
            output: 加权后的 Value 向量,形状为 [batch_size, seq_len, d_v]
            attn_weights: 注意力权重矩阵,形状为 [batch_size, seq_len, seq_len]
        """
        # 1. 计算 QK^T 得到注意力分数
        K_T = K.transpose(-1, -2)  # [batch_size, d_k, seq_len]
        scores = torch.matmul(Q, K_T) / math.sqrt(Q.size(-1))  # [batch_size, seq_len, seq_len]

        # 2. 如果有 mask,应用掩码(例如 Decoder 中防止看到未来词)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)

        # 3. 应用 softmax 得到注意力权重
        attn_weights = self.softmax(scores)  # [batch_size, seq_len, seq_len]

        # 4. 权重 × Value 得到最终输出
        output = torch.matmul(attn_weights, V)  # [batch_size, seq_len, d_v]

        return output, attn_weights

📐 示例调用与输出解析:

复制
# 创建 Q、K、V 张量
Q = torch.randn(5, 10, 64)  # [batch_size=5, seq_len=10, d_k=64]
K = torch.randn(5, 10, 64)
V = torch.randn(5, 10, 64)

# 创建 Self-Attention 层
attention = ScaleDotProductAttention()

# 前向传播
output, attn_weights = attention(Q, K, V)

# 打印输出形状
print(f"ScaleDotProductAttention output shape: {output.shape}")      # [5, 10, 64]
print(f"attn_weights shape: {attn_weights.shape}")                # [5, 10, 10]

变量

形状

描述

Q, K, V

[5, 10, 64]

batch=5,序列长度=10,嵌入维度=64

scores

[5, 10, 10]

注意力得分矩阵,反映 token 之间的相似度

attn_weights

[5, 10, 10]

softmax 后的注意力权重,用于加权聚合 Value

output

[5, 10, 64]

最终输出,融合了上下文信息的 Value 加权表示

(3) Multi-Head Attention

Multi-Head Attention(MHA)是在Self-Attention基础上引入的一种增强机制。其核心理念是:将输入向量空间划分为多个子空间,在每个子空间中独立计算Self-Attention,最后将多个子空间的输出拼接在一起并进行线性变换,从而得到最终的输出。

对于 MHA,之所以需要对 Q、K、V 进行多头(head)划分,其目的是为了增强模型对不同信息的关注。具体来说,多组 Q、K、V 分别计算 Self-Attention,每个头自然就会有独立的 Q、K、V 参数,从而让模型同时关注多个不同的信息,这有些类似 CNN 架构模型的多通道机制。

下图是论文中 Multi-Head Attention 的结构图。

Transformer 模型结构详解及代码实现!

从图中可以看出, MHA 结构的计算过程可总结为下述步骤:

  • 将输入 Q、K、V 张量进行线性变换(Linear 层),输出张量尺寸为 [batch_size, seq_len, d_model];
  • 将前面步骤输出的张量,按照头的数量(n_head)拆分为 n_head 子张量,其尺寸为 [batch_size, n_head, seq_len, d_model//n_head];
  • 每个子张量并行计算注意力分数,即执行 dot-product attention 层,输出张量尺寸为 [batch_size, n_head, seq_len, d_model//n_head];
  • 将这些子张量进行拼接 concat ,并经过线性变换得到最终的输出张量,尺寸为 [batch_size, seq_len, d_model]。

📐 数学表达式

Transformer 模型结构详解及代码实现!

其中:

Transformer 模型结构详解及代码实现!

  • Transformer 模型结构详解及代码实现!:第 i 个 head 的可学习参数;
  • Transformer 模型结构详解及代码实现!:最终输出的线性变换矩阵;
  • Concat表示将各个 head 的输出拼接在一起。

(4) Multi-Head Attention 实现

复制
import torch
import math
import torch.nn as nn

class MultiHeadAttention(nn.Module):
    """Multi-Head Attention Layer"""
    
    def __init__(self, d_model, n_head):
        """
        Args:
            d_model: 模型嵌入维度(通常为 512 或 768);
            n_head: 注意力头的数量(如 8);
        """
        super(MultiHeadAttention, self).__init__()
        
        # 初始化参数
        self.n_head = n_head
        self.attention = ScaleDotProductAttention()  # 使用前面定义的 Self-Attention
        
        # 线性变换层
        self.w_q = nn.Linear(d_model, d_model)       # Query 变换
        self.w_k = nn.Linear(d_model, d_model)       # Key 变换
        self.w_v = nn.Linear(d_model, d_model)       # Value 变换
        self.fc = nn.Linear(d_model, d_model)         # 输出投影层

    def forward(self, q, k, v, mask=None):
        """
        Args:
            q: Query 张量,[batch_size, seq_len, d_model]
            k: Key 张量,[batch_size, seq_len, d_model]
            v: Value 张量,[batch_size, seq_len, d_model]
            mask: 掩码张量,[batch_size, seq_len, seq_len]
        """
        # Step 1: 线性变换
        q, k, v = self.w_q(q), self.w_k(k), self.w_v(v)

        # Step 2: 拆分到多个 head
        q = self.split(q)   # [batch_size, n_head, seq_len, d_tensor]
        k = self.split(k)   # [batch_size, n_head, seq_len, d_tensor]
        v = self.split(v)   # [batch_size, n_head, seq_len, d_tensor]

        # Step 3: 计算每个 head 的 attention
        sa_output, attn_weights = self.attention(q, k, v, mask)

        # Step 4: 拼接所有 head 的输出
        mha_output = self.concat(sa_output)  # [batch_size, seq_len, d_model]

        # Step 5: 最终线性变换
        mha_output = self.fc(mha_output)

        return mha_output, attn_weights

    def split(self, tensor):
        """
        拆分输入张量为多个 head

        Args:
            tensor: [batch_size, seq_len, d_model]
        Returns:
            [batch_size, n_head, seq_len, d_tensor]
        """
        batch_size, seq_len, d_model = tensor.size()
        d_tensor = d_model // self.n_head  # 每个 head 的维度
        
        # reshape + transpose 实现拆分
        tensor = tensor.view(batch_size, seq_len, self.n_head, d_tensor)
        tensor = tensor.transpose(1, 2)  # [batch_size, n_head, seq_len, d_tensor]
        
        return tensor

    def concat(self, sa_output):
        """
        拼接多个 head 的输出

        Args:
            sa_output: [batch_size, n_head, seq_len, d_tensor]
        Returns:
            [batch_size, seq_len, d_model]
        """
        batch_size, n_head, seq_len, d_tensor = sa_output.size()
        d_model = n_head * d_tensor
        
        # transpose + reshape 实现合并
        sa_output = sa_output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)
        
        return sa_output

📊 示例调用与输出解析:

复制
# 定义参数
d_model = 512
n_head = 8
seq_len = 10
batch_size = 32

# 创建 Q、K、V 张量
Q = torch.randn(batch_size, seq_len, d_model)
K = torch.randn(batch_size, seq_len, d_model)
V = torch.randn(batch_size, seq_len, d_model)

# 构建 MHA 层
mha_layer = MultiHeadAttention(d_model=d_model, n_head=n_head)

# 前向传播
output, weights = mha_layer(Q, K, V)

# 打印输出形状
print("MHA Output Shape:", output.shape)      # [32, 10, 512]
print("Attn Weights Shape:", weights.shape)   # [32, 8, 10, 10]

变量

形状

描述

Q, K, V

[32, 10, 512]

输入张量,表示 batch=32,seq_len=10,d_model=512

q, k, v

[32, 8, 10, 64]

拆分后的 Q/K/V,每个 head 64 维

sa_output

[32, 8, 10, 64]

每个 head 的 attention 输出

mha_output

[32, 10, 512]

拼接后的最终输出

attn_weights

[32, 8, 10, 10]

每个 head 的注意力权重矩阵

3. Encoder结构

Transformer 中的 Encoder 是整个模型中用于编码输入序列的部分。它由 N=6 个相同的 encoder block 堆叠而成。每个 encoder block 主要包含两个子层(sub-layers):多头自注意力机制(Multi-Head Self-Attention)和位置全连接前馈网络(Position-wise Feed Forward Network)。

这两个子层之间都使用了 残差连接(Residual Connection) 和 层归一化(Layer Normalization),以增强训练稳定性和模型表达能力。

下图中红色框选部分表示一个标准的 Encoder Block,其内部结构如下:

Transformer 模型结构详解及代码实现!

由以下四个关键部分构成。

模块

描述

Multi-Head Attention

使用多个 attention head 并行提取序列中的不同特征

Add & Norm (1)

残差连接(Residual Connection)+ 层归一化(LayerNorm)

Position-wise FeedForward

两层线性变换 + 激活函数,对每个词独立建模

Add & Norm (2)

同样应用残差连接和 LayerNorm

(1) 每一层的计算流程(以单个 encoder block 为例)

① 多头自注意力机制(Multi-Head Self-Attention)

  • 输入:嵌入后的张量 Transformer 模型结构详解及代码实现!
  • 输出:通过自注意力加权后的新张量 sa_output
复制
sa_output, attn_weights = MultiHeadAttention(q=x, k=x, v=x, mask=src_mask)

其中,

  • Query、Key 和 Value 来自同一个输入 ;
  • 可选 mask 通常用于屏蔽 padding token 或控制位置感知范围。

② 残差连接 + 层归一化(Sublayer 1)

复制
x = x + dropout(sa_output)
x = layer_norm(x)
  • 应用残差映射,缓解梯度消失问题;
  • 使用 LayerNorm 对每个 token 的向量进行标准化处理;
  • 整体目标:提升模型表达能力与训练稳定性。

③ 位置全连接前馈网络(Position-wise FeedForward)

定义为: 

Transformer 模型结构详解及代码实现!

复制
nn.Sequential(
    nn.Linear(d_model, d_ff),
    nn.ReLU(),
    nn.Linear(d_ff, d_model),
    nn.Dropout(drop_prob)
)
  • d_model:模型隐层维度(如 512);
  • d_ff:FeedForward 网络中间维度(如 2048);
  • ReLU 导致非线性更强的语义表达。

④ 再次残差连接 + 层归一化(Sublayer 2)

复制
x = x + dropout(ffn_output)
x = layer_norm(x)
  • 保证模型在经过复杂变换后仍能保留原始信息;
  • 达成对上下文感知表示的稳定学习。

(2) 维度变化说明(输入输出保持一致)

无论经过多少层 Encoder block,每个 block 的输入与输出形状始终一致:

张量

形状

描述

输入

[batch_size, seq_len, d_model]

批次大小 × 序列长度 × 模型维度

MHA 输出

[batch_size, seq_len, d_model]

注意力加权后的输出

FFN 输出

[batch_size, seq_len, d_model]

每个 Token 的前馈网络输出

最终输出

[batch_size, seq_len, d_model]

经过两次 Sublayer 后仍然保持相同维度

(3) PyTorch 模块封装示例

复制
import torch
import torch.nn as nn

class EncoderBlock(nn.Module):
    def __init__(self, d_model, n_head, d_ff, drop_prob=0.1):
        """
        Args:
            d_model: 嵌入维度(例如 512)
            n_head: 多头数量(通常设为 8)
            d_ff: Feed Forward 网络中间维度(通常为 2048)
            drop_prob: Dropout 概率
        """
        super(EncoderBlock, self).__init__()
        self.attention = MultiHeadAttention(d_model, n_head)
        self.norm1 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model),
            nn.Dropout(drop_prob)
        )
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(drop_prob)

    def forward(self, x, src_mask=None):
        # Step 1: Multi-Head Self-Attention
        sa_output, _ = self.attention(x, x, x, src_mask)
        x = self.norm1(x + self.dropout(sa_output))

        # Step 2: Position-wise FeedForward
        ffn_output = self.ffn(x)
        x = self.norm2(x + self.dropout(ffn_output))

        return x

(4) 封装整个 Encoder 模块

有了 EncoderBlock 后,我们可以将它 重复 N 次 构建完整的 Encoder:

复制
class TransformerEncoder(nn.Module):
    def __init__(self, num_layers, d_model, n_head, d_ff, drop_prob=0.1):
        """
        Args:
            num_layers: encoder block 堆叠层数(原论文为 6)
            d_model: 模型维度(如 512)
            n_head: 注意力头数(如 8)
            d_ff: FeedForward 网络维度(如 2048)
        """
        super(TransformerEncoder, self).__init__()
        self.blocks = nn.ModuleList([
            EncoderBlock(d_model, n_head, d_ff, drop_prob)
            for _ in range(num_layers)
        ])

    def forward(self, x, mask=None):
        for block in self.blocks:
            x = block(x, mask)
        return x

📈 示例调用

复制
# 创建输入张量
x = torch.randn(batch_size=32, seq_len=20, d_model=512)  # [32, 20, 512]

# 构建 Encoder
encoder = TransformerEncoder(num_layers=6, d_model=512, n_head=8, d_ff=2048)
output = encoder(x)

print("Encoder 输出形状:", output.shape)  # [32, 20, 512]

4. Decoder结构

Decoder是Transformer架构中用于生成输出序列的部分。与Encoder类似,它由N=6个相同的Decoder block堆叠而成,但结构更为复杂。

(1) Decoder Block 的核心组件

一个标准的Decoder block包含三个主要子层:

  • Masked Multi-Head Self-Attention
  • Encoder-Decoder Attention
  • Position-wise Feed Forward Network

每个子层后面都跟随 残差连接(Residual Connection) 和 层归一化(Layer Normalization)。

如下图右侧红框表示一个标准的 Decoder Block。

Transformer 模型结构详解及代码实现!

①「Masked Multi-Head Self-Attention」

这是Decoder的第一个注意力机制,用于处理目标语言的输入序列(即解码器自身的输入)。

  • 使用 masking 技术 防止在预测当前词时看到未来的词,保持因果关系。
  • 实现方式:通过 trg_mask 屏蔽未来信息。
复制
x = self.mha1(q=dec_out, k=dec_out, v=dec_out, mask=trg_mask)

②「Encoder-Decoder Attention」

这是 Decoder 的第二个注意力机制,用于将 Encoder 的输出信息融合到 Decoder 中。

  • Query (Q) 来自上一层 Decoder 的输出;
  • Key (K) 和 Value (V) 来自 Encoder 的输出;
  • 这样 Decoder 在生成每个词时都能关注到整个输入句子的信息。
复制
x = self.mha2(q=x, k=enc_out, v=enc_out, mask=src_mask)

③ 「Position-wise Feed Forward Network」

这是一个简单的两层全连接网络,对每个位置的向量进行非线性变换。

复制
x = self.ffn(x)

④「残差连接 + 层归一化(Add & Norm)」

每个子层都应用:

复制
x = self.ln(x_residual + dropout(sublayer_output))
  • 提升训练稳定性;
  • 缓解梯度消失问题。

⑤「Decoder的完整实现」

DecoderLayer类:

复制
class DecoderLayer(nn.Module):
    def __init__(self, d_model, ffn_hidden, n_head, drop_prob):
        super(DecoderLayer, self).__init__()
        # 第一个 Multi-Head Attention: Masked Self-Attention
        self.mha1 = MultiHeadAttention(d_model, n_head)
        self.ln1 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(p=drop_prob)

        # 第二个 Multi-Head Attention: Encoder-Decoder Attention
        self.mha2 = MultiHeadAttention(d_model, n_head)
        self.ln2 = nn.LayerNorm(d_model)
        self.dropout2 = nn.Dropout(p=drop_prob)

        # 前馈网络
        self.ffn = PositionwiseFeedForward(d_model, ffn_hidden)
        self.ln3 = nn.LayerNorm(d_model)
        self.dropout3 = nn.Dropout(p=drop_prob)

    def forward(self, dec_out, enc_out, trg_mask, src_mask):
        x_residual1 = dec_out
        # Step 1: Masked Self-Attention
        x = self.mha1(q=dec_out, k=dec_out, v=dec_out, mask=trg_mask)
        x = self.ln1(x_residual1 + self.dropout1(x))

        if enc_out is not None:
            # Step 2: Encoder-Decoder Attention
            x_residual2 = x
            x = self.mha2(q=x, k=enc_out, v=enc_out, mask=src_mask)
            x = self.ln2(x_residual2 + self.dropout2(x))

        # Step 3: Position-wise Feed Forward
        x_residual3 = x
        x = self.ffn(x)
        x = self.ln3(x_residual3 + self.dropout3(x))

        return x

Decoder 类:

复制
class Decoder(nn.Module):
    def __init__(self, dec_voc_size, max_len, d_model, ffn_hidden, n_head, n_layers, drop_prob, device):
        super().__init__()
        # 输入嵌入 + 位置编码
        self.emb = TransformerEmbedding(
            d_model=d_model,
            drop_prob=drop_prob,
            max_len=max_len,
            vocab_size=dec_voc_size,
            device=device
        )

        # 堆叠多个 Decoder Layer
        self.layers = nn.ModuleList([
            DecoderLayer(
                d_model=d_model,
                ffn_hidden=ffn_hidden,
                n_head=n_head,
                drop_prob=drop_prob
            ) for _ in range(n_layers)
        ])

        # 最终输出层:映射到目标词汇表大小
        self.linear = nn.Linear(d_model, dec_voc_size)

    def forward(self, trg, src, trg_mask, src_mask):
        # trg: 目标序列 [batch_size, trg_seq_len]
        # src: Encoder 输出 [batch_size, src_seq_len, d_model]
        trg = self.emb(trg)

        for layer in self.layers:
            trg = layer(trg, src, trg_mask, src_mask)

        # 输出:[batch_size, trg_seq_len, dec_voc_size]
        output = self.linear(trg)
        return output

三、Transformer实现

基于前面实现的 Encoder 和 Decoder 组件,我们就可以实现 Transformer 模型的完整代码,如下所示:

复制
import torch
from torch import nn

class Transformer(nn.Module):
    def __init__(self, src_pad_idx, trg_pad_idx, trg_sos_idx, enc_voc_size, dec_voc_size, 
                 d_model, n_head, max_len, ffn_hidden, n_layers, drop_prob, device):
        super().__init__()
        
        # 保存特殊标记的索引
        self.src_pad_idx = src_pad_idx  # 源语言填充索引
        self.trg_pad_idx = trg_pad_idx  # 目标语言填充索引
        self.trg_sos_idx = trg_sos_idx  # 目标语言起始符号索引
        self.device = device  # 设备信息
        
        # 构建 Encoder
        self.encoder = Encoder(
            d_model=d_model,  # 模型维度
            n_head=n_head,  # 注意力头数量
            max_len=max_len,  # 最大序列长度
            ffn_hidden=ffn_hidden,  # 前馈网络隐藏层维度
            enc_voc_size=enc_voc_size,  # 源语言词汇表大小
            drop_prob=drop_prob,  # Dropout 概率
            n_layers=n_layers,  # Encoder 层数
            device=device  # 设备信息
        )
        
        # 构建 Decoder
        self.decoder = Decoder(
            d_model=d_model,  # 模型维度
            n_head=n_head,  # 注意力头数量
            max_len=max_len,  # 最大序列长度
            ffn_hidden=ffn_hidden,  # 前馈网络隐藏层维度
            dec_voc_size=dec_voc_size,  # 目标语言词汇表大小
            drop_prob=drop_prob,  # Dropout 概率
            n_layers=n_layers,  # Decoder 层数
            device=device  # 设备信息
        )

    def forward(self, src, trg):
        # 创建源序列的 padding mask
        src_mask = self.make_pad_mask(src, src, self.src_pad_idx, self.src_pad_idx)
        
        # 创建目标序列到源序列的 mask
        src_trg_mask = self.make_pad_mask(trg, src, self.trg_pad_idx, self.src_pad_idx)
        
        # 创建目标序列的 padding mask 和因果mask的组合
        trg_mask = self.make_pad_mask(trg, trg, self.trg_pad_idx, self.trg_pad_idx) * \
                   self.make_no_peak_mask(trg, trg)

        # 编码器前向传播
        enc_src = self.encoder(src, src_mask)
        
        # 解码器前向传播
        output = self.decoder(trg, enc_src, trg_mask, src_trg_mask)
        
        return output

    def make_pad_mask(self, q, k, q_pad_idx, k_pad_idx):
        # 获取输入序列长度
        len_q, len_k = q.size(1), k.size(1)

        # 创建针对 key 的 mask
        # batch_size x 1 x 1 x len_k
        k_mask = k.ne(k_pad_idx).unsqueeze(1).unsqueeze(2)
        # batch_size x 1 x len_q x len_k
        k_mask = k_mask.repeat(1, 1, len_q, 1)

        # 创建针对 query 的 mask
        # batch_size x 1 x len_q x 1
        q_mask = q.ne(q_pad_idx).unsqueeze(1).unsqueeze(3)
        # batch_size x 1 x len_q x len_k
        q_mask = q_mask.repeat(1, 1, 1, len_k)
        
        # 组合两个 mask
        mask = k_mask & q_mask
        
        return mask

    def make_no_peak_mask(self, q, k):
        # 创建因果mask,防止解码器看到未来信息
        len_q, len_k = q.size(1), k.size(1)
        
        # 创建下三角矩阵,保证解码器只能关注当前词及之前的词
        mask = torch.tril(torch.ones(len_q, len_k)).type(torch.BoolTensor).to(self.device)
        
        return mask

相关资讯

Phi-4-multimodal:图、文、音频统一的多模态大模型架构、训练方法、数据细节

Phi-4-Multimodal 是一种参数高效的多模态模型,通过 LoRA 适配器和模式特定路由器实现文本、视觉和语音/音频的无缝集成。 训练过程包括多阶段优化,确保在不同模式和任务上的性能,数据来源多样,覆盖高质量合成数据。 它的设计体现了小型语言模型在多模态任务上的潜力。
3/10/2025 2:00:00 AM
余俊晖

重磅开源!Kimi把自家底层推理架构都开源了,开源贡献阵容相当豪华:清华、阿里、华为、AISoft、面壁智能

就在昨天,Kimi宣布要把自家底层的大模型推理架构Mooncake开源出来! 有媒体称该架构正是承载了月之暗面Kimi线上80%以上流量的正在用的底层架构。 小编立马求证了一番,的确Github上有提到:Mooncake 正在服务Kimi平台。
11/29/2024 1:54:13 PM

1-bit大模型还能再突破!新一代BitNet架构启用4位激活值

量化到1 bit的LLM还能再突破? 这次,他们对激活值下手了! 近日,BitNet系列的原班人马推出了新一代架构:BitNet a4.8,为1 bit大模型启用了4位激活值:图片论文地址:,激活值量化通常是比较难办的。
12/6/2024 7:17:07 AM
新智元
  • 1