扫码打开虎嗅APP

No.3

2024-04-11

子非AI 拥抱AI

llm.c:Andrej Karpathy带你从零复现LLM

主理人:
Karpathy 最近发布了一个名为llm.c的项目。这个项目允许用户在没有PyTorch和cPython的情况下,仅使用纯C语言来训练大型语言模型(LLM)。llm.c项目的特点是代码量少(大约1000行清晰的代码),编译和运行速度快,且能够精确匹配PyTorch的参考实现。

本文来自微信公众号:子非AI(ID:you_are_not_ai),作者:非子爱,原文标题:《llm.c:Andrej Karpathy带你从零复现LLM》

 

 

Andrej Karpathy是知名的计算机科学家,专注于深度学习和计算机视觉。他曾是特斯拉人工智能和Autopilot Vision的总监,后于2022年离开。Karpathy在2023年重返OpenAI,但在2024年2月又离开了OpenAI。

 

Karpathy 最近发布了一个名为llm.c的项目。这个项目允许用户在没有PyTorch和cPython的情况下,仅使用纯C语言来训练大型语言模型(LLM)。llm.c项目的特点是代码量少(大约1000行清晰的代码),编译和运行速度快,且能够精确匹配PyTorch的参考实现。

 

Karpathy选择GPT-2作为起点,因为它是LLM的鼻祖,是第一次将LLM技术栈以现代形式组合在一起,并且提供了模型权重。他的这项工作不仅减少了对庞大依赖库的需求,而且为LLM的训练提供了一种更轻量化和高效的方法¹。这对于希望在资源受限的环境中进行LLM训练的开发者和研究者来说,是一个非常有价值的贡献。

 

 

 

 

极简主义编程:化繁为简

 

在深度学习框架铺天盖地的今天,一个用约1000行C代码就能完整复现PyTorch版GPT-2模型的项目,无疑让人大开眼界。这就是llm.c项目带给我们的震撼 —— 用最精简、最底层的编程方式,还原了语言模型的本质。

 

傻瓜式复现GPT-2:1000行代码完全一致

 

llm.c项目用C语言重现了GPT-2模型的所有核心组件,包括多头注意力层、前馈网络、LayerNorm等,Forward和Backward都做到了与PyTorch实现完全相同。更难能可贵的是,整个项目的代码量只有1000行左右,堪称语言模型领域的"FromScratch之最"。  

 

避免庞大框架和依赖库的复杂性

 

传统做法需要引入数百MB的框架库才能运行,而llm.c项目仅靠C语言标准库,就独立完成了所有的数学计算操作。这不仅让项目变得无比精简,更摆脱了庞大框架内外层层依赖导致的复杂性,使语言模型的底层原理一目了然,易于理解和修改。

 

直观理解模型底层细节

 

相较于使用高级框架,底层实现的优势就在于可以充分掌控每一个细节。作者手把手地推导出各种算子的数学表达式,并对照着写下对应的C代码,每一行几乎都与模型方程一一对应。这种最直白的编程方式,或许并不高效,但却让我们能够真正窥见模型的本质。

 

手工精雕细琢,性能彪悍

 

不过,底层实现的缺陷也是显而易见的:相较于深度优化的框架库,纯C代码在性能上难免逊色许多。不过,通过手工优化,llm.c项目仍能释放出C语言潜能,有望突破性能瓶颈。

 

CPU版本:SIMD/OpenMP并行加速

 

作者在llm.c的CPU版本中借助了SIMD和OpenMP,极大地提升了并行计算能力。如利用AVX2和ARM的NEON扩展指令集实现矢量化,充分发挥现代CPU所有可用的SIMD计算资源。

 

CUDA版本:GPU进一步提速  

 

对于拥有强大并行计算能力的GPU,作者将借助CUDA在GPU上实现高度优化的版本,以进一步提升性能表现。我们知道,大型深度学习框架诸如PyTorch在GPU加速这一领域下足了功夫。

 

即将实现与PyTorch性能旗鼓相当

 

作者信心十足,经过在CPU和GPU上的多方位手工优化,llm.c项目的性能终将能够接近,甚至超越PyTorch等框架库的水平。这对于一个只有1000行代码的精简项目来说,实在是一个了不起的成就。

 

支持新型架构,更上层楼

 

除了针对经典的GPT-2等架构,llm.c项目还将进军更先进的语言模型,以展现其通用性和延展性。

 

从GPT-2架构出发打好基础

 

