🧮 Transformer / LLM 架构 & 手算反向传播

先用一张全景图看清一个 token 如何在 LLM 里流动,再把每个模块(注意力、FFN、LayerNorm、输出 softmax)拆成能用笔算出数字的最小例子。所有数值例子都用 d=2、序列长度=2 的迷你规模,且已用 NumPy 逐一对拍验证——你可以照着一步步复算,落到具体的梯度矩阵上。

🔤 Embedding → Attention → FFN ∂L/∂z = p − y 🎯 softmax 反传 📐 LayerNorm 梯度公式 ✍️ 全程手算
第 0 章

LLM 全景数据流 —— 一个 token 的旅程

现代大语言模型(GPT 系)是纯解码器(decoder-only)的 Transformer:一叠相同的 Block 摞起来。先建立"数据在哪些形状之间变换"的直觉,后面每一步反传都是在给这些箭头配一条反向的梯度箭头。

① 分词 Tokenize

"你好世界" → [id₁, id₂, …]int[L]

② 词嵌入 + 位置编码

每个 id 查表成向量 + 位置信息[L, d_model]

↓  进入 N 层相同的 Block  ↓
③ 多头自注意力 + 残差 + LayerNorm

token 之间互相"看",交换信息[L, d_model]

④ 前馈网络 FFN + 残差 + LayerNorm

逐 token 独立地做非线性变换[L, d_model]

↓  ×N 层之后  ↓
⑤ 输出投影 → logits → softmax

映射到词表,得到下一个 token 的概率[L, vocab]

⑥ 交叉熵损失 L

与真实的下一个 token 比对,得到标量损失

🔑 反向传播是这条流水线的倒放。 损失 L 是一个标量,反传就是从 L 出发,沿着⑥→①逐段乘上每个模块的"局部导数",把梯度一路推回到每一个可学习参数(各层的权重矩阵、嵌入表)。链式法则 = ∂L/∂x = (∂L/∂y)·(∂y/∂x),全篇就是把这个式子在每个模块上具体化。
💡 两个记号约定(贯穿全文):① 向量按排(batch/序列在行方向),所以线性层写成 Y = XW;② 用 dX 简记上游传来的梯度 ∂L/∂X,它和 X 形状永远相同——这是手算时最好用的对形状检查法。
第 1 章

输入 —— 分词、词嵌入、位置编码

分词与词嵌入

文本先被分词器切成整数 id 序列。嵌入矩阵 E 是一张 [vocab × dmodel] 的查找表:token id 就是行号,取出对应那一行作为该 token 的向量。它是可学习参数,反传时只有被用到的那几行会收到梯度(其余行梯度为 0)。

xi = E[idi]    (第 i 个 token 的嵌入 = 嵌入表的第 idi 行)

位置编码

注意力本身不含顺序信息(打乱 token 结果不变),所以要额外注入位置。经典正弦编码给每个位置 pos、每个维度 2i / 2i+1 一个固定值:

PE(pos,2i) = sin(pos100002i/d),   PE(pos,2i+1) = cos(pos100002i/d)

最终每层的输入是 hi = xi + PEi。现代模型多改用可学习位置嵌入RoPE(旋转位置编码),思想一致:让模型知道"谁在前谁在后"。

💡 正弦编码是常数(无梯度);可学习位置嵌入则和词嵌入一样,反传时按位置收梯度。
第 2 章

自注意力 —— 核心机制(带数值前向)

自注意力是 Transformer 的心脏:让每个 token 根据"相关性"从其它 token 那里加权取信息。三步——投影出 Q/K/V → 打分并 softmax → 用权重对 V 加权求和

公式

输入 H[L × d])先经三个权重矩阵投影成 Query、Key、Value:Q = HWQK = HWKV = HWV。然后:

Attention(Q,K,V) = softmax(QKT√dk) V 缩放点积注意力

为什么除以 √dk 点积的方差随维度 dk 线性增长,值过大会把 softmax 推到"几乎 one-hot"的饱和区,梯度趋近 0。除以 √dk 把方差拉回 1 附近,保住梯度。因果掩码(causal mask):GPT 里每个 token 只能看自己和左边,做法是把 QKT 上三角(未来位置)设成 −∞,softmax 后自然为 0。

