复现 "Attention Is All You Need"
- 这篇论文基本算是大模型的开山之作
- 从零复现论文中的 Base Transformer 模型(德语→英语翻译)
- 主要参考 The Annotated Transformer,逐步实现模型代码
- 总耗时约 9 天(从精读论文到训练完成)
- 最终 BLEU 达到 28.2,基本复现论文结果
过程
- 先看了几个讲解论文的视频,精读论文原文
- 跟着 Annotated Transformer 一步一步实现核心代码:模型架构、Multi-Head Attention、数据准备、训练函数
- 先用小数据集 Multi30K(3 万条)在 Mac 上验证代码正确性
- 确认无误后,上 AutoDL 用 H800 GPU 跑 WMT14 大数据集
- 训练跑了4轮,中间做了吞吐量优化(bucket batching、LabelSmoothing 内存修复等)
- 即便论文、代码、数据全部现成,从头做一遍仍花了 9 天——数据处理、训练参数调整、代码细节排查都需要不少时间
训练数据
- 小数据集:Multi30K,3 万条德英翻译对,用于本地验证
- 大数据集:WMT14 DE→EN,约 450 万条德英翻译对
- 分词:SentencePiece,共享词表,37000 个 token
- 平均序列长度约 30-50 个 token
训练环境
- GPU:AutoDL 上的单卡 H800
- 费用:8.8 元/小时,总训练费用约 40 元(约 4 小时训练)
- 精度:bfloat16 autocast + TF32 matmul
- 编译:torch.compile(max-autotune-no-cudagraphs 模式)
- Micro batch size:128
- Gradient accumulation:8 步
训练结果
- 吞吐量:优化后单 GPU 约 80k tokens/second(优化前约 47k)
- 单 epoch 耗时:优化后约 40 分钟(优化前约 53 分钟)
- BLEU 进展:
- 约 4 小时(7 个 epoch)后,BLEU 达到 27-28
- 最佳 BLEU:28.2(step 30500,第 6 个 epoch)
- 基本复现了论文中 Base Transformer 的结果(论文报告 27.3)
- Validation Loss:从 epoch 0 的 2.22 逐步降至 epoch 6 的 1.51
学到的东西
- 对 Transformer 架构有了更深的理解,结合之前复现 GPT-2 的经验,对整个训练流程更加熟悉
- 虽然 NanoGPT-2 和 Transformer 模型架构有所不同,但整个流程(准备数据→tokenize→data loader→创建模型→训练迭代)是相似的
- 即使所有资料都已公开,完整复现一个模型的细节仍然很多,并不是一两天就能跑出来的
- 数据处理、训练参数、代码正确性等细节都需要仔细打磨
- 使用 Claude Code 来辅助学习和排查问题。遇到模型架构、训练过程、优化方案中不理解的地方,直接向它提问,它能用简洁的文字和 ASCII 图解来解释清楚,节省了很多时间,非常有帮助
例子:Bucket Batching 的梯度累积偏差
在做吞吐量优化时,引入了 bucket batching(按序列长度分组),TPS 提升了 77%,但发现 validation loss 比之前更高。通过和 Claude Code 的讨论,用 ASCII 图快速定位了问题的根因:
问题:每个 micro-batch 内的序列长度一致,但不同 micro-batch 之间长度差异很大。loss 按每个 micro-batch 的 token 数归一化后再 backward,导致短序列 batch 对梯度的贡献被放大了约 3 倍:
短序列 micro-batch 长序列 micro-batch
┌───────────────────────┐ ┌───────────────────────┐
│ █████░░░░░░░░░░░░░░░░ │ │ ████████████████████░ │
│ ██████░░░░░░░░░░░░░░░ │ │ ███████████████████░░ │
│ █████░░░░░░░░░░░░░░░░ │ │ ████████████████████░ │
│ ████░░░░░░░░░░░░░░░░░ │ │ ██████████████████░░░ │
└───────────────────────┘ └───────────────────────┘
ntokens ≈ 2,500 ntokens ≈ 7,500
梯度权重 = 1/2500 = 4.0e-4 梯度权重 = 1/7500 = 1.3e-4
← 短序列的影响力是长序列的 3 倍!
修复:改为在整个梯度累积窗口(8 个 micro-batch)结束后,按总 token 数统一归一化,确保每个 token 对梯度的贡献相同:
修复前:∇ = Σᵢ ∂Loss_i/∂θ / ntokens_i ← 短序列 batch 权重过大
修复后:∇ = G × Σᵢ ∂Loss_i/∂θ / Σᵢntokens_i ← 每个 token 权重相同