# nanoGPT 源码解读

> 逐行读懂 Karpathy 的 nanoGPT——约 300 行模型 + 300 行训练。

- 作者：David（道雾轩）
- 最后更新：2026-10-01
- 原文：https://daiw.org/manual/nanogpt
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 序 · 六百行，看懂一个 GPT 是怎么长出来的

「大模型」这三个字，今天被说得神乎其神。但如果你把工程脚手架——分布式、混合精度、各种加速——统统剥掉，只留下「一个 GPT 的本体」，它会小到让你意外：

- 模型定义 [`model.py`](https://github.com/karpathy/nanoGPT/blob/master/model.py)：**约 300 行**。
- 训练脚本 [`train.py`](https://github.com/karpathy/nanoGPT/blob/master/train.py)：**约 300 行**。

这就是 [nanoGPT](https://github.com/karpathy/nanoGPT)——Andrej Karpathy 写的、以干净著称的 GPT 最小实现。它不玩花活，却结构完整：GPT-2、GPT-3、乃至今天的 Llama、Qwen，在**骨架**上和它是同一套东西，区别只在规模和几处零件的替换。

所以这个专栏的目标很直接：

> **带你把这六百行，一行一行读懂。** 读完，你不再把大模型当黑箱——你会知道一段文本是怎么变成数字的，数字是怎么流过一层层网络的，「注意力」到底在算什么，梯度又是怎么把这一切调准的。

## 为什么值得逐行读它

市面上讲 Transformer 的文章不少，但大多停在「原理示意图」那一层：画几个方块、几个箭头，公式一摆，就算讲完了。可真正让人「懂」的，往往是那些图里没有、只有代码里才有的细节——

- 为什么 Q、K、V 三个投影要**合成一个矩阵**一次算完？
- 「因果掩码」在代码里其实只是一行 `tril`，它凭什么就让模型「只能看过去」？
- 权重初始化为什么要给残差投影乘一个 `1/√(2·n_layer)`？
- 训练时那句 `loss = loss / gradient_accumulation_steps` 到底在补偿什么？

这些「魔鬼细节」，恰恰是原理与工程之间的那道坎。nanoGPT 短到你能把每一处都盯着看，又真到每一处都不是玩具。**逐行读它，是把「大概懂」变成「真的懂」性价比最高的一条路。**

<Callout type="info">
  本专栏的方法：**代码在左，人话在右**。每章先贴出 nanoGPT 的真实源码片段（不改写、不简化），再逐行翻译成大白话，配上类比和图。我们不假设你记得线性代数细节——需要的数学（矩阵乘法、softmax、交叉熵）会在用到时就地补一小段，够用就好。
</Callout>

## 路线图

我们按「一段文本 → 一个会写字的模型」这条真实的数据流，分五站走：

**开篇 · 先认识它** —— nanoGPT 是什么、仓库长什么样，以及五分钟把一个莎士比亚模型跑起来，先有个手感。

**第一站 · 数据：文本怎么变成数字** —— 读 `data/shakespeare_char/prepare.py`。一段莎士比亚全集，怎么变成硬盘上一串整数(`train.bin`)。字符级分词、词表、`train/val` 划分，全在这一小段里。

**第二站 · 模型：一个 GPT 只有五个零件** —— 读 `model.py`。先看全景：`LayerNorm`、`CausalSelfAttention`、`MLP`、`Block`、`GPT` 五个类怎么拼在一起；再逐行啃下最关键、也最难的**注意力**。

**第三站 · 训练：让模型学起来** —— 读 `train.py`。数据怎么成批喂进来、学习率怎么调度、梯度累积/混合精度/梯度裁剪各自在解决什么、`checkpoint` 怎么存怎么续。

**第四站 · 采样：让模型开口** —— 读 `generate` 与 `sample.py`。训练好的模型怎么一个 token 一个 token 地把新文本「吐」出来，`temperature` 和 `top-k` 又是怎么控制它的「脑洞」。

**第五站 · 进阶：更大、更快、更省** —— `from_pretrained` 装载 GPT-2、DDP 多卡、`torch.compile`、Flash Attention、MFU 这些让它「上规模」的招数。

<Callout type="warn">
  本专栏聚焦 nanoGPT **仓库本身**。所有代码均来自其公开源码（截至撰写时的 `master` 分支），行号与措辞可能随上游更新略有出入，以你本地 `git clone` 到的版本为准。我们只讲清楚「它为什么这么写」，不做二次发挥。
</Callout>

## 配套仓库

强烈建议边读边把仓库开在旁边：

```bash
git clone https://github.com/karpathy/nanoGPT.git
cd nanoGPT
pip install torch numpy transformers datasets tiktoken wandb tqdm
```

文章讲「为什么」，代码给「具体长什么样」，两边对照着看，效率最高。

## 读之前，你最好有

- **会读 Python**：看得懂类、函数、`for` 循环就行，不需要是高手。
- **一点点 PyTorch**：知道 `Tensor` 大概是个多维数组、`nn.Module` 是网络的积木。完全没用过也没关系，关键处会解释。
- **不怕数学，但不必精通**：矩阵乘法、softmax、求导的「感觉」有就够，细节我们随讲随补。
- **一颗想拆开看的好奇心**：读完你会发现，GPT 一点都不魔法，全是**扎实、可追问的工程**。

准备好了，我们先花五分钟认识它、把它跑起来：[nanoGPT 是什么，为什么值得逐行读](https://daiw.org/manual/nanogpt/what-is-nanogpt)。

## 本专栏篇目

- [nanoGPT 源码解读](https://daiw.org/manual/nanogpt.md): 逐行读懂 Karpathy 的 nanoGPT——约 300 行模型 + 300 行训练。

### 开篇 · 先认识它

- [nanoGPT 是什么，为什么值得逐行读](https://daiw.org/manual/nanogpt/what-is-nanogpt.md): 一句话讲清 GPT 在做什么，给 nanoGPT 仓库画张地图，并用五分钟跑通一个字符级莎士比亚模型，先建立手感，再动手拆代码。

### 数据 · 文本怎么变成数字

- [数据 · 一段莎士比亚怎么变成一串数字](https://daiw.org/manual/nanogpt/data-prepare-char.md): 逐行拆解 data/shakespeare_char/prepare.py——下载文本、建字符表、编码解码、划分训练/验证集、存成 train.bin 与 meta.pkl。模型只认数字，这一章就是把文字翻译成数字的全过程。

### 模型 · 一个 GPT 只有五个零件

- [模型全景 · 一个 GPT 只有五个零件](https://daiw.org/manual/nanogpt/model-overview.md): 鸟瞰 model.py——LayerNorm、CausalSelfAttention、MLP、Block、GPT 五个类怎么拼成一台 GPT，数据怎么从 token 编号流到「下一个 token 的分数」，以及 GPTConfig 七个超参各自决定什么。
- [注意力 · CausalSelfAttention 逐行拆解](https://daiw.org/manual/nanogpt/attention.md): 一行不跳地读完 nanoGPT 的因果自注意力——qkv 合并投影、多头拆分、缩放点积、因果掩码、Flash Attention 与手写版的对照，把 GPT 里最关键也最容易被示意图糊弄过去的部分彻底讲透。
- [其余三个零件 · LayerNorm、MLP、Block](https://daiw.org/manual/nanogpt/the-other-pieces.md): 啃完注意力，剩下三个零件轻松得像甜点。逐行读 nanoGPT 的 LayerNorm（为什么自己写）、MLP（先胀 4 倍再压回）、Block（Pre-LN + 残差怎么把注意力和 MLP 拼成一层）。
- [搭起整台机器 · GPT 的 init 与 forward](https://daiw.org/manual/nanogpt/gpt-assembly.md): 逐行读 GPT 类——__init__ 怎么把词嵌入、位置嵌入、一摞 Block、输出层拼起来，forward 怎么从 token 编号一路算到 loss。外加三个精妙细节：权重绑定、推理期只算最后一个位置、以及那个 0.02/√(2·n_layer) 的残差缩放初始化。

### 训练 · 让模型学起来

- [训练的起点 · 配置与初始化](https://daiw.org/manual/nanogpt/train-setup.md): 读 train.py 的开头——一大堆超参当全局变量摊着，configurator.py 用一句 exec 实现「穷人配置法」从命令行覆盖它们；以及三种起手式：从零 scratch、断点 resume、还是从 GPT-2 权重接着练。
- [优化器 · 哪些参数该做权重衰减](https://daiw.org/manual/nanogpt/optimizer.md): 逐行读 configure_optimizers——一个值得停下来看的小设计：把参数按维度分两组，二维以上的权重矩阵做权重衰减、一维的偏置和 LayerNorm 增益不做。外加 fused AdamW 的小心机。
- [数据加载 · get_batch 怎么喂饭](https://daiw.org/manual/nanogpt/get-batch.md): 逐行读 get_batch——用 np.memmap 把几百 MB 的 train.bin 当数组用而不撑爆内存，随机取一批窗口，输入 x 与目标 y 只差一个位置（这就是「预测下一个」的全部监督信号），再用 pin_memory 异步搬上 GPU。
- [训练主循环 · 一步到底怎么走](https://daiw.org/manual/nanogpt/training-loop.md): 逐行读 train.py 的主循环——学习率的 warmup + 余弦退火、梯度累积怎么用小显存模拟大 batch、混合精度的 GradScaler 在防什么、梯度裁剪、以及评估与 checkpoint。一步「前向—反向—更新」的全部细节。

### 采样 · 让模型开口

- [采样 · 让模型开口](https://daiw.org/manual/nanogpt/generate-and-sample.md): 逐行读 generate——自回归地「猜一个、接上、再猜」，以及 temperature 和 top-k 怎么调节模型的「脑洞」。再看 sample.py 怎么载入模型、用 meta.pkl 把吐出的数字翻译回文本。

### 进阶 · 更大、更快、更省

- [装载 GPT-2 · from_pretrained 与 Conv1D 转置](https://daiw.org/manual/nanogpt/from-pretrained.md): 逐行读 from_pretrained——怎么把 OpenAI 公开的 GPT-2 权重搬进 nanoGPT。重点是那个烧脑的坑：GPT-2 用的 Conv1D 模块把权重存成了转置，所以四类权重要 .t() 一下才能对上 nn.Linear。
- [更大、更快、更省 · DDP、编译、Flash 与 MFU](https://daiw.org/manual/nanogpt/scaling-and-speed.md): 一次收拾 nanoGPT 让训练上规模的几个开关——DDP 多卡数据并行（只在最后一个 micro-step 同步梯度）、torch.compile 编译、Flash Attention、混合精度，以及那个衡量「GPU 跑得多满」的 MFU 指标。

### 收尾

- [收尾 · 从 bit 到会写莎士比亚，再往上是什么](https://daiw.org/manual/nanogpt/recap.md): 回望整趟旅程——六百行代码，从一段莎士比亚长出一个会写字的 GPT，没剩一处黑箱。再把这副读懂了的骨架接到真实世界：它每一个零件，到了 Llama、DeepSeek 那儿都变成了一个可拨的旋钮。
