浅显易懂地介绍 llm.c [译]

作者:

Andrej Karpathy

当我们训练像 ChatGPT 这样的大语言模型时,需要处理大量的代码和复杂过程。

以 PyTorch 深度学习库为例,它因应用广泛而显得复杂。PyTorch 设计了一种称为张量(Tensor)的结构,用于组织和处理数据数组,这些数据是神经网络的基础,掌握网络的参数和活动状态。此外,它还有一个自动微分系统,支持反向传播算法,这是训练神经网络的核心技术。整个 PyTorch 包含超过三百三十万行代码,散布在一万多个文件中。

PyTorch 是用 Python 这种高级编程语言编写的,这意味着你需要通过 Python 解释器,把训练指令转换为计算机可以直接执行的底层指令。举个例子,负责这种转换的 cPython 项目,代码量也超过两百四十万行,分布在四千多个文件中。

我正在消除所有复杂元素,将大语言模型 (LLM) 的训练过程剥离到最基本的层面,直接用极为基础的 C 语言与计算机进行交流,且不依赖任何其他库。此外,更底层的抽象仅是汇编代码本身。相比之下,像 GPT-2 这样的大语言模型的训练实际上只需约 1000 行的 C 代码,集中在一个文件内,这一点可能会令人感到意外。我通过直接在 C 中实施 GPT-2 的神经网络训练算法来实现这种简化。这一过程十分艰难,因为你需要深入理解训练算法,并能够推导出所有层的前向和反向传播过程,还必须非常小心地处理所有的数组索引计算,因为 PyTorch 中的张量抽象并不可用。尽管布局非常脆弱,一旦设置成功并通过与 PyTorch 的核对确认无误,你最终得到的将是一件既简单又小巧,我认为非常精美的作品。

那么,为什么这种方法不被广泛采用呢?

第一:这样做会牺牲很大的灵活性。如果需要调整神经网络,使用 PyTorch 可能只需修改一行代码;而在 llm.c 文件中,同样的修改可能涉及更多代码,难度更大,且需要更多的专业技能。比如说,如果加入一个新的运算,你可能需要进行一些微积分运算,编写该运算的前向和反向传播,并确保其数学上的正确性。

第二:你初期将会牺牲一些运行速度。毕竟,天下没有免费的午餐 - 不要期望仅用 1000 行代码就能达到顶尖的速度。PyTorch 在后台进行大量工作,确保神经网络运行高效。所有的 Tensor 操作都极为谨慎地调用了最有效的 CUDA 核函数,并且有类似 torch.compile 的工具进一步优化了你的神经网络,让其在你的计算机上运行得更加高效。理论上,llm.c 也能调用所有这些核函数,并直接操作。但这需要更多精力和关注,如同之前提到的,如果你对神经网络或使用的计算机做了改变,可能需要调用不同的核函数,调整参数,手动做更多的修改。

总之,llm.c 是直接实现了 GPT-2 训练的程序,其实现方式出人意料的简洁。它不支持其他任何神经网络,仅限于 GPT-2,更改网络需具备相应的专业知识。幸运的是,当前最先进的大语言模型与 GPT-2 的差异并不大,因此这并不是一个严格的限制。llm.c 还需进一步调整和优化,但原则上它有望接近甚至超越 PyTorch 的性能,而代码量不会比现状多很多,适用于大多数现代大语言模型。

为什么我要投入这项工作呢?因为这非常有趣。此外,这还是一个教育过程,因为仅用简单的 C 语言编写的 1000 行代码就足够了。这些代码仅包含一些数字数组及其元素上的简单数学运算,如加法(+)和乘法(*)。而且,随着目前正在进行的一些额外工作,这项工作最终可能证实有实际的应用价值。