
做算法开发这几年Transformer 几乎是绕不开的一道门槛。不管你是做 NLP、CV还是最近在折腾大模型微调最终都会回到同一个问题上Transformer 内部到底是怎么工作的我刚开始接触时也被 QKV、多头注意力、位置编码这些概念绕得头晕网上的资料要么太偏数学要么只有代码没有解释。这篇文章便是我自己的学习梳理笔记用“图解 最小代码 工程经验”的方式把 Transformer 讲清楚。文中会从循环神经网络并行化困境讲起逐步拆解 Self-Attention、Multi-Head Attention、位置编码、Encoder-Decoder 结构最后给出 PyTorch 手写核心模块和 Hugging Face 实际调用示例。新手可以把这篇文章当作第一份学习地图有基础的开发者则可以直接跳到代码和排错部分。本文不追求论文级严谨只追求“讲得明白、能跑起来、能用上”。1. 为什么最后是 Transformer从 RNN 到自注意力的演进1.1 RNN / LSTM 的并行化困境在 Transformer 出现之前处理序列数据如文本、语音、时间序列的主流模型是 RNN循环神经网络和 LSTM长短期记忆网络。RNN 的基本思路是按时间步顺序读取序列每一步把当前输入和历史隐藏状态融合生成新的隐藏状态。h_t f(h_{t-1}, x_t)这个结构有两个明显问题无法并行。第 t 步必须等第 t-1 步算完文本越长算得越慢。GPU 虽然有很多计算单元但 RNN 这种串行模式让 GPU 难以充分发挥算力。长距离依赖衰减。虽然 LSTM 通过门控机制缓解了梯度消失问题但当序列长度达到几百甚至上千时早期信息传递到后期仍然会大幅衰减。1.2 Attention 带来的改变Attention注意力机制最早被引入到 Seq2Seq 模型中用来解决“编码器把整个源句子压缩成一个向量信息损失严重”的问题。它的核心思想是在解码时不要只依赖一个固定向量而是动态地查看原始输入的不同位置并分配不同权重。这给了我们一个重要启发如果每个位置都能直接看到其他所有位置那么长距离依赖问题可以被大幅缓解。2017 年Google 在论文《Attention Is All You Need》中把这个思想推到极致——直接用 Attention 替代循环结构这就是 Transformer。它的核心优势是全局建模能力强。每个 token 都能直接与其他所有 token 交互。并行度高。矩阵运算可以由 GPU 高效并行执行。扩展性好。通过堆叠层数和扩大参数量模型效果持续提升这也是后来 BERT、GPT 等一系列大模型的基础架构。1.3 Transformer 的定位简单来说Transformer 是一个基于自注意力机制的序列模型架构。它不再按顺序迭代而是通过注意力矩阵来捕捉输入内部的关系。这样的设计让它在自然语言处理、图像分类、语音识别、推荐系统等场景中都能适用。接下来我们进入核心部分Transformer 的完整架构。2. Transformer 整体架构图解2.1 宏观视角Encoder-Decoder 结构Transformer 的原始版本采用 Encoder-Decoder 框架。左侧是 Encoder编码器右侧是 Decoder解码器。输入序列: [我, 爱, 学习] │ ▼ ┌──────────────────────┐ │ Input Embedding │ 词向量嵌入 └──────────────────────┘ │ ┌──────────────────────┐ │ Positional Encoding│ 位置编码 └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Encoder Layer ×N │ 多头注意力 前馈网络 └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Encoder 输出 │ 即 KV 的来源 └──────────────────────┘ 输出侧流程类似 输入序列: [s, 我, 爱] │ ▼ ┌──────────────────────┐ │ Output Embedding │ └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Positional Encoding │ └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Decoder Layer ×N │ │ Masked Self-Attention│ │ Cross-Attention │ │ Feed Forward │ └──────────────────────┘ │ ▼ ┌──────────────────────┐ │ Linear Softmax │ → 预测下一个 token └──────────────────────┘在机器翻译、文本摘要、对话生成这类“输入一段文本输出另一段文本”的任务中Encoder 负责理解输入文本Decoder 负责生成输出文本。但很多人刚开始学习时会有一个困惑现在流行的 GPT 模型好像只用 DecoderBERT 好像只用 Encoder没错这是 Transformer 的发展分支Encoder-only适合理解类任务例如文本分类、命名实体识别、语义相似度。典型代表是 BERT。Decoder-only适合生成类任务例如对话、续写、代码生成。典型代表是 GPT 系列。Encoder-Decoder适合序列到序列的转换任务例如翻译、摘要。典型代表是 T5、BART。2.2 每个子模块的作用一个标准的 Transformer Block 内部包含以下组件组件作用Embedding将 token 映射为稠密向量Positional Encoding给模型提供 token 的顺序信息Multi-Head Self-Attention捕捉 token 之间的依赖关系Add Norm残差连接 层归一化帮助训练稳定Feed-Forward Network每个 token 独立通过的全连接网络增强非线性表达能力Masked Multi-Head AttentionDecoder 中防止看到未来 token下面我们从最核心的 Self-Attention 开始拆解。3. 自注意力机制Self-Attention详解3.1 从“查字典”理解 Q、K、VSelf-Attention 中最难理解的就是 QQuery、KKey、VValue这三个向量。其实可以类比一个检索场景你脑子里有一个问题这是 Query。你面前有多个知识点每个知识点有标题这是 Key。每个知识点正文内容这是 Value。你要做的是用问题Query去和每个标题Key做匹配得到一个相关度分数然后按照相关度去加权提取正文内容Value。相关度越高的内容在最终结果中占的比例越大。在 Transformer 中输入序列的每个 token 都会生成自己的 Q、K、V。具体做法是Q X · W_Q K X · W_K V X · W_V其中 X 是输入向量矩阵W_Q、W_K、W_V 是可学习的权重矩阵。3.2 注意力分数的计算假设输入序列有 n 个 token每个 token 的维度是 d_model。那么Q 的维度是 [n, d_k]K 的维度是 [n, d_k]V 的维度是 [n, d_v]注意力分数的计算公式如下Attention(Q, K, V) softmax( Q · K^T / sqrt(d_k) ) · V这里每个步骤的含义是计算 Q 和所有 K 的点积得到“每个 token 与其他 token 的关注度打分”。除以 sqrt(d_k)防止点积数值过大导致 softmax 梯度消失。d_k 是 K 的维度。用 softmax 归一化让每个 token 对整句话的注意力权重之和为 1。用权重对 V 加权求和得到当前 token 的上下文表示。3.3 一个手动计算的例子为了看得更明白我们设计一个极小的例子。假设只有两个 token“我”和“爱”每个 token 映射成 2 维向量“我”的向量: [1, 0] “爱”的向量: [0, 1]为了方便展示我们直接忽略 W_Q、W_K、W_V 的具体学习过程假设Q [[1, 0], [0, 1]] K [[1, 0], [0, 1]] V [[1, 0], [0, 1]]第一步计算 Q 和 K 的点积矩阵[1, 0] · [1, 0]^T 1 [1, 0] · [0, 1]^T 0 [0, 1] · [1, 0]^T 0 [0, 1] · [0, 1]^T 1 得分矩阵 [[1, 0], [0, 1]]第二步假设 d_k 2那么 sqrt(2) ≈ 1.414。将得分除以 1.414 后做 softmax。对第一行 [0.707, 0] 做 softmaxsoftmax([0.707, 0]) ≈ [0.668, 0.332]这里“我”对“我”自己的关注度约 0.668对“爱”的关注度约 0.332。第三步用这个权重去加权 V0.668 × [1, 0] 0.332 × [0, 1] [0.668, 0.332]这就是“我”经过自注意力机制后的新向量。新向量同时蕴含了“我”和“爱”的信息具体比例由注意力权重决定。3.4 为什么需要缩放点积的数值会随向量维度增大而变大。当 d_k 很大时点积结果分布会变得非常尖锐softmax 之后绝大多数概率集中在一个位置上其他位置梯度接近 0这会导致训练困难。除以 sqrt(d_k) 可以把点积分布拉回到方差适中的区间让梯度更稳定。很多初学者容易忽略缩放这一步甚至直接删掉这在训练深层 Transformer 时往往会导致 loss 不降。4. 多头注意力与残差连接4.1 多头注意力的动机只有一个注意力头时模型只能从一种关系视角去看输入。但真实文本中关系是多种多样的。比如句子“小狗追着猫跑它跑得很快。”“它”可能指“小狗”也可能指“猫”具体含义需要结合上下文判断。单头注意力可能只关注到一种语法关系而多头注意力可以让模型同时从多个子空间去捕捉不同关系。多头注意力的做法是把 Q、K、V 分成 h 组每组维度为原维度的 1/h。每组独立计算注意力。把 h 个头的输出拼接起来通过一个线性层压缩回原始维度。MultiHead(Q, K, V) Concat(head_1, ..., head_h) · W_O head_i Attention(Q·W_Q^i, K·W_K^i, V·W_V^i)这里 W_Q^i、W_K^i、W_V^i 是第 i 个头独立的投影矩阵W_O 是输出投影矩阵。4.2 多头注意力的直观理解可以这么想一个注意力头相当于一个“专家”关注某一类关系。一个头关注语法依赖另一个头关注指代关系还有一个头关注语义相似度。多头机制让模型能够并行地学习多种不同表示。在 BERT-base 中注意力头数为 12每头维度是 64。在 GPT-3 中注意力头数达到 96 个模型容量大幅提升。但不是说头越多越好实际工程中需要结合模型总参数量和训练数据量做权衡。4.3 残差连接与 LayerNorm在多头注意力后面通常会接一个 Add Norm 结构。X LayerNorm(X Attention(X))这个设计有两个作用残差连接让梯度可以跨层直接回传缓解深层网络训练困难。即使某一层的输出变化很大模型也可以选择让信息“直通”。LayerNorm对每个样本的所有特征维度做归一化让数据分布更加稳定加速训练收敛。在原始论文中LayerNorm 被放在残差之后的 Add Norm。但在近期很多大模型实现中更常用 Pre-LN先归一化再计算例如X X Attention(LayerNorm(X))两种方式各有优劣。Post-LN 效果上限高但训练不稳定Pre-LN 训练更稳深层次数更多时更常用。工程上优先使用 Pre-LN更省心。5. 位置编码Transformer 如何感知顺序5.1 为什么需要位置编码RNN 天然按顺序读取输入所以序列顺序被编码在结构中。Transformer 不同它一次性看到所有 token自注意力计算本身不带顺序信息。如果只是把“我爱学习”变成“学习爱我”的向量输入模型看到的特征完全相同。因此必须在输入中额外加入位置信息这就是位置编码Positional Encoding。5.2 原始 Transformer 的正余弦编码原始论文使用固定频率的正弦和余弦函数来生成位置编码PE(pos, 2i) sin(pos / 10000^(2i / d_model)) PE(pos, 2i1) cos(pos / 10000^(2i / d_model))其中 pos 是 token 在序列中的位置i 是向量维度下标。这个设计有两个特点不同位置得到不同编码向量且相邻位置的编码比较相似。这种函数形式让模型更容易学习到“相对位置”关系因为任意两个位置之间的偏移可以通过某种线性变换表示。5.3 可学习位置编码与相对位置编码在 BERT 中位置编码被改成可学习的 Embedding效果和正余弦编码接近。后来很多模型又引入了更复杂的相对位置编码如 RoPE、ALiBi它们对长文本泛化更好。尤其是 RoPE旋转位置编码被 LLaMA、ChatGLM 等模型广泛采用。工程上如果你只是调用现成大模型不需要手动实现位置编码。但如果要自己设计序列模型需要记住位置信息必须显式注入模型否则序列顺序对模型而言是无效的。6. Encoder 与 Decoder从哪里生成到哪里预测6.1 Encoder 的编码逻辑Encoder 的核心任务是理解输入。每个 Encoder Layer 包含两个子层Multi-Head Self-Attention捕捉输入内部 token 之间的关系。Feed-Forward Network对每个 token 表示逐位置做非线性变换。Encoder 输出的是一组向量每个向量对应输入中的一个 token并且已经融合了全句上下文信息。这组向量被称为“编码表示”。6.2 Decoder 的掩码自注意力Decoder 的输入是“已经生成的 token 序列”。在生成第 t 个 token 时模型不应该看到未来的 token。因此 Decoder 内部使用 Masked Self-Attention。具体做法是在计算注意力分数时把未来位置的分数设为负无穷这样 softmax 之后它们的权重为 0。得分矩阵3个token [[0.7, -inf, -inf], [0.5, 0.8, -inf], [0.3, 0.6, 0.9]]这样第 1 个 token 只能关注自己第 2 个 token 只能关注前两个第 3 个 token 能关注前三个。这个设计保证了模型的自回归特性每一步生成都只依赖历史输出。6.3 Cross-AttentionEncoder 与 Decoder 的桥Decoder 中除了 Masked Self-Attention还有一个 Cross-Attention交叉注意力。在这个子层中Query 来自 Decoder 当前层的输出。Key 和 Value 来自 Encoder 最后一层的输出。这样 Decoder 在生成每一个 token 时都可以动态地“查阅”原始输入中它最关注的片段。这就是翻译模型能够根据源语言逐步生成目标语言的关键机制。对于只用 Decoder 的 GPT 类模型没有 Encoder 和 Cross-Attention它们纯粹是自回归地生成文本。7. 从原理到代码手写核心模块与 Hugging Face 实战7.1 用 PyTorch 实现多头注意力下面给出一个最小化但结构完整的 PyTorch 实现。注意这里重点演示原理不追求与论文完全一致的最优实现。import torch import torch.nn as nn import torch.nn.functional as F class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super().__init__() assert d_model % num_heads 0 self.d_model d_model self.num_heads num_heads self.d_k d_model // num_heads self.w_q nn.Linear(d_model, d_model) self.w_k nn.Linear(d_model, d_model) self.w_v nn.Linear(d_model, d_model) self.w_o nn.Linear(d_model, d_model) def forward(self, x, maskNone): batch_size, seq_len, _ x.size() Q self.w_q(x) # [batch, seq, d_model] K self.w_k(x) V self.w_v(x) # 拆成多头 Q Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) K K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) V V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # 计算注意力分数 scores torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtypetorch.float32)) # scores: [batch, num_heads, seq, seq] if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) attn F.softmax(scores, dim-1) output torch.matmul(attn, V) # [batch, num_heads, seq, d_k] output output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) output self.w_o(output) return output这里的关键点通过view和transpose把 d_model 拆成 num_heads 个子空间。scores除以 sqrt(d_k) 对应缩放点积注意力。masked_fill对应 decoder 中的掩码操作。最终把多头结果拼接后经w_o输出。7.2 用 PyTorch 实现一个 Transformer 编码层有了多头注意力就可以组装一个完整的 Encoder Layer。class FeedForward(nn.Module): def __init__(self, d_model, d_ff): super().__init__() self.fc1 nn.Linear(d_model, d_ff) self.fc2 nn.Linear(d_ff, d_model) self.relu nn.ReLU() def forward(self, x): return self.fc2(self.relu(self.fc1(x))) class EncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, num_heads) self.feed_forward FeedForward(d_model, d_ff) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) def forward(self, x, maskNone): # Pre-LN 结构 attn_output self.self_attn(self.norm1(x), mask) x x self.dropout(attn_output) ff_output self.feed_forward(self.norm2(x)) x x self.dropout(ff_output) return x你可以用下面这段代码快速验证model EncoderLayer(d_model512, num_heads8, d_ff2048) x torch.randn(2, 10, 512) # batch_size2, seq_len10, d_model512 y model(x) print(y.shape) # torch.Size([2, 10, 512])7.3 使用 Hugging Face 加载预训练 Transformer实际项目中基本不会从零训练 Transformer而是加载预训练模型做微调。Hugging Face 的 Transformers 库是目前最常用的工具。以下示例演示加载一个 BERT 模型做文本分类。pip install transformers torchfrom transformers import AutoTokenizer, AutoModelForSequenceClassification import torch # 1. 加载分词器和预训练模型 tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) model AutoModelForSequenceClassification.from_pretrained(bert-base-chinese, num_labels2) # 2. 对输入文本编码 texts [这个电影太棒了剧情精彩演员演技在线。, 电影很无聊浪费时间。] inputs tokenizer(texts, paddingTrue, truncationTrue, return_tensorspt) # 3. 前向推理 with torch.no_grad(): outputs model(**inputs) logits outputs.logits predictions torch.argmax(logits, dim-1) print(predictions)如果你想要一个 GPT 风格的续写示例可以加载 GPT-2from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer AutoTokenizer.from_pretrained(gpt2) model AutoModelForCausalLM.from_pretrained(gpt2) text Once upon a time inputs tokenizer(text, return_tensorspt) outputs model.generate(**inputs, max_length50, do_sampleTrue, top_p0.9) print(tokenizer.decode(outputs[0], skip_special_tokensTrue))这里需要说明gpt2的官方权重主要针对英文训练中文续写效果很一般。做中文生成任务建议使用bert-base-chinese配合分类任务或者搜索中文本地化生成模型。版本方面Transformers 4.x 和 PyTorch 2.x 是当前常见组合具体版本以你的开发环境为准。8. 常见问题与排查思路8.1 问题汇总问题现象常见原因解决思路训练 loss 不下降学习率过大/过小未做缩放数据未归一化检查学习率使用 warmup确认注意力缩放检查数据预处理显存不足 OOM序列过长注意力矩阵复杂度为 O(n²)缩短 max_length使用梯度累积或用长文本稀疏注意力方案生成结果一直重复解码参数不恰当训练数据存在重复模式调整 no_repeat_ngram_size增大 top_p 或 temperature加载模型报错模型权重与 Transformers 版本不匹配升级或回退 Transformers 版本检查from_pretrained参数序列长度超过模型限制BERT 一般限制 512GPT 一般是 1024/2048对长文本切片、摘要或选择支持长文本的模型中文预测效果差使用了不支持中文的预训练模型换成bert-base-chinese、chinese-roberta等中文预训练模型8.2 排查清单遇到 Transformer 相关问题时可以按以下顺序排查确认输入张量形状是否正确特别是 batch size 和 seq len 的维度。确认注意力掩码是否有效。训练完看不到推理时是否传给模型。打印 attention 最后一层的权重确认模型是否关注到了合理位置。检查 loss 曲线如果出现 NaN优先降低学习率并检查输入是否有异常值。如果是 OOM先减小 batch size再考虑序列长度压缩。如果是推理速度慢先检查是否开启了torch.no_grad()再考虑半精度。9. 最佳实践与工程建议9.1 数据与 Tokenizer 规范文本输入前必须走同一个 tokenizer 流程。训练和推理时使用的 tokenizer 必须是同一个版本否则会出现 token id 无法对齐的问题。中文文本建议直接使用中文预训练模型的专门 tokenizer不要用英文 tokenizer 处理中文。9.2 输入长度与注意力复杂度Transformer 的标准注意力复杂度是 O(n²)也就是说序列长度翻倍计算量变成原来的 4 倍。这在实际工程中很容易成为瓶颈。应对方案能截断就截断先保证输入长度在模型支持范围内。使用长文本模型如 Longformer、BigBird、或者带 RoPE 的 LLaMA处理超长文本。对文档类任务先做段落拆分再按段落处理最后聚合结果。9.3 训练稳定性训练 Transformer 时最忌讳直接全量微调。推荐顺序是冻结大部分底层参数只训练顶层。确认 loss 下降后再逐步解冻更多层。使用 warmup 学习率策略前几步让学习率从小慢慢升到目标值。如果显存允许使用 AdamW 优化器并设置正确的权重衰减。9.4 推理性能优化对生产环境常见的推理加速手段包括使用半精度FP16或 INT8 量化减少显存占用。使用批处理batch inference一次推理多条请求。对 Decoder 模型使用 KV Cache避免重复计算已经生成的历史 token。通过 ONNX Runtime、TensorRT 等推理加速框架部署。9.5 数据偏差与公平性Transformer 训练过程会继承训练数据中的偏见。如果训练语料本身有性别、地域、职业上的偏向模型输出也可能带有这种偏向。在模型上线前应该做一定程度上的偏差测试尤其是面向 C 端用户的产品。这个环节不决定模型效果上限但决定产品能不能稳定落地。9.6 成本与 Token 计量大模型训练和推理的成本往往和 Token 数量直接挂钩。Token 是模型处理文本的基本单位一个中文汉字可能对应一个或多个 Token具体要看 tokenizer 切词方式。在生产环境中建议在调用日志中统计 Token 消耗用于成本核算和异常流量检测。做计费系统时要明确“输入 Token”和“输出 Token”是否分别计量以及上下文字符串如何计算。这个细节在规模化运营时非常重要。10. 总结与下一步学习路线Transformer 是当前人工智能大模型的基础架构。本文从 RNN 的并行化瓶颈出发逐步拆解了 Self-Attention 的 QKV 计算过程、多头注意力的作用、位置编码的必要性以及 Encoder 和 Decoder 的分工差异。通过手写最小多头注意力实现又通过 Hugging Face 加载预训练模型相信你对 Transformer 已经有了一个“既能看懂理论、也能跑通代码”的认识。接下来你可以从几个方向继续深入阅读源码去看 PyTorch 的nn.Transformer模块或者 Hugging Face 里的BertSelfAttention源码代码会比你手写的版本更工程化。动手微调用一个小规模的中文数据集做一次文本分类或者文本生成微调亲手踩一遍过拟合、显存不足、loss 不降这些坑。关注大模型架构演进新一代模型如 LLaMA、ChatGLM、Qwen在原始 Transformer 上做了哪些改动为什么说 RoPE 重要为什么 GQA 能节省显存这些问题都可以顺着这次的基础继续探索。回看 Transformer 与 CNN/RNN 的对比理解为什么在图像任务上 Vision Transformer 也能奏效核心原因就是自注意力机制本身不依赖输入是文本还是图像。学习 Transformer 不要等到把所有数学公式都搞懂才开始动手先跑通一个最小示例再慢慢完善认知这条路是最快的。希望这篇文章能帮你省下一些探路时间。