Andrej Karpathy分享介绍开发llm.c

本文是Andrej Karpathy在cude mode hackathon上做的分享,主要介绍开发llm.c这一纯C/CUDA大模型训练项目过程中遇到的问题及解决方案,同时也谈到了如何利用CUDA和GPU优化,将模型从CPU移植到GPU上运行及加速训练。

视频地址:https://www.bilibili.com/video/BV1Ght2ejEkQ/

项目地址:https://github.com/karpathy/llm.c

分享主要内容:llm.c训练和部署过程中遇到的问题,及通过底层细节和代码优化解决这些问题。使用Python进行训练,并将模型转换为C++代码以提高效率。利用CUDA编程和GPU优化,将模型移植到GPU上,及通过并行计算和分布式训练来加速训练过程。开发过程中遇到的一些具体技术问题,如内存管理、内核融合、精度优化等。并分享了如何通过社区开源来克服挑战,及如何将一个项目从概念阶段打造成能够高效训练大型模型的成熟项目。对软件开发和AI发展趋势的一些思考,如自定义应用程序的编译器可能如何改变代码编写和部署的形式等。

访谈全文内容如下。

非常高兴来到这里,这是我非常喜欢的一种活动。非常感谢邀请,也感谢cuda mode组织了这么精彩的活动。
接下来,我想和大家分享一下我最近在做的一些事情。我们正在尝试开发llm.c大模型训练项目,并且涉及到了一些C++编程,使得模型训练不依赖像pytorch等大型深度学习框架。这个项目是怎么开始的呢?大约一年前,我在我的YouTube频道上发布了一系列的视频,教大家如何进行机器学习模型的训练,比如LLM和GAN等。我的目标是让这些复杂的技术变得易于理解和操作。

在这个过程中,我发现了许多挑战。比如,当你有一个感知模型时,你需要考虑很多抽象的概念,比如如何将模型部署到不同的设备上,如何编译模型,以及如何使用DDP技术来加速训练。这些步骤可能会变得相当复杂,因为涉及到的抽象概念很多,而且顺序和具体操作可能会让人感到困惑。

我遇到的一个问题是,虽然我能够训练模型,但是在评估和推理阶段却遇到了困难。我尝试运行评估,却遇到了编译器错误,而在推理时也遇到了不同的问题。这让我感到非常沮丧,因为我并没有做什么特别复杂的事情,只是在尝试训练一个小型的GPT模型。
在尝试解决这些问题的过程中,我意识到我需要更深入地理解底层的计算细节。我决定不再依赖于现有的工具,而是要自己掌控命运,深入到代码层面去解决问题。这意味着我需要手动进行内核融合,优化性能,并且处理分布式训练的通信问题。
我们首先从Python代码开始,这是我们用来检查正确性的参考。我们有一个小型的transformer模型,它包含了几个模块,我们确保一切都是正确无误的。然后我们将这些模块转换成C++代码,这是我们用来实际运行和优化的代码。
这个过程中,我们没有使用任何现成的库,这意味着我们需要从零开始处理所有的细节。这包括内存管理、精度转换、内核融合和分布式通信。虽然这个过程非常具有挑战性,但也非常有趣,因为它让我能够更深入地理解整个模型的工作原理。
总的来说,这个项目让我有机会深入到机器学习的底层,去解决一些非常具体和复杂的问题。虽然过程中遇到了很多困难,但这也是学习和成长的机会。我期待能够和大家分享更多的进展和成果。

我想给大家展示一个如何将神经网络中的一个图层从Python移植到C语言的过程。首先,我们需要进行正向传播,这要求我编写一个正向传播的层。在PyTorch框架中,这相当于编写一个会调用底层核心功能的模块。然后我还要编写反向传播的代码,确保它与PyTorch的层规范相匹配。

接下来,我们尝试将这些代码移植到C语言中,这比想象中要复杂得多。在Python中,我们处理的是复杂的数据结构,而在C语言中,我们只使用基本的浮点数数组。我们尽量保持简单,不创建额外的数据结构或张量抽象。
移植完成后,我们需要在C代码中分配所有在训练过程中需要的内存。这包括数据、权重、偏差和激活项等。所有的内存分配都是在程序开始时一次性完成的,之后就是动态地进行数据处理和模型训练。

