你有没有想过,ChatGPT 背后那个神秘的大家伙——GPT,到底是怎么工作的?

别慌,Andrej Karpathy 又来救你了。这位前特斯拉 AI 总监、OpenAI 创始成员,最近搞了一个超级接地气的项目:Microgpt。顾名思义,就是"微型 GPT"——整整 200 行 Python 代码,没有 pip install,没有第三方依赖,训练推理全包圆。

你没看错,200 行。还不够你写个中等复杂度的爬虫。

事情是这样的

Karpathy 老师傅在 AI 教育领域有个外号叫"极简主义之父"。从当年的 micrograd、makemore、nanogpt 到现在的 microgpt,他一直致力于把复杂的东西拆到不能再拆为止。

这次他放话说:“我已经没法再简化了,这就是 GPT 的底线。”

好奇害死猫,但我还是忍不住想看看这 200 行到底装了什么魔法。

先看看 Microgpt 效果

训练数据是 32033 个英文名字,模型参数只有 4192 个(相比之下 GPT-2 有 16 亿,GPT-4 估计得上万亿)。

跑完 1000 步训练后,模型开始"幻觉"出一些看起来像名字但实际不存在的东西:

sample  1: kamon
sample  2: ann
sample  3: karai
sample  4: jaire
sample  5: vialan
sample  6: karia

你仔细品品,是不是有点那意思?模型确实学会了英文名字的拼写规律:元音辅音交替、常见后缀(-anna, -ia, -en)等等。

虽然只有 4192 个参数,但这已经是完整的语言模型了。

200 行代码里到底装了什么

让我带你拆开看看这口神秘的箱子。

第一层:数据

代码里直接内置了一个 32033 行的名字列表,作为训练语料。每个名字就是一个"文档"。

docs = [l.strip() for l in open('input.txt').read().strip().split('\n') if l.strip()]

模型的任务很简单:给定前面的字符,预测下一个字符是什么。比如输入 “em”,模型应该预测出 “m”。

第二层:分词器

这是整个流程里最"原始"的部分。

uchars = sorted(set(''.join(docs)))  # 收集所有唯一字符
vocab_size = len(uchars) + 1  # 加1是给特殊token

每个字符直接对应一个数字 ID。英文字母一共 26 个,加上特殊标记,BOS(Begin Of Sequence),总共 27 个"词汇"。

没错,比你高考英语词汇量还少。

这和现在动辄几万个 token 的大模型Tokenizer 比起来,简直是玩具。但对于理解原理来说,够用了。

第三层:反向传播

这是最硬核的部分。训练神经网络需要知道"往哪个方向调整参数",反向传播就是干这个的。

Karpathy 写了一个 Value 类:

class Value:
    def __init__(self, data, children=(), local_grads=()):
        self.data = data
        self.grad = 0
        self._children = children
        self._local_grads = local_grads

每个数字都是一个"节点",记录自己是怎么算出来的(_children),以及每个输入对输出的影响(_local_grads)。

调用 backward() 时,梯度像水流一样从结果倒着流回参数。

听起来很抽象?Karpathy 举了个例子:

“如果汽车速度是自行车的 2 倍,自行车速度是步行的 4 倍,那么汽车速度就是步行的 2 × 4 = 8 倍。”

链式法则就是乘法,一层一层乘回去。

第四层:GPT 架构

真正的重头戏来了。

模型结构长这样:

def gpt(token_id, pos_id, keys, values):
    # 1. 嵌入层:把字符ID变成向量
    tok_emb = state_dict['wte'][token_id]
    pos_emb = state_dict['wpe'][pos_id]
    x = tok_emb + pos_emb  # 位置编码

    # 2. Transformer 层
    for li in range(n_layer):
        # 2.1 注意力机制
        q = linear(x, state_dict[f'layer{li}.attn_wq'])
        k = linear(x, state_dict[f'layer{li}.attn_wk'])
        v = linear(x, state_dict[f'layer{li}.attn_wv'])

        # 3. MLP 前馈网络
        x = linear(x, state_dict[f'layer{li}.mlp_fc1'])
        x = [xi.relu() for xi in x]
        x = linear(x, state_dict[f'layer{li}.mlp_fc2'])

    # 4. 输出层
    logits = linear(x, state_dict['lm_head'])
    return logits

注意力机制就是让每个位置"看看"前面的字,然后决定要从它们那里获取什么信息。

举个例子,当模型看到 “em” 要预测第三个字母时,它会注意到前面的 “e” 是个元音,然后更倾向于预测下一个也是元音(或者辅音)。

这就是语言模型学会语法的方式。

第五层:训练和推理

训练就是不断重复:输入字符 → 预测下一个 → 计算损失 → 反向传播 → 更新参数。

推理时,模型一次只生成一个字符,然后把生成的字符塞回输入,继续预测下一个。直到模型输出结束标记(BOS),或者达到最大长度。

这就是为什么 ChatGPT 是一个字一个字地蹦出来的。

跑起来

你只需要:

python train.py

在 MacBook 上大概跑 1 分钟就能看到效果。loss 会从 3.3(完全随机瞎猜)降到 2.37 左右。

然后你就能得到一堆看起来像名字但实际不存在的"幻觉"产物。

这东西能干嘛

说实话,4192 个参数的小模型,生成质量肯定不能和 ChatGPT 比。

但它的价值不在于"好用",而在于"能懂"。

如果你一直被 PyTorch、TensorFlow 那些复杂的 API 绕得头晕,想搞清楚神经网络到底在干什么,这段代码就是最好的起点。

没有隐藏层、没有魔法,就是最纯粹的数学。

Karpathy 的教学路径是这样的:

文件内容
train0.py统计频率——连神经网络都没有
train1.pyMLP + 手写梯度 + SGD
train2.py自动求导(就是 Value 类)
train3.py位置编码 + Transformer
train4.pyGPT 完整版 + Adam 优化器

Microgpt 就是最后的成品。

写在最后

200 行代码,放到十年前可能就是一个本科生的课程作业。

十年后,这些简单的积木变成了估值千亿的 AI 帝国。

如果你想搞清楚 GPT 到底是怎么回事,从这 200 行开始就够了。


常见问题

Q: Microgpt 和真实的 GPT 有什么区别? A: 核心算法完全一样,都是 Transformer + 自回归生成。区别在于规模:Microgpt 只有 4192 个参数,GPT-4 有上万亿参数。规模决定了能力的上限,但底层原理是相通的。

Q: 运行 Microgpt 需要什么环境? A: 只要有 Python 就行,不需要 pip install,不需要 GPU。Karpathy 特意去掉了所有第三方依赖,就是为了让每个人都能跑起来。

Q: 这个项目适合什么人看? A: 想入门 AI/神经网络但被复杂代码劝退的人。200 行代码,讲完从数据到训练到推理的全流程,很难找到更简洁的教程了。


相关资源:


关注梦兽编程微信公众号,解锁更多黑科技