karpathy/llm.c
GitHub: karpathy/llm.c
用纯 C/CUDA 实现的极简 LLM 预训练框架,无需 PyTorch 等框架依赖即可复现 GPT-2 训练。
Stars: 30222 | Forks: 3645
# llm.c
使用简单、纯粹的 C/CUDA 实现 LLMs,无需 245MB 的 PyTorch 或 107MB 的 cPython。目前的重点是预训练,特别是复现 [GPT-2](https://github.com/openai/gpt-2) 和 [GPT-3](https://arxiv.org/abs/2005.14165) 系列,并在 [train_gpt2.py](train_gpt2.py) 中提供了一个平行的 PyTorch 参考实现。你会发现这个文件就是稍作修改的 [nanoGPT](https://github.com/karpathy/nanoGPT),这是我之前的一个项目。目前,llm.c 比 PyTorch Nightly 稍快(约 7%)。除了 [train_gpt2.cu](train_gpt2.cu) 中最前沿的主线代码外,我们还在单文件 [train_gpt2.c](train_gpt2.c) 中提供了一个约 1,000 行整洁代码的简单 CPU fp32 参考实现。我希望这个仓库仅维护 C 和 CUDA 代码。非常欢迎将其移植到其他语言或仓库,但应在单独的仓库中进行,我很乐意在下面的“值得关注的分支”部分链接到它们。开发者协调工作在 [讨论](https://github.com/karpathy/llm.c/discussions) 和 Discord 上进行,包括 [Zero to Hero](https://discord.gg/3zy8kqD9Cp) 频道的 `#llmc` 频道,或 [GPU MODE](https://discord.gg/gpumode) Discord 上的 `#llmdotc` 频道。
## 快速开始
如今了解 llm.c 仓库的最佳入门方式是复现 GPT-2 (124M) 模型。[讨论 #481](https://github.com/karpathy/llm.c/discussions/481) 详细介绍了这一过程。我们可以在 llm.c 和并行的 PyTorch 实现中复现 GPT-2 和 GPT-3 系列的其他模型。请查看 [脚本 README](scripts/README.md)。
调试提示:当你运行 `make` 命令构建二进制文件时,可以通过将 `-O3` 替换为 `-g` 进行修改,这样你就可以在你喜欢的 IDE(如 vscode)中单步调试代码。
## 快速开始(单 GPU,仅限 fp32)
如果你不打算在多个节点上进行训练,对混合精度不感兴趣,并且想学习 CUDA,那么 fp32(旧版)文件可能正合你意。这些文件是在 llm.c 早期历史中作为“检查点”被保留下来的,并冻结在了那个时刻。它们更简单、更易于移植,并且可能更容易理解。按如下方式运行单 GPU、fp32 代码:
```
chmod u+x ./dev/download_starter_pack.sh
./dev/download_starter_pack.sh
make train_gpt2fp32cu
./train_gpt2fp32cu
```
`download_starter_pack.sh` 脚本是一种快速且简便的入门方式,它会下载一系列 .bin 文件来帮助你起步。这些文件包含:1) 以 fp32 和 bfloat16 保存的 GPT-2 124M 模型,2) 用于单元测试的“调试状态”(一小批数据,以及目标激活值和梯度),3) GPT-2 tokenizer,以及 3) 已 token 化的 [tinyshakespeare](https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt) 数据集。或者,如果你不想运行该 .sh 脚本,也可以按如下方式手动重新生成这些产物:
```
pip install -r requirements.txt
python dev/data/tinyshakespeare.py
python train_gpt2.py
```
## 快速开始(CPU)
本节是“我穷得连一个 GPU 都没有”专区。你仍然可以观看 llm.c 进行训练!但你走不了太远。就像上面的 fp32 版本一样,CPU 版本是 llm.c 历史中更早的一个检查点,那时它仅仅是 C 语言中的一个简单参考实现。例如,作为示例,你可以微调 GPT-2 small (124M) 以输出类似莎士比亚的文本,而不是从头开始训练:
```
chmod u+x ./dev/download_starter_pack.sh
./dev/download_starter_pack.sh
make train_gpt2
OMP_NUM_THREADS=8 ./train_gpt2
```
如果你不想运行入门包脚本,那么正如上一节所述,你可以通过运行 `python dev/data/tinyshakespeare.py`,然后再运行 `python train_gpt2.py`,来复现完全相同的 .bin 文件和产物。
上面的命令行 (1) 会下载已经 token 化的 [tinyshakespeare](https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt) 数据集并下载 GPT-2 (124M) 权重,(3) 在 C 语言中基于这些权重进行初始化,并使用 AdamW(batch size 设为 4,context 仅为 64)在 tinyshakespeare 上训练 40 步,接着评估验证集 loss,并采样生成一些文本。说实话,除非你拥有一颗性能强劲的 CPU(并且能在启动命令中调大 OMP 线程数),否则在 CPU 上训练 LLMs 不会有太大进展,但这可能是一个很好的演示/参考。以下是在我的 MacBook Pro(Apple Silicon M3 Max)上的输出结果:
```
[GPT-2]
max_seq_len: 1024
vocab_size: 50257
num_layers: 12
num_heads: 12
channels: 768
num_parameters: 124439808
train dataset num_batches: 1192
val dataset num_batches: 128
num_activations: 73323776
val loss 5.252026
step 0: train loss 5.356189 (took 1452.121000 ms)
step 1: train loss 4.301069 (took 1288.673000 ms)
step 2: train loss 4.623322 (took 1369.394000 ms)
step 3: train loss 4.600470 (took 1290.761000 ms)
... (trunctated) ...
step 39: train loss 3.970751 (took 1323.779000 ms)
val loss 4.107781
generating:
---
Come Running Away,
Greater conquer
With the Imperial blood
the heaviest host of the gods
into this wondrous world beyond.
I will not back thee, for how sweet after birth
Netflix against repounder,
will not
flourish against the earlocks of
Allay
---
```
## 数据集
`/dev/data/(dataset).py` 内部的数据文件负责下载、token 化数据,并将 token 保存为 .bin 文件,以便在 C 语言中轻松读取。例如,当你运行以下命令时:
```
python dev/data/tinyshakespeare.py
```
我们会下载并对 [tinyshakespeare](https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt) 数据集进行 token 化。此过程的输出如下所示:
```
writing 32,768 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_val.bin
writing 305,260 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_train.bin
```
.bin 文件包含一个简短的文件头(1024 字节),随后是 uint16 格式的 token 流,表示使用 GPT-2 tokenizer 编码的 token id。更多数据集可在 `/dev/data` 中找到。
## 测试
我还附加了一个简单的单元测试,以确保我们的 C 代码与 PyTorch 代码的计算结果一致。以 CPU 为例,使用以下命令编译并运行:
```
make test_gpt2
./test_gpt2
```
这将会加载由 `train_gpt2.py` 生成的 `gpt2_124M_debug_state.bin` 文件,执行一次前向传播,将 logits 和 loss 与 PyTorch 参考实现进行比较,然后使用 Adam 进行 10 次训练迭代,并确保 loss 与 PyTorch 匹配。要测试 GPU 版本,我们运行:
```
# fp32 测试(不支持 cudnn)
make test_gpt2cu PRECISION=FP32 && ./test_gpt2cu
# 混合精度 cudnn 测试
make test_gpt2cu USE_CUDNN=1 && ./test_gpt2cu
```
这将同时测试 fp32 路径和混合精度路径。测试应该会通过并打印 `overall okay: 1`。
## 教程
我在这里附带了一个非常简短的教程:[doc/layernorm/layernorm.md](doc/layernorm/layernorm.md)。这是一个简单的、循序渐进的指南,教你如何实现 GPT-2 模型中的单层,即 layernorm 层。这是理解 C 语言中各层如何实现的绝佳起点。
**flash attention**。截至 2024 年 5 月 1 日,我们使用了来自 cuDNN 的 Flash Attention。因为 cuDNN 会将编译时间从几秒钟膨胀到约一分钟,而且这个代码路径目前还非常新,所以默认是禁用的。你可以像这样编译来启用它:
```
make train_gpt2cu USE_CUDNN=1
```
这将尝试使用 cudnn 进行编译并运行。你的系统上必须安装了 cuDNN。使用 apt-get 的 [cuDNN 安装说明](https://developer.nvidia.com/cudnn) 将会获取默认的 cuDNN 包。对于最小化安装来说,cuDNN 的开发包就足够了,例如,在适用于 CUDA 12.x 的 Ubuntu 22.04 上:
```
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get -y install libcudnn9-dev-cuda-12
```
在此基础之上,你还需要 [cuDNN frontend](https://github.com/NVIDIA/cudnn-frontend/tree/main),但这只是一些头文件。只需将该仓库克隆到你的磁盘上即可。Makefile 目前会在你的主目录或当前目录中寻找它。如果你把它放在了其他地方,请在 `make` 命令行中添加 `CUDNN_FRONTEND_PATH=/path/to/your/cudnn-frontend/include`。
## 多 GPU 训练
确保你安装了 MPI 和 NCCL,例如在 Linux 上:
```
sudo apt install openmpi-bin openmpi-doc libopenmpi-dev
```
至于 NCCL,请按照[官方网站](https://developer.nvidia.com/nccl/nccl-download)上的说明进行操作(例如网络安装程序)。
然后执行:
```
make train_gpt2cu
mpirun -np ./train_gpt2cu
```
或者直接运行 `./scripts/` 下的其中一个脚本。
## 多节点训练
确保你已按照[多 GPU](#multi-gpu-training)部分中的说明安装了 `NCCL`。
目前我们支持 3 种方式来进行多节点训练:
1. 使用 OpenMPI 交换 nccl id 并初始化 NCCL。详情请参见 `./scripts/multi_node/run_gpt2_124M_mpi.sh` 脚本。
2. 使用共享文件系统初始化 NCCL。详情请参见 `./scripts/multi_node/run_gpt2_124M_fs.sbatch` 脚本。
3. 使用 TCP sockets 初始化 NCCL。详情请参见 `./scripts/multi_node/run_gpt2_124M_tcp.sbatch` 脚本。
注意:
* 如果你在 slurm 环境中运行,并且你的 slurm 不支持 PMIx(鉴于 `slurm-wlm` 已放弃对 PMIx 的支持,我们假设这将是一种常见情况),你将不得不使用共享文件系统 (2) 或 TCP (3) 方法。要测试你的 slurm 是否支持 PMIx,请运行:`srun --mpi=list`,看看输出中是否包含 `pmix`。
* 如果你没有配置 slurm,你可以使用 `mpirun` - 即 MPI (1) 来启动多节点运行。
这 3 种方法没有哪一种是绝对优越的,我们只是为你提供选项,以便你能在特定的环境中运行。
## 实验 / 参数搜索
作为在配备 4 个 GPU 的机器上对 TinyStories 进行学习率参数搜索的示例流程。运行 shell 脚本 `sweep.sh`(当然,前提是你已经执行了 `chmod u+x sweep.sh`):
```
#!/bin/bash
learning_rates=(3e-5 1e-4 3e-4 1e-3)
for i in {0..3}; do
export CUDA_VISIBLE_DEVICES=$i
screen -dmS "tr$i" bash -c "./train_gpt2cu -i data/TinyStories -v 250 -s 250 -g 144 -l ${learning_rates[$i]} -o stories$i.log"
done
# 你可以使用以下命令关闭它们
# screen -ls | grep -E "tr[0-3]" | cut -d. -f1 | xargs -I {} screen -X -S {} quit
```
这个示例会开启 4 个 screen 会话,并运行带有不同学习率 (LR) 的四个命令。这将生成记录了所有 loss 的 `stories$i.log` 日志文件,你可以根据需要使用 Python 对其进行绘图。在 [dev/vislog.ipynb](dev/vislog.ipynb) 中有一个关于如何解析和绘制这些日志文件的简单示例。
## 仓库
关于我希望这个仓库成为什么样的项目,再说几句:
首先,我希望 `llm.c` 成为一个教育平台。例如,我们的 `dev/cuda` 文件夹存放着一个 kernel 库,包含了所有层对应的手写且注释非常详尽的实现,从非常基础的 kernel 到更复杂、更快速的 kernel 应有尽有。如果你有带各种不同权衡的全新 kernel,非常欢迎在这里贡献。
话虽如此,我也希望 `llm.c` 能有极致的速度,甚至在实际训练网络时也能派上用场。例如,首先,我们应该能够复现大型 GPT-2 (1.6B) 的训练过程。这就要求我们整合所有可用的最快 kernel,包括使用诸如 cuBLAS、cuBLASLt、CUTLASS、cuDNN 等库。我也认为这样做具有教育意义,它可以建立一个专家级的上限标准和衡量单位,例如,你可以说你手写的 kernel 速度达到了 cuBLAS 的 80% 等等。随后,你可以选择进行一次极速运行,或者选择“拖放”任何你想使用的手写 kernel,并用它们来运行。
然而,作为一个限制条件,我希望将根目录下的 `llm.c` 主线代码保持简单易读。如果有一个 PR 能将性能提升 2%,但代价是引入 500 行复杂的 C 代码,甚至还带有一个非主流的第三方依赖,我可能会拒绝它,因为得不偿失。举个具体的例子 - 在根目录训练循环中将 matmul 的默认实现改为使用 cuBLAS 是理所当然的:它让主线代码快得多,仅是一行易于解读的代码,而且是一个非常常见的依赖。除此之外,我们可以在 `dev/cuda` 中拥有能够与 cuBLAS 竞争的手写实现。
最后,我对项目根目录(包含项目的主要/默认文件)中的代码复杂度会更加敏感。相比之下,`dev/` 文件夹更像是一个工作区,供我们开发 kernel 库或类,并分享有用的、相关的或具有教育意义的代码,其中部分代码在局部范围内具有一定的复杂性也是可以接受的。
## 值得关注的分支
- AMD 支持
- 由 @[anthonix](https://github.com/anthonix) 开发的 [llm.c](https://github.com/anthonix/llm.c):支持 AMD 设备,如 7900 XTX
- C#
- 由 @[azret](https://github.com/azret) 开发的 [llm.cs](https://github.com/azret/llm.cs):本项目的 C# 移植版
- 由 @[nietras](https://github.com/nietras) 开发的 [Llm.cs](https://github.com/nietras/Llm.cs):本项目的 C# 移植版,专注于在任何平台上轻松上手。克隆并运行 ✅
- CUDA C++
- 由 @[gevtushenko](https://github.com/gevtushenko) 开发的 [llm.cpp](https://github.com/gevtushenko/llm.c):使用 [CUDA C++ Core Libraries](https://github.com/NVIDIA/cccl) 的本项目移植版
- [GPU MODE Discord Server](https://discord.gg/cudamode) 中的[这节讲座](https://www.youtube.com/watch?v=WiB_3Csfj_Q)涵盖了该分支的介绍
- C++/CUDA
- 由 @[zhangpiu](https://github.com/zhangpiu) 开发的 [llm.cpp](https://github.com/zhangpiu/llm.cpp/tree/master/llmcpp):使用 [Eigen](https://gitlab.com/libeigen/eigen) 的本项目移植版,支持 CPU/CUDA。
- WebGPU C++
- 由 @[austinvhuang](https://github.com/austinvhuang) 开发的 [gpu.cpp](https://github.com/AnswerDotAI/gpu.cpp):一个使用原生 WebGPU 的 C++ 轻量级 GPU 计算库。旨在成为一个通用库,同时也致力于将 llm 的 kernels 移植到 WGSL。
- C++
- 由 @[GaoYusong](https://github.com/GaoYusong) 开发的 [llm.cpp](https://github.com/GaoYusong/llm.cpp):本项目移植版,主打 C++ 单头文件 [tinytorch.hpp](https://github.com/GaoYusong/llm.cpp/blob/main/tinytorch.hpp) 库
- Go
- 由 @[joshcarp](https://github.com/joshcarp) 开发的 [llm.go](https://github.com/joshcarp/llm.go):本项目的 Go 移植版
- Java
- 由 @[harryjackson](https://github.com/harryjackson) 开发的 [llm.java](https://github.com/harryjackson/llm.java):本项目的 Java 移植版
- Metal
- 由 @[regrettable-username](https://github.com/regrettable-username) 开发的 [llm.metal](https://github.com/regrettable-username/llm.metal):使用简单、原始的 C/Metal Shading Language 进行 LLM 训练
- Mojo
- 由 @[dorjeduck](https://github.com/dorjeduck) 开发的 [llm.🔥](https://github.com/dorjeduck/llm.mojo):本项目的 Mojo 移植版
- OpenCL
- 由 @[krrishnarraj](https://github.com/krrishnarraj) 开发的 [llm.c](https://github.com/krrishnarraj/llm.c):本项目的 OpenCL 移植版
- Rust
- 由 @[Yijun Yu](https://github.com/yijunyu) 开发的 [llm.rs](https://github.com/yijunyu/llm.rs):旨在实现同等性能的 Rust 重写版
- 由 @[ToJen](https://github.com/ToJen) 开发的 [llm.rs](https://github.com/ToJen/llm.rs):本项目的 Rust 移植版
- Swift
- 由 @[otabuzzman](https://github.com/otabuzzman) 开发的 [llm.swift](https://github.com/otabuzzman/llm.swift):本项目的 Swift 移植版
- Zig
- 由 @[saimirbaci](https://github.com/Saimirbaci) 开发的 [llm.zig](https://github.com/Saimirbaci/llm.zig):本项目的 Zig 移植版
- Habana Gaudi2
- 由 @[abhilash1910](https://github.com/abhilash1910) 开发的 [llm.tpc](https://github.com/abhilash1910/llm.tpc):本项目的 Habana Gaudi2 移植版
- Nim
- 由 @[Vindaar](https://github.com/Vindaar) 开发的 [llm.nim](https://github.com/Vindaar/llm.nim):本项目的 Nim 移植版
## 讨论
组织开发的方式:
- 遇到仓库的具体问题?请使用 [Issues](https://github.com/karpathy/llm.c/issues)。
- 有代码想要贡献?请提交一个 [PR](https://github.com/karpathy/llm.c/pulls)
- 想要讨论仓库、提问等?请查看 [讨论区](https://github.com/karpathy/llm.c/discussions)。
- 想要更快的沟通?我在我的 [Zero to Hero Discord 频道](https://discord.gg/3zy8kqD9Cp) 创建了一个新的 `#llmc` 频道。
## 许可证
MIT
标签:CUDA, DLL 劫持, GPT-2, Vectored Exception Handling, 人工智能, 凭据扫描, 大语言模型, 模型训练, 用户模式Hook绕过, 逆向工具, 高性能计算