☰
modded-nanogpt Speedrun 151.5s 新纪录:删除首个注意力层、扩展验证窗口与 iteration_extension 调度参数解析
2026/10/3 8:31:09 网站建设 项目流程
  • 人工智能
  • 大模型
  • 预训练
  • 分布式训练
  • 模型优化
  • 深度学习

【免费下载链接】modded-nanogpt

NanoGPT (124M) in 90 seconds

项目地址:https://gitcode.com/GitHub_Trending/mo/modded-nanogpt
点击查看免费下载

本文基于 modded-nanogpt 仓库 speedrun 纪录 2025-09-21_DropAttn/README.md 展开,系统讲解这次把 124M 参数 GPT-2 训练时间推进到151.5 秒的纪录是如何达成的:删除第一个注意力层、将训练步数从 1645 提升到 1680、把验证期的所有长注意力窗口扩展到 20 个 block,并新增iteration_extension参数用于在最终学习率与窗口配置下追加训练步数。读完本文你将理解"哪些层可以安全删掉"背后的可解释性依据、如何用独立于步数的扩展阶段隔离超参影响,以及 Muon 优化器处理 1D 标量参数时的实际行为。

一、本次纪录的改动总览

该纪录建立在此前所有 speedrun 改进(含 PR #130)之上,核心改动共四项:

改动内容收益
删除首个注意力层Block中 layer 0 不再实例化CausalSelfAttention减少一个[768, 768]注意力矩阵的前向/反向开销
增加步数训练步数从 1645 → 1680换取更低损失
扩展验证窗口验证时所有长窗口统一扩展到 20 个 block损失约 -0.001,约等于 10 步(≈1 秒)的提升
新增iteration_extension参数指定在最终 lr 与 ws 下继续训练的步数损失约 -0.001,且大幅简化后续调参

从仓库源码看,这次"删除首个注意力层"的拓扑选择已经固化在当时的训练脚本 records/track_1_short/2025-09-21_DropAttn/06350b97-4a98-47da-90c1-3b957af8af6c.txt 的Block定义中:

class Block(nn.Module): def __init__(self, dim: int, head_dim: int, num_heads: int, layer_idx: int): super().__init__() # skip attention of blocks.7 (the 8th layer) by @YouJiacheng self.attn = CausalSelfAttention(dim, head_dim, num_heads) if layer_idx != 7 and layer_idx != 0 else None # skip MLP blocks for first MLP layer by @EmelyanenkoK self.mlp = MLP(dim) if layer_idx != 0 else None

layer_idx != 0表明第一个(layer 0)注意力层被直接置为None,前向时跳过;同时 layer 0 的 MLP 也在更早的纪录中被删除(layer_idx != 0),从而形成了"首个 block 只做残差缩放与注入、不承担任何子层变换"的拓扑。

二、为什么可以安全删除第一个注意力层

文档给出了三个相互支撑的理由,这正是"删除层"这一激进操作的可解释性基础:

  1. 首个注意力层对 induction heads 没有实质贡献:通过观察训练完成后的模型权重(观察文章见仓库纪录背景),发现第一个注意力层没有产生有意义的 induction head 行为,属于"训练后可用性"极低的冗余计算。
  2. PR #120 已删除第一个 MLP:此前纪录把第一个 MLP 删掉后,模型前两个 block 之间出现了"两个注意力层之间没有中间变换"的结构——注意力层的输出直接喂给下一个注意力层,进一步削弱了首层注意力的存在意义。
  3. PR #130 的 smear 模块增强了 token 间信息传递:smear 模块(见 2025-09-18_Smear/README.md)以极轻量的方式实现了"把前一个 token 的 embedding 掺入当前 token"(近似token + 0.07 * prior_token),这恰恰承担了首层注意力中"注意前一个 token"这类低阶功能,使模型不再需要首层注意力做这件事。

从源码层面看,Block.forward中if self.attn is not None与if self.mlp is not None的条件执行让这种稀疏拓扑非常廉价:跳过某层时,该 block 只做x = lambdas[0] * x + lambdas[1] * x0的残差缩放,不触发任何 GEMM。同时,这一设计也考虑了优化器的形状分桶:注释中明确写着"2 matrices x 12 layers = 24 total, which is divisible by 8 GPU world size",即保留 12 个 MLP 槽位、每槽 2 个矩阵,共 24 个[768, 2816]矩阵,恰好能被 8 卡 world size 整除,从而保持 Muon 的 reduce-scatter/all-gather 分片均匀。

三、iteration_extension:把"尾段微调"从总步数中解耦

3.1 为什么需要它

在引入该参数之前,lr(学习率)与ws(注意力窗口)都与总步数强绑定:改动步数会牵动整个后半段训练的行为。文档给出了一个直观例子——如果把步数从 1645 增加到 1655,那么第 1000 步处看到的损失就会突然变化,导致很难判断"损失变化究竟是步数增加造成的,还是其它改动造成的"。这给隔离变量影响带来了巨大困难。

iteration_extension的解法是:在主线训练结束后,追加一段以最终 lr 与最终 ws 运行的扩展阶段。主线步数的任何改动都不会再波及扩展阶段的行为,扩展步数单独可调,从而:

  • 带来约 0.001 的损失提升;
  • 让针对步数的微调(sweep)变得容易得多。

3.2 在仓库中的现代形态

这一机制在仓库演进中保留了下来。当前 track_1_short/config.py 中:

num_scheduled_iterations: int = int(os.environ.get("NUM_SCHEDULED_ITERATIONS", "1122")) num_extension_iterations: int = 20

而 track_1_short/schedule.py 中:

self.total_steps = self.scheduled_iterations + extension_iterations

训练阶段表(TRAINING_STAGES)的最后一项就是扩展阶段,其duration为None(不占主线比例),lr_mul被注释为"not used(lr sits at the floor)",并以较小的 batch(8 * 2048 * 8)与更长的窗口(window_sizes=(6, 13))运行——这正是"在最终 lr 与最终 ws 下继续训练"这一设计意图的延续。可见iteration_extension已经从小步追加演化为独立的"扩展阶段"概念,但其核心思想(延长尾段、不触碰主线调度)一脉相承。

四、验证窗口扩展到 20:让最终验证更接近模型真实能力

本次改动把验证期的所有长窗口(long window)统一扩展到 20 个 block(每 block 128 token,即 2560 token 的注意力视野),换来约 -0.001 的验证损失,折算约 10 步、1 秒的训练收益。

这与仓库此前的 YaRN 验证扩展实验一脉相承。2025-09-10_Yarn/ReadMe.md 曾记录"将最终验证窗口从 11 扩展到 13 带来约 0.0015 的损失提升",并观察到奇数窗口表现更佳(ws_short = ws_long // 2的整除行为对不同奇偶窗口不同)。本次纪录把验证窗口进一步推到 20,配合 YaRN 的频率缩放(attn_scale *= 0.2 * math.log(new_window / old_window) + 1),在验证时对更长上下文做无损扩展,从而在"训练窗口较小、验证窗口较大"的配置下更充分地展示模型潜力。

在当前的 track_1_short/config.py 中,这一机制固化为WS_POST_YARN_EXT = 20,注释为 "The final validation extends the long attention window to this many blocks (record #360)",而当前模型 model/gpt.py 的LONG_WINDOW_LAYERS = (3, 10)定义哪些层使用长窗口。可以推断:本次纪录(长窗口层带final_bm = ws_final_layer * args.block_size,对应bm_sizes中下标 4 与 11 的final_bm槽位)正是"验证时把长窗口层放大"这一做法的早期源头。

五、Future Opportunities:Muon 分片的填充浪费

文档还指出了下一步优化方向,这需要结合 Muon 的实现来理解。

5.1 现状:10 个注意力变量 vs 22 个 MLP 变量

删除首个注意力层后,模型中注意力变量(每个[4, 768, 768],即合并的 QKVO 四矩阵)总数降到10,而 MLP 变量(每个[768, 2816],c_fc与c_proj各 12 个)为22。在 Muon 中,同一形状的参数被分到同一 group,并跨 world_size 做 reduce-scatter 后各自计算 Newton-Schulz 正交化,再 all-gather 回全量参数(见 track_1_short/optim/anvil.py 的分片模式)。

文档指出:attention 梯度计算按 16 槽位分片时,10 个真实变量只占 10/16,意味着6/16 的计算落在 padding(全零填充)token 上——这是纯浪费。反观 MLP 侧 22/24,浪费只有 2/24。

5.2 设想:把注意力变量并入 MLP 分片

文档提出的设想是:若能把 2 个注意力变量移入 MLP 分片,使得 MLP 变成 24/24、attention 变成 8/8(即各分片均无 padding),则两块 GEMM 与 NS 迭代的利用率都能打满。这在当时的拓扑下是一个明确的系统级优化空间——它不改变模型结构,只改变优化器按形状分桶的边界,属于"零损失、省时间"的类型。

六、Muon 对 1D 标量变量的行为研究

本次纪录还顺带做了一个优化器层面的实证:

  • 当前 attention gates 与 smear gate 这类 1D 标量参数被传给了 Muon;
  • 从实现粗看,Muon 的 Newton-Schulz 正交化在 1D 变量上近似退化为F.normalize(x, p=2, dim=-1)——即无论梯度多大,每一步的步长都被归一化到大致相同的距离,于是 Muon 对 1D 变量来说变成了"对先前梯度做指数平滑、且每步长度近似恒定"的更新器;
  • 把这类变量换到 Adam 上,实测约 0.5 秒的运行时增加,且损失无改善;
  • 直接用F.normalize(x, p=2, dim=-1)替换 Newton-Schulz 处理这些变量,表现反而略差。

文档作者明确表示对背后的理论尚无完整解释("I do not understand the theory here yet"),但经验上当前方案性能良好。这是一个典型的"先有实证、后补理论"的工程决策,也提醒读者:优化器与参数维度的匹配需要逐个形状实测,不能仅凭理论推断。值得注意的是,仓库在后续演进中(如 2025-10-24_NorMuon、2025-11-10_CautiousWD 等纪录)持续围绕 Muon 的标量参数处理与权重衰减展开调优,说明这个方向在 speedrun 的收益曲线上一直很有价值。

七、验证方法:单样本 t 检验 + 均值标准差

纪录的验证方法论非常严谨,采用多次独立运行 + 统计检验,而不是单次跑分:

import scipy.stats import torch accs = [3.2786, 3.2798, 3.2762, 3.2781, 3.2778, 3.2801, 3.2774, 3.2772, 3.2777, 3.2789] times = [151.559, 151.526, 151.516, 151.527, 151.606, 151.771, 151.546, 151.547, 151.44 , 151.872] print("p=%.4f" % scipy.stats.ttest_1samp(accs, 3.28, alternative="less").pvalue) # p=0.0005 print("acc:", torch.std_mean(torch.tensor(accs))) # acc: (tensor(0.0012), tensor(3.2782)) print("time:", torch.std_mean(torch.tensor(times))) # time: (tensor(0.1305), tensor(151.5910))

关键点:

  • 单样本单尾 t 检验:ttest_1samp(accs, 3.28, alternative="less")检验"这批运行的均值是否显著低于 3.28"。得到p=0.0005,远小于 0.05,说明相对 3.28 的基线(此前纪录约 3.2790,见 2025-09-18_Smear/README.md 的统计)改进在统计上显著;
  • 损失均值 3.2782、标准差 0.0012:10 次运行非常稳定,证明改动不是单次运行的噪声;
  • 耗时均值 151.591 秒、标准差 0.1305 秒:整个训练流程的耗时抖动被控制在约 0.13 秒内,这也是 speedrun 竞速能精确对比的前提。

文档同时说明:验证运行完成后,代码的语法与命名做了轻度重构(Code syntax/naming was lightly refactored after performing validation runs),因此发布版脚本与验证版逻辑一致、仅组织方式更整洁。

八、总结与延伸阅读

这次 151.5s 纪录的本质是"三管齐下":

  1. 结构上:用可解释性依据(induction head 观察 + smear 模块接管低阶功能)安全删除首个注意力层,让 124M 模型的注意力计算进一步向必要功能收敛;
  2. 调度上:通过iteration_extension把尾段微调与主线步数解耦,使后续调参可以隔离变量;同时把验证长窗口扩到 20,让验证损失更接近模型真实能力;
  3. 系统上:指出 Muon 形状分片中 attention 侧 10/16 的 padding 浪费,并验证了 1D 标量参数在 Muon 下的实际行为。

想深入跟进这一系列演进,可以继续阅读仓库内的相关纪录:2025-09-18_Smear/README.md(smear 模块)、2025-09-10_Yarn/ReadMe.md(YaRN 窗口调度与验证扩展);调度与参数的现代实现见 track_1_short/schedule.py 与 track_1_short/config.py;稀疏层拓扑在后续纪录中不断演化,当前 11 层模型的层级配置(NO_ATTN_LAYERS、LONG_WINDOW_LAYERS等)可参见 track_1_short/model/gpt.py 顶部常量区,从中可以清楚看到"哪些层跑注意力、哪些层只跑 MLP、哪些层两者皆无"最终如何被系统化地表达与调度。

  • 人工智能
  • 大模型
  • 预训练
  • 分布式训练
  • 模型优化
  • 深度学习

【免费下载链接】modded-nanogpt

NanoGPT (124M) in 90 seconds

项目地址:https://gitcode.com/GitHub_Trending/mo/modded-nanogpt
点击查看免费下载

相关推荐

上一篇:如何用3个步骤实现小红书无水印下载,告别平台限制的烦恼
下一篇:如何通过GTA5线上小助手实现游戏参数深度定制:完整技术指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询