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绕过, 逆向工具, 高性能计算