- Authors

- Name
- Youngju Kim
- @fjvbn20031

引言
ChatGPT、Claude、Gemini —— 我们每天使用的 AI,其核心都是 GPT(Generative Pre-trained Transformer) 架构。但你亲手做过一个吗?
在这个系列里,我们会以 Andrej Karpathy 的 nanoGPT 为基础,用家里的 GPU 把一个语言模型从零训练出来。它是大模型的缩小版,但原理和 GPT-4 完全相同。
为什么要自己动手做?
- 只读论文只能理解 30%,亲自写代码则能理解 90%
- 我们有 GPU 服务器,可以真正拿来训练(GB10 128GB!)
- 简历上写"从零训练过 LLM 的经验" —— 一个差异化亮点
- 读 AI 论文时,会产生"啊,就是这里!"这样的直觉
GPT 架构核心
GPT 是纯解码器(Decoder-only)Transformer。核心可以归纳为 3 点:
1. 分词(Tokenization)
这是把文本转换成数字的第一步。
# Character-level 分词器(最简单)
text = "hello world"
chars = sorted(list(set(text)))
# chars = [' ', 'd', 'e', 'h', 'l', 'o', 'r', 'w']
stoi = {ch: i for i, ch in enumerate(chars)} # char → int
itos = {i: ch for i, ch in enumerate(chars)} # int → char
encode = lambda s: [stoi[c] for c in s]
decode = lambda l: ''.join([itos[i] for i in l])
print(encode("hello")) # [3, 2, 4, 4, 5]
print(decode([3, 2, 4, 4, 5])) # "hello"
实际的 GPT 使用 BPE(Byte-Pair Encoding) 分词器:
import tiktoken
enc = tiktoken.get_encoding("gpt2")
tokens = enc.encode("让我们自己搭建一个 GPT!")
print(tokens) # [171, 120, 230, 168, 245, ...]
print(len(tokens)) # ~15 tokens
2. Self-Attention(核心中的核心)
学习"每个 token 应该在多大程度上关注其他所有 token"。
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttention(nn.Module):
def __init__(self, embed_dim, head_dim):
super().__init__()
self.query = nn.Linear(embed_dim, head_dim, bias=False)
self.key = nn.Linear(embed_dim, head_dim, bias=False)
self.value = nn.Linear(embed_dim, head_dim, bias=False)
def forward(self, x):
B, T, C = x.shape
q = self.query(x) # (B, T, head_dim)
k = self.key(x) # (B, T, head_dim)
v = self.value(x) # (B, T, head_dim)
# Attention scores
weights = q @ k.transpose(-2, -1) # (B, T, T)
weights = weights * (C ** -0.5) # Scale
# Causal mask — 未来的 token 不可见!
mask = torch.tril(torch.ones(T, T))
weights = weights.masked_fill(mask == 0, float('-inf'))
weights = F.softmax(weights, dim=-1)
out = weights @ v # (B, T, head_dim)
return out
核心直觉:在预测 "The cat sat on the ___" 中的空白处时,会给 "cat" 和 "sat" 分配较高的 attention weight。
3. Transformer Block
class TransformerBlock(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
head_dim = embed_dim // num_heads
self.heads = nn.ModuleList([
SelfAttention(embed_dim, head_dim)
for _ in range(num_heads)
])
self.proj = nn.Linear(embed_dim, embed_dim)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, 4 * embed_dim),
nn.GELU(),
nn.Linear(4 * embed_dim, embed_dim),
)
self.ln1 = nn.LayerNorm(embed_dim)
self.ln2 = nn.LayerNorm(embed_dim)
def forward(self, x):
# Multi-Head Attention + Residual
attn_out = torch.cat([h(self.ln1(x)) for h in self.heads], dim=-1)
x = x + self.proj(attn_out)
# Feed-Forward + Residual
x = x + self.ffn(self.ln2(x))
return x
完整的 GPT 模型
class MicroGPT(nn.Module):
def __init__(self, vocab_size, embed_dim=384, num_heads=6,
num_layers=6, block_size=256):
super().__init__()
self.token_emb = nn.Embedding(vocab_size, embed_dim)
self.pos_emb = nn.Embedding(block_size, embed_dim)
self.blocks = nn.Sequential(*[
TransformerBlock(embed_dim, num_heads)
for _ in range(num_layers)
])
self.ln_f = nn.LayerNorm(embed_dim)
self.head = nn.Linear(embed_dim, vocab_size)
def forward(self, idx, targets=None):
B, T = idx.shape
tok_emb = self.token_emb(idx) # (B, T, embed_dim)
pos_emb = self.pos_emb(torch.arange(T)) # (T, embed_dim)
x = tok_emb + pos_emb
x = self.blocks(x)
x = self.ln_f(x)
logits = self.head(x) # (B, T, vocab_size)
loss = None
if targets is not None:
loss = F.cross_entropy(
logits.view(-1, logits.size(-1)),
targets.view(-1)
)
return logits, loss
def generate(self, idx, max_new_tokens):
for _ in range(max_new_tokens):
logits, _ = self(idx[:, -256:]) # block_size 限制
probs = F.softmax(logits[:, -1, :], dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
idx = torch.cat([idx, next_token], dim=1)
return idx
模型大小对比:
| 模型 | 参数量 | 层数 | 训练时间(1 GPU) |
|---|---|---|---|
| 我们的 MicroGPT | 10M | 6 | 约 30 分钟 |
| GPT-2 Small | 124M | 12 | 约数天 |
| GPT-3 | 175B | 96 | 数千 GPU-天 |
| GPT-4 | ~1.8T(估计) | ? | ? |
实践:用 Shakespeare 训练
数据准备
# 在 spark01(GB10 128GB)上运行
cd ~/nanoGPT
python3 data/shakespeare_char/prepare.py
# → train: 1,003,854 tokens / val: 111,540 tokens
开始训练
python3 train.py config/train_shakespeare_char.py \
--device=cuda \
--max_iters=5000 \
--eval_interval=500 \
--log_interval=100
生成结果(5000 次迭代后)
ROMEO:
What say you to this? Let me not stay a whit;
And yet I feel the thing I have forgot
To take upon the honour of my word.
从零生成了莎士比亚风格的文本!
下一步:韩语 GPT
- 训练韩语分词器(SentencePiece BPE)
- 收集 Namuwiki / 新闻数据
- 训练 MicroGPT 500M(spark01 128GB)
- 通过 LoRA 微调 打造对话模型
系列路线图
| 期 | 主题 | 状态 |
|---|---|---|
| 第 1 期 | 用 nanoGPT 理解 GPT(本文) | ✅ |
| 第 2 期 | 制作韩语分词器 | 🔜 |
| 第 3 期 | 训练 500M 韩语 GPT | 🔜 |
| 第 4 期 | 用 RLHF 打造对话模型 | 🔜 |
| 第 5 期 | 从零开始的图像生成模型(DDPM) | 🔜 |
📝 小测验 — 打造属于自己的 GPT(点击查看!)
Q1. GPT 是 Encoder 还是 Decoder 结构? ||Decoder-only Transformer||
Q2. Self-Attention 中 Causal Mask 的作用是什么? ||阻止看到未来的 token —— 为了自回归生成,用下三角矩阵进行掩码||
Q3. 计算 Attention score 时为什么要除以 sqrt(d_k)? ||内积值变大会让 softmax 变得极端,导致 gradient vanishing —— 通过缩放来稳定||
Q4. BPE 分词器和 Character-level 分词器的优缺点是什么? ||BPE:词表效率高(序列更短),但实现复杂。Character-level:简单,但序列变长,长距离依赖更难学习||
Q5. Residual Connection(残差连接)在 Transformer 中为什么重要? ||为了防止深层网络中 gradient 消失,把原始输入加回去 —— 提升训练稳定性和收敛速度||
Q6. Feed-Forward Network 为什么要先扩展 4 倍再压缩回去? ||为非线性变换获取更强的表达能力 —— 在更宽的空间中提取特征后,再压缩回原始维度||
Q7. 我们的 MicroGPT 参数量大约是多少,和 GPT-2 Small 相差几倍? ||约 10M,与 GPT-2 Small(124M)相差约 12 倍||
Q8. 仅通过 next token prediction 训练,为什么就能理解语言? ||要准确预测下一个 token,就必须内在地学会语法、语义、上下文乃至世界知识 —— 压缩即是理解||
📚 参考资料 & GitHub 参考
原始代码
- nanoGPT — Andrej Karpathy 的最小 GPT 实现: github.com/karpathy/nanoGPT
- minGPT — nanoGPT 的前身,教学用实现: github.com/karpathy/minGPT
- microGPT 分析(基于本博客): github.com/fjvbn2003/ai-model-analysis
原始论文
- Attention Is All You Need (2017) — Transformer 架构原始论文
- Improving Language Understanding by Generative Pre-Training (GPT-1, 2018)
- Language Models are Unsupervised Multitask Learners (GPT-2, 2019)
视频讲座
- Andrej Karpathy — Let's build GPT from scratch — 2 小时的 nanoGPT 实现讲座(必看!)
- Andrej Karpathy — Let's reproduce GPT-2 (124M) — GPT-2 复现
📖 相关系列 & 推荐文章
- AI 数学完全指南 — 线性代数、微积分、信息论(理解 GPT 的必备知识)
- GPT 系列论文完全解析:从 GPT-1 到 GPT-4 — GPT 演化脉络
- torchaudio 完全指南 — 音频 AI(GPT 之后的多模态)
- torchvision 完全指南 — 视觉 AI(ViT = Vision Transformer)
- vLLM 推理优化指南 — 想要部署你训练好的 GPT 时
- LLM 量化 GPTQ/AWQ/GGUF — 模型轻量化