数值前向:2 个 token、d = 2

为了后面能手算反传,这里先把一组具体数字的前向算出来。设已经投影好的(取 W 使得):

Q = 1001 K = 1001 V = 1234

注意力前向 · 四步

1
打分 S = QKT / √2(这里 QKT = I):
S = 0.7071000.7071
2
逐行 softmax 得注意力权重 A。第 0 行:[e0.7071, e0] / (e0.7071+1) = [2.028, 1]/3.028
A = 0.66980.33020.33020.6698
3
加权求和 O = A·V。第 0 行 = 0.6698·[1,2] + 0.3302·[3,4]
O = 1.66052.66052.33953.3395
4
这个 O 就是注意力层输出,进入残差与 FFN。记住这些数字,第 7 章要沿它们反传回 Q/K/V。
💡 注意 A 每一行都和为 1(softmax 的性质),这是后面 softmax 反传公式成立的关键。
第 3 章

多头注意力 · FFN · 残差 · LayerNorm

多头注意力(Multi-Head)

dmodel 切成 h 份,每份独立做一次上面的注意力(各有自己的 WQ/WK/WV),再把 h 个输出拼接,过一个输出投影 WO。直觉:不同的头关注不同的关系(有的看语法、有的看指代)。反传时各头彼此独立,拼接处只是把梯度按维度切开分给各头。

MultiHead(H) = [head1; head2; …; headh] WO

前馈网络 FFN

逐 token 独立的两层 MLP,中间维度通常放大到 4d,激活用 ReLU 或 GELU:

FFN(x) = W2 · σ(W1x + b1) + b2

注意力负责"token 之间"混信息,FFN 负责"token 内部"做非线性加工——两者交替,是 Transformer 表达力的来源。

残差连接 + LayerNorm

每个子层都包一层 残差LayerNorm。残差 y = x + Sublayer(x) 让梯度有一条"高速公路"直达底层(∂y/∂x 含一个恒等项 I,缓解梯度消失)。LayerNorm 对每个 token 向量做标准化再缩放平移:

LN(x) = γ x − μ√(σ² + ε) + β μ,σ² 是该向量自己的均值/方差
结构公式反传时的意义
Post-LN(原版)LN(x + Sublayer(x))残差在 LN 内部,深层训练不稳,需 warm-up
Pre-LN(现代主流)x + Sublayer(LN(x))残差通路上无 LN,梯度更稳,GPT/LLaMA 都用它
🔑 残差是"梯度高速路":因为 y = x + f(x)dx = dy + df,上游梯度 dy 直接原样加到 x 上,无论中间 f 多深都不会衰减掉那一份。这是几十上百层能训起来的根本原因。
第 4 章

输出 —— 投影到词表、softmax、自回归生成

最后一层 Block 的输出 h[L × d])经输出投影 Wout[d × vocab],常与嵌入表共享权重)得到 logits z = h Wout,再 softmax 成概率分布:

p = softmax(z),   pk = ezkΣj ezj

训练时用交叉熵把预测分布拉向真实的下一个 token(teacher forcing,所有位置并行算损失)。推理时自回归:每步取概率分布采样一个 token,拼回输入,再预测下一个,如此循环——温度/top-k/top-p 都是在这一步对 p 做采样调节。

L = log py   (y = 真实下一个 token 的 id,单位置交叉熵)
🔑 下一章开始反传。整条链的起点就是这个 L,而第一段梯度 ∂L/∂z 有一个极其漂亮的结果——它让"softmax + 交叉熵"成为最好手算的一环。
第 5 章

反传三大基石 —— 记住这三条就够用

整个 Transformer 的反传,拆到底只是这三种局部导数的反复拼装。把它们背下来,任何模块都能手推。

基石一:softmax + 交叉熵 ⇒ ∂L/∂z = p − y

这是深度学习里最优雅的结果。真实标签 one-hot 为 y,则损失对 logits 的梯度就是"预测概率减真实概率"

∂L∂zi = pi yi softmax 与 CE 的"梯度相消"

