第 14 天 · 论文每一段,都对应一段能跑的代码
一句话
这篇博客按原顺序拆开第 13 天的论文,每段文字后面紧跟实现它的 PyTorch 代码。读完就得到一个能训练、能解码的 Transformer。它补上了论文没写清的工程细节:掩码、批处理、标签平滑和学习率调度。
论文讲了什么
它是什么。这不是一篇新论文,而是一份可以运行的阅读笔记。作者把 Attention Is All You Need 的正文按段落摆出来,每段后面写出对应的 PyTorch 代码。这种写法叫文学编程:文字和代码交错,读者从上往下读,同时就在执行。
第 1 部分:模型结构。代码用一组小类自底向上搭出模型:
attention()函数实现缩放点积注意力,只有几行。MultiHeadedAttention用 4 个线性层得到 Q、K、V 和输出投影,中间做多头的形状变换。SublayerConnection封装"归一化、子层、dropout、残差"这个反复出现的模式。EncoderLayer、DecoderLayer由子层组成,clones()把一层复制 N 份。Embeddings把词向量乘以sqrt(d_model),PositionalEncoding预先算好正弦表。make_model()一次性组装,默认 N = 6、d_model = 512、d_ff = 2048、h = 8。
第 2 部分:训练。论文第 5 节只有一页,这里展开成完整代码。Batch 类生成两种掩码:源句的填充掩码,以及目标句的"填充 + 后续位置"掩码。subsequent_mask(5) 输出一个 5 × 5 下三角矩阵,第 3 行只有前 3 个位置为真。优化器用 Adam,学习率先预热再衰减。标签平滑用 KLDivLoss 实现:正确词拿 0.9 的概率,剩下 0.1 分给其他词。
第 3 部分:例子。第 1 个例子是复制任务:输入一串随机符号,输出原样复制。几分钟就能训练好,适合检查代码有没有错。第 2 个例子是真实的德译英任务,2022 版使用 Multi30k 数据集和 spaCy 分词。文章最后把各层各头的注意力权重画成热力图。
两个版本。2018 年 4 月的原版由 Rush 发布。2022 年版由 Austin Huang 等人更新:适配新版 PyTorch,把代码整理成可以直接运行的笔记本。今天建议读 2022 版。
值得注意的一处差异。代码的 SublayerConnection 先做层归一化,再进子层,即 x + dropout(sublayer(norm(x)))。论文写的是 norm(x + sublayer(x))。作者在注释里说明,这样写是为了代码简洁。巧的是,后来的大模型大多采用了这种归一化前置的写法。
关键概念
- 文学编程(Literate Programming)
- Knuth 提出的写法:程序按人的阅读顺序组织,解释文字和代码交错。本文把这种写法用在论文复现上。
- 子层连接(Sublayer Connection)
- 把归一化、子层、dropout 和残差相加封装成一个模块。编码器每层用 2 次,解码器每层用 3 次。
- 后续位置掩码(Subsequent Mask)
- 一个下三角布尔矩阵。解码器第 i 个位置只能看到第 1 到第 i 个位置,训练时可以一次并行算完整句。
- 标签平滑(Label Smoothing)
- 训练目标不用 one-hot,而是给正确词 0.9、其余词平分 0.1。困惑度会变差,但 BLEU 会提高。
- Noam 学习率调度(Noam Schedule)
- 学习率 = d_model^-0.5 × min(step^-0.5, step × warmup^-1.5)。先线性升高,到 warmup 步后按平方根倒数下降。
- 贪心解码(Greedy Decoding)
- 推理时每一步取概率最高的词,接到输入末尾,再预测下一个词。实现最简单,但不保证整句概率最高。
历史位置:为什么值得读
2017 年论文发表时,官方代码在 Google 的 Tensor2Tensor 库里。那套代码功能全,但层层抽象,初学者很难找到"注意力到底在哪一行"。论文本身又省略了很多实现细节,例如掩码的形状、批次怎么组织。
2018 年,Harvard NLP 的 Alexander Rush 发布了这篇博客,用一套完整、可运行的 PyTorch 代码讲清整个模型。它很快成为学习 Transformer 的标准材料,并带出了一种写作体裁:
- Rush 后来写了 The Annotated S4,用同样方式讲解状态空间模型。
- Hugging Face 博客发布了 The Annotated Diffusion Model,逐段实现扩散模型。
- Karpathy 的 nanoGPT 和配套视频沿用了"最小可运行实现"的思路。
它和清单的关系很直接:第 13 天给出设计,第 14 天给出实现。读论文时觉得"差不多懂了",写代码时才会发现形状、掩码和广播处处是坑。这篇博客的价值,就是把这些坑提前标出来。
学前准备
- 先读第 13 天,知道 Transformer 的各个部件
- 会写 PyTorch 的 nn.Module 和 forward
- 熟悉张量形状、view、transpose 和广播
- 了解 Adam 优化器和交叉熵损失
在原仓库怎么学
- 先在浏览器打开原博客,再读 README.md,了解本目录和第 13 天的分工。
- 读 paper_notes.md,把论文章节和代码类名对上。
- 读 implementation.py。类名与原博客一致:
clones、subsequent_mask、SublayerConnection、MultiHeadedAttention、EncoderDecoder,可以并排对照。 - 做 exercises/ 里的 5 个练习:
exercise_01_attention.py写带掩码的注意力,exercise_02_multihead.py写多头模块,exercise_03_encoder.py搭编码器栈,exercise_04_training.py搭训练流程,exercise_05_inference.py写解码。答案在 exercises/solutions/。 - 运行 train_minimal.py,在复制任务上训练,确认模型能原样输出。
- 用 visualization.py 画注意力热力图和位置编码图案。
- 用 notebook.ipynb 整体复习,CHEATSHEET.md 速查。
今天再看
这份代码今天仍是讲解 Transformer 最常用的参考实现。但直接拿它做项目,要注意 4 个变化:
- 结构:大语言模型只用解码器。想看解码器版本,可以读 nanoGPT。
- 算子:PyTorch 2.x 提供
F.scaled_dot_product_attention,会自动选用 FlashAttention 一类的融合实现。手写的attention()适合学习,不适合长序列。 - 位置编码:新模型多用 RoPE,不再用预先算好的正弦表。
- 数据工具:2022 版的数据部分依赖 torchtext。torchtext 在 2024 年停止活跃开发,新环境里可以换成 Hugging Face datasets。
这份代码最持久的价值是方法:读论文时同步写代码,用最小任务验证每个部件。这个习惯在 2026 年仍然适用。
容易卡住的地方
- 把代码当成论文的逐字实现:SublayerConnection 是归一化前置,论文是归一化后置。复现论文数字时要改回来,或至少知道两者不同。
- 掩码形状对不上:源句掩码是 (batch, 1, src_len),目标掩码是 (batch, tgt_len, tgt_len)。多头时还要在第 1 维 unsqueeze,靠广播扩到 h 个头。
- 标签平滑的 KLDivLoss 要求输入是 log_softmax 的结果,不是 logits。填充符对应的列和行要置 0,否则模型会学着预测填充符。
- 2022 版完整跑通需要特定版本的 torch、torchtext 和 spaCy 模型。建议先只跑复制任务,它不需要下载任何数据。
延伸阅读
- 原论文:The Annotated Transformer
- 原仓库第 14 天目录:14_annotated_transformer/ —— 代码、笔记本、练习与答案(英文)
- 2018 年原版博客 —— Rush 的原始版本,可对比 2022 版改动
- harvardnlp/annotated-transformer —— 2022 版源码和笔记本
- karpathy/nanoGPT —— 只有解码器的最小 GPT 实现
- The Annotated S4 —— 同一体裁讲解状态空间模型