Transformer 实现机制 · 附录

Transformer 全景图 —— 跟着一句话走完一趟前向

⏱ 约 10 分钟 · 随时可读:已学的站给回链,前方的站标课号当预告 · 本页目标:把已拆过的零件装回整台机器——看到一句话怎么变成下一个词,自回归循环怎么转;并亲手用 numpy 真算一遍(玩具规模)

到这里,你已经拆过不少零件:token 怎么切(第 2·3 课)、意思怎么变成几何(第 4·5 课)、顺序怎么写进向量(第 6 课)、注意力怎么打分怎么配比(第 7·8 课)。但零件拆得再熟,也有一个问题悬着——整台机器到底怎么转?这张全景图回答它:「猫 吃 鱼」从进入模型到吐出下一个词的完整流水线,一站不漏。

三种用法:① 每站可点,下方面板给「人话 + 形状 + 对应课程链接」——你走过的站标 ✓,前方的站标课号,当预告片看;② 按 ▶ 播放,看数据流动画——下半屏里,「猫吃鱼」先变成编号,编号查表摞成矩阵 (3×8),盖上位置戳,被打分、被掩码切掉右上三角、被 softmax 摊成配比,一路变形到最后变成 8 根概率条;抽中的词拼回句尾,同一台机器再转一圈,连转 5 圈。每个数字都是真算的(下面那块代码、同种子同权重预计算),不是手绘示意;③ 底部维度账本全程对账,矩阵走到哪站长成什么形状,一格一格看得见。课程后面的课学完回来看,这张图会一站一站变亮。

先打个预防针:播放到最后,模型写出的续句会是一串不通的胡话——这是故意的,也是本页最重要的一课。这台玩具机器的参数全是随机数:随机参数写不出通顺的话,写得出才是假的(那说明数字是手造的)。动画里每个矩阵、每张概率表都真算自下方代码——胡话,正是"数字没造假"的证明。第 15 课训练之后,同一条流水线一个零件都不换,胡话才会变人话。

交互 · Transformer 全景:12 站地图 + 数据流动画(每一步都是真算的数字)
这张图最重要的一条规律:形状不变量 走完全程你会发现,L(词数)和 d_model(向量维度)从头到尾没变过——Block 进去 (L, d_model)、出来还是 (L, d_model),它不改形状,只精炼内容:每过一站,每个词的向量里就多写进一层上下文。形状只在两处改变:入口(文本 → ids → 查表变成 (L, d_model))和出口(投影回词表,变成 (L, V) 的 logits,生成时只取最后一行)。玩具版 L=3、d_model=8、V=8;真实 GPT-2:L≤1024、d_model=768、Block×12、V≈5 万——形状链一模一样,只是每个数都大了三个数量级。
一句辨清:图上这台是哪种 Transformer? 这张图是 GPT 式(decoder-only,只解码器):一路从左到右、因果掩罩住未来、出口是「下一个词」。它有两个表亲:BERT 式(只编码器)——去掉因果掩码,双向都看,出口不是生成而是每个位置的表示(拿来分类、填空);原版翻译 Transformer(编码器+解码器)——两截网络,中间靠交叉注意力连接(第 7 课讲对齐热图时见过:解码器每一步回头看编码器的全部状态)。今天的大语言模型(GPT/Claude/Qwen 文本模型)几乎都是图上这种 decoder-only。

真算一遍:全景图的 numpy 版(玩具规模)

上面动画里的每个矩阵、每张概率表,是不是真能算出来?——跑一遍就知道。下面这块代码和动画同种子、同权重:动画里的分数表 S、配比表 A、每一圈的概率条,就是这段代码算出的数字。零件全是你学过的(查表、正弦位置编码、四步注意力),只有三处是预习版黑盒:因果掩码、残差、FFN(分别是第 9、11、12 课的主角,此处先用最简版,学完回来看会秒懂):