推导(关键一步):L = −Σk yk log pk,而 ∂log pk / ∂zi = δki − pi,代入并利用 Σk yk = 1 即得 pi − yi。软饱和的指数和对数在这里恰好抵消,所以数值稳定、形式简单。

基石二:线性层 Y = XW

已知上游 dY,则三个梯度(注意都靠"转置对齐形状"记忆):

dW = XT dY,   dX = dY WT,   db = Σ dY

形状助记X[n×din]W[din×dout]dY[n×dout]。要凑出 dW[din×dout])只能 XT(din×n)·dY(n×dout);要凑出 dX[n×din])只能 dY(n×dout)·WT(dout×din)。形状唯一确定了乘法顺序,几乎不用背。

基石三:逐元素运算(激活、加法、⊙)

前向反向(dX 或 dW)说明
y = ReLU(x)dx = dy ⊙ [x>0]正的位置梯度直通,负的位置置 0
y = a + b(残差)da = dy,db = dy加法把梯度原样复制给两条支路
y = a ⊙ bda = dy⊙b,db = dy⊙a逐元素乘,梯度交叉相乘
C = A·B(矩阵乘)dA = dC·Bᵀ,dB = Aᵀ·dC基石二的一般形式,注意力反传全靠它
💡 手算总策略:把复杂模块画成"计算图"(一串矩阵乘、softmax、逐元素运算的节点),从 L 端出发,每遇到一个节点就套上面某一条规则,把梯度往输入侧推。始终用"梯度与变量同形状"来自查每一步没算错。
第 6 章

手算 ① —— 输出层的完整数值反传

这是最适合入手的一段:从损失 L 出发,把梯度推到输出投影 Wout 和隐藏向量 h。全部数字可复算。

设定

隐藏向量 h = [1, 2]d=2),词表大小 3,输出投影:

h = 12 Wout = 0.10.20.30.40.50.6  真实 token = 第 0 个,即 y = 100
✍️

前向 → 损失 → 反向,六步到底

1
logits z = h·Woutz0=1·0.1+2·0.4=0.9,同理 z1=1.2, z2=1.5
z = 0.91.21.5
2
softmaxe0.9,e1.2,e1.5 = 2.460, 3.320, 4.482,和 = 10.261
p = 0.23970.32360.4368
3
损失 L = −log p0 = −log 0.2397 = 1.428
4
🎯 基石一dz = p − y(第 0 位减去 1):
dz = −0.76030.32360.4368
读法:真实 token(第0位)概率不够高 → 负梯度 → 会被"推高";另两个是模型多给的概率 → 正梯度 → 会被"压低"。
5
🔧 基石二dWout = hT dz(外积,[2×1]·[1×3]=[2×3]):
dWout = −0.76030.32360.4368−1.52060.64710.8735
第二行是第一行的 2 倍——因为 h1=2·h0,权重梯度正比于输入激活。
6
🔧 基石二dh = dz·WoutT(继续往下游层传的梯度):
dh0 = −0.7603·0.1 + 0.3236·0.2 + 0.4368·0.3 = 0.1197
dh = 0.11970.1197
这个 dh 就是"最后一层 Block 输出"收到的梯度,接着按第 8、9 章继续往注意力/FFN 里推。
💡 一次参数更新:拿到 dWout 后,Wout ← Wout − η·dWoutη 学习率)。真实模型里成千上万个 token 的 dWout 求和平均,方向一致处累积、噪声处抵消——这就是"学习"。
第 7 章

手算 ② —— 注意力的反向传播

接第 2 章的数值前向(Q=K=IV=[[1,2],[3,4]])。设上游传来的梯度 dO = I(单位阵,便于手算),把它一路推回 V、A、S、Q、K。这是全篇最难但最值得动手的一段。

注意力反传的四条公式

①  dV = AT dO       ②  dA = dO VT
③  逐行过 softmax:dSi = Ai (dAi (Ai·dAi))
④  dQ = dS·K / √dk       dK = dST·Q / √dk