一旦我们将所有的层都转换为面向消费者的C语言代码,并且它们都与我们的参考实现相匹配,我们就可以开始将这些代码整合到一起。这个过程包括将所有的层连接起来,确保所有的数据流正确无误。

最后,我们将所有东西编译成可运行的程序。我们从一个包含GPT-2权重的二进制文件开始,这个文件非常基础,还包括了一些基本的工具,比如标记器。然后,我们编译并运行这个C语言程序,它将进行一些训练,并输出一些类似莎士比亚风格的文本。通过这个过程,我们验证了Python代码和C代码的一致性,确保一切都按预期工作。

我们正在讨论一个非常高效的C语言程序,它能够独立运行,不需要任何外部依赖,这意味着你可以立即编译并运行它。这个程序设计得非常紧凑,所有的内存都在一开始就分配好了,这使得程序的运行非常确定和高效。它甚至可以用来训练像GPT-2这样的大型模型,尽管这可能需要一些时间。这种类型的程序非常适合在资源受限的环境中使用,比如太空探测器,因为它们不需要额外的库或服务。

这个程序的编写过程有点像是一种艺术,它需要在凌晨醒来,头脑清醒的时候开始,然后在日出时分完成。这样的环境和时间安排,对于创作和编程来说,都是非常理想的。
现在,虽然我们有了这个高效的C语言程序,但我们还想让它运行得更快。为了实现这一点,我们计划将其转换为GPU代码,以便在多个GPU上并行运行。这就需要我们深入到CUDA编程的领域,开发出能够在GPU上运行的内核。

我们的目标是为每个层开发多个版本的内核,这些内核虽然实现方式不同,但都能提供相同的结果。随着时间的推移,我们不断优化这些内核,使它们运行得越来越快。这个过程可能从简单的复制粘贴C代码到CUDA内核开始,但随着我们对CUDA的深入理解,我们会开发出更复杂的内核,使用共享内存、优化数据传输等高级功能。

在这个项目中,我意识到学习CUDA编程并不是一件容易的事情。虽然有一些书籍和资源可以帮助入门,但要真正精通并开发出高效的CUDA代码,还需要大量的实践和经验。有些资源可能已经过时,或者没有涵盖我们实际需要的所有内容。幸运的是,网络上有一些优秀的博客文章和教程,它们提供了非常有价值的信息和技巧,帮助我们提高CUDA编程的技能。

总的来说,这个项目是一个不断学习和改进的过程,我们通过实践和探索,逐步提高了程序的性能和效率。

在开发过程中,我遇到了一些挑战,尤其是在使用CUDA编程方面。我读了一些书籍,但发现它们并没有提供足够的帮助。幸运的是,一群来自互联网的开发者,包括Eric和Alexa,他们都是深度学习和CUDA编程的专家,开始为项目贡献代码。他们优化并编写了许多核心的CUDA内核,这对项目来说是一个巨大的帮助,我也从中学到了很多。

随着时间的推移,我们吸引了更多的贡献者,总共有60个人为这个项目做出了贡献。我们非常感谢Lambda,她赞助了所有的文档工作,使得我们能够集中精力优化内核。我特别喜欢开源社区的这一点,人们从互联网上自发地加入,为项目做出贡献。
我们成功地将所有的神经网络层转换为CUDA代码,现在可以在单个GPU上训练模型,这非常令人兴奋。之后,我们开始进行更多的优化工作。

首先,我们尽量避免在32位浮点数上使用map操作,转而使用更高效的数据类型。其次,我们决定不自己编写Flash Attention代码,因为Megatron已经有了非常好的实现,所以我们直接使用了他们的代码。
为了进一步提高速度,我们开始关注张量的精度,考虑哪些应该使用32位浮点数,哪些可以使用更低的精度。我们还实现了自动转换,以确保数据类型的正确使用。

随着时间的推移,我们实现了更多的优化,包括内核融合、重新计算设置以及最小化反向传播过程中所需的内存量。我们还发现,使用特定的数据结构可以迫使编译器使用更高效的加载和存储指令。
在优化过程中,我们也遇到了一些挑战,比如在并行计算中处理复杂的依赖关系和竞争条件。有时候,我们会简化代码,以避免这些问题。

最后,我们的目标是使用多个GPU来进一步提升性能,这就是我们引入NVIDIA的多GPU技术的原因。通过这些努力,我们希望能够最大限度地提高模型的训练效率和速度。

