从零看懂 MicroGPT:给程序员的“无数学负担”大模型入门
用可视化和工程直觉讲清 Token、Softmax、交叉熵、反向传播、注意力与训练循环,帮助没有数学基础的程序员真正理解 GPT。
很多程序员第一次接触大模型时,都会有一种感受:
- 代码大概看得懂;
- 公式一出现就开始“失焦”;
- 一堆术语(logits、softmax、cross-entropy、backprop)像黑魔法。
如果你也这样,这篇就是为你写的。
本文基于 Andrej Karpathy 的 200 行教学脚本(通常被称为 microGPT 思路)来讲,但我会尽量用程序员视角解释,不要求你有机器学习和高数基础。
你可以把它当成一篇“把 GPT 拆开看内部零件”的图解说明书。
1. 先定一个最小目标:让模型学会“起名字”
这个教学模型的训练数据很简单:3.2 万个人名,一行一个,例如:
- emma
- olivia
- sophia
- isabella
模型的任务也很简单:
看前面的字符,预测下一个字符。
训练完后,它可能生成:
- kamon
- karai
- anna
- anton
这些名字不一定真实存在,但“像人名”。
这件事和 ChatGPT 的关系是:
- 在名字任务里,单位是字符;
- 在 ChatGPT 里,单位是 token(词片段);
- 本质都是“根据上下文,做下一个 token 预测”。
2. 机器不认识字母,只认识数字
神经网络只处理数值,所以第一步是把字符映射成整数。
例如:
a-z对应0-25- 再加一个特殊符号
BOS(序列开始/结束)对应26
于是 emma 会变成:
1
2
[BOS, e, m, m, a, BOS]
[26, 4, 12, 12, 0, 26]
这里要记住一件非常重要的事:
token id 只是“编号”,不表示大小关系。
4 并不比 2“更大更强”,就像数据库主键,只有区分作用,没有数学语义。
3. GPT 的核心训练任务:Next Token Prediction
以 emma 为例,训练样本会被拆成多个“输入→目标”:
- 给
[BOS],预测e - 给
[BOS, e],预测m - 给
[BOS, e, m],预测m - 给
[BOS, e, m, m],预测a - 给
[BOS, e, m, m, a],预测BOS
这就是滑动窗口训练。对 ChatGPT 也是同一套逻辑。
你可以把它想成代码补全:
- IDE 看你前面写了什么;
- 给出后续最可能的候选。
GPT 只是把这个过程做到了超大规模。
4. 从“原始分数”到“概率”:Softmax 在做什么
模型每一步会输出一组分数(logits),每个候选 token 一个分数。
这些分数可能是负数、正数、很大、很小,不满足概率要求。于是要经过 softmax:
- 对每个分数做
exp - 再除以总和
- 得到一组
0~1且和为 1 的概率
可以把它当成“归一化投票”:
- 分数高的候选拿更多票;
- 分数差距会被指数函数放大。
常见实现里会先减去最大值再 exp,这是数值稳定技巧:
- 数学上结果不变;
- 工程上避免
exp(1000)这种溢出。
5. 怎么衡量模型“猜错得有多离谱”:交叉熵
我们只关心一件事:
模型给“正确答案”分配了多大概率?
损失函数用 -log(p):
- 若
p=0.9,损失很小; - 若
p=0.01,损失很大。
直觉上:
- 猜对且自信,奖励高(loss 低);
- 猜错且自信,重罚(loss 高)。
这对训练非常关键,因为它会强烈纠正“自信但错误”的预测。
6. 反向传播:不是魔法,是自动算导数的流水线
很多人怕反向传播,是因为“导数”这两个字。
你可以先这样理解:
它在回答:每个参数改一点点,最终 loss 会变好还是变坏,变化幅度多少?
在 microGPT 里,每次运算(加法、乘法、exp、log)都是计算图中的一个节点。每个节点保存:
- 前向结果(data)
- 反向梯度(grad)
- 局部导数规则
backward() 的流程:
- 先拓扑排序,确定反向顺序;
- 从损失节点开始(梯度设为 1);
- 按链式法则把梯度传回去;
- 如果某变量走过多条路径,梯度会累加。
这和你在 PyTorch 里写 loss.backward() 是同一件事,只是这里是标量版本,便于看透底层。
7. Embedding:把离散 ID 变成可学习向量
token id 只是整数编号,模型需要的是“可计算的向量”。
做法:
- 在 token embedding 表里查一行(比如 16 维);
- 在 position embedding 表里再查一行;
- 两个向量相加,得到当前位置输入。
为什么要位置向量?
因为“a 在开头”和“a 在结尾”的语义角色不同。模型要知道顺序。
可以把 embedding 理解成每个 token 的“可训练特征画像”,训练过程中会不断调整。
8. Attention:让当前位置读取“历史上下文”
这是 Transformer 最核心的机制。
每个位置会生成三组向量:
- Query:我想找什么
- Key:我这里有什么
- Value:如果你关注我,我提供什么信息
流程是:
- 用当前 Query 去和历史 Key 做点积,得到相关性分数;
- softmax 变成注意力权重;
- 按权重对历史 Value 加权求和。
多头注意力(multi-head)就是并行做多套“检索视角”:
- 有的头可能偏向最近字符;
- 有的头可能关注 BOS;
- 有的头可能学到元音/辅音模式。
同时还有因果掩码(causal mask):
当前位置只能看过去,不能偷看未来。
这保证了自回归生成的正确性。
9. GPT 一层里到底发生了什么
简化后可以记成:
- 输入向量
- 归一化(RMSNorm)
- 注意力
- 残差相加
- 再归一化
- MLP(升维→ReLU→降维)
- 再残差相加
- 线性投影输出 logits
其中两个组件尤其“工程关键”:
- 残差连接:给梯度提供捷径,避免深层网络训练时梯度消失;
- 归一化:稳定激活值范围,减少训练发散。
一句话:
- Attention 负责“和上下文沟通”;
- MLP 负责“当前位置内部思考”。
10. 训练循环:重复 1000 次的参数微调
一次训练迭代基本是:
- 取一个名字并 token 化;
- 每个位置前向计算并得到 loss;
- 平均 loss;
backward()得全部参数梯度;- 用 Adam 更新参数;
- 梯度清零,进入下一轮。
为什么初始 loss 大约是 3.3?
因为词表大小是 27,随机猜中概率约 1/27, -log(1/27)≈3.3。
随着训练推进,loss 下降到约 2.37,生成结果会从乱码变得“像名字”。
11. 推理(生成)阶段:其实非常直接
训练完后,生成流程是:
- 输入
BOS - 前向得到下一 token 概率
- 按概率采样一个 token
- 把新 token 喂回去
- 直到采到
BOS或达到最大长度
这里有个很实用的旋钮:temperature。
<1.0:分布更尖锐,更保守、更稳定;=1.0:按模型原分布采样;>1.0:更发散,更多样,也更容易胡说。
在名字任务里,0.5 往往比较自然。
12. 从 microGPT 到 ChatGPT:核心几乎没变
最值得建立的认知是:
大模型的“算法骨架”并不神秘,复杂度主要来自规模和工程化。
本质差异主要是:
- 数据量:几万名字 → 万亿 token
- 词表:字符级 27 → 子词级十万左右
- 参数量:几千 → 千亿级
- 计算:CPU 标量对象 → GPU 张量并行
- 训练:单机几分钟 → 多机多卡数周到数月
但核心循环仍然是:
Tokenize → Embed → Attend/MLP → Next-token 概率 → Loss → Backward → 参数更新。
13. 给程序员的学习路线(不走“公式地狱”)
如果你想真正入门 LLM,我建议按这个顺序:
- 先跑通一个最小实现(microGPT 或 nanoGPT 教学版)
- 手改超参数并观察现象(维度、层数、学习率、温度)
- 画出一次前向/反向的数据流(哪怕是纸笔)
- 再回头看数学定义(softmax、交叉熵、链式法则)
- 最后迁移到框架实现(PyTorch + tensor)
顺序不要反:
- 先有系统直觉,
- 再补数学严谨性,
你会轻松很多。
14. 一个最短总结
如果你今天只记住三句话:
- GPT 做的事就是根据上下文预测下一个 token。
- 训练的事就是让正确 token 的概率更高(loss 更低)。
- 反向传播的事就是算清每个参数该往哪个方向微调。
理解了这三点,再大的模型也只是“同一算法在更大规模上的工程实现”。
如果你愿意,下一篇我可以继续写:
- 如何从这个最小版本迁移到 PyTorch(tensor 版);
- 如何把字符级模型改成中文分词版本;
- 如何用可视化工具观察 attention 头到底在关注什么。