跳到主要内容

数据截至 (上游 commit 3adf61e154c3)

nanoGPT — 架构与原理

30 秒导读: nanoGPT 是 Karpathy 写的「最小可读 GPT 训练库」:整个仓库的核心只有两个文件——model.py(约 300 行的 GPT 模型)和 train.py(约 300 行的训练循环),却完整覆盖「准备数据 → 从零预训练 → 微调 GPT-2 → 采样生成」的全流程,且能在 8×A100 上 4 天复现 GPT-2 (124M)。它是学习「现代 LLM 训练到底由哪几件事组成」的最佳起点,因为这里没有任何框架把细节藏起来。


1. 这是什么(零基础也能懂)

一句话定义

nanoGPT 是一个用纯 PyTorch 写的、刻意保持极小的 GPT 训练/微调/采样仓库:它把一个 decoder-only Transformer 语言模型和它的完整预训练循环,各自塞进一个 300 行左右的文件里。

解决什么问题 / 给谁用

设想你想知道「训练一个 GPT 到底需要哪些零件」,去看工业级训练框架(Megatron、HF Trainer),你会淹死在几万行抽象里。nanoGPT 的回答是:把每一层抽象剥掉,只留骨架,让你一个下午就能读完一个能真跑出 GPT-2 水平模型的全部代码。

主要读者是想学懂原理的人:学生、研究者、要自己动手写训练循环的工程师。它不是给生产环境用的框架。

它能做什么

