介绍:Andrej Karpathy 技能库
本课程将带你深入了解 Andrej Karpathy 的编程技能和工程哲学。Karpathy 是前 Tesla AI 总监、OpenAI 联合创始人,他的"从零开始"教学风格影响了无数开发者。通过本课程,你将学习如何将这些技能应用于实际开发中。
Andrej Karpathy 是谁?
Andrej Karpathy 是深度学习领域最有影响力的教育者之一。他的核心贡献包括:在 Stanford 讲授 CS231n(计算机视觉课程)、担任 Tesla AI 总监领导 Autopilot 团队、创建 YouTube 频道分享"从零开始构建"系列教程。
Karpathy 的核心理念:
- 从零开始构建 — 不依赖高级框架,手动实现每一个组件,真正理解底层原理
- 教学即学习 — 通过教授他人来深化自己的理解(费曼学习法)
- 简洁至上 — 用最少的代码实现最核心的功能,拒绝不必要的复杂性
- 工程实践导向 — 理论结合实践,每个概念都配有可运行的代码
Karpathy 技能体系
神经网络从零构建
micrograd 项目:用 150 行 Python 实现自动微分引擎,理解反向传播的数学本质。
大语言模型构建
makemore / nanoGPT:从字符级语言模型到 GPT 架构,逐步构建完整的语言模型。
视频处理工程
video2x / vid2denseflow:视频超分辨率和光流估计的工程实现方法。
AI 工具开发
pic2card / arxiv-sanity:实用的 AI 应用开发,涵盖图像处理和信息检索。
micrograd:理解自动微分
# micrograd — Karpathy 的自动微分引擎
# 完整实现不到 150 行代码
class Value:
"""存储标量值及其梯度"""
def __init__(self, data, _children=(), _op=''):
self.data = data
self.grad = 0.0
self._backward = lambda: None
self._prev = set(_children)
self._op = _op
def __add__(self, other):
other = other if isinstance(other, Value) else Value(other)
out = Value(self.data + other.data, (self, other), '+')
def _backward():
self.grad += 1.0 * out.grad
other.grad += 1.0 * out.grad
out._backward = _backward
return out
def __mul__(self, other):
other = other if isinstance(other, Value) else Value(other)
out = Value(self.data * other.data, (self, other), '*')
def _backward():
self.grad += other.data * out.grad
other.grad += self.data * out.grad
out._backward = _backward
return out
def relu(self):
out = Value(max(0, self.data), (self,), 'ReLU')
def _backward():
self.grad += (out.data > 0) * out.grad
out._backward = _backward
return out
def backward(self):
"""反向传播:计算所有参数的梯度"""
topo = []
visited = set()
def build_topo(v):
if v not in visited:
visited.add(v)
for child in v._prev:
build_topo(child)
topo.append(v)
build_topo(self)
self.grad = 1.0
for v in reversed(topo):
v._backward()
def __repr__(self):
return f"Value(data={self.data}, grad={self.grad})"
# 使用示例:构建简单的计算图
a = Value(2.0)
b = Value(-3.0)
c = Value(10.0)
d = a * b + c # d = 2 * (-3) + 10 = 4
d.backward()
print(f"a.grad = {a.grad}") # -3.0 (dd/da = b)
print(f"b.grad = {b.grad}") # 2.0 (dd/db = a)
print(f"c.grad = {c.grad}") # 1.0 (dd/dc = 1)
micrograd 是 Karpathy 教学方法的完美体现:用不到 150 行代码实现完整的自动微分引擎。核心思想是构建计算图(DAG),每个 Value 节点存储数据和梯度。backward() 通过拓扑排序实现反向传播。这个实现让你从根本上理解 PyTorch 的 autograd 是如何工作的。
Karpathy 的学习方法论
四步学习法:
- 阅读论文 — 原始论文是最权威的知识来源,不要只看博客摘要
- 手动实现 — 用纯 Python/NumPy 实现,不用框架,理解每一行
- 可视化调试 — 用 Jupyter Notebook 可视化中间结果,建立直觉
- 教授他人 — 写博客、录视频,用自己的话解释,发现理解盲区
Karpathy 的代表作品
# Karpathy 的 GitHub 开源项目(按学习难度排序)
# 1. micrograd — 自动微分引擎(入门级)
# github.com/karpathy/micrograd
# 约 150 行 Python,理解反向传播
# pip install micrograd
# 2. nn-zero-to-hero — 神经网络零到英雄系列(初级)
# YouTube 播放列表,从 micrograd 到 GPT
# 包含 6 个独立项目,逐步进阶
# 3. makemore — 字符级语言模型(中级)
# github.com/karpathy/makemore
# Bigram → MLP → RNN → WaveNet → Transformer 逐步构建
# 4. nanoGPT — 最简 GPT 训练框架(中高级)
# github.com/karpathy/nanoGPT
# 可以在单 GPU 上训练 GPT-2 规模模型
# 训练 Shakespeare 文本只需要几分钟
# 5. minbpe — 最简 BPE tokenizer(中级)
# github.com/karpathy/minbpe
# 约 100 行代码实现 Byte Pair Encoding
# 6. arxiv-sanity — 论文推荐系统(高级)
# github.com/karpathy/arxiv-sanity-lite
# 检索增强的论文推荐,完整的 Web 应用
# 使用 nanoGPT 快速训练
# git clone https://github.com/karpathy/nanoGPT
# cd nanoGPT
# python data/shakespeare_char/prepare.py
# python train.py config/train_shakespeare_char.txt
# python sample.py --out_dir=out-shakespeare-char
Karpathy 的项目按难度递进:micrograd 是最简单的起点,理解自动微分;nn-zero-to-hero 系列覆盖完整知识体系;makemore 展示语言模型的演进;nanoGPT 是实战训练工具;minbte 深入 tokenizer。建议按照这个顺序学习,每个项目都有配套的 YouTube 视频讲解。
Karpathy 的核心教学理念是什么?
核心概念:从零构建神经网络
本节将深入探讨 Karpathy 的核心教学项目——如何从零构建一个多层感知机(MLP),这是理解深度学习的基础。
从 micrograd 到神经网络
基于 micrograd 的自动微分引擎,我们可以构建完整的神经网络。Karpathy 展示了如何用最少的代码实现一个可以训练的 MLP。
# 基于 micrograd 构建神经网络
# Karpathy 风格:简洁、自包含、可运行
import random
from micrograd import Value
class Module:
"""神经网络模块基类"""
def parameters(self):
return []
def zero_grad(self):
for p in self.parameters():
p.grad = 0.0
class Neuron(Module):
"""单个神经元:w * x + b"""
def __init__(self, nin, nonlin=True):
# Xavier 初始化
self.w = [Value(random.uniform(-1, 1) * (6/nin)**0.5)
for _ in range(nin)]
self.b = Value(0.0)
self.nonlin = nonlin
def __call__(self, x):
act = sum((wi * xi for wi, xi in zip(self.w, x)), self.b)
return act.relu() if self.nonlin else act
def parameters(self):
return self.w + [self.b]
class Layer(Module):
"""全连接层"""
def __init__(self, nin, nout, **kwargs):
self.neurons = [Neuron(nin, **kwargs) for _ in range(nout)]
def __call__(self, x):
out = [n(x) for n in self.neurons]
return out[0] if len(out) == 1 else out
def parameters(self):
return [p for n in self.neurons for p in n.parameters()]
class MLP(Module):
"""多层感知机"""
def __init__(self, nin, nouts):
sz = [nin] + nouts
self.layers = [Layer(sz[i], sz[i+1], nonlin=i!=len(nouts)-1)
for i in range(len(nouts))]
def __call__(self, x):
for layer in self.layers:
x = layer(x)
return x
def parameters(self):
return [p for l in self.layers for p in l.parameters()]
# 使用:构建一个简单的分类器
model = MLP(3, [4, 4, 1]) # 3输入 → 4隐藏 → 4隐藏 → 1输出
print(f"参数数量: {len(model.parameters())}") # 3*4+4 + 4*4+4 + 4*1+1 = 33
三层架构设计:Neuron 封装单个神经元的计算(wx + b + 激活函数);Layer 管理一组神经元;MLP 将多个层串联。所有类继承 Module 基类,统一参数管理和梯度清零。这种设计模仿了 PyTorch 的 nn.Module 体系,但实现极其简洁。
训练循环:梯度下降
# Karpathy 风格的训练循环
# 没有框架依赖,只有纯 Python + micrograd
# 准备训练数据(简单的二分类)
xs = [
[2.0, 3.0, -1.0],
[3.0, -1.0, 0.5],
[0.5, 1.0, 1.0],
[1.0, 1.0, -1.0],
]
ys = [1.0, -1.0, -1.0, 1.0] # 目标标签
# 训练循环
model = MLP(3, [4, 4, 1])
for k in range(100):
# 前向传播
ypred = [model(x) for x in xs]
# 计算损失(MSE)
loss = sum((ypred_i - ys_i)**2 for ypred_i, ys_i in zip(ypred, ys))
# 反向传播
model.zero_grad() # 关键:清零梯度
loss.backward()
# 参数更新(梯度下降)
learning_rate = 0.05
for p in model.parameters():
p.data -= learning_rate * p.grad
if k % 10 == 0:
print(f"Step {k}: loss = {loss.data:.4f}")
# 最终预测
print("\n预测结果:")
for x, y_true in zip(xs, ys):
y_pred = model(x)
print(f" 输入: {x} → 预测: {y_pred.data:.4f} (实际: {y_true})")
# 学习率调度策略(Karpathy 推荐)
# 1. 开始用较大的 lr(0.1),观察 loss 下降
# 2. 当 loss 下降变慢时,降低 lr(0.01)
# 3. 最后用很小的 lr(0.001)精细调整
# 4. 如果 loss 爆炸(NaN),降低 lr 重新开始
Karpathy 的训练循环四步法:1) 前向传播计算预测值;2) 计算损失函数(MSE 用于回归,交叉熵用于分类);3) 反向传播计算梯度;4) 梯度下降更新参数。关键细节:每轮必须 zero_grad() 清零梯度,否则梯度会累积。学习率是最重要的超参数,需要耐心调试。
makemore:构建语言模型
Karpathy 的 makemore 系列是学习语言模型的绝佳教程,从字符级 bigram 模型逐步演进到 Transformer。
# makemore 系列第一步:Bigram 语言模型
# 用纯 Python 实现,理解语言模型的本质
# 准备数据
words = ["emma", "olivia", "ava", "sophia", "isabella"]
# 统计 bigram 频率
bigram_counts = {}
for word in words:
chs = ['.'] + list(word) + ['.'] # . 表示开始/结束
for ch1, ch2 in zip(chs, chs[1:]):
bigram = (ch1, ch2)
bigram_counts[bigram] = bigram_counts.get(bigram, 0) + 1
# 构建概率矩阵
import torch
chars = sorted(list(set(''.join(words) + '.')))
stoi = {s: i for i, s in enumerate(chars)}
itos = {i: s for s, i in stoi.items()}
N = torch.zeros((len(chars), len(chars)), dtype=torch.int32)
for (ch1, ch2), count in bigram_counts.items():
N[stoi[ch1], stoi[ch2]] = count
# 转换为概率(行归一化)
P = (N + 1).float() # +1 平滑(拉普拉斯平滑)
P /= P.sum(dim=1, keepdim=True)
# 采样生成新名字
g = torch.Generator().manual_seed(42)
for _ in range(5):
out = []
ix = 0 # 从 . 开始
while True:
# 按概率采样下一个字符
p = P[ix]
ix = torch.multinomial(p, num_samples=1, generator=g).item()
if ix == 0: # 遇到 . 则结束
break
out.append(itos[ix])
print(''.join(out))
# 计算损失(负对数似然)
log_likelihood = 0.0
n = 0
for word in words:
chs = ['.'] + list(word) + ['.']
for ch1, ch2 in zip(chs, chs[1:]):
prob = P[stoi[ch1], stoi[ch2]]
log_likelihood += torch.log(prob)
n += 1
nll = -log_likelihood / n
print(f"\n平均负对数似然: {nll:.4f}") # 越小越好
makemore 的第一步是 bigram 模型:统计相邻字符的共现频率,构建概率矩阵,然后按概率采样生成新文本。关键概念:拉普拉斯平滑(+1 避免零概率)、负对数似然(NLL)作为损失函数。这个简单模型是理解更复杂语言模型的基础。后续教程会将其扩展为 MLP、RNN、WaveNet 和 Transformer。
Karpathy 的调试技巧
"Shape 是深度学习的一半" — Karpathy 强调,理解张量维度是调试神经网络最重要的技能。以下是他的调试检查清单:
- 打印每层的输出 shape,确认维度传递正确
- 检查梯度的 shape 是否与参数一致
- 可视化 loss 曲线,确认在下降
- 用极小的数据集测试,确保能过拟合(overfit a single batch)
- 检查初始 loss 是否接近 -ln(1/num_classes)(随机猜测的 loss)
- 使用 torch.autograd.gradcheck 验证自定义梯度计算
Karpathy 的训练循环包含哪四个步骤?
实践应用:nanoGPT 与 GPT 架构
本节将展示 Karpathy 如何从零构建 GPT(Generative Pre-trained Transformer),这是他最著名的教学项目 nanoGPT 的核心内容。
Self-Attention 机制详解
Transformer 的核心是自注意力机制。Karpathy 坚持先用纯 NumPy/PyTorch 手动实现,再使用框架的高级 API。
# Self-Attention 的 Karpathy 式实现
# 从数学公式到代码,每一步都清晰可见
import torch
import torch.nn as nn
import math
class Head(nn.Module):
"""单头自注意力"""
def __init__(self, head_size, n_embd, block_size, dropout=0.1):
super().__init__()
self.key = nn.Linear(n_embd, head_size, bias=False)
self.query = nn.Linear(n_embd, head_size, bias=False)
self.value = nn.Linear(n_embd, head_size, bias=False)
# 因果掩码:防止看到未来的 token
self.register_buffer('tril', torch.tril(
torch.ones(block_size, block_size)
))
self.dropout = nn.Dropout(dropout)
def forward(self, x):
B, T, C = x.shape
# 计算 Key 和 Query
k = self.key(x) # (B, T, head_size)
q = self.query(x) # (B, T, head_size)
# 计算注意力分数(缩放点积)
wei = q @ k.transpose(-2, -1) * (C ** -0.5)
# wei shape: (B, T, T)
# 应用因果掩码(未来位置设为 -inf)
wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf'))
# Softmax 归一化
wei = torch.nn.functional.softmax(wei, dim=-1)
wei = self.dropout(wei)
# 加权聚合 Value
v = self.value(x) # (B, T, head_size)
out = wei @ v # (B, T, head_size)
return out
class MultiHeadAttention(nn.Module):
"""多头自注意力"""
def __init__(self, num_heads, head_size, n_embd, block_size, dropout=0.1):
super().__init__()
self.heads = nn.ModuleList([
Head(head_size, n_embd, block_size, dropout)
for _ in range(num_heads)
])
self.proj = nn.Linear(num_heads * head_size, n_embd)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 拼接所有头的输出
out = torch.cat([h(x) for h in self.heads], dim=-1)
out = self.dropout(self.proj(out))
return out
# 关键理解点:
# 1. Key = "我有什么信息" → 每个位置的信息摘要
# 2. Query = "我在找什么" → 当前位置想关注什么
# 3. Value = "我的实际内容" → 传递给下游的实际信息
# 4. Attention = softmax(Q·K^T / sqrt(d)) · V
# 5. 缩放因子 sqrt(d) 防止点积过大导致 softmax 饱和
Karpathy 将 Self-Attention 分解为四个直观组件:Key(每个位置的信息摘要)、Query(当前位置想关注什么)、Value(传递的实际内容)、注意力分数(Query 和 Key 的点积,决定关注程度)。因果掩码确保只能看到之前的 token,这对语言模型至关重要。缩放因子 sqrt(d) 防止 softmax 饱和。
完整的 GPT 模型
# nanoGPT 核心 — Karpathy 的最小 GPT 实现
# 约 200 行代码,训练即可生成文本
class FeedForward(nn.Module):
"""位置级前馈网络"""
def __init__(self, n_embd, dropout=0.1):
super().__init__()
self.net = nn.Sequential(
nn.Linear(n_embd, 4 * n_embd), # 内部维度是 4 倍
nn.GELU(), # GPT 使用 GELU 激活
nn.Linear(4 * n_embd, n_embd),
nn.Dropout(dropout),
)
def forward(self, x):
return self.net(x)
class Block(nn.Module):
"""Transformer Block:Attention + FFN"""
def __init__(self, n_embd, n_head, block_size, dropout=0.1):
super().__init__()
head_size = n_embd // n_head
self.sa = MultiHeadAttention(n_head, head_size, n_embd, block_size, dropout)
self.ffwd = FeedForward(n_embd, dropout)
self.ln1 = nn.LayerNorm(n_embd) # Pre-LN(GPT-2 风格)
self.ln2 = nn.LayerNorm(n_embd)
def forward(self, x):
# 残差连接 + Layer Norm
x = x + self.sa(self.ln1(x))
x = x + self.ffwd(self.ln2(x))
return x
class GPT(nn.Module):
"""完整的 GPT 模型"""
def __init__(self, vocab_size, n_embd=384, block_size=256,
n_head=6, n_layer=6, dropout=0.2):
super().__init__()
self.block_size = block_size
# Token 嵌入 + 位置嵌入
self.token_embedding = nn.Embedding(vocab_size, n_embd)
self.position_embedding = nn.Embedding(block_size, n_embd)
# Transformer Blocks
self.blocks = nn.Sequential(*[
Block(n_embd, n_head, block_size, dropout)
for _ in range(n_layer)
])
self.ln_f = nn.LayerNorm(n_embd)
self.lm_head = nn.Linear(n_embd, vocab_size)
def forward(self, idx, targets=None):
B, T = idx.shape
# 嵌入
tok_emb = self.token_embedding(idx) # (B, T, n_embd)
pos_emb = self.position_embedding(
torch.arange(T, device=idx.device)
) # (T, n_embd)
x = tok_emb + pos_emb # (B, T, n_embd)
# Transformer
x = self.blocks(x)
x = self.ln_f(x)
# 输出 logits
logits = self.lm_head(x) # (B, T, vocab_size)
# 计算损失
if targets is not None:
loss = nn.functional.cross_entropy(
logits.view(-1, logits.size(-1)),
targets.view(-1)
)
return logits, loss
return logits, None
def generate(self, idx, max_new_tokens):
"""自回归生成文本"""
for _ in range(max_new_tokens):
# 裁剪到 block_size
idx_cond = idx[:, -self.block_size:]
logits, _ = self(idx_cond)
# 只取最后一个位置的 logits
logits = logits[:, -1, :]
# 采样
probs = torch.nn.functional.softmax(logits, dim=-1)
idx_next = torch.multinomial(probs, num_samples=1)
idx = torch.cat((idx, idx_next), dim=1)
return idx
# 使用:训练一个字符级 GPT
model = GPT(vocab_size=65, n_embd=128, block_size=64, n_head=4, n_layer=4)
print(f"参数量: {sum(p.numel() for p in model.parameters()):,}") # ~400K
nanoGPT 的三层架构:Block 包含 Self-Attention + FFN + 残差连接 + LayerNorm;GPT 将 Token/Position 嵌入 + N 个 Block + 输出层组合。Karpathy 的关键教学点:1) Pre-LN 比 Post-LN 更稳定;2) FFN 内部维度是嵌入维度的 4 倍;3) 生成时裁剪输入到 block_size。generate() 方法展示了自回归生成的完整流程。
优化器与学习率调度
# Karpathy 推荐的训练配置
# AdamW 优化器(权重衰减的 Adam)
optimizer = torch.optim.AdamW(
model.parameters(),
lr=3e-4, # 学习率(最敏感的超参数)
betas=(0.9, 0.999),
weight_decay=0.1, # L2 正则化
)
# 学习率预热 + 余弦衰减
from torch.optim.lr_scheduler import CosineAnnealingLR
warmup_steps = 100
total_steps = 10000
def get_lr(step):
"""带预热的余弦学习率调度"""
if step < warmup_steps:
return step / warmup_steps * 3e-4 # 线性预热
decay_ratio = (step - warmup_steps) / (total_steps - warmup_steps)
return 3e-4 * 0.5 * (1 + math.cos(math.pi * decay_ratio))
# 训练循环
for step in range(total_steps):
# 采样 mini-batch
ix = torch.randint(0, len(data) - block_size, (batch_size,))
x = torch.stack([data[i:i+block_size] for i in ix])
y = torch.stack([data[i+1:i+block_size+1] for i in ix])
# 前向 + 反向
logits, loss = model(x, y)
optimizer.zero_grad(set_to_none=True) # 更高效的梯度清零
loss.backward()
# 梯度裁剪(防止梯度爆炸)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 更新学习率并步进
lr = get_lr(step)
for param_group in optimizer.param_groups:
param_group['lr'] = lr
optimizer.step()
if step % 100 == 0:
print(f"Step {step}: loss={loss.item():.4f}, lr={lr:.6f}")
# Karpathy 的超参数调优建议:
# 1. 先用小模型确认 loss 能下降
# 2. 逐步增大模型规模
# 3. 监控训练/验证 loss 曲线
# 4. 过拟合 → 增加正则化/减少模型大小
# 5. 欠拟合 → 增大模型/训练更久/降低学习率
Karpathy 的训练配方:AdamW 优化器(lr=3e-4 是 Transformer 的"默认值")、余弦学习率调度带线性预热、梯度裁剪(max_norm=1.0)防止爆炸。关键调试策略:先在小模型上验证 loss 能正常下降,再逐步扩大规模。观察训练/验证 loss 曲线是判断过拟合/欠拟合的最佳方式。
可视化理解 Attention
Karpathy 强调可视化是理解 Attention 的最佳方式。通过观察注意力热力图,你可以直观地看到模型在关注什么。
# 可视化 Attention 权重(Karpathy 风格)
import torch
import matplotlib.pyplot as plt
def visualize_attention(attention_weights, tokens, head=0):
"""
可视化注意力权重矩阵
参数:
- attention_weights: (B, n_head, T, T) 注意力分数
- tokens: 当前输入的 token 列表
- head: 要可视化的注意力头
"""
# 取第一个样本的指定头
attn = attention_weights[0, head].detach().cpu().numpy()
T = attn.shape[0]
fig, ax = plt.subplots(figsize=(8, 8))
im = ax.imshow(attn[:T, :T], cmap='Blues', vmin=0, vmax=1)
# 标签
tick_labels = tokens[:T]
ax.set_xticks(range(T))
ax.set_yticks(range(T))
ax.set_xticklabels(tick_labels, rotation=90, fontsize=8)
ax.set_yticklabels(tick_labels, fontsize=8)
ax.set_xlabel("Key 位置")
ax.set_ylabel("Query 位置")
ax.set_title(f"Attention Head {head}")
plt.colorbar(im, ax=ax, label="注意力权重")
plt.tight_layout()
plt.savefig("attention_viz.png", dpi=150)
plt.show()
# 使用示例
tokens = list("Attention is all you need")
T = len(tokens)
# 模拟注意力权重
attn = torch.zeros(1, 4, T, T)
for i in range(T):
for j in range(i+1):
attn[0, 0, i, j] = 1.0 / (i - j + 1)
# 归一化
attn = torch.softmax(attn, dim=-1)
visualize_attention(attn, tokens, head=0)
# Karpathy 的可视化建议:
# 1. 观察对角线附近是否权重最高(局部关注)
# 2. 查看特殊 token(如标点)是否获得全局关注
# 3. 不同头的关注模式是否互补
# 4. 训练过程中注意力如何演化
Attention 可视化是 Karpathy 调试 Transformer 的核心工具。注意力热力图展示每个位置对其他位置的关注程度:颜色越深表示关注越强。好的模型通常展现局部关注(对角线附近)+ 特殊位置的全局关注模式。可视化不同头可以发现互补的关注策略——有的关注语法,有的关注语义。
Self-Attention 机制中的 Key、Query、Value 分别代表什么?
进阶主题:LLM 训练与工程实践
本节将探讨 Karpathy 在大语言模型训练和 AI 工程方面的进阶经验,包括数据准备、分布式训练和模型评估。
数据准备的最佳实践
# Karpathy 的数据准备流程
# 以训练 Shakespeare 文本生成器为例
# 1. 读取原始数据
with open('input.txt', 'r', encoding='utf-8') as f:
text = f.read()
# 2. 构建词汇表
chars = sorted(list(set(text)))
vocab_size = len(chars)
print(f"词汇量: {vocab_size}")
print(f"数据长度: {len(text)} 字符")
# 3. 字符级编码器
stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for i, ch in enumerate(chars)}
encode = lambda s: [stoi[c] for c in s]
decode = lambda l: ''.join([itos[i] for i in l])
# 4. 编码为张量
import torch
data = torch.tensor(encode(text), dtype=torch.long)
# 5. 划分训练/验证集(90/10)
n = int(0.9 * len(data))
train_data = data[:n]
val_data = data[n:]
# 6. 批量采样函数
def get_batch(split, block_size=256, batch_size=32, device='cuda'):
data = train_data if split == 'train' else val_data
ix = torch.randint(len(data) - block_size, (batch_size,))
x = torch.stack([data[i:i+block_size] for i in ix]).to(device)
y = torch.stack([data[i+1:i+block_size+1] for i in ix]).to(device)
return x, y
# 7. 评估函数
@torch.no_grad()
def estimate_loss(model, eval_iters=200):
model.eval()
losses = {}
for split in ['train', 'val']:
losses[split] = 0
for _ in range(eval_iters):
x, y = get_batch(split)
_, loss = model(x, y)
losses[split] += loss.item()
losses[split] /= eval_iters
model.train()
return losses
# BPE Tokenizer(更高级的方案)
# Karpathy 在 minbpe 项目中从零实现了 BPE
class BasicTokenizer:
"""最简 BPE 实现"""
def __init__(self):
self.merges = {} # 合并规则
self.vocab = {} # 词汇表
def train(self, text, vocab_size=256):
tokens = list(text.encode('utf-8'))
num_merges = vocab_size - 256
for i in range(num_merges):
# 统计相邻 token 对的频率
counts = {}
for pair in zip(tokens, tokens[1:]):
counts[pair] = counts.get(pair, 0) + 1
# 找到最高频的对
top_pair = max(counts, key=counts.get)
# 合并:用新 token 替换
new_token = 256 + i
tokens = self._merge(tokens, top_pair, new_token)
self.merges[top_pair] = new_token
self.vocab = {i: bytes([i]) for i in range(256)}
for (p0, p1), new in self.merges.items():
self.vocab[new] = self.vocab[p0] + self.vocab[p1]
def _merge(self, tokens, pair, new_token):
merged = []
i = 0
while i < len(tokens):
if i < len(tokens) - 1 and (tokens[i], tokens[i+1]) == pair:
merged.append(new_token)
i += 2
else:
merged.append(tokens[i])
i += 1
return merged
数据准备七步法:读取 → 构建词汇表 → 编码器 → 转张量 → 划分数据集 → 批量采样 → 评估函数。Karpathy 还从零实现了 BPE(Byte Pair Encoding),这是 GPT 使用的 tokenizer。BPE 的核心思想是合并高频的 token 对,逐步构建更长的子词单元,平衡词汇量和序列长度。
LLM 训练的三阶段
Karpathy 总结的 LLM 训练三阶段:
- Pre-training(预训练) — 在大规模文本上训练 next-token prediction,模型学习语言知识和世界知识。需要大量 GPU(数千 A100),成本最高。
- SFT(监督微调) — 在高质量的指令-回答数据上微调,模型学会遵循指令、回答问题。数据量小但质量要求高。
- RLHF(人类反馈强化学习) — 通过人类偏好数据训练奖励模型,用 PPO 算法优化主模型,使输出更符合人类偏好。
Karpathy 的 AI 工程哲学
简洁性优于复杂性
一个好的实现应该让人一眼就能看懂。如果需要大量注释才能解释,说明设计有问题。nanoGPT 的代码比任何框架文档都更有教育意义。
先理解再使用
不要急于使用 PyTorch/TF 的高级 API。先手动实现一遍,理解每个组件的作用,再用框架提高效率。知道"为什么"比知道"怎么做"更重要。
过度工程是敌人
不需要的东西不要加:不必要的抽象、过早优化、过度配置。150 行的 micrograd 比数百万行的框架更适合学习。
动手胜过阅读
看 100 遍论文不如自己实现一遍。每个概念都应该有可运行的代码。调试的过程就是学习的过程。
实用工具与脚本
# Karpathy 风格的模型检查点保存
import os
def save_checkpoint(model, optimizer, step, path='checkpoint.pt'):
checkpoint = {
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'step': step,
}
# 原子写入:先写临时文件,再重命名
tmp_path = path + '.tmp'
torch.save(checkpoint, tmp_path)
os.replace(tmp_path, path)
print(f"Checkpoint saved at step {step}")
def load_checkpoint(model, optimizer, path='checkpoint.pt'):
if os.path.exists(path):
checkpoint = torch.load(path)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
return checkpoint['step']
return 0
# 模型参数统计
def count_parameters(model):
total = sum(p.numel() for p in model.parameters())
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"总参数量: {total:,}")
print(f"可训练参数: {trainable:,}")
print(f"模型大小: {total * 4 / 1024**2:.1f} MB (FP32)")
return total
# GPU 利用率监控
def gpu_status():
if torch.cuda.is_available():
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.memory_allocated()/1024**3:.1f}/"
f"{torch.cuda.get_device_properties(0).total_memory/1024**3:.1f} GB")
print(f"Utilization: {torch.cuda.utilization()}%")
# 快速推理测试
def quick_test(model, tokenizer, prompt="The meaning of life is", max_tokens=50):
model.eval()
tokens = tokenizer.encode(prompt)
x = torch.tensor([tokens], dtype=torch.long)
with torch.no_grad():
for _ in range(max_tokens):
logits, _ = model(x)
next_token = logits[0, -1].argmax()
x = torch.cat([x, next_token.unsqueeze(0).unsqueeze(0)], dim=1)
return tokenizer.decode(x[0].tolist())
工程实践工具集:原子写入的检查点保存(避免写入中断导致文件损坏)、参数统计(快速了解模型规模)、GPU 监控(确保资源不超载)、快速推理测试(验证模型生成质量)。这些工具简洁实用,是 Karpathy "用最少的代码解决问题"哲学的体现。
LLM 训练的三个阶段是什么?
总结与综合练习
本节将对整个 Karpathy 技能课程进行总结,并提供综合练习帮助巩固所学。
课程知识图谱
Karpathy 技能体系回顾:
- 模块 1:Karpathy 的背景和教学理念 — 从零开始、教学即学习、简洁至上
- 模块 2:核心概念 — micrograd 自动微分、MLP 构建、makemore 语言模型
- 模块 3:实践应用 — Self-Attention、nanoGPT、训练循环和超参数调优
- 模块 4:进阶主题 — 数据准备、BPE Tokenizer、LLM 三阶段训练、工程哲学
综合练习:从零构建一个完整的字符级 GPT
"""综合练习:Karpathy 风格的字符级 GPT 完整实现
参考 nanoGPT,但用最简洁的代码实现
"""
import torch
import torch.nn as nn
import math
# ===== 超参数 =====
config = {
"block_size": 128, # 上下文窗口
"n_embd": 128, # 嵌入维度
"n_head": 4, # 注意力头数
"n_layer": 4, # Transformer 层数
"dropout": 0.1, # Dropout 比率
"batch_size": 64, # 批大小
"learning_rate": 3e-4, # 学习率
"max_steps": 5000, # 训练步数
"eval_interval": 500, # 评估间隔
}
# ===== 数据准备 =====
text = open('input.txt', 'r').read()
chars = sorted(set(text))
vocab_size = len(chars)
stoi = {c: i for i, c in enumerate(chars)}
itos = {i: c for c, i in stoi.items()}
data = torch.tensor([stoi[c] for c in text], dtype=torch.long)
n = int(0.9 * len(data))
train_data, val_data = data[:n], data[n:]
def get_batch(split):
d = train_data if split == 'train' else val_data
ix = torch.randint(len(d) - config["block_size"], (config["batch_size"],))
x = torch.stack([d[i:i+config["block_size"]] for i in ix])
y = torch.stack([d[i+1:i+config["block_size"]+1] for i in ix])
return x, y
# ===== 模型定义 =====
class TinyGPT(nn.Module):
def __init__(self):
super().__init__()
bs = config["block_size"]
ne = config["n_embd"]
nh = config["n_head"]
nl = config["n_layer"]
head_size = ne // nh
self.tok_emb = nn.Embedding(vocab_size, ne)
self.pos_emb = nn.Embedding(bs, ne)
self.blocks = nn.Sequential(*[
self._make_block(ne, nh, head_size, bs) for _ in range(nl)
])
self.ln_f = nn.LayerNorm(ne)
self.head = nn.Linear(ne, vocab_size)
def _make_block(self, ne, nh, hs, bs):
class Block(nn.Module):
def __init__(self):
super().__init__()
self.ln1 = nn.LayerNorm(ne)
self.ln2 = nn.LayerNorm(ne)
self.attn = nn.MultiheadAttention(ne, nh, dropout=config["dropout"], batch_first=True)
self.ff = nn.Sequential(
nn.Linear(ne, 4*ne), nn.GELU(), nn.Linear(4*ne, ne), nn.Dropout(config["dropout"])
)
mask = torch.triu(torch.ones(bs, bs), diagonal=1).bool()
self.register_buffer('mask', mask)
def forward(self, x):
B, T, C = x.shape
attn_out, _ = self.attn(
self.ln1(x), self.ln1(x), self.ln1(x),
attn_mask=self.mask[:T,:T], is_causal=False
)
x = x + attn_out
x = x + self.ff(self.ln2(x))
return x
return Block()
def forward(self, idx, targets=None):
B, T = idx.shape
x = self.tok_emb(idx) + self.pos_emb(torch.arange(T))
x = self.blocks(x)
x = self.ln_f(x)
logits = self.head(x)
loss = None
if targets is not None:
loss = nn.functional.cross_entropy(
logits.view(-1, vocab_size), targets.view(-1)
)
return logits, loss
def generate(self, idx, max_tokens=500):
for _ in range(max_tokens):
idx_cond = idx[:, -config["block_size"]:]
logits, _ = self(idx_cond)
probs = torch.softmax(logits[:, -1, :], dim=-1)
idx = torch.cat([idx, torch.multinomial(probs, 1)], dim=1)
return idx
# ===== 训练 =====
model = TinyGPT()
optimizer = torch.optim.AdamW(model.parameters(), lr=config["learning_rate"])
print(f"模型参数量: {sum(p.numel() for p in model.parameters()):,}")
print(f"词汇量: {vocab_size}")
print(f"训练数据: {len(train_data):,} 字符")
for step in range(config["max_steps"]):
x, y = get_batch('train')
logits, loss = model(x, y)
optimizer.zero_grad(set_to_none=True)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
if step % config["eval_interval"] == 0:
model.eval()
val_loss = sum(model(*get_batch('val'))[1].item()
for _ in range(20)) / 20
model.train()
print(f"Step {step:5d} | Train Loss: {loss.item():.4f} | Val Loss: {val_loss:.4f}")
# ===== 生成示例 =====
context = torch.zeros((1, 1), dtype=torch.long)
output = model.generate(context, max_tokens=200)
print("\n生成文本:")
print(''.join([itos[i] for i in output[0].tolist()]))
这个综合练习将课程所有知识点串联成一个完整的字符级 GPT:数据准备(字符级编码)、模型定义(嵌入 + Transformer Blocks + 输出层)、训练循环(AdamW + 梯度裁剪)、评估(训练/验证 loss 监控)、生成(自回归采样)。约 100 行代码实现了一个可以训练和生成文本的 GPT,完美体现了 Karpathy "简洁至上"的工程哲学。
推荐学习路径
micrograd
从 150 行的自动微分引擎开始,理解反向传播的本质。github.com/karpathy/micrograd
makemore
从 bigram 到 MLP 到 RNN 到 Transformer,逐步构建语言模型。YouTube 系列教程。
nanoGPT
最简单最快的 GPT 训练框架,用于训练和推理中等规模的 GPT。github.com/karpathy/nanoGPT
minbpe
从零实现 BPE tokenizer,理解 GPT 的文本编码过程。github.com/karpathy/minbpe
Karpathy 名言
关于学习:"I've always found that the best way to learn something is to try to build it from scratch."
关于简洁:"If you can't explain it simply, you don't understand it well enough."
关于实践:"The best way to learn is to build. Don't just read papers, implement them."
关于 AI:"We're all building on the shoulders of giants, but sometimes you need to climb down and look at the foundations."
延伸阅读与社区
Karpathy 的内容渠道:
- YouTube — youtube.com/@AndrejKarpathy(从零构建系列、GPT 训练教程)
- GitHub — github.com/karpathy(所有开源项目,代码即文档)
- Twitter/X — @karpathy(AI 见解、项目更新、行业评论)
- 博客 — karpathy.ai(经典文章:Software 2.0、Yes you should understand backprop)
推荐阅读:
- "Software 2.0" — 为什么神经网络是一种新的编程范式
- "Yes, you should understand backprop" — 每个工程师都应该理解反向传播
- "A Recipe for Training Neural Networks" — 神经网络训练的系统方法论
- "State of GPT" — GPT 模型的现状与未来(Microsoft Build 演讲)
Karpathy 技能体系的核心学习要点是什么?