③ 是整段的难点:softmax 的雅可比是 diag(a) − a aT,作用到 dAi 上化简后就是这条"减去自身加权平均"的式子。它保证 dSi 每行和为 0(softmax 的输出被约束在概率单纯形上,梯度必须与"全体同增"方向正交)——这是手算时最好的自查点。

✍️

从 dO = I 反推到 dQ / dK / dV

1
dV = ATdO = AT(因 dO=I)。A 对称,故:
dV = 0.66980.33020.33020.6698
2
dA = dO·VT = VT
dA = 1324
3
③ softmax 反传,第 0 行A0=[0.6698,0.3302]dA0=[1,3]。先算加权平均 A0·dA0=0.6698·1+0.3302·3=1.6604,再 dS0=A0⊙([1,3]−1.6604)
dS0 = [0.6698·(−0.6604),  0.3302·(1.3396)] = [−0.4424, 0.4424]
第 1 行同理得 [−0.4424, 0.4424]每行和为 0 ✓(自查通过)。
dS = −0.44240.4424−0.44240.4424
4
dQ = dS·K/√2 = dS/√2(K=I):
dQ = −0.31280.3128−0.31280.3128
5
dK = dST·Q/√2 = dST/√2(Q=I):
dK = −0.3128−0.31280.31280.3128
6
最后再各过一次基石二dWQ=HTdQdWK=HTdKdWV=HTdV,并把 dQ,dK,dV 经各自投影汇成 dH 传给下一层。至此注意力层反传闭合。
⚠️ 最常见的手算错误:把 softmax 反传写成 dS = A ⊙ dA(漏掉减加权平均那一项)。检查方法永远是"dS 每行是否和为 0"——不为 0 一定错了。
第 8 章

LayerNorm 与 FFN 的反向传播

FFN 反传(就是基石的串联)

FFN y = W2·ReLU(W1x+b1)+b2 反传,从上游 dy 依次:

dW2 = aTdy,  da = dy·W2T  (a = ReLU 后的中间激活)
dz1 = da [z1>0]  (ReLU 反传,z₁=W₁x+b₁)
dW1 = xTdz1,  dx = dz1·W1T

全是第 5 章三条基石的机械套用,没有新东西——这也是为什么"背熟三基石"能覆盖整个网络。

LayerNorm 反传(唯一需要单独记的公式)

LN 里 μ、σ 都依赖整个向量,所以某个分量的梯度会牵动所有分量,公式比线性层复杂。设 x̂ = (x−μ)/√(σ²+ε),维度为 d,则:

= Σ dyx̂,   dβ = Σ dy,   dx̂ = dyγ

dxi = 1√(σ²+ε) ( dx̂i 1dΣjdx̂j i·1dΣjdx̂jj ) 已用数值梯度对拍验证

怎么理解那三项? 第一项是"直接梯度";第二项减去均值方向的分量(因为 x 整体平移不改变 );第三项减去缩放方向的分量(x 整体伸缩不改变 )。LN 把这两个自由度"归一化掉了",反传就要把对应方向的梯度扣除——几何上就是把 dx̂ 投影到与 𝟙 都正交的子空间。

💡 面试常考"为什么 LN 反传比 BN 简单":LN 在单个样本的特征维上归一化,与 batch 无关,反传不跨样本、无需维护 running 统计量;BN 沿 batch 维归一化,反传要在整个 batch 上求和,还得区分训练/推理。Transformer 一律用 LN 正是图这份简洁与 batch 无关性。
第 9 章

一个 Transformer Block 的完整反传顺序

把前面所有零件按倒序串起来。以 Pre-LN 结构 x → x+Attn(LN(x)) → +FFN(LN(·)) 为例,梯度从顶部 dy 流回底部 dx 的完整路径:

⑧ 上游梯度 dy 到达 Block 顶部

来自上一层(或第6章的 dh)[L,d]

↓ FFN 子层(残差)
⑦ 残差分流:dy 同时给"直连"和"FFN 支路"

da_res = dy;进入 FFN 反传的也是 dy(基石三·加法复制)

⑥ FFN 反传(第8章)→ 再过 LN 反传

dW₂,dW₁ 收梯度;穿过 LN 得该支路对输入的贡献

