OpenAI 创始成员、前研究科学家 Andrej Karpathy 最近尝试在 llm.c 中重现了 GPT-2。这里的 GPT-2 是 15.58B 参数的完整版本,最初亮相于 OpenAI 2019 年 2 月 14 日发布的博文《Better Language Models and their Implications》当中。
“2019 年时,GPT-2 的训练工作还是一个涉及整个团队、需要规模化投入的项目。但如今 5 年过去,随着计算(H100 GPU)、软件(CUDA\cuBLAS、cuDNN、FlashAttention)和数据(例如 FineWeb-Edu 数据集)等层面的改进,我们已经能够在 24 个小时之内凭借单个八 H100 节点成功对该模型进行重现,且总成本仅为 672 美元。”Karpathy 说道。
Karpathy 在 2017 年离职后进入特斯拉担任 AI 高级总监,但在 2023 年再次回到 OpenAI 组建团队,并推出了 ChatGPT。一年后,Karpathy 离开了 OpenAI,并出于教育意义开发了 llm.c。llm.c 是简单、纯 C/CUDA 的 LLM(总计约 5000 行代码),无需使用涉及 Python 解释器或者高复杂度深度学习库(例如 PyTorch/JAX、huggingface/transformers 等)的典型训练技术栈。
在 Karpathy 公布了这一结果后,有网友问到当时训练 GPT-2 的成本,Karpathy 回答道:
这些信息从未公开过,但我估计成本要高得多。按乘数倍率来算,数据方面可能要高了 3 倍,硬件利用率方面高 2 倍。2019 年的计算集群可能使用的是 V100 (~100 fp16 TFLOPS),现在可能换成了 H100 (~1,000),这样算下来性能大概提高了 10 倍。所以成本方面非常粗略地估计,可能要高出 100 倍,也就是大约 100,000 美元左右?
对此有网友评价道,“随着英伟达对 AI 工作负载加速硬件开发的不断深入,我预计未来几年内,这款硬件的成本可能只有几十美元,并且训练时间只需几个小时。”
至于具体效果,Karpathy 与 19 年的 GPT-2 版本做了对比。同样用的当时博文介绍里的提示词“In a shocking finding, scientist discovered a herd of unicorns living in a remote, previously unexplored valley, in the Andes Mountains. Even more surprising to the researchers was the fact that the unicorns spoke perfect English.” 结果新模型的输出结果相当连贯,质量也大致与 GPT-2 相当。
两个模型生成的文字较长,有兴趣的朋友可以点击查看:http://llmc.s3-us-west-2.amazonaws.com/html/gpt2_vs_llmc30kedu.html
下面我们来看下 Karpathy 的复刻过程。
复现过程
首先,Karpathy 强调,使用 llm.c 训练 GPT-2 非常简单,因为它是用 C/CUDA 编写的,所以全程不涉及 minconda、Python、PyTorch 等。只需要一台八 H100 GPU 的设备即可。
“总之,不必担心,llm.c 在算力要求方面非常灵活,哪怕只有一张 GPU,大家也仍然可以训练出自己的 GPT-2——只不过需要等待 8 天,而不是像我这样的 1 天。而如果您拥有 16 张 GPU(例如使用新的 Lambda 1 Click Clusters),还可以开展多节点训练,前后只需要等待 12 个小时。”Karpathy 说道。
在节点启动之后,下面来看看 GPT-2 训练的完整说明,不用担心,Karpathy 表示保证一分钟以内开始执行:
稍后会对参数做具体解释。接下来开始优化:
可以看到,每个步骤大约需要 2.75 秒,而其中总共涉及 3.2 万个步骤,所以现在需要等待约 24 个小时。
在每一步中,训练作业的运行都会占用约 100 万个 FineWeb-EDU token(数据内容来自互联网上的教育网页),并对模型的 15.58 亿个权重进行更新,使其能够更好地预测序列中将要出现的下一个 token,到最后将总计处理 3.2 万 x 1048576 = 33.6 B 个 token。随着预测下一 token 的能力越来越强,loss 也会随之下降。
接下来的工作是归一化(将数值范围控制在 0.1 至 1 之间),学习率也在前几个步骤中逐渐升温。从结果来看,这套模型的 flops 利用率(MFU)约为 50%,可说是相当高效了。
现在唯一要做的,就是等待 24 小时让其完成,之后可以使用 dev/vislog.ipynb jupyter notebook 对 main.log 日志文件进行可视化。为此,大家需要安装 Mython 和 matplotlib。
评估
如左图,正在跟踪 FineWeb-EDU 验证数据的 loss。如果大家只运行 OpenAI 发布的 GPT-2 并在此基础上评估其 loss,得到的就是红色水平线(loss 2.83)。而 Karpathy 模型的运行结果快速将其超越,步长约为 5000。
当然,这样的比较并不公平,毕竟 GPT-2 是在从未发布的 WebText 数据集上训练而成,因此素材内容可能存在很大的分布差异。比方说,如果在 LR 1e-4 下对 OpenAI 模型进行 1000 步微调,loss 就会迅速下降至划线(loss 2.61),代表其正在快速适应新的统计数据。
但就个人而言,Karpathy 认为 loss 验证更多只是一种健全性检查,要实际比较模型性能,还是得借助更靠谱的第三方评估。
这时候就要请出 HellaSwag 评估了,这也是目前市面上表现良好、流畅、普及度高、常被引用且能够提供早期信号的评估方案之一。其中提供的都是简单的常识性场景,大模型必须据此做出正确的内容延展。
Karpathy 在右侧窗格中评估了 HellaSwag,并发现在约 25K 步左右与 GPT-2 模型的性能发生交叉(早于 GPT-2,据估计 GPT-2 的训练数据集共有约 1000 亿个 token。但这可能与数据质量的提高有关,之前 Karpathy 在 124M 训练期间也观察到了类似的现象)。绿线为同等参数规模的 GPT-3 模型,其模型架构与 GPT-2 几乎相同、仅存在细微差别(上下文长度从 1024 增长至 2048),同时是针对 3000 亿 token 进行了训练(相当于我们此次实验训练 token 量的 10 倍左右)。
必须承认,HellaSwag 也不能算是完美的单点比较选项,毕竟它测试的主要是简单的英语和常识,并不涉及多语言、数学或者代码内容。也许是因为 WebText 数据集在这几个方面拥有更高的比重,所以才把模型规模推到了这样的水平,但 Karpathy 团队并不确定,毕竟 OpenAI 从未对此做出过解释。
Karpathy 指出,一般来讲,在 GPT-2 等低能力模型上很难获得良好的评估结果,毕竟这类模型无法理解多项选择题;而且其样本质量不够高,无法正常完成涉及标准数学或者代码的评估测试。
参数指南
现在让我们仔细看看我们在训练中传递的参数。OpenAI 发布的 GPT-2 虽然包含模型权重,但相关细节却很少;GPT-3 版本并未开放权重,但相关细节较多。因此在多数情况下,我们只能参考 GPT-3 论文中提及的超参数,毕竟 GPT-2 论文几乎没有提到这方面信息:
-i -j 用于训练和验证分割标记文件,需要提前使用 edu_fineweb.sh 进行下载。
-o 是写入日志和检查点的输出目录。
-v 250 要求每 250 步执行评估并记录验证 loss。
-s 300000 要求每 30 万步采样部分 token。因为总步数不足 30 万,所以这其实是一种关闭采样的灵活方式,实际只会在最后采样一次。
-g 384 将最后需要采样的 token 数设置为 384。
-h 1 要求评估 HellaSwag 准确性。
-b 16 将微批次大小设置为 16。如果内存不足,请降低此值,例如依次尝试 8、4、2、1。
-t 1024 将最大序列长度设置为 1024,与原版 GPT-2 保持一致。
-d 1048576 要求总批次大小为 2 的 20 次方,与 GPT-3 论文中的超参数设置相同。代码将确保满足所需的总批次大小,并计算优化所需的梯度累积“内循环”步骤。例如,之前提到 Karpathy 拥有 8 张 GPU,每张 GPU 执行 16 x 1024 个 token,因此每个微步(即一次向前向后)对应 8 x 16 x 1024 = 131072 个 otken,因此代码计算梯度累积步数应该为 8 以满足每步所需的 1M 批次大小。即每向前+向后 8 次,而后进行一次更新。
-r 0 将重新计算设置为 0。重新计算是一种在计算与内存之间求取平衡的方法。如果设为-r 1,则代表在反向过程中重新计算前向传递的一部分(GeLU)。就是说 Karpathy 不必须通过对其缓存来节约内存,但需要付出更高的算力成本。因此如果内存不足,请尝试设置-r 1 或者-r 2(同时重新计算 layernorms)。
-z 1 在多个 GPU 上启用 ZeRO-1(即优化器状态分片)。如果使用多于 1 张 GPU 进行训练,则应当选择这样的设置,且基本上应始终将其保持为开启状态。但在单 GPU 上,此设置没有实际效果。
-c 0.1 将权重衰减设置为 0.1。只有(2D)权重的衰减与 GPT-2 完全相同,具体数值来自 GPT-3 论文。
-k "cosine" 设置余弦学习率计划,这里姑且直接使用默认值。
-l 0.0006 将最大学习率设置为 6e-4。根据 GPT-3 论文的解释,Karpathy 这个大小的模型应当使用 2e-4,但这里 Karpathy 将其增加至三倍,似乎训练速度更快且没有引发任何问题。这项参与未经认真调整。
-q 0.1 代表在训练过程中,将学习率衰减至最大 LR 的 10%,取值参考自 GPT-3 论文。
-u 700 表示将在前 700 次迭代中将学习率从 0 提升至最大,总批次大小为 0.5M 时对应 3.5 亿个 token,取值同样来自 GPT-3 论文。
-n 2000 要求每 2000 步保存一次模型检查点。
-x 32000 要求总共 32K 步。之所以选择这个数字是因为其好读好记,而且正好对应 24 个小时。
-ge 1 为 CublasLt 设置最近合并的 gelu 重新计算设置(可选)。
-y 1 用于将“恢复”标记设置为开启。如果训练因任何原因而崩溃或者挂起,则可按下 CTRL+C 并重新运行此命令,其将尝试恢复优化。Llm.c 具备按 bit 确定性,因此大家将获得与崩溃之前完全相同的结果。
-e "d48" 要求从头开始初始化深度为 48 的 GPT-2 模型。
内存指南
大多数朋友面临的最大限制,可能就是自己的 GPU 内存达不到 80 GB。Karpathy 表示,“没关系,只要有耐心,之前提到的这些任务也都能顺利运行完成,只是速度会稍慢一些。”
但如果模型实在太大,又该如何处理?Karpathy 表示,最重要的是调整微批次大小-b,尝试将其缩小并保持在合适的水平。例如 16 -> 8 -> 4 -> 2 -> 1。以此为基础,尝试使用重新计算设置-r,即 0(最快,但占用的内存最大)、1(速度慢得多,但可以节约大量内存)或者 2(稍慢一些,但内存节约量较少)。
下一步优化思路则是禁用 fp32 中的主权重,这里可怜请用 -w 0(默认值为 1)来执行此操作。Karpathy 并没有为参数维护 fp32 副本,因为根据经验,之前的几次运行都没有问题,可能是因为使用了随机舍入。
“但如果大家在亲自尝试时遇到了问题(其实可能性极低),也可以使用-t 减少最大序列长度,将默认值从 1024 下调至 512、256 等。但这意味着缩小了其最大注意力范围,所以模型的性能也会变得更差。 ”Karpathy 建议道。
代码
“虽然我可能有点倾向性,但 llm.c 真的非常优雅”Karpathy 介绍道:
它只需要基本 CUDA 依赖即可运行。
它是 C/CUDA 中最直接、最小且可读的实现。llm.c 总计约有 5000 行 C/CUDA 代码。Karpathy 主要尝试使用 C,而非 C++,以保持代码简洁。神经网络训练只是对单个浮点数组执行相同的简单算术运算(就是加减乘除)的一个 while 循环,实在没必要搞得太过复杂。
它的编译和运行速度极快(几秒钟内),因此可以执行更多步骤并减少等待时间。
它会在开始时一次性分配所有 GPU 内存,并在之后的训练期间将内存占用量保持恒定。因此只要执行步骤启动,我们就能保证接下来的运行状态始终良好、不会发生 OOM。
具备按 bit 确定性。 运行效率很高,MFU 略低于约 50%。
主要入口点和大部分代码位于文件 tarin_gpt2.cu 当中。该文件包含 GPT-2 模型定义和约 2000 LOC 中的训练循环,并从 llmc 目录处导入了一大堆带有各种实用程序和各层实现的辅助文件。cloc llmc 报告了 23 个文件,3170 LOC,而 cloc train_gpt2.cu 目前为 1353 LOC。
多节点训练
如果您是位手握大量 GPU 的“土豪”,llm.c 也支持多节点训练。Karpathy 表示,其见过的 llm.c 训练最多支持约 500 张 GPU。
“个人迄今为止进行过最大的一次运行,是依靠 Lambda 全新一键集群功能上实现的,当时是在 2 个节点上使用了 16 张 H100 GPU。Lambda 团队提供了关于如何在其一键集群上训练 llm.c 模型的详细说明。例如在使用 512-GPU H100 集群时,每小时费用为 2300 美元,这时候整个 GPT-2 训练周期就仅为 30 分钟。当然,这时您需要增加总批次大小(例如增加到约 8M)并稍微调整一下超参数。我还没有尝试过,但相信会有效而且相当爽快! ”Karpathy 说道。
PyTorch 比较
使用 Karpathy 的并行 PyTorch 实现,与 PyTorch 的运行效果对比应该类似于以下形式:
这里的 PyTorch 代码仅供参考,而非实际实现,因为其中的训练循环在某些位置可能略有不同(例如,数据加载器不会对分片进行置换等),总之大家看看就好。Karpathy 还将默认词汇大小修改为 50257 -> 50304 以提高效率。经过一夜运行,PyTorch 给出以下结果:
Karpathy 强调,这份 PyTorch 脚本可能还有很大的优化空间,但至少可以当作观察基准。PyTorch 占用的内存量似乎更大(此次运行约为 80 GB),而 llm.c 仅占用了 57 GB(节约比例为 29%)。内存资源非常重要,因为它能帮助我们容纳更大的训练批次(例如,llm.c 在这里可以将微批次提升至 24 个),从而加快训练速度。
其次,我们看到每次迭代大约为 3386 毫秒,而 llm.c 的迭代为 2750 毫秒,速度要快约 19%。
另外还有一些已知优势,例如 llm.c 包含启动反向传递的融合分类器等优化选项,据 Karpathy 所说,目前的 torch.compile 还做不到。但 Karpathy 表示,这样的性能差异可能是因为他的脚本没有充分调优,所以比较结果仅供大家看看、试试和作为调试思路的启发。
“我想表达的是,llm.c 的优化程度和速度水平已经相当不错,当然只是在 GPT-2/3 训练的特定场景之下。 ”Karpathy 说道。
最终模型
感兴趣的朋友可以参考以下几条链接:
main.log文件。
model_00032000.bin llm.c bin 模型文件
我已经将模型转换为 huggingface transformers GPT-2 并上传至这里: karpathy/gpt2_1558M_final2_hf。
模型导出
模型导出可以按如下方式进行,例如:
之后大家可以运行 Eleuther 评估工具,或者运行 huggingface 采样管线以获取模型样本:
另外大家也可以查看 dev/eval 以获取关于如何运行 Eleuther Evaluation Harness、HuggingFace Open LLM Leaderboard 的具体说明。
400B token 运行
Karpathy 还尝试用远超 33B token 的规模训练了 GPT-2。具体来讲,Karpathy 将-x 更改为 400000 以训练 420B token(规模甚至比 300B 的 GPT-3 还要大)。
结果显示,这套模型前半阶段运行得不错,但到大约 33 万步时开始出问题: 这套模型在 HellaSwag 上全面碾压了 GPT-2 及同等体量的 GPT-3(最高性能优势可达约 61%),但遗憾的是之后新模型开始不稳定并发生崩溃。
在此过程中虽然也出现过一些较小的峰值,Karpathy 将代码配置为当检测到瞬时不稳定时跳过更新(使用了-sl 5.0 -sg 5.0 标记),这有助于缓解并推迟问题的出现。但 Karpathy 承认,模型在初始化、激活范围和整体模型训练的稳定性方面还不够谨慎,对很多深层次问题也没有涉及。
这些问题会令模型逐渐变得不稳定,特别是对于规模较大、训练时间较长的模型更是如此。当然,我的实验仍在进行当中。如果大家对稳定模型训练有任何想法和建议,请在评论区中与我们分享。
常见问题解答
Q:我可以从 llm.c 中的模型里采样吗?
A:也不是不行,但效率很低而且效果不好。如果大家想要提示模型,推荐使用前文提供的 huggingface 版本。
Q:我能跟它聊天吗?
A:还不行,目前这个版本只完成了预训练,还没有接受过聊天微调。
Q:可以在 fp8 精度下训练吗?
A:不行,我们目前主要是在 bf16 下训练,但早期版本正在尝试当中。
Q:我的 GPU 不是英伟达的,可以运行 llm.c 吗?
A:不行,llm.c 目前仅支持 C/CUDA,但已经提供良好分支。比如 @anothonix 积极维护的 AMD 分叉(https://github.com/anthonix/llm.c)就相当不错。 GPT-2(124M)。这里再贴一篇关于在 llm.c 中训练 GPT-2(124M)模型的老帖,其中包含与 llm.c 运行相关的更多信息。124M 属于 GPT-2 迷你系列中的小体量模型,只有 124M 个参数,远低于本文讨论的 1558M 参数。
结束语
Karpathy 让我们看到了更多可能,但这似乎也难以意味着未来整个训练成本会下降。不久前,AI 初创公司 Anthropic 的首席执行官 Dario Amodei 就在采访中表示,目前 GPT-4o 这样的模型训练成本约为 1 亿美元,而目前其正在开发的 AI 大模型训练成本可能高达 10 亿美元。 他还预计,未来三年内,AI 大模型的训练成本将上升至 100 亿美元甚至 1000 亿美元。
参考链接:
评论