llama2.c
llama2.c 是一个极简的开源项目,旨在用纯 C 语言实现 Llama 2 大模型的推理功能。它解决了传统大模型部署依赖复杂、环境配置繁琐的痛点,将原本需要庞大框架支持的推理过程浓缩为一个仅约 700 行代码的单文件(run.c),无需任何外部依赖即可编译运行。
该项目特别适合开发者、研究人员以及希望深入理解大模型底层原理的教育者使用。通过它,用户可以在资源受限的环境甚至嵌入式设备上轻松运行小型 Llama 2 模型,快速验证想法或进行教学演示。虽然目前主要支持浮点精度(fp32)的小型模型(如 15M 或 42M 参数版本),但其架构与 Meta 官方的 Llama 2 完全一致,具备极高的参考价值。
llama2.c 的核心亮点在于“极致简约”与“教育意义”。作者受 llama.cpp 启发,但更进一步硬编码了模型架构,摒弃了所有冗余库,让使用者能直接透过代码看清 Transformer 模型的运作机制。配合提供的训练脚本,它还构成了一个完整的“训练 + 推理”全栈解决方案,让用户不仅能跑通模型,还能亲手训练专属的小型故事生成模型,体验从零构建大模型的乐趣。
使用场景
一位嵌入式开发者需要在资源受限的物联网网关上部署一个能生成简单设备故障报告的本地 AI 助手,且无法依赖庞大的 Python 环境或联网服务。
没有 llama2.c 时
- 环境依赖沉重:必须移植完整的 Python 解释器及 PyTorch 库到嵌入式 Linux,占用数百 MB 存储空间,远超硬件限制。
- 推理延迟过高:通用框架在低算力 CPU 上运行缓慢,生成一条简短报告需数秒,无法满足实时交互需求。
- 部署流程复杂:需要配置复杂的虚拟环境、处理版本兼容性问题,维护成本极高。
- 内存开销巨大:加载标准模型往往需要 GB 级内存,导致设备频繁触发 OOM(内存溢出)崩溃。
使用 llama2.c 后
- 极致轻量部署:仅需一个约 700 行的纯 C 文件(run.c)和编译后的模型二进制文件,无需任何外部依赖,总占用仅几十 MB。
- 原生高性能推理:利用纯 C 直接调用硬件指令,在同样的低功耗芯片上实现了每秒上百 token 的流畅生成速度。
- 一键编译运行:通过简单的
make命令即可完成编译,直接嵌入现有 C/C++ 固件工程,大幅简化集成流程。 - 可控内存占用:支持加载 15M 至 42M 参数量的小型模型,将运行内存控制在几 MB 以内,确保系统稳定运行。
llama2.c 通过将大模型推理浓缩为单文件纯 C 实现,让资源受限的边缘设备也能轻松拥有本地化的智能文本生成能力。
运行环境要求
- Linux
- macOS
不需要 GPU,纯 CPU 运行(基于 C 语言实现)
运行 7B 模型需约 26GB+ RAM(fp32),小模型(15M-110M)仅需少量内存