能力具体形态
从零预训练train.py 单机或 DDP 多卡,默认配置复现 GPT-2 (124M)(config/train_gpt2.py
加载 GPT-2 权重GPT.from_pretrained 直接搬 OpenAI 官方 checkpoint(model.py:206
微调换一个数据集目录 + init_from='gpt2-xl',其余与预训练相同(config/finetune_shakespeare.py
采样生成sample.py 从 checkpoint 或 GPT-2 权重自回归生成文本
基准测试bench.py 去掉训练杂事,只测每步耗时和 MFU

用起来什么样

最快的体验路径(README 的 quick start):字符级小莎士比亚模型。

# 1. 下载 tiny-shakespeare 并转成 train.bin / val.bin / meta.pkl
python data/shakespeare_char/prepare.py

# 2. 训练一个 6 层小 GPT(A100 上约 3 分钟)
python train.py config/train_shakespeare_char.py

# 3. 从 checkpoint 采样
python sample.py --out_dir=out-shakespeare-char

严肃路径是复现 GPT-2 (124M):先 python data/openwebtext/prepare.py 生成约 9B token 的 train.bin,再 torchrun --standalone --nproc_per_node=8 train.py config/train_gpt2.py,约 4 天、val loss 到 ~2.85(README「reproducing GPT-2」一节)。

一句话直觉

把 nanoGPT 想成「GPT 的解剖标本」。 别的训练框架是穿好了衣服的完整动物,能跑能干活但看不清骨骼;nanoGPT 把皮肉都剥了,只剩一副骨架挂在架子上——每根骨头(注意力、初始化、优化器、学习率)都标着名字,你伸手就能摸到。


2. 顶层全景(它大概怎么转)

2.1 一张图看全流程

数据准备 训练 生成
───────────── ───────────────────── ──────────────
data/<语料>/ train.py sample.py
prepare.py │ │
│ ▼ ▼
原始文本 ──► token id ──► train.bin ──► get_batch ──► GPT 前向
(uint16 大数组) val.bin 随机切 (B,T) │

loss ◄── 错位一个 token 的 y


backward → 梯度裁剪 → AdamW 更新


out_dir/ckpt.pt ──► 加载 ──► 逐 token 采样

怎么读这张图: 从左到右是数据的一生。三个脚本各管一段,train.bin(二进制 token 流)和 ckpt.pt(权重文件)是它们之间仅有的两个交接物。

2.2 部件职责

部件干什么在哪个文件
GPT 及子模块模型本身:嵌入、N 个 Block、LayerNorm、lm_headmodel.py
训练主循环取 batch、前向、反传、优化器步进、评估、存 checkpointtrain.py:255-333
get_batch从 .bin 随机切出 (x, y) 批次并送上 GPUtrain.py:116-131
配置覆盖器exec 让配置文件/命令行直接改写脚本全局变量configurator.py
超参集合每个真实跑过的实验一份(GPT-2 复现、微调等)config/*.py
数据准备下载语料、tokenize、写成 uint16 二进制data/*/prepare.py
采样脚本加载权重、编码 prompt、调用 GPT.generatesample.py
基准脚本train.py 主循环的精简版,测速度和 MFUbench.py

2.3 主线走一遍:一次预训练

以「从零训练 GPT-2 (124M)」为例,高层流程是:

  1. 数据落地data/openwebtext/prepare.py 把 800 万篇文档 tokenize 成 GPT-2 BPE id,拼成一个 ~17GB 的 train.bin(uint16 数组,data/openwebtext/prepare.py:59-74)。
  2. 配置注入train.py 先把约 40 个超参写成全局变量(train.py:32-74),再 exec configurator.py,让命令行和配置文件直接覆写这些全局变量(train.py:77)。
  3. 模型与优化器:按超参建 GPTtrain.py:156-157),用 configure_optimizers 把参数分成「衰减/不衰减」两组建 AdamW(train.py:199),可选 torch.compile 和 DDP 包装(train.py:205-212)。
  4. 主循环:每个 iteration 先做 gradient_accumulation_steps 个 micro-step 的前向+反传(train.py:292-305),再梯度裁剪、优化器步进、清零梯度(train.py:307-314);学习率由 warmup + 余弦衰减函数给出(train.py:231-242)。
  5. 评估与存档:每 eval_interval 步在 train/val 上各估一次 loss,由 master 进程把 model/optimizer/iter_num 等写入 ckpt.pttrain.py:263-286)。
  6. 生成:训练完后 sample.py 加载 ckpt.pt,从 prompt 出发逐 token 采样(sample.py:84-89model.py:305-330)。

3. 阅读地图

建议按下面的顺序读,恰好是「模型 → 训练 → 数据 → 生成」由浅入深的一条线:

章节讲什么适合谁
01-model.mdGPT 模型:注意力、残差块、初始化、权重共享、加载 GPT-2想读懂 Transformer 实现细节的人
02-training-loop.md训练循环:DDP、梯度累积、混合精度、LR 调度、checkpoint想自己写训练循环的人
03-data-pipeline.md数据:.bin 格式、memmap、随机切片、错位标签关心数据怎么喂进 GPU 的人
04-sampling.md生成:自回归循环、temperature、top-k、无 KV cache关心推理侧的人

如果你只有 10 分钟:读第 1 章的「Block 与 pre-LN 残差」和第 2 章的「主循环一步」,这两节是整个仓库的浓缩。


4. 巧妙之处(可借鉴的技术)

每条先白话点出妙在哪,再给源码锚点;细节在各章节展开。

  • 用「2D 与否」一刀切权重衰减分组:所有维度 ≥2 的参数(矩阵、嵌入)做 weight decay,所有 1D 参数(bias、LayerNorm)不衰减——不用按名字匹配,两行搞定(model.py:268-275configure_optimizers)。
  • 残差分支的 c_proj1/√(2·n_layer) 缩小初始化:让深层网络在初始化时残差路径的贡献不至于随层数累积爆炸,直接照 GPT-2 论文(model.py:143-145)。
  • DDP 梯度同步开关代替 no_sync 上下文:梯度累积时只在最后一个 micro-step 打开 model.require_backward_grad_sync,省掉重复写两遍前向代码(train.py:293-298)。
  • 每个 batch 重建 memmap:绕开 numpy memmap 迭代时的内存泄漏,一行注释附 StackOverflow 出处(train.py:117-122)。
  • 前向时就把下一个 batch 预取get_batch 调用放在 forward 之后、backward 之前,让 CPU→GPU 拷贝与 GPU 计算重叠(train.py:302-303)。
  • vocab 对齐到 64 的倍数:把 GPT-2 的 50257 补成 50304,让矩阵乘在 GPU 上走更整齐的 tile(model.py:111)。
  • MFU 用一行公式估算6N + 12LHQT 个 FLOP/token(PaLM 论文附录 B 的口径),除以 A100 的 312 TFLOPS 峰值,训练时实时报告硬件利用率(model.py:289-303)。

5. 边界与局限

诚实清单:这个库刻意不做实际做不到的事。

  • 不为速度优化,为可读性优化。 README 自己定位为「simplest, fastest」是指「改起来快」,不是训练最快;正式继任者 nanochat 和 C 实现的 llm.c 才是性能线。
  • 已冻结。 README 顶部声明仓库 deprecated,作者推荐去 nanochat(README.md:9)。本库的价值是教学标本,不是持续维护的框架。
  • 无 KV cache。 generate 每生成一个 token 都对完整序列重算一遍前向(model.py:312-316),生成成本是 O(T²) 级别——采样脚本只是演示,不是推理引擎。
  • 并行只有 DDP。 没有模型并行/流水线并行(README 的 todos 里 FSDP 一项至今未做,README.md:213),单模型必须放得下一张卡的显存。
  • 数据管线是「穷人版」。 全语料拼成一个连续 token 流、随机切片当样本,没有 document 边界感知的 packing、没有去重、没有洗牌后的 epoch 概念(train.py:116-131)。
  • 评估只有 loss。 没有任何下游任务评测(zero-shot perplexity、LAMBADA 等在 todos 里列着但没做)。
  • checkpoint 是全量的。 模型 + 优化器状态一把存(train.py:277-284),大模型下很大;也没有只存权重的选项。

6. 横向对比

同书架上与 nanoGPT 血脉最近的三个项目,取舍各不相同:

项目与 nanoGPT 的关系关键取舍
micrograd同作者的「再往下剥一层」:标量级自动微分引擎连 PyTorch 都不用,~150 行 Python 演示反向传播本身;只能算标量,不能训真模型
llm.c同一目标(复现 GPT-2)的 C/CUDA 重写抛掉 PyTorch 换零依赖和极致性能;可读性让位给手写 kernel
nanochat官方继任者:从预训练一路到 chat 全栈覆盖 tokenizer、SFT、RL、推理服务的完整管线;代价是代码量大一个数量级

一句话定位:micrograd 回答「反向传播是什么」,nanoGPT 回答「GPT 训练由哪几件事组成」,llm.c 回答「剥掉框架后还能多快」,nanochat 回答「全栈 chatbot 怎么搭」。


7. 代码地图(导航索引)

按主题跳源码,符号名可直接 grep:

主题文件符号
模型总装model.pyGPTGPTConfig
因果自注意力model.pyCausalSelfAttention
残差块(pre-LN)model.pyBlock
权重初始化model.py_init_weightsGPT.__init__c_proj 缩放段
加载 GPT-2 权重model.pyGPT.from_pretrained
优化器分组model.pyconfigure_optimizers
MFU 估算model.pyestimate_mfu
自回归生成model.pygenerate
训练主循环train.py顶层 while True 循环(train.py:255
取 batchtrain.pyget_batch
学习率调度train.pyget_lr
评估train.pyestimate_loss
配置覆盖configurator.py顶层 for arg in sys.argv[1:] 循环
字符级数据准备data/shakespeare_char/prepare.py顶层脚本
OpenWebText 准备data/openwebtext/prepare.py顶层脚本、process
采样入口sample.py顶层脚本
基准测试bench.py顶层脚本