① 装机:训练 = 把整机所有权重,从随机雪花调成有结构
整台 GPT 就是下面这一堆权重矩阵(参数真身)。训练做的唯一一件事:用梯度下降把它们从随机雪花,慢慢调出结构。
推理时它们全部冻结、不再变。点下面看一次"训练动画"。
从随机 → 有结构,扫一遍(示意)
输入层 · 把字符变成向量
4 × Block · 每层 = 多头注意力 + FFN(各带残差/LayerNorm)
收尾 · 把向量映射回字符打分
这些矩阵和"中间产物"别混(谁是真身?)
真身 = 被训练、会存盘的权重:上面这些 token_emb / pos_emb / Wq,Wk,Wv / proj / FFN / ln / lm_head。
而 q / k / v / wei / 注意力输出 / 每层的 x 都是推理时现算的中间产物,不存储、训练也不直接拧它们。
训练拧的永远是矩阵;中间产物只是"矩阵 × 输入"算出来的临时结果。
0.8M 是怎么来的?
粗算:嵌入 65×128 + 64×128 ≈ 16.5k;每个 Block 里
注意力投影(Wq/Wk/Wv/proj)≈ 66k、FFN(128×512 + 512×128)≈ 131k,
合 ≈ 197k,×4 层 ≈ 0.79M;lm_head 128×65 ≈ 8k。总计 ≈ 0.8M。GPT-2 是 124M,大了 150 倍,但结构一模一样。
② 跑起来:一个字符流过整机 → 采样出下一个 → 接着生
选一个预设开头(不用你打字),机器自动逐字生成。每生成一个字符,都要把当前序列完整过一遍下面四站,
最后 softmax 出概率、采样一个字符接到末尾 —— 这叫自回归生成。
序列当前文本(灰=开头,橙=机器生成)
流水线每个字符都走完这四站(高亮 = 正在算)
STAGE 1
嵌入
末位字符 → 128n_embd 向量
(tok_emb + pos_emb)
STAGE 2
4 × Block
1234
每层:多头注意力(看前文)
+ FFN(逐位置思考)
👆 点开看内部
STAGE 3
ln_f + lm_head
末位向量 → 65vocab 个打分
(下一个字符的 logits)
forward 前向 · STAGE 1–3 算出 logits(推理只有前向,没有反向)
decode · 挑 token
选一个开头,机器开始自动生成 ↑
掀开 Block 看内部
一个 Block = 在残差流上挂两个模块,各自「读 → 算 → 加回」
收起 ✕
残差流 x
→ LN →
注意力跨 token
⊕
→ LN →
FFN跨维度
⊕
x'
看第几层:
左:注意力矩阵(行=谁在看 query,列=被看 key,越深权重越大)。右:选中行的连线。移到某一行,看这个 token 在读哪些前文。
128 维 → 放大 512 维 → ReLU(砍负为0) → 压回 128 维 → 加回残差流
点「▶」看值流过:亮起的线 = 正在走的路径;灰色隐藏节点 = 被 ReLU 砍掉。全程只有 1 个 token。
为什么生成的是"莎士比亚味儿"的乱码,不是真句子?
这台只有 0.8M 参数 + 字符级,又只训练几千步,能学到的是"字母怎么搭、单词长啥样、谁跟谁常一起出现",
所以采样出来像英文、有词形和对话结构,但不成句意。把模型放大(GPT-2 124M)、上更多数据,同一套机器就会越说越像人话。
(本页生成文本是预设示意,为了动画稳定;真跑 03_transformer.py 每次结果不同。)
采样为什么不直接取最高分?
若每次都取最高分(argmax),模型会陷入重复死循环(一直输出同几个字)。按概率随机采样能保持多样性,
所以同一个开头每次生成都不一样。代码里就是 torch.multinomial(probs, 1)。