快速开始
llama2.c
你有没有想过用纯 C 语言来推理一个小型的 Llama 2 模型?没有吗?那现在就可以啦!
你可以先用 PyTorch 训练 Llama 2 的大模型架构,然后用一个简单的、只有 700 行的 C 文件(run.c)来进行推理。你可能会觉得只有拥有数十亿参数的大模型才能做点有用的事情,但实际上,如果你把任务领域限定得足够窄,非常小的模型也能表现出令人惊讶的强大能力(参考:TinyStories 论文)。这个仓库提供了一个“全栈”的 Llama 2 大模型训练与推理解决方案,重点在于极简和易用。
由于架构完全一致,你也可以加载并推理 Meta 官方的 Llama 2 模型。不过,目前的代码仅支持以 fp32 精度进行推理,因此对于超过 70 亿参数的模型,可能很难高效地运行。我们正在开发模型量化功能。
请注意,这个仓库最初只是一个有趣的周末项目:我基于之前做的 nanoGPT,将其调整为实现 Llama-2 架构而非 GPT-2,而其中的核心工作就是用 run.c 编写纯 C 语言的推理引擎。所以这个项目还很年轻,进展迅速。特别感谢优秀的 llama.cpp,它给了我很多启发。相比 llama.cpp,我希望做到超级简单、极简且具有教育意义,因此我选择将 Llama 2 架构硬编码进去,并只用一个没有任何依赖的纯 C 文件来完成推理。
感受魔法
首先,导航到你存放项目的文件夹,并将这个仓库克隆到该文件夹中:
git clone https://github.com/karpathy/llama2.c.git
然后进入仓库目录:
cd llama2.c
现在,让我们用 C 语言运行一个小型的 Llama 2 模型。你需要一个模型检查点。下载我在这个 TinyStories 数据集上训练的 1500 万参数模型(约 60MB 下载):
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
编译并运行 C 代码:
make run
./run stories15M.bin
你会看到一段文本被逐步生成。在我使用的 M1 MacBook Air 上,它的速度约为 110 个 token/秒。更多关于性能的信息以及可以显著提升速度的编译选项,请参阅 性能 部分或 Makefile。
我们还可以尝试一个稍大的 4200 万参数模型:
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories42M.bin
./run stories42M.bin
这个模型仍然能够以交互式的速度运行,并生成更加连贯和多样的故事:
从前,有一个叫莉莉的小女孩。她最喜欢在床上玩她的玩具。有一天,她决定和她的毛绒玩具们一起开个茶话会。她倒了一些茶进一个小茶壶里,然后把它放在茶壶上。突然,她的小弟弟马克也跑进了房间,想加入茶话会。莉莉不想和马克分享她的茶,于是她让马克走开。马克开始哭起来,莉莉心里很难过。她最终决定把茶话会让给马克,两人一起分享了茶壶。然而,就在这时,一件意想不到的事情发生了。茶壶突然开始摇晃起来,莉莉和马克都吓坏了,不知道该怎么办。突然,茶壶飞了起来,一直飞到床的顶端才停了下来。莉莉和马克都很惊讶,他们紧紧抱在一起。他们意识到,分享比自私要有趣得多。从那天起,他们总是会一起分享他们的茶话会和玩具。
你还可以通过前缀或额外的命令行参数来引导模型生成,比如以温度 0.8 采样 256 步,并添加一个提示词:
./run stories42M.bin -t 0.8 -n 256 -i "有一天,莉莉遇到了一只修格斯"
有一天,莉莉遇到了一只修格斯。他非常害羞,但也很慷慨。莉莉说:“你好,修格斯!我可以做你的朋友吗?”修格斯很高兴能交到朋友,于是回答道:“当然可以,让我们一起探索宇宙吧!”于是,他们踏上了一段旅程,去探索宇宙。在旅途中,修格斯耐心地向莉莉介绍着宇宙中的各种奇妙事物。一天结束时,莉莉和修格斯收集了许多来自宇宙的美好东西,他们都感到无比自豪。他们承诺今后会永远携手探索宇宙,并且始终彼此慷慨相待。
此外,还有一个更好的 1.1 亿参数模型可供使用,详情请参阅 模型 部分。
关于采样的几点说明:为了获得最佳效果,建议使用 -t 1.0 -p 0.9 进行采样,即默认的温度 1.0,同时启用 top-p 采样,值为 0.9。直观来说,top-p 可以确保那些概率极低的 token 不会被选中,从而避免在采样过程中出现“运气不好”的情况,也减少了后续生成内容偏离主题的风险。更一般地,如果你想控制生成结果的多样性,可以选择调节温度(即在 0 到 1 之间调整 -t 参数,同时关闭 top-p 采样,设置为 -p 0),或者直接调整 top-p 值(即在 0 到 1 之间调整 -p 参数,同时保持 -t 1),但不要同时调整两者。关于大模型采样策略的优秀解释文章包括 这篇、这篇 和 这篇。
Meta的Llama 2模型
由于神经网络架构完全相同,我们也可以对Meta发布的Llama 2模型进行推理。遗憾的是,这里存在一些许可方面的限制(我认为我无法直接上传检查点文件)。因此,第一步是按照Meta的说明获取Llama 2的检查点文件。一旦有了这些检查点,我们需要将其转换为llama2.c格式。
为此,我们需要安装Python依赖项(pip install -r requirements.txt),然后使用export.py脚本,例如对于7B模型:
python export.py llama2_7b.bin --meta-llama path/to/llama/model/7B
导出过程大约需要10分钟左右,并会在当前目录下生成一个大小为26GB的文件(即7B模型的float32权重),名为llama2_7b.bin。有报告指出,尽管已经做出了努力,但目前仍存在问题。基于以下两点原因,我现在不会尝试运行超过7B的模型:首先,13B及以上版本由于指针运算中的整数溢出问题尚未修复而无法正常工作;其次,即使修复了该问题,这个仓库目前仍然采用float32精度进行推理,速度会非常慢,实用性不高。导出完成后,我们可以运行它:
./run llama2_7b.bin
在我的云服务器上,使用96线程的OpenMP编译后,该模型在CPU上的运行速度约为每秒4个token。(而在我的MacBook Air M1上,如果仅使用make runfast进行编译,每个token则需要近30秒。)示例输出如下:
本文旨在突出钴氧化物生成技术的最新进展,包括近期的发展以及已投入商业应用的技术。重点放在那些最有潜力成为未来主流工艺、因而值得科学技术研发部门关注的技术上。因此,文中较为深入地介绍了俄罗斯、日本和欧洲开发的钴氧化物生成技术。文章首先简要介绍了钴氧化物这一复杂产品,并概述了钴作为关键材料的重要性。随后,文章讨论了现有钴氧化物生成工艺在能源和资本消耗以及环境影响方面的表现。
基础模型……¯\(ツ)/¯。既然我们可以对基础模型进行推理,那么也应该能够相当容易地对聊天模型进行推理,并与之对话。如果我们能找到更高效地运行7B模型的方法,就可以开始将LoRA添加到我们的训练脚本中,在这个仓库内尽情地进行微调!
你也可以与Llama聊天模型进行对话。导出聊天模型的方式与上述相同:
python export.py llama2_7b_chat.bin --meta-llama /path/to/7B-chat
然后通过指定-m chat标志来启用聊天模式,例如:
./run llama2_7b_chat.bin -m chat
你还可以尝试Meta的Code Llama模型,尽管对其支持尚不完善。特别是,一些超参数发生了变化(例如RoPE层中的常数),因此当前的推理结果并不完全准确,且存在一定bug。目前正在寻找解决方案。请确保为普通版和指令版分别构建分词器,并在推理时传入相应的分词器。
python export.py codellama2_7b.bin --meta-llama /path/to/CodeLlama-7b
python tokenizer.py --tokenizer-model=/path/to/CodeLlama-7b/tokenizer.model
./run codellama2_7b.bin -z /path/to/CodeLlama-7b/tokenizer.bin
与Code Llama Instruct对话:
python export.py codellama2_7b_instruct.bin --meta-llama /path/to/CodeLlama-7b-Instruct
python tokenizer.py --tokenizer-model=/path/to/CodeLlama-7b-Instruct/tokenizer.model
./run codellama2_7b_instruct.bin -m chat -z /path/to/CodeLlama-7b-Instruct/tokenizer.bin
int8量化
上述默认脚本run.c采用的是float32前向传播,整个前向计算过程都以fp32精度进行。就参考代码而言,这种方式非常易于理解,但也存在以下缺点:模型检查点文件体积庞大(每个权重占用4字节),且前向传播速度相对较慢。实践中常用的优化方法是将模型参数量化为较低精度,以牺牲少量精度为代价,换取更小的检查点文件和更快的前向传播速度(因为大多数推理操作都使用整数运算)。经验表明,LLM可以容忍低至4位甚至更低的精度,但我们在这里选择int8,因为它是一种“安全”的设置,能够在获得优势的同时不过度牺牲模型的准确性。只有参与矩阵乘法的权重会被量化。其他参数(尤其是RMSNorm中的缩放和偏置)则保持为float32,因为这些层对精度非常敏感。如果你仅仅是为了减小检查点文件的大小,也可以只量化权重并保存检查点,然后在run.c中将其反量化回float32,再像往常一样进行推理即可,这样做并无不可。然而,在这里,我们更进一步(这也是行业标准做法),在前向传播过程中也对激活值进行量化。这需要我们在运行时动态地在float32和int8之间进行量化和反量化,从而增加了一定的开销。但好处在于,现在大部分计算(尤其是矩阵乘法!)都使用纯整数运算,权重和激活值均以int8形式输入。正是这一点带来了根本性的速度提升。我们使用的版本是“Q8_0”量化(llama.cpp术语),其中的0表示权重量化是对称于0的,量化范围为[-127, 127]。
量化后的前向传播实现于runq.c中。要使用它,我们必须以量化格式导出模型。例如,Llama 2 7B的float32版本导出命令如下:
python export.py llama2_7b.bin --meta-llama path/to/llama/model/7B
这会生成一个26GB的文件,因为7B模型中的每个参数都是4字节(fp32)。若要以量化格式导出,则需使用版本2的导出命令:
python export.py llama2_7b_q80.bin --version 2 --meta-llama path/to/llama/model/7B
这个过程只需几分钟,但最终生成的文件仅为6.7GB。对于非Meta的检查点,应使用--checkpoint参数而非--meta-llama参数(更多相关说明将在下文介绍)。接下来让我们对这些模型进行推理。我喜欢在这里使用OMP,因为这些都是大模型,例如在我的Linux服务器上:
make runomp
OMP_NUM_THREADS=64 ./run llama2_7b.bin -n 40
OMP_NUM_THREADS=64 ./runq llama2_7b_q80.bin -n 40
这里运行40步只是为了测量速度。对我而言,float32版本的运行速度为每秒4.6个token,而int8版本则为每秒14个token。因此,我们在将检查点文件大小缩小4倍的同时,实现了3倍的速度提升。然而,由于前向传播被量化为int8,其质量也会相应地略有下降。
Hugging Face 模型
我们可以加载任何使用 Llama 2 架构的 Hugging Face 模型。请参阅脚本 export.py 和 --hf 标志,以导出模型的 .bin 文件。
模型
为了提供一些较小、从头开始训练的示例模型,我在 TinyStories 数据集上训练了一系列小型模型。这些模型都在我的训练环境中(4 张 A100 40GB GPU)花费了几小时便完成了训练,其中 1.1 亿参数的模型大约用了 24 小时。我将这些模型托管在 Hugging Face Hub 的 tinyllamas 仓库中,既有原始的 PyTorch .pt 格式,也有 llama2.c 格式的 .bin 文件:
| 模型 | 模型维度 | 层数 | 注意力头数 | 用于键值的注意力头数 | 最大上下文长度 | 参数量 | 验证损失 | 下载链接 |
|---|---|---|---|---|---|---|---|---|
| 26万 | 64 | 5 | 8 | 4 | 512 | 26万 | 1.297 | stories260K |
| OG | 288 | 6 | 6 | 6 | 256 | 1500万 | 1.072 | stories15M.bin |
| 4200万 | 512 | 8 | 8 | 8 | 1024 | 4200万 | 0.847 | stories42M.bin |
| 1.1亿 | 768 | 12 | 12 | 12 | 1024 | 1.1亿 | 0.760 | stories110M.bin |
你会发现,1.1 亿参数的模型在规模上与 GPT-1 相当。或者也可以将其视为 GPT-2 系列中最小的模型(GPT-2 small),只是最大上下文长度只有 1024 而非 2048。Llama 相较于 GPT-1/2 架构的主要区别在于:Llama 使用 RoPE 相对位置编码而非绝对或可学习的位置编码,在 MLP 中采用了更为复杂的 SwiGLU 非线性激活函数,使用 RMSNorm 而不是 LayerNorm,所有 Linear 层均不使用偏置项,并且可以选择多查询注意力机制。
训练
让我们看看如何使用本仓库中的代码从零开始训练一个小型 Llama 2 模型。首先,我们需要下载并预处理一些源数据集。例如,我喜欢 TinyStories 数据集,因此目前本仓库中仅提供了该数据集的示例。不过,添加其他数据集应该非常容易,请参考代码。
python tinystories.py download
python tinystories.py pretokenize
然后就可以开始训练我们的模型了:
python train.py
简要训练指南。更多复杂的启动方式和超参数覆盖,请参阅 train.py 脚本。以下是一个关于如何设置参数的简要指南。请参考 Chinchilla 论文 末尾的表格,了解 Transformer 的各个参数(模型维度、层数、注意力头数)是如何协同变化的。根据这一规律外推或插值,即可得到更大或更小的 Transformer 模型。至于最大上下文长度,则可以根据具体任务自行设定:它应为预测下一个 token 时需要考虑的最大 token 数量。例如,Llama 2 使用 2048。接下来,你需要确保每次更新的总批量大小(脚本会打印“每次迭代将处理的 token 数量为:”)在中等规模应用中保持在约 10 万个 token 左右。对于小型应用可以更低,而对于大规模训练(如 GPT 或 Llama)通常约为 50 万个,甚至更多。实现方法是先将 batch_size 设置到系统允许的最大值(例如,我最近的运行中设置为 16,因为再高就会导致 GPU 内存不足),然后再通过增加 gradient_accumulation_steps 来达到约 10 万个 token 的总批量大小。最后,你需要调整学习率(LR)。尽量将其设置到训练允许的最高值。非常小的网络可以承受较高的学习率(如 1e-3 甚至更高)。而大型网络则需要较低的学习率。3e-4 是大多数中等规模应用的安全选择,但对于小型网络可能过低,因此可以尝试适当提高。max_iters 则决定了训练的持续时间。你可以尝试不同的设置。我通常只调整这些参数,而其他大部分参数都保持不变。以下是我训练 1.1 亿参数模型的一个例子,虽然我认为并不算最优,但对我来说还算合理:模型维度 768,层数 12,注意力头数 12(每个头的大小为 768 / 12 = 64 个通道),序列长度 1024,batch_size 16(这是我 A100 40GB GPU 能够容纳的最大值),gradient_accumulation_steps 设为 8,这样每次更新的总 token 数量就能达到 16 × 1024 × 8 = 131,072 个 token。很好。学习率设为 4e-4(可能还是有点低)。max_iters 设为 20 万(可能又有点高)。dropout 设为 0.1,因为在中等规模下这通常会有一定帮助。就这样。我在云服务器上的 4 张 GPU 上使用分布式数据并行(DDP)进行训练,整个过程大约花费了一天左右的时间。
当然,如果你只想进行简单的演示,完全可以跳过模型训练,直接下载其中一个预训练好的模型(见 模型 部分),例如:
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
一旦我们有了 model.bin 文件,就可以用 C 语言进行推理。首先编译 C 代码:
make run
然后就可以简单地运行:
./run stories15M.bin
看着 token 流水般地输出,真是有趣!我们也可以运行 PyTorch 推理脚本进行对比。再次从 Hugging Face Hub 下载一个模型,并将其路径指向 sample.py 脚本:
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.pt -P out15M
python sample.py --checkpoint=out15M/stories15M.pt
这样得到的结果是一样的。
自定义分词器
在上述内容中,我们一直假设使用的是拥有32,000个词汇的自定义 Llama 2 分词器。然而,在许多小型专精的 LLM 中,使用如此庞大的词汇表可能显得过于冗余。如果你有一个小型的应用场景,训练一个属于自己的分词器可能会更加合适。这样做有许多好处:更小的词汇表意味着模型参数量减少(因为词嵌入表会小很多),推理速度也会更快(因为需要预测的词元数量更少),而且每个样本的平均序列长度也可能缩短(因为在你的数据上压缩效率更高)。那么,接下来我们就来看看如何训练一个自定义分词器。
默认情况下,为了对 tinystories 数据集进行预分词处理,我们需要依次运行以下命令:
python tinystories.py download
python tinystories.py pretokenize
这里的 pretokenize 阶段会加载 Llama 2 分词器(词汇表大小为32,000),并用它将下载的文本转换为整数序列,然后保存到文件中。现在我们将按照如下方式修改,以训练一个4096个词元的示例分词器:
python tinystories.py download
python tinystories.py train_vocab --vocab_size=4096
python tinystories.py pretokenize --vocab_size=4096
train_vocab 阶段会调用 sentencepiece 库来训练分词器,并将其保存到一个新的文件 data/tok4096.model 中。我尽可能地复现了 Meta 训练其词汇表时所采用的设置。该过程使用字节对编码算法,从原始的 UTF-8 字节序列开始,逐步合并最常见的连续词元对,从而构建出词汇表。请查看 tinystories.py 文件——自定义分词器会被存储在一个特殊的目录结构中,该结构按词汇表大小进行索引。
值得一提的是,专门针对 tinystories 数据训练的4096个词元的词汇表,生成的整数序列的平均长度与默认的32,000个词元的 Llama 2 分词器几乎相同!这意味着我们的自定义分词器更能适应特定的文本数据,能够非常高效地对其进行压缩。因此,我们训练出的模型体积更小、运行速度更快。
现在我们已经使用自定义分词器对数据集进行了预分词处理,接下来就可以开始训练模型了。训练脚本 train.py 并不关心具体的词元内容,它只关注词汇表大小,以便正确初始化模型。所以在训练模型时,请确保传递以下参数:
python train.py --vocab_source=custom --vocab_size=4096
(默认值分别为 llama2 和 32000,即默认的 Llama 2 分词器。)这样就可以开始训练模型了。最后,我们就可以使用 run.c 脚本进行推理了。为此我们需要两样东西。第一,我们需要将分词器导出为 .bin 格式,可以通过以下命令完成:
python tokenizer.py --tokenizer-model=data/tok4096.model
这会将分词器写入 data/tok4096.bin 文件中。现在我们可以运行推理程序,并通过 -z 标志指向这个分词器:
./run out/model.bin -z data/tok4096.bin
这样应该就能打印出生成的样本。如果省略 -z 标志,程序将会使用默认的 Llama 2 分词器,虽然它会生成一串不错的整数序列,但这些整数会使用不同的词汇表被翻译成文本,因此看起来会像乱码。
性能
根据你的系统配置,有多种方法可以进一步提升代码的性能。请查看 Makefile,其中包含大量注释。目前,make run 命令默认使用 -O3 优化选项,即:
gcc -O3 -o run run.c -lm
-O3 包含一些编译时间和内存消耗较高的优化措施,例如向量化、循环展开以及分支预测等。若想获得更好的性能,可以尝试使用 make runfast 进行编译。该选项启用了 -Ofast 标志,除了包含 -O3 的所有优化外,还增加了一些可能不符合 C/IEEE 标准的优化。更多信息请参阅 GCC 文档。
此外,还可以尝试使用 -march=native 编译选项,使程序针对当前编译机器的架构进行优化,而不是使用通用的 CPU 指令集。这可能会启用额外的优化和硬件相关的调优,比如改进的向量指令宽度等。我在 MacBook Air (M1) 上测试过的最快吞吐量就是使用 make runfast 得到的。
你也可以尝试用 clang 替代 gcc 来编译。
如果继续使用 gcc 编译,可以尝试实验 -funroll-all-loops 选项,详情请参见 PR #183。
OpenMP。通过 OpenMP 编译也能带来显著的性能提升,它会“激活”矩阵乘法和注意力机制中的 #pragma omp parallel for 指令,从而将循环中的计算任务分配到多个处理器上。你需要先安装 OpenMP 库和 clang 编译器(例如,在 Ubuntu 系统上可以运行 apt install clang libomp-dev)。之后,你可以使用 make runomp 进行编译,该命令会执行以下操作:
clang -Ofast -fopenmp -march=native run.c -lm -o run
在运行推理时,请务必设置 OpenMP 相关的线程数标志,例如:
OMP_NUM_THREADS=4 ./run out/model.bin
根据你的系统资源情况,你可能需要调整这些超参数,使用更多的线程。不过,线程数并不是越多越好,通常呈现 U 形曲线。特别是如果你的 CPU 支持 SMT(多线程技术),建议将线程数设置为物理核心的数量,而非逻辑核心的数量。这是因为过多的线程会导致缓存抖动和通信开销,从而影响性能。PyTorch 官方文档中的 CPU 特定优化指南 也提供了与此相关的一些有用信息。
平台支持
在 Windows 系统上,可以在 Visual Studio 命令提示符中使用 build_msvc.bat 脚本通过 MSVC 编译器进行构建;或者使用 make win64 命令,利用来自 Linux 或 Windows 的 MinGW 编译工具链来构建适用于 Windows 的目标文件。MSVC 构建会自动启用 OpenMP,并根据你的 CPU 设置合适的线程数,除非你手动设置了 OMP_NUM_THREADS 环境变量。
在 CentOS 7 和 Amazon Linux 2018 系统上,可以使用 rungnu Makefile 目标:make rungnu 或 make runompgnu 来启用 OpenMP。
在 Mac 系统上,可以使用 Homebrew 安装的 clang 工具链来进行 OpenMP 编译。首先通过 brew install llvm 安装 clang,然后使用已安装的 clang 二进制文件进行编译:make runomp CC=/opt/homebrew/opt/llvm/bin/clang。
测试
你可以使用 pytest 轻松运行测试:
$ pip install pytest
$ pytest
目前这将调用 test_all.py 中的两个测试,它们分别以 C 和 Python 语言对模型进行 200 步前向传播,并将输出与已知正确的预期输出进行比对。这些测试通常只需几秒钟即可完成,不过在首次运行时会下载并缓存 stories260K 模型到一个临时的 test 目录中(下载量仅约 2MB)。
此外,C 语言中也有部分测试,位于 test.c 文件中。你可以通过运行 make testcc 来执行这些测试,或者使用以下命令查看更多输出信息:
make testcc VERBOSITY=1
诚邀大家帮忙添加更多测试!
致谢
我是在 Lambda Labs 提供的一台配备 4 张 A100 40GB 显卡的机器上训练了 llama2.c 故事生成模型,在此特别感谢他们提供的支持。
Discord
我想可以复用我现有的 Discord 频道(用于我的 零到英雄 YouTube 系列),欢迎加入 Discord 上的 #llama2c 频道,讨论相关问题或进行交流。
贡献说明
关于本仓库以及可能被接受的 PR 类型,我想简单说明几点。这个仓库的目标是什么?我认为未来将会涌现出大量对训练或微调小型自定义大模型(参数量大约在 1 亿到 10 亿之间,最多可达 100 亿)的兴趣,这些模型将应用于各种不同的场景,并部署到边缘计算环境中(例如微控制器、手机、浏览器、笔记本电脑等)。我希望这个仓库能够成为最简单、最小、最具可 hack 性的工具库,同时支持训练和推理流程。具体来说,它并不是一个拥有上千个配置项、代码晦涩难懂且目录结构复杂、包含数百个文件的大型框架。相反,我预计大多数用户会选择基于本仓库创建分支,并根据自身需求和部署平台对其进行定制化改造。
如果有人最关心部署效率,那么应该关注 llama.cpp。本仓库同样重视效率,但不会以牺牲简洁性、可读性和可移植性为代价。实际上,许多人选择本仓库正是因为其训练代码仅有两份易于阅读的 Python 文件,而推理代码则是一段约 500 行的 C 代码。因此,我希望它继续作为最简单的“参考实现”,方便用户在其基础上创建分支,快速适配到自己感兴趣的下游应用中。它不需要具备完整功能,也不需要支持上百种选项或设置,更不必追求极致的效率。以下是一些例子:
- 如果有人调整了两个循环的顺序以提升数据局部性从而获得小幅性能提升,这样的 PR 将会被立即合并。
- 如果有人添加了一行
#pragma omp parallel for,这样就可以启用 OpenMP 编译来显著加速代码;如果不启用 OpenMP,则这一行代码也会被当作注释而不会产生影响——这种改动同样会被立即合并。 - 对于 bug 修复和小的优化等,我也非常乐意合并。
然而,以下类型的 PR 则不太适合:
- 在代码中到处添加过多的
#ifdef宏定义。如果这些宏定义是局部性的且数量不多,或许还可以接受。 - 添加大量针对特定平台(如微控制器、某些特殊版本的 Linux 或处理器)的专用代码。这类内容更适合放在项目的分支中,我也很乐意在下方列出这些分支的链接。
- 向
run.c文件中添加数百行仅在特定场景或平台上生效的代码。
需要注意的是,即使你的 PR 包含上述内容,也并不意味着一定无法被合并,只是可能会进入一个较为模糊的范畴。简而言之:我非常期待合并那些规模较小、改动局部化、适用范围广、代码整洁,并且能够在保持易用性和可读性的同时提升仓库效率和可移植性的更改。感谢所有致力于改进本项目的人!<3
值得关注的分支
- Rust
- llama2.rs 由 @gaxler:该项目的 Rust 移植版
- llama2.rs 由 @leo-du:该项目的 Rust 移植版
- llama2-rs 由 @danielgrittner:该项目的 Rust 移植版
- llama2.rs 由 @lintian06:该项目的 Rust 移植版
- pecca.rs 由 @rahoua:利用 ndarray 的 Rust 移植版,支持 BLAS。
- llama2.rs 由 @flaneur2020:该项目的 Rust 移植版。
- llama2-burn:利用 Burn 的该项目的 Rust 移植版
- Go
- Android
- llama2.c-android:由 @Manuel030:添加了该项目的 Android 二进制文件
- llama2.c-android-wrapper:由 @celikin:添加了 JNI 封装,PoC
- C
- llama3.c:由 @jameswdelancey:该项目的 LLaMA 3 8B Base 和 Instruct 移植版
- C++
- llama2.cpp 由 @leloykun:该项目的 C++ 移植版
- llama2.cpp 由 @coldlarry:该项目的 C++ 移植版
- JavaScript
- llama2.js 由 @epicure:该项目的 JavaScript 移植版
- llamajs 由 @agershun:该项目的 JavaScript 移植版
- llama2.ts 由 @oleksandr_now:该项目的 TypeScript 移植版。完全支持 Llama2-7B。
- llama2.c-emscripten 由 @gohai:基于 @ggerganov 最初原型的 Emscripten (JavaScript) 移植版
- Zig
- llama2.zig 由 @cgbur:该项目的 Zig 移植版
- llama2.zig 由 @vodkaslime:该项目的 Zig 移植版
- llama2.zig 由 @clebert:该项目的 Zig 移植版
- Julia
- Scala
- llama2.scala 由 @jrudolph:该项目的 Scala 移植版
- Java
- llama2.java 由 @mukel:该项目的 Java 移植版
- llama2.java 由 @neoremind:该项目的 Java 移植版
- llama2.tornadovm.java 由 @mikepapadim:通过 TornadoVM 为 llama2.java 添加 GPU 支持的扩展版本。
- Kotlin
- llama2.kt 由 @madroidmaq:该项目的 Kotlin 移植版
- llama2-kmp 由 @stepango:该项目的 Kotlin 多平台(KMP)移植版
- Python
- C#
- F#
- Dart
- llama2.dart 由 @yiminghan:该项目的一键式 Dart 移植版,可与 Flutter 配合使用!
- Web
- llama2c-web 由 @dmarcos:将未修改的 llama2.c 轻松编译为 WASM 并在浏览器中运行。演示
- llama2.rs.wasm 由 @mtb0x1:所有列出的 Rust 移植版都被编译为 WASM,并整合到一个网页中,提供演示。
- WebAssembly
- icpp-llm:适用于 Internet Computer 的 LLM
- Fortran
- llama2.f90:该项目的 Fortran 移植版
- Mojo
- OCaml
- Hare
- llama2.c - Llama 2 无处不在 由 @trholding:独立、可引导且便携的二进制 Llama 2
- llama2.c-zh - 中英双语 由 @chenyangMl:扩展分词器,以支持中文和英文的训练与推理
- Haskell
未分类待办事项
- 在 run.c 中添加支持从导出文件读取版本 1 及以上文件的功能,随后弃用“版本 0”
- 对 run.cu (CUDA) 进行研究并合并
- 在 test.c 中添加更多测试
- 在 sample.py 中添加 Engine 类,用于在 PyTorch 中进行高效推理,例如保持 KV 缓存
- 使添加新数据集变得更加容易,减少复杂性
- (LoRA)对 Llama 2 模型进行微调并导出
许可证
MIT
常见问题
相似工具推荐
openclaw
OpenClaw 是一款专为个人打造的本地化 AI 助手,旨在让你在自己的设备上拥有完全可控的智能伙伴。它打破了传统 AI 助手局限于特定网页或应用的束缚,能够直接接入你日常使用的各类通讯渠道,包括微信、WhatsApp、Telegram、Discord、iMessage 等数十种平台。无论你在哪个聊天软件中发送消息,OpenClaw 都能即时响应,甚至支持在 macOS、iOS 和 Android 设备上进行语音交互,并提供实时的画布渲染功能供你操控。 这款工具主要解决了用户对数据隐私、响应速度以及“始终在线”体验的需求。通过将 AI 部署在本地,用户无需依赖云端服务即可享受快速、私密的智能辅助,真正实现了“你的数据,你做主”。其独特的技术亮点在于强大的网关架构,将控制平面与核心助手分离,确保跨平台通信的流畅性与扩展性。 OpenClaw 非常适合希望构建个性化工作流的技术爱好者、开发者,以及注重隐私保护且不愿被单一生态绑定的普通用户。只要具备基础的终端操作能力(支持 macOS、Linux 及 Windows WSL2),即可通过简单的命令行引导完成部署。如果你渴望拥有一个懂你
stable-diffusion-webui
stable-diffusion-webui 是一个基于 Gradio 构建的网页版操作界面,旨在让用户能够轻松地在本地运行和使用强大的 Stable Diffusion 图像生成模型。它解决了原始模型依赖命令行、操作门槛高且功能分散的痛点,将复杂的 AI 绘图流程整合进一个直观易用的图形化平台。 无论是希望快速上手的普通创作者、需要精细控制画面细节的设计师,还是想要深入探索模型潜力的开发者与研究人员,都能从中获益。其核心亮点在于极高的功能丰富度:不仅支持文生图、图生图、局部重绘(Inpainting)和外绘(Outpainting)等基础模式,还独创了注意力机制调整、提示词矩阵、负向提示词以及“高清修复”等高级功能。此外,它内置了 GFPGAN 和 CodeFormer 等人脸修复工具,支持多种神经网络放大算法,并允许用户通过插件系统无限扩展能力。即使是显存有限的设备,stable-diffusion-webui 也提供了相应的优化选项,让高质量的 AI 艺术创作变得触手可及。
everything-claude-code
everything-claude-code 是一套专为 AI 编程助手(如 Claude Code、Codex、Cursor 等)打造的高性能优化系统。它不仅仅是一组配置文件,而是一个经过长期实战打磨的完整框架,旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。 通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能,everything-claude-code 能显著提升 AI 在复杂任务中的表现,帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略,使得模型响应更快、成本更低,同时有效防御潜在的攻击向量。 这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库,还是需要 AI 协助进行安全审计与自动化测试,everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目,它融合了多语言支持与丰富的实战钩子(hooks),让 AI 真正成长为懂上
ComfyUI
ComfyUI 是一款功能强大且高度模块化的视觉 AI 引擎,专为设计和执行复杂的 Stable Diffusion 图像生成流程而打造。它摒弃了传统的代码编写模式,采用直观的节点式流程图界面,让用户通过连接不同的功能模块即可构建个性化的生成管线。 这一设计巧妙解决了高级 AI 绘图工作流配置复杂、灵活性不足的痛点。用户无需具备编程背景,也能自由组合模型、调整参数并实时预览效果,轻松实现从基础文生图到多步骤高清修复等各类复杂任务。ComfyUI 拥有极佳的兼容性,不仅支持 Windows、macOS 和 Linux 全平台,还广泛适配 NVIDIA、AMD、Intel 及苹果 Silicon 等多种硬件架构,并率先支持 SDXL、Flux、SD3 等前沿模型。 无论是希望深入探索算法潜力的研究人员和开发者,还是追求极致创作自由度的设计师与资深 AI 绘画爱好者,ComfyUI 都能提供强大的支持。其独特的模块化架构允许社区不断扩展新功能,使其成为当前最灵活、生态最丰富的开源扩散模型工具之一,帮助用户将创意高效转化为现实。
gemini-cli
gemini-cli 是一款由谷歌推出的开源 AI 命令行工具,它将强大的 Gemini 大模型能力直接集成到用户的终端环境中。对于习惯在命令行工作的开发者而言,它提供了一条从输入提示词到获取模型响应的最短路径,无需切换窗口即可享受智能辅助。 这款工具主要解决了开发过程中频繁上下文切换的痛点,让用户能在熟悉的终端界面内直接完成代码理解、生成、调试以及自动化运维任务。无论是查询大型代码库、根据草图生成应用,还是执行复杂的 Git 操作,gemini-cli 都能通过自然语言指令高效处理。 它特别适合广大软件工程师、DevOps 人员及技术研究人员使用。其核心亮点包括支持高达 100 万 token 的超长上下文窗口,具备出色的逻辑推理能力;内置 Google 搜索、文件操作及 Shell 命令执行等实用工具;更独特的是,它支持 MCP(模型上下文协议),允许用户灵活扩展自定义集成,连接如图像生成等外部能力。此外,个人谷歌账号即可享受免费的额度支持,且项目基于 Apache 2.0 协议完全开源,是提升终端工作效率的理想助手。
markitdown
MarkItDown 是一款由微软 AutoGen 团队打造的轻量级 Python 工具,专为将各类文件高效转换为 Markdown 格式而设计。它支持 PDF、Word、Excel、PPT、图片(含 OCR)、音频(含语音转录)、HTML 乃至 YouTube 链接等多种格式的解析,能够精准提取文档中的标题、列表、表格和链接等关键结构信息。 在人工智能应用日益普及的今天,大语言模型(LLM)虽擅长处理文本,却难以直接读取复杂的二进制办公文档。MarkItDown 恰好解决了这一痛点,它将非结构化或半结构化的文件转化为模型“原生理解”且 Token 效率极高的 Markdown 格式,成为连接本地文件与 AI 分析 pipeline 的理想桥梁。此外,它还提供了 MCP(模型上下文协议)服务器,可无缝集成到 Claude Desktop 等 LLM 应用中。 这款工具特别适合开发者、数据科学家及 AI 研究人员使用,尤其是那些需要构建文档检索增强生成(RAG)系统、进行批量文本分析或希望让 AI 助手直接“阅读”本地文件的用户。虽然生成的内容也具备一定可读性,但其核心优势在于为机器