↓ 两条支路梯度相加
⑤ 汇合:dx' = da_res + (FFN支路传回的梯度)

残差处梯度相加[L,d]

↓ 注意力子层(残差),重复同样结构
④ 残差分流 → 注意力反传(第7章)→ LN 反传

dW_O,dW_Q,dW_K,dW_V 收梯度

↓ 两条支路再相加
③ dx = 直连梯度 + 注意力支路梯度

这就是 Block 对其输入的梯度,传给下一层(更靠底)

↓ ×N 层,一直到底
①② 传回嵌入表:只有被用到的 token 行累加梯度

dE[id_i] += dx_i;位置嵌入同理

🔑 三个"手算全局观"要点
残差 = 加法 = 梯度复制:每个子层的梯度都走"直连"和"支路"两条,最后相加——这保证了底层也能拿到强梯度。
反传严格是前向的倒序:前向最后做的(输出 softmax)反传最先算,前向最先做的(嵌入)反传最后更新。
每个权重矩阵的梯度都是"输入激活ᵀ × 上游梯度"(基石二),所以前向时必须缓存各层激活——这正是训练比推理吃显存得多的原因。
PyTorch · 一眼看穿:框架替你做的就是上面这套倒序
# 前向:每一步都被 autograd 记进计算图(缓存激活)
logits = model(tokens)                     # 第0~4章的前向
loss = F.cross_entropy(logits, targets)    # 第4章的 L

# 反向:一行触发第5~9章的全部手算,倒序自动完成
loss.backward()                            # dz=p-y 起,链式推回每个 .grad

# 参数更新:W ← W - η·dW
optimizer.step(); optimizer.zero_grad()
💡 你手算的每一个 dW,就是 loss.backward() 之后存进 param.grad 的那个张量。手推一遍再对照框架,"自动微分"就不再是黑盒。
第 10 章

自测五题 —— 检验你能不能独立手算

🧠

Transformer 反传 · 结业小测

点选答案,立即判分并给解析
Q1.softmax + 交叉熵中,损失对 logits 的梯度 ∂L/∂z 等于:
Ap − y(预测概率减真实 one-hot)
By − p
Cp ⊙ (1 − p)
解析:softmax 与交叉熵的导数恰好相消,得到极简的 p − y。真实 token 概率不足→负梯度被推高,多余概率→正梯度被压低。方向别记反:是"预测减真实"。
Q2.线性层 Y = XW,已知 dY,则权重梯度 dW 是:
AdY · Wᵀ
BXᵀ · dY
CdY · Xᵀ
解析:dW 必须和 W 同形状 [d_in×d_out],只有 Xᵀ(d_in×n)·dY(n×d_out) 能凑出来。dY·Wᵀ 是 dX。用"形状唯一确定乘法顺序"来记最稳。
Q3.注意力里 softmax 反传后,dS 每一行必然满足:
A每行元素全为正
B每行元素之和为 0
C每行元素之和为 1
解析:softmax 输出被约束在概率单纯形上(和为1),其梯度必与"全体同增"方向正交,故 dSᵢ 每行和为 0。这是手算注意力反传时最好用的自查点——不为0一定漏了"减加权平均"那一项。
Q4.残差连接 y = x + f(x) 对反向传播最关键的作用是:
A减少参数量
B让上游梯度 dy 原样加到 x 上(dx=dy+df),形成不衰减的梯度高速路
C让 softmax 数值更稳定
解析:加法在反传时把梯度"原样复制"给两条支路,恒等项 I 保证无论中间 f 多深,底层都能收到那一份完整的 dy。这是深层 Transformer 能训起来的根本。
Q5.为什么训练比推理消耗多得多的显存?最主要的原因是:
A训练用更大的 batch
B每个权重的梯度是"输入激活ᵀ × 上游梯度",必须缓存前向时各层的激活供反传使用
C推理不需要加载模型权重
解析:基石二 dW = Xᵀ·dY 需要前向的输入激活 X,所以 autograd 必须把每一层激活留到 backward 用完为止。这份"激活缓存"是训练显存的大头,也是梯度检查点(gradient checkpointing)用时间换显存的针对对象。