GPT-2架构因其简单而被选为llm.c项目的起步,作为复现经典架构的范例,也为实现更先进架构打下了坚实基础。

 

拥抱Llama2/Gemma等新型架构

 

接下来,llm.c将把视野转向Llama2、Gemma等新兴架构。这些架构通过诸如RMSNorm替代LayerNorm等改进,在保持性能的同时大幅简化了模型结构,为底层实现提供了重大便利。

 

保留简洁与高度优化两个版本并行发展  

 

有意思的是,llm.c将同时保留简洁的参考实现和高度优化的版本,并行推进这两条路线。前者侧重对算法原理的展现,后者将性能发挥到极致,二者相辅相成。

 

核心代码解读:train_gpt2.c

 

train_gpt2.c实现了一个基本的 GPT-2 模型训练过程,包含了模型定义、数据加载、前向传播、反向传播、参数更新等功能。以下是代码的主要部分解读。

 

1. 层级操作函数:encoder_forward 和 encoder_backward: 处理输入数据的编码,包括词嵌入和位置编码。layernorm_forward 和 layernorm_backward: 层归一化操作,用于稳定训练过程。matmul_forward 和 matmul_backward: 矩阵乘法操作,用于线性变换。attention_forward 和 attention_backward: 注意力机制的实现,包括计算注意力权重和加权求和。gelu_forward 和 gelu_backward: GELU 激活函数的实现。residual_forward 和 residual_backward: 残差连接的实现。softmax_forward: Softmax 函数的实现,用于计算概率分布。crossentropy_forward 和 crossentropy_softmax_backward: 交叉熵损失函数的实现,用于评估模型预测结果。

2. GPT-2 模型定义:ParameterTensors: 定义了模型的参数张量,包括词嵌入矩阵、位置编码矩阵、层归一化参数、注意力机制参数、全连接层参数等。ActivationTensors: 定义了模型的激活张量,用于存储中间计算结果。GPT2Config: 定义了模型的配置参数,包括最大序列长度、词表大小、层数、注意力头数、通道数等。GPT2: 定义了 GPT-2 模型结构体,包含了模型配置、参数张量、激活张量、梯度张量、优化器状态等信息。

3. 模型构建和训练:gpt2_build_from_checkpoint: 从检查点文件中加载模型参数,并初始化模型结构体。gpt2_forward: 执行模型的前向传播,计算模型输出和损失函数值。gpt2_zero_grad: 将模型参数和激活的梯度清零。gpt2_backward: 执行模型的反向传播,计算模型参数和激活的梯度。gpt2_update: 使用 AdamW 优化器更新模型参数。

4. 数据加载:DataLoader: 定义了数据加载器结构体,用于从文件中读取训练数据。dataloader_init: 初始化数据加载器,打开文件并分配内存。dataloader_reset: 重置数据加载器,回到文件开头。dataloader_next_batch: 读取下一批训练数据。

5. 采样生成:sample_mult: 从概率分布中采样一个索引。主循环中使用采样函数生成文本,并输出生成的序列。

 

train_gpt2.c展示了 GPT-2 模型训练的基本流程,包括模型定义、数据加载、前向传播、反向传播、参数更新等步骤。它是一个简洁的参考实现,方便理解 GPT-2 模型的训练过程。

 

前景展望:语言模型民主化

 

如此一个极简主义编程的探索,不仅是对语言模型底层实现的一种解构和重塑,更为语言模型的民主化带来了全新契机。

 

简单实现方式有利理解和控制

 

相较于封闭复杂的框架,直接翻阅1000行核心代码无疑更有利于理解和掌控语言模型本质。对研究者而言,这种从零构建的方式开启了更直观的视角;对从业人员而言,也无需深陷复杂依赖中,可借此全面掌控模型核心。

 

部署在资源受限环境的可行路径  

 

除此之外,这种精简高效的实现方式还为语言模型在物联网、移动终端等资源受限环境的部署铺平了道路。不再需要庞大的框架作为运行时依赖,单独的可执行文件就可以轻松投入使用,大大提升了灵活性。

 

期待更多极简主义编程的探索

 

总的来说,llm.c这种极简主义编程范式虽然看似"返璞归真",但却从底层逐步重构了复杂的语言模型,让人耳目一新。我们有理由相信,这种以C/CUDA等底层语言编写的极简实现,必将给语言模型的发展带来新的启发。

 

 

 

本文来自微信公众号:子非AI(ID:you_are_not_ai),作者:非子爱