- 人工智能
- 大模型
- 预训练
- 分布式训练
- 模型优化
- 深度学习
【免费下载链接】modded-nanogpt
NanoGPT (124M) in 90 seconds
modded-nanogpt 的 track 2(medium)赛道上,有一类看似"零成本"的调优:不引入新算子、不改模型结构,只调整学习率调度曲线。本文以 2025-03-06 的记录 LongerCooldown 为核心,完整还原一次"把学习率冷却(cooldown)阶段从总时长的 40% 拉长到 60%、同时把训练步数从 7050 减到 6950"的实验,并结合配套训练脚本与当前仓库的调度源码,讲清冷却阶段的数学含义、实现方式与统计验证方法。读完本文,你将掌握 modded-nanogpt 风格学习率调度的核心参数,并能够复现这类"同预算下降低验证损失"的超参实验。
一次只改调度的记录:背景与动机
track 2 的目标是什么
modded-nanogpt 仓库中 track 2(medium)赛道最初的任务,是匹配 Karpathy 用 llm.c 在 30B tokens 上训练 350M 参数模型所达到的性能。根据 2024-12-31_Target350M 记录,首条记录在 8×H100 上约 26 分钟达到约 2.95 的验证损失。此后该赛道上每一份记录都在同一评估协议(固定验证 token 数、固定算力预算)下,通过调整优化器、调度与架构细节逐点压低验证损失。
本记录的两处改动
LongerCooldown 记录 的 Changelog 只有两行:
- 将学习率冷却阶段时长从总训练时长的40% 增加到 60%;
- 将训练步数从7050 减少到 6950。
记录作者为 @YouJiacheng。这两处改动是"配套"的:步数减少意味着同样训练时长内多出约 100 步的预算余量(在本记录的实际日志中,总耗时约 1631 秒,步均约 234.7ms,100 步约对应 23 秒),而更长的冷却阶段则把这段"省下来的预算"全部投入到学习率衰减最剧烈的收尾阶段,让模型在最后阶段更充分地收敛。
为什么冷却阶段值得单独调
在超大规模 token 预算训练中,学习率往往遵循"先稳定(stable)后衰减(decay)"的两段式策略:训练前半程保持接近峰值的学习率快速推进,后半程线性(或按曲线)衰减到接近 0,从而在有限的预算内把参数"打磨"到更低的损失。冷却阶段占全程的比例(本记录中记为cooldown_frac)直接决定了"平稳期"与"打磨期"的分界线:比例太小,模型大部分时间在高学习率下震荡,损失在末端来不及充分下降;比例太大,则平稳期过短,模型可能尚未充分探索就被迫降速。因此cooldown_frac是一个几乎零额外计算成本、但对最终验证损失有显著影响的调度超参。
冷却阶段的实现:从记录脚本到当前仓库源码
记录脚本中的 get_lr
配套的完整训练脚本 779c041a-2a37-45d2-a18b-ec0f223c2bb7.txt 中,与本次改动直接相关的有两处:
num_iterations = 6950 # number of iterations to run cooldown_frac = 0.6 # fraction of training spent cooling down the learning rate - increased by @YouJiacheng以及学习率函数:
def get_lr(step: int): x = step / args.num_iterations # progress in training assert 0 <= x < 1 if x < 1 - args.cooldown_frac: return 1.0 else: return (1 - x) / args.cooldown_frac注意该函数返回的是相对倍率(相对各优化器参数组的initial_lr),训练循环中再乘回去:
for opt in optimizers: for group in opt.param_groups: group["lr"] = group["initial_lr"] * get_lr(step)从公式可以看出:当训练进度x < 1 - cooldown_frac时,学习率保持为峰值(乘数 1.0);进入冷却段后,学习率从 1.0 线性衰减,在x = 1时精确降为 0。cooldown_frac = 0.6意味着最后 60% 的训练步数都处于线性衰减中,衰减斜率(即(1 - x) / cooldown_frac的斜率)比 0.4 时更平缓。
当前仓库中的同源实现
这一调度思想在仓库当前代码中被进一步参数化,集中在 track_1_short/schedule.py 的TrainingSchedule.get_lr中:
def get_lr(self, step: int) -> float: # learning rate schedule: tied to batch size schedule, with cooldown at the end stage, _ = self.lookup(step) lr = stage.lr_mul cd_start = int(self.scheduled_iterations * (1 - self.cooldown_frac)) if step >= cd_start: t = min(1.0, (step - cd_start) / (self.scheduled_iterations - cd_start)) lr = lr * (1 - t) + LR_FLOOR * t return lr对比可见两处关键差异:
- 冷却起始点相同:
cd_start = scheduled_iterations * (1 - cooldown_frac)与本记录中x < 1 - cooldown_frac的判断完全对应; - 衰减终点不同:当前实现不再把学习率衰减到 0,而是线性衰减到
LR_FLOOR这个绝对乘数(schedule.py 顶部注释表明这是记录 #360 的代码,README 中写的数值为 0.20,代码中取LR_FLOOR = 0.30),同时冷却比例进一步拉长——track_1_short/config.py 中的LR_COOLDOWN_FRAC = 0.80表明后续记录已将冷却阶段扩展到主阶段时长的 80%。
这说明"延长冷却阶段"并非一次性技巧,而是被后续记录持续复用的调度设计主线:从 40% → 60% → 80%,衰减终点也从 0 改为非零 floor,配合批大小调度(batch 8 → 16 → 24 → 20 收尾)共同工作。
实验结果:36 次运行的统计验证
原始数据
README 给出了 36 次独立运行的最终验证损失列表:
[2.9199, 2.9185, 2.9195, 2.9194, 2.9206, 2.9209, 2.9188, 2.9193, 2.9207, 2.9181, 2.9186, 2.9196, 2.9202, 2.9174, 2.9185, 2.9197, 2.9179, 2.9204, 2.9184, 2.9186, 2.9178, 2.9192, 2.9194, 2.9194, 2.9189, 2.9193, 2.9212, 2.9181, 2.9192, 2.9203, 2.9198, 2.9192, 2.919, 2.9196, 2.9182, 2.9186]对这 36 个数据点可以直接计算的统计量:均值约2.9192,最小值 2.9174,最大值 2.9212,整体分布在 ±0.002 的窄带内,方差极小。记录作者给出的结论是P(<2.92) > 99.9%,即这批运行的最终验证损失几乎必然落在 2.92 之下。
单次运行日志佐证
同目录记录文件的日志末尾(约第 7703 行)给出了这次改动的实际运行结果:
step:6950/6950 val_loss:2.9184 train_time:1631354ms step_avg:234.73ms peak memory allocated: 59737 MiB reserved: 70818 MiB- 最终验证损失2.9184,与 36 次运行的均值高度一致;
- 总训练耗时约1631.35 秒(约 27.2 分钟,8×H100),步均约 234.73ms;
- 峰值显存约 59.7 GiB(保留 70.8 GiB)。
需要说明的是,这份日志记录于 2025-03-08,运行环境为 PyTorch 2.7.0.dev + CUDA 12.6、8×H100 80GB;日志中的验证损失是固定验证 token 数(val_tokens = 10485760)下多次前向的平均,步均耗时包含每 125 步一次的验证开销。
如何在自己的环境复现与验证
- 准备数据:按 data/fineweb10B 的格式准备好 FineWeb 10B 的 train/val
.bin分片,脚本中默认的train_files与val_filesglob 分别指向data/fineweb10B/fineweb_train_*.bin与fineweb_val_*.bin; - 修改超参:将记录脚本中
Hyperparameters.num_iterations设为 6950、cooldown_frac设为 0.6(或直接以 0.4 为对照跑一组 A/B 实验); - 以
torchrun在 8 卡环境启动(脚本内assert world_size == 8,代码面向 8×H100 设计),参考仓库根目录的 run.sh 的启动方式; - 观察日志:每 125 步输出一次
val_loss,最终步的验证损失即为报告口径;统计口径上,建议像本记录一样多次重复运行(36 次)并报告分布,而不是依赖单次结果。
若只需验证调度逻辑本身,也可以直接阅读并单测 track_1_short/schedule.py 中的TrainingSchedule.get_lr,构造不同cooldown_frac对比冷却曲线形状。
小结与启示
本记录给出一条非常经济的调优路径:保持模型与优化器不变,仅把学习率冷却阶段从 40% 延长到 60%、同步减少 100 步,就在 36 次重复运行中稳定把验证损失压到 2.92 以下(均值约 2.9192)。其机制可以归纳为两点:一是更长的冷却段让衰减斜率更平缓、末端收敛更充分;二是削减步数释放的预算被更陡峭的"打磨期"吸收,整体收益大于损失。
这一思路在当前仓库的调度实现中已演化为更完整的形态——cooldown_frac被提升到 0.80 并配合LR_FLOOR非零衰减终点(见 track_1_short/config.py 与 track_1_short/schedule.py)。对于任何预算受限的大模型训练实验,学习率冷却比例都是一个值得优先扫描的调度超参:它不改变前向/反向计算,却能在统计意义上显著改善最终验证损失。
- 人工智能
- 大模型
- 预训练
- 分布式训练
- 模型优化
- 深度学习
【免费下载链接】modded-nanogpt
NanoGPT (124M) in 90 seconds
相关推荐
modded-nanogpt 实验记录:将 Token Value Embeddings 扩展至 5 个,以更少步数取得更低验证损失
modded nanogpt 实验记录:将 Token Value Embeddings 扩展至 5 个,以更少步数取得更低验证损失 本篇技术指南以 modde
人工智能大模型预训练分布式训练模型优化深度学习modded-nanogpt 记录复盘:BOS 对齐数据加载与学习率冷却再调优(2025-07-12 BosAlign)
modded nanogpt 记录复盘:BOS 对齐数据加载与学习率冷却再调优(2025 07 12 BosAlign) 本篇以 modded nanogpt
人工智能大模型预训练分布式训练模型优化深度学习Modded-NanoGPT 中的 SOAP 优化器实战:3.15B Tokens 达成 3.28 验证损失的样本效率记录
Modded NanoGPT 中的 SOAP 优化器实战:3.15B Tokens 达成 3.28 验证损失的样本效率记录 本文基于 Modded NanoGP
人工智能大模型预训练分布式训练模型优化深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考