2017 Transformer原文【公式附代码手写】:Attention is All you need Reading Note【Transformer阅读笔记】

发布时间:2026/10/9 11:20:58
2017 Transformer原文【公式附代码手写】:Attention is All you need Reading Note【Transformer阅读笔记】 1. 从论文公式到可运行代码Transformer 自注意力机制到底怎么落地2017 年那篇《Attention Is All You Need》我第一次读的时候卡在 3.2.1 节整整一个下午。公式Attention(Q,K,V) softmax(QK^T/√d_k)V看着就一行但真动手写的时候Q、K、V 从哪来、维度怎么对齐、mask 加在哪一步、为什么除以 √d_k 而不是 d_k全是坑。这篇笔记就按论文逐节走一遍把每个公式手写推导再配一份能直接跑的 PyTorch 代码帮你从公式到代码完整走通。Transformer 是什么一句话它是第一个完全靠注意力机制做序列转换的模型把 RNN 的循环和 CNN 的卷积全扔了编码器和解码器各堆 6 层每层就是多头自注意力加逐位置前馈网络。它能做什么机器翻译、文本摘要、语言建模后来所有大模型的底座都是它。适合谁刚读完论文想动手复现的、面试被问自注意力细节答不利索的、想搞懂多头注意力到底在并行什么的同学。我试过直接抄 GitHub 上的实现结果维度对不上、mask 广播报错、softmax 梯度消失折腾两天才跑通。所以这篇不堆概念每个公式后面都跟一段可复制代码你跟着敲一遍比看十遍论文都管用。下面从论文的问题背景讲起再到 TaoToken 的前置准备然后是完整配置、验证请求、报错排查最后给一个语义一致的收尾。2. 论文问题背景与 TaoToken 前置准备为什么自注意力能替代循环论文第 1、2 节讲得很清楚RNN 类模型沿序列位置逐步计算隐藏状态 h_t 依赖 h_{t-1}这种固有的顺序性阻断了训练样本内的并行化。序列一长内存约束就限制 batch 大小训练慢得离谱。ConvS2S 和 ByteNet 用卷积减少顺序计算但关联两个任意位置信号所需的操作数随距离增长——ConvS2S 是线性增长ByteNet 是对数增长学远距离依赖依然困难。Transformer 的做法是把任意两个位置之间的操作数降到常数级。代价是平均注意力加权位置降低了有效分辨率论文用多头注意力来抵消这个影响。这就是 3.2.2 节多头注意力的动机不是花哨是刚需。在动手写代码之前我建议先把环境和一个稳定的模型调用入口准备好方便你边写边验证。我平时用 TaoToken 做模型对话和 API 调试它的接入文档写得很清楚Base URL 和 Key 的配置方式对新手友好。你可以先到官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 看一下整体介绍然后到 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 生成一个 Key。如果你只是想先跑通对话验证公式理解可以直接用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 试一下。这里要强调一点TaoToken 是合规的模型调用入口不是让你去搞什么灰色中转。它的 API 地址是 https://taotoken.net/api配置的时候 Base URL 填这个就行。我实测下来把注意力模块的维度打印出来对照论文再用模型对话问几个关于 QK^T 维度的问题理解会快很多。前置准备清单Python 3.9、PyTorch 2.0、numpy。装好之后先跑一个最小验证确认 torch 能正常 import然后我们进入 3.1 节的编码器解码器结构。3. 可复制配置编码器解码器栈与多头注意力模块论文 3.1 节说编码器由 N6 个相同层堆叠每层两个子层多头自注意力、逐位置全连接前馈网络。每个子层周围用残差连接再做层归一化输出是LayerNorm(x Sublayer(x))。为了残差能相加所有子层和嵌入层输出维度都是 d_model512。解码器也是 6 层但每层多一个第三子层对编码器栈输出做多头注意力并且自注意力子层要加因果 mask防止位置 i 关注到 i 之后的位置。先写一个可复制的配置片段把超参数固定下来后面所有代码都引用它。我用 JSON 格式路径和字段名保持和论文一致{ d_model: 512, num_heads: 8, d_k: 64, d_v: 64, d_ff: 2048, num_layers: 6, dropout: 0.1, max_seq_length: 5000, warmup_steps: 4000, label_smoothing: 0.1 }注意 d_k d_v d_model / num_heads 512 / 8 64这是论文 3.2.2 节的设置。多头注意力把 d_model 切成 8 个头每个头独立算缩放点积注意力最后拼接再过一个 W^O 投影。下面是缩放点积注意力的完整实现对应公式 (1)import torch import torch.nn as nn import torch.nn.functional as F import math def scaled_dot_product_attention(Q, K, V, maskNone): 论文公式 (1): Attention(Q,K,V) softmax(QK^T / sqrt(d_k)) V Q: (batch, seq_len, d_k) K: (batch, seq_len, d_k) V: (batch, seq_len, d_v) d_k Q.size(-1) # 步骤1: QK^T得到 (batch, seq_len, seq_len) 的相关性矩阵 scores torch.matmul(Q, K.transpose(-2, -1)) # 步骤2: 除以 sqrt(d_k)防止 softmax 进入饱和区 scores scores / math.sqrt(d_k) # 步骤3: 加 mask被屏蔽位置设 -1e9 if mask is not None: scores scores.masked_fill(mask 0, -1e9) # 步骤4: softmax 归一化 attention_weights F.softmax(scores, dim-1) # 步骤5: 加权求和 output torch.matmul(attention_weights, V) return output, attention_weights为什么除以 √d_k论文 3.2.1 节解释当 d_k 较大时点积在幅度上变大softmax 会被推入梯度极小的区域。假设 Q、K 各分量独立、均值 0 方差 1点积 QK^T 的方差就是 d_k除以 √d_k 把方差拉回 1。你可以自己验证把 d_k 设成 512 不缩放softmax 输出会接近 one-hot梯度几乎为 0。接下来是多头注意力对应公式MultiHead(Q,K,V) Concat(head_1,...,head_h)W^O其中head_i Attention(QW_i^Q, KW_i^K, VW_i^V)class MultiHeadAttention(nn.Module): def __init__(self, d_model512, num_heads8, dropout0.1): 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) self.dropout nn.Dropout(dropout) def forward(self, query, key, value, maskNone): batch_size, seq_len query.size(0), query.size(1) # 线性投影 Q self.W_q(query) K self.W_k(key) V self.W_v(value) # 拆成多头: (batch, seq_len, d_model) - (batch, num_heads, seq_len, d_k) 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) if mask is not None: mask mask.unsqueeze(1).repeat(1, self.num_heads, 1, 1) # 每个头并行算缩放点积注意力 out, attn scaled_dot_product_attention(Q, K, V, mask) # 拼接多头: (batch, num_heads, seq_len, d_k) - (batch, seq_len, d_model) out out.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) # 输出投影 W^O out self.W_o(out) return out, attn这里有个容易踩的坑view之后必须transpose拼接回来的时候要先transpose再contiguous().view否则维度会乱。我第一次写的时候忘了 contiguous报错 view size is not compatible查了半天。逐位置前馈网络对应公式 (2)FFN(x) max(0, xW_1 b_1)W_2 b_2就是两个线性层中间夹 ReLU内层维度 d_ff2048class PositionwiseFeedForward(nn.Module): def __init__(self, d_model512, d_ff2048, dropout0.1): super().__init__() self.linear1 nn.Linear(d_model, d_ff) self.linear2 nn.Linear(d_ff, d_model) self.dropout nn.Dropout(dropout) def forward(self, x): return self.linear2(self.dropout(F.relu(self.linear1(x))))位置编码对应 3.5 节公式PE(pos,2i) sin(pos/10000^(2i/d_model))和PE(pos,2i1) cos(pos/10000^(2i/d_model))class PositionalEncoding(nn.Module): def __init__(self, d_model512, max_seq_length5000, dropout0.1): super().__init__() self.dropout nn.Dropout(dropout) pe torch.zeros(max_seq_length, d_model) position torch.arange(0, max_seq_length, dtypetorch.float).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) pe pe.unsqueeze(0) self.register_buffer(pe, pe) def forward(self, x): return self.dropout(x self.pe[:, :x.size(1), :])把这些模块拼成编码器层和解码器层就得到完整的 Transformer。解码器自注意力要加因果 mask用torch.tril生成下三角矩阵def create_causal_mask(seq_len, device): mask torch.tril(torch.ones(seq_len, seq_len, devicedevice)) return mask.unsqueeze(0).unsqueeze(0)到这里论文 3.1 到 3.5 节的核心公式全部落地。你可以把上面的代码存成一个transformer_blocks.py下一步我们跑验证。4. 验证请求与成功结果逐行打印维度确认公式正确写完代码不验证等于没写。我习惯用固定随机种子把每一步的维度打印出来对照论文的 d_model512、num_heads8、d_k64 逐个核对。if __name__ __main__: torch.manual_seed(42) batch_size, seq_len, d_model 2, 10, 512 num_heads 8 # 构造输入 x torch.randn(batch_size, seq_len, d_model) print(f输入维度: {x.shape}) # (2, 10, 512) # 位置编码 pos_enc PositionalEncoding(d_model) x pos_enc(x) print(f位置编码后: {x.shape}) # (2, 10, 512) # 多头自注意力 mha MultiHeadAttention(d_model, num_heads) out, attn mha(x, x, x) print(f多头注意力输出: {out.shape}) # (2, 10, 512) print(f注意力权重: {attn.shape}) # (2, 8, 10, 10) # 前馈网络 ffn PositionwiseFeedForward(d_model) out ffn(out) print(fFFN输出: {out.shape}) # (2, 10, 512) # 因果 mask 验证 causal_mask create_causal_mask(seq_len, x.device) out_masked, attn_masked mha(x, x, x, maskcausal_mask) print(f因果mask后注意力权重[0,0]:\n{attn_masked[0,0].round(decimals3)})跑出来应该看到输入 (2,10,512)位置编码后不变多头注意力输出 (2,10,512)注意力权重 (2,8,10,10)——8 个头每个头一个 10x10 的相关性矩阵。因果 mask 后注意力权重矩阵应该是下三角上三角全为 0。如果你看到上三角有非零值说明 mask 没生效检查masked_fill的条件是不是写反了。再验证一下缩放的作用。把scores / math.sqrt(d_k)去掉打印 softmax 后的最大值Q torch.randn(1, 10, 512) K torch.randn(1, 10, 512) scores_no_scale torch.matmul(Q, K.transpose(-2, -1)) scores_scaled scores_no_scale / math.sqrt(512) print(f不缩放 softmax 最大值: {F.softmax(scores_no_scale, dim-1).max().item():.4f}) print(f缩放后 softmax 最大值: {F.softmax(scores_scaled, dim-1).max().item():.4f})不缩放时 softmax 最大值会接近 1分布极度尖锐缩放后会平缓很多。这就是论文说的推入梯度极小区域的直观体现。如果你想用模型对话快速验证对某个公式的理解可以到 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 把 QK^T 的维度问题丢进去问比翻论文快。API 调试的话Base URL 填 https://taotoken.net/apiKey 用你在 API Keys 页面生成的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 有完整示例。5. 本篇常见错排查401、维度不匹配、mask 广播失败复现过程中我踩过的坑基本集中在这几类对照着排查能省不少时间。报错一401 Unauthorized / invalid api key。这个一般出现在你调 TaoToken API 验证的时候。检查三件套Base URL 是不是https://taotoken.net/apiKey 是不是从 API Keys 页面复制的完整字符串Model ID 是不是填对了。三者缺一不可。如果还报 401去 console 页面 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 看一下 Key 的状态是不是被禁用或过期。报错二RuntimeError: The size of tensor a (10) must match the size of tensor b (8)。这是多头注意力里 mask 广播失败。mask 原始形状是 (batch, seq_len, seq_len)但 QK^T 是 (batch, num_heads, seq_len, seq_len)需要在 num_heads 维度上扩展。正确写法是mask.unsqueeze(1).repeat(1, self.num_heads, 1, 1)。如果你忘了这一步就会报维度不匹配。报错三view size is not compatible with input tensors size and stride。这是拼接多头输出时忘了contiguous()。transpose之后张量在内存里不连续直接view会报错。正确顺序是out.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)。报错四local proxy failed / connection refused。这个通常是网络配置问题检查你的 API 地址有没有写错或者本地有没有奇怪的代理设置干扰。TaoToken 的 API 地址就是https://taotoken.net/api不要加多余路径。报错五reading choices 相关解析错误。如果你在解析模型返回的 JSON 时遇到KeyError: choices先打印原始 response 看看结构。不同接口返回格式可能不同别硬套一个模板。报错六OAuth 相关错误。如果你用的是 Claude Code 或 Codex 这类工具接入OAuth 流程走不通时检查回调地址和 Key 的权限范围。Claude Code 接入可以参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 的配置说明Base URL、Key、Model ID 三件套要写全。报错七softmax 输出全为 nan。检查 mask 里是不是用了-inf而不是-1e9。-inf在某些情况下会导致 nan论文实现里用-1e9是更稳的选择。另外检查有没有除零math.sqrt(d_k)里 d_k 不能为 0。报错八位置编码维度对不上。self.pe[:, :x.size(1), :]这里 x.size(1) 是 seq_len如果你的输入是 (seq_len, batch, d_model) 格式要改成 x.size(0)。建议统一用 batch_first 格式少踩这个坑。排查顺序建议先看报错类型维度问题查 shape网络问题查 Base URL 和 Key数值问题查 mask 和缩放。把每一步的 shape 打印出来90% 的问题一眼就能定位。6. 从公式到代码走通之后长期编码与 Agent 场景的接入建议把上面这套代码跑通你对自注意力、多头、位置编码、mask 的理解就落地了。接下来如果要做长期编码或者 Agent 相关的项目建议把模型调用也规范化。TaoToken 的 Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 有适合长期编码场景的配置说明Base URL、Key、Model ID 三件套配好之后可以直接在编辑器或 Agent 框架里调用。我自己的习惯是公式推导用纸笔代码实现用 PyTorch模型验证用 TaoToken 的对话和 API。三者结合理解速度比单看论文快很多。注意力权重矩阵打印出来之后你可以直观看到每个头关注的位置这比任何文字描述都清楚。最后留一个实用技巧把attn_masked[0,0]那个 10x10 矩阵存成 CSV用 Excel 或 pandas 画个热力图你会看到下三角的注意力分布。论文附录里展示的注意力可视化你自己就能复现。这一步做完Transformer 的阅读笔记才算真正闭环。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询