在开发过程中,我们进行了一系列的优化,包括在多个工作器之间执行分布式计算,以及优化图表的状态。我们采用了一种浮动的优化器状态,这有助于减少每个视图对内存的需求,从而提高效率。

随着我们处理的数据量增加到多个GB,我们需要在多台机器上运行程序,并确保它们能够同步和相互发现。为此,我们实施了一系列的同步机制。

这些工作最终使我们能够转向使用GPT2,并在完成所有这些工作后,能够复制它。我们甚至发表了一篇论文,讨论了如何训练一个16亿参数的GPT2模型,这是2019年左右的最先进水平。你可以在大约24小时内,使用单个H100 GPU完成训练,成本大约是600美元。

我们的方法非常依赖于C语言,不需要Python。我们只需要两个DNN库,这是最重的依赖项,但C语言的DNN库是可选的。如果你想要完全控制你的环境,你可以自己编写代码,但之后,它就只是一堆C代码。你编译并运行它,不需要任何其他东西。所以,对于安装的Conda环境,没有什么特别复杂的地方。然后你编译代码并运行它,它开始执行,你等待24小时,它就会打印出一些诊断信息,比如在一个节点上近50%的内存使用率,这是相当不错的。

你会得到非常漂亮的效果图,并且你在背后击败了GPT2,这基本上只是表明优化工作做得很好,没有出现不可预测的数值问题。我们仍然可以将其与PyTorch训练进行比较,因为我们在并行实现了所有这些内容的PyTorch训练版本。

因此,你几乎可以在PyTorch中运行等效的训练组,进而进行对比。我们使用的模型内存减少了30%,训练速度加快了20%,这是非常真实的改进。我不知道我是否完全超级优化了PyTorch的实现,我尽了自己最大的努力。我认为特别是在PyTorch中训练GPT2,如果你想训练它,你会遇到很多麻烦,你必须经常修改你的代码。但是对于GPT2的训练,我们在完成所有这些工作之后,编译和运行速度也更快,这是一个漂亮的扭矩编译器。但同样需要等待相当长时间,而且来回优化也不一定得到正确的结果。所以这也是我个人通常不喜欢合作的事情。

我们正在增加对多种数据类型的支持,同时会对LLam3.1进行训练,而且将支持FP8。所以每个人都在为此努力,并且FP8支持带来了一个很大的进步,并且在github上有很多值得注意的fork可供参考。如ms-amp及C++ CUDA就不错。

在开始这个项目的时候,我有一个明确的目标,那就是探索PyTorch在自动编译器中的作用,它有点像软件2.0时代的GCC编译器。而llm.c则像是在写汇编语言,我们需要手动完成所有工作。我们在短时间内将llm.c编写成多人协作的项目,并在特定的GPT-2模型训练设置中,实现了比PyTorch更快的性能。这个实践证明了,通过手动优化,我们可以达到比自动化工具更好的效果。

但这个过程需要花费数月的时间和多人的努力。如果随着时间的推移,我们在编码方面变得越来越熟练,那么我们可以期待llm.c实际上可以为任何自定义应用程序做到这一点。随着时间的推移,llm.c可以作为任何自定义应用程序的编译器,完成所有llm.c的工作,输出为特定应用程序编译运行的二进制文件。

这让我思考,我们是否真的需要依赖Python和其他工具。它们可能只是我们人类有限的知识、智力和注意力的辅助工具。实际上,难道你不想为所有事情编写自定义代码吗?也许在未来,我们可以使用智能系统来帮助我们编写这些代码。例如,如果我们给智能系统一些提示,让它们编写GPT2和C代码,它们可能不会直接给出完美的结果,但如果我们将这些智能系统放在llm.c的上下文中,它们更有可能给出有用的结果。少量的学习可能对llm.c非常有帮助,所以我认为所有的文档和示例代码对于转向llm.c都非常有用。

因此,智能系统可能会开始编写我们所有的定制应用程序。虽然这种情况可能不会立即发生,但它是有可能的。我认为这可能会导致软件开发方式发生很大的变化。对我来说,所有这些都是一种探索,探索这是否可能,因为如果可能,那么也许这就是将要发生的事情。

posted @ 2024-09-22 22:08  立体风  阅读(309)  评论(0)    收藏  举报