# ============================================================ # 【演示目标】把上方全景动画真算一遍(玩具规模),并连跑 5 圈自回归: # 「猫 吃 鱼」→ 嵌入 → +位置编码 → 2 层 Block(因果注意力 + FFN) # → 投影到词表 → softmax → 下一个词分布 → 拼回句尾再来一圈 ×5 # ——动画里每个矩阵、每张概率表,就是这段代码(同种子、同权重)算出的数字。 # 【思路】 # 1. 玩具词表 8 词、d_model=8;权重一次抽齐后【冻结】—— # 生成 5 圈用的是同一台机器(和动画的 5 圈同一组数字) # 2. 三处"预习版黑盒":因果掩码(第 9 课)、残差(第 11 课, # LayerNorm 简化省略)、FFN+GELU(第 12 课) # 3. 出口投影复用嵌入表(= weight tying;真实模型常用独立矩阵,第 14 课) # 读法: 5 圈后生成「猫吃鱼。追追追猫」——高置信,却在复读: # 这就是"没训练"。把 RandomState(0) 改成 1、2、3 再跑,"文风"立刻变; # 第 15 课训练后,这条流水线一个零件都不换——换的只是里面所有的数字。 # ============================================================ import numpy as np rng = np.random.RandomState(0) # 换成 1、2、3 再跑,"文风"立刻变 vocab = ["猫", "吃", "鱼", "。", "它", "追", "饱", "了"] V, d = len(vocab), 8 E = rng.randn(V, d) * 0.5 # 嵌入表(第 4 课) Ws = [] # 权重一次抽齐,之后【冻结】—— for layer in range(2): # 生成 5 圈用的是同一台机器 Ws += [rng.randn(d, d) * 0.4, # W_q(第 7 课) rng.randn(d, d) * 0.4, # W_k rng.randn(d, d) * 0.4, # W_v rng.randn(d, 4 * d) * 0.3, # W_1(FFN 升维,第 12 课预习) rng.randn(4 * d, d) * 0.3] # W_2(FFN 降维) def softmax(x, axis=-1): e = np.exp(x - x.max(axis=axis, keepdims=True)) return e / e.sum(axis=axis, keepdims=True) def gelu(x): # FFN 的激活(第 12 课预习) return 0.5 * x * (1 + np.tanh(0.7978845608 * (x + 0.044715 * x ** 3))) def PE(L): # 正弦位置编码(第 6 课) pos = np.arange(L)[:, None] freq = 10000 ** (np.arange(0, d, 2) / d) P = np.zeros((L, d)) P[:, 0::2] = np.sin(pos / freq); P[:, 1::2] = np.cos(pos / freq) return P def forward(ids): # 一趟完整前向 = 上方全景动画 L = len(ids) H = E[ids] + PE(L) # 查表 + 位置(形状 (L, 8)) for layer in range(2): # Block × 2(真实 GPT-2 ×12) Wq, Wk, Wv, W1, W2 = Ws[5 * layer: 5 * layer + 5] Q, K, Vv = H @ Wq, H @ Wk, H @ Wv # 三副眼镜(第 7 课) S = Q @ K.T / np.sqrt(d) # 打分 + ÷√d_k(第 8 课) S[np.triu(np.ones((L, L), dtype=bool), 1)] = -1e9 # 因果掩码(第 9 课预习) H = H + softmax(S) @ Vv # 加权 V + 残差(第 11 课预习) H = H + gelu(H @ W1) @ W2 # FFN + 残差(LayerNorm 简化省略) return H # 出来仍是 (L, 8)——形状不变量 sent = [0, 1, 2] # 分词(第 2 课):猫 吃 鱼 for circle in range(5): # 自回归:一圈出一个词 ×5 H = forward(sent) # (权重全程冻结——同一台机器) P = softmax(H[-1] @ E.T) # 只看最后一行:下一个词分布 if circle == 0: print("[形状] ids(3,) → X(3,8) → +PE(3,8) → Block×2(3,8) → 末行(8,) → P(8,)") print("第 %d 圈 输入「%s」→ 下一个词:" % (circle + 1, "".join(vocab[i] for i in sent)), " ".join("%s%.0f%%" % (w, p * 100) for w, p in zip(vocab, P))) sent.append(int(P.argmax())) # 贪心取最大,拼回句尾 print("\n没训练的模型接着写:" + "".join(vocab[i] for i in sent)) print("高置信,却在复读乱跳——零件齐了,参数还是随机数;第 15 课训练后才说人话。")

这一块代码值得存着:第 9–12 课每学一个零件,回来把对应的"预习版黑盒"换成真货(真掩码、真 LayerNorm、多头切分);到第 14 课,它会长成完整的迷你 Transformer——你现在看到的是它的骨架。

检索练习

复习入口:每一站回哪课

全景图上的站回去翻哪课
输入文本 / 概率表 / 采样·拼回(自回归)第 1 课 · LLM 全景
分词 tokenizer第 2 课 · 分词与 BPE · 第 3 课 · 中文分词
嵌入查表第 4 课 · 词嵌入 · 第 5 课 · 余弦相似度
+ 位置编码第 6 课 · 位置编码
多头因果注意力第 7 课 · Q/K/V 投影 · 第 8 课 · 打分缩放 softmax(已抵达);因果掩码 = 第 9 课、多头 = 第 10 课(筹备中)
残差 + LayerNorm / FFN + GELU第 11 课 · 第 12 课(筹备中)
Block × N / 词表投影第 13 课 全景与权衡 · 第 14 课 numpy 手拼(筹备中)
把随机参数练成真预测第 15 课 · T4 实战(筹备中)

延伸

去读(可选)

The Illustrated Transformer — Jay Alammar:架构全景图经典,注意它画的是原版编码-解码结构,与本图的 GPT 式对照着看。

Attention Is All You Need 图 1:整台机器的原版图纸——现在你能认出里面大半的盒子。

我是你的老师,别客气 指着图问最好使:"Block 为什么不改形状""FFN 为什么偏偏升维 4 倍""logits 为什么只看最后一行"——任何一站都能往下挖一整课。哪个站看着不顺眼(太笼统/太跳),也直接说,我改图。