第 3 课:现代 Transformer 的五大改造

2017 年的 Transformer 只是个起点。从 Llama 到 Qwen,现代大模型对原始 Transformer 做了五处关键改造——每一处都在解决一个具体的工程痛点。

🎯 学完这 5 项改造(推理链 6 步)你将能够:
① 用自己的话说清 RoPE(按位置给词向量拧一个旋转角度)如何用旋转编码相对位置,以及为什么它比绝对位置编码更利于外推
② 画出 MHA → MQA(所有 Query 头共享同一份 K/V)→ GQA(把多个 Query 头分成几组、每组共享同一份 K/V)的演进链,算出 GQA 省了多少 KV Cache(把已算过的键和值存下来复用)
③ 指出 RMSNorm(砍掉减均值、只保留缩放)、SwiGLU(用门控决定信息通过多少)、Pre-Norm 各自解决了什么训练/推理瓶颈
🗺 本节推理链(6 步:约束一「位置」→ 逼出 RoPE;约束二/三/四「显存·算力·梯度」是三条并列支链,各逼出一个解;最后汇总成一个 Block。↓ 表示并列归属,不是先后递进)
1约束一:位置信息必须能外推 — 要在 4096 长序列上工作、训练却只见过固定长度;「绝对坐标 + 加法」给不出相对距离,这就要求先看清它加在 embedding 上的死穴
↓
2既然要相对位置,就用旋转注入 — 把「角度差」直接编码进 Q·K 的打分里,这就要求旋转是连续数学操作(cos/sin 对任意角度都有定义),因此可外推 → 逼出 RoPE
↓
3约束二 · 支链(显存):位置解决了,显存却成了新瓶颈 — 推理要缓存全部历史 K/V(KV Cache),随上下文线性膨胀,不压缩就 OOM,因此必须减少 KV 头数 → 32 个 Q 头分 8 组共享 KV → 逼出 GQA
↓
4约束三 · 支链(算力):显存压住了,算力预算仍紧 — 同预算下要更低困惑度、还要少一次跨卡同步,这就要求砍掉 normalization 里多余的一步(减均值),于是用门控替代 FFN 里的一刀切 → RMSNorm + SwiGLU
↓
5约束四 · 支链(梯度):还差最后一条——梯度要能穿过 32+ 层回到第 1 层 — Norm 堵在残差主干上会让梯度衰减到 0.5³²,因此必须把 Norm 移出主干、让残差成为直通路径 → 逼出 Pre-Norm
↓
6四条约束的解互不冲突,所以能拼成一个 Block — 把 RoPE / GQA / RMSNorm / SwiGLU / Pre-Norm 装进同一个 Block,重复 32 次,于是 KV Cache 只占 1/4、梯度可直达第 1 层 → Llama
RoPEGQARMSNormSwiGLUPre-NormLlama
1
"我打你"和"你打我"——三个字一模一样,模型怎么知道意思完全相反?
提示:把每个字转成向量后,两句的向量组有什么区别?
▾
知识背景
Sinusoidal 绝对位置编码(Vaswani et al., 2017,《Attention Is All You Need》)。用固定公式 PE(pos,2i)=sin(pos/10000^(2i/d))、PE(pos,2i+1)=cos(pos/10000^(2i/d)) 为每个位置生成唯一的向量,直接加到词向量上。参数为零、可算任意位置,但只表达"绝对坐标",不直接表达"相对距离"。

把"我打你"和"你打我"的每个字转成向量——"我"永远是 [0.5,0.3,0.8],"打"永远是 [0.2,0.9,0.4]。三个字的 Embedding 完全相同,顺序不同但模型看到的向量集合一样。不加位置信息的话,模型分不清谁打了谁。打个比方:一副扑克牌洗过之后张数没变、点数也没变,可你再也说不出谁在谁前面——拿到的就是这副洗过的牌。所以位置编码要回答的不是这个词是什么,而是这个词排在第几位。

🧮 完整走查:同一个 Embedding 表,两个完全不同的句子
Step 1:Embedding 表(3个词 × 3维)
字d₁d₂d₃
"我"0.50.30.8
"打"0.20.90.4
"你"0.70.10.6
Step 2:不加位置 — 模型眼里两句是同一组向量
位置1位置2位置3
"我打你"[0.5,0.3,0.8][0.2,0.9,0.4][0.7,0.1,0.6]
"你打我"[0.7,0.1,0.6][0.2,0.9,0.4][0.5,0.3,0.8]
"打"在位置2,两个句子拿到的都是 [0.2,0.9,0.4]——模型看不出它在"我打你"里还是在"你打我"里。顺序信息丢失了。
Step 3:原始 Transformer 的补救——Sinusoidal 绝对位置编码
Vaswani 2017 的原始方案:用 sin/cos 函数为每个位置生成一个唯一的向量,加到词向量上。PE(pos,2i)=sin(pos/10000^(2i/d)), PE(pos,2i+1)=cos(pos/10000^(2i/d))。
位置Sinusoidal 编码(前2维)"我打你"每个位置 = 词向量 + 编码
位置1[sin(1)≈0.84, cos(1)≈0.54, 0]我: [0.5+0.84, 0.3+0.54, 0.8] = [1.34, 0.84, 0.8]
位置2[sin(2)≈0.91, cos(2)≈−0.42, 0]打: [0.2+0.91, 0.9−0.42, 0.4] = [1.11, 0.48, 0.4]
位置3[sin(3)≈0.14, cos(3)≈−0.99, 0]你: [0.7+0.14, 0.1−0.99, 0.6] = [0.84, −0.89, 0.6]
"你打我"的位置1是"你"向量+Sinusoidal(1) = [0.7+0.84, 0.1+0.54, 0.6] = [1.54, 0.64, 0.6]——和"我打你"完全不同了。✅ 绝对位置编码让两个句子产生了区别。
Step 4:但有个致命问题——只编码了"你在第几位",没编码"你和别人差多远"
场景位置1的向量位置3的向量能否看出"距离=2"?
"我打你"[1.34, 0.84, 0.8][0.84, −0.89, 0.6]不能——点积 1.34×0.84+0.84×(−0.89)+0.6×0.8 = 0.858
"你打我"[1.54, 0.64, 0.6][0.64, −0.69, 0.8]也不能——点积 1.5415×0.6411+0.6403×(−0.69)+0.6×0.8 = 1.026,和上面不相等
核心矛盾:同样是位置1和位置3(距离都是 2),两个句子的点积却并不相同(0.858 vs 1.026)。因为点积里混入了词向量本身的内容,不是纯净的"距离=2"信号。Sinusoidal 编码在训练长度 512 之外会显著退化(ALiBi 论文 Press et al. 2021 实测超出训练长度后困惑度大幅上升)——它理论上能线性表示相对位置,但无法保证超出训练长度仍可外推。
🎯 核心矛盾

绝对位置编码像给每个座位贴一个 GPS 坐标(第 1 排、第 2 排…)。模型拿到的是"你在 36.5°N, 120.3°E",但它真正需要知道的是"你离我 2 米远"——相对距离。不管用 Sinusoidal 函数还是查表,只要是"绝对位置 + 加法",都绕不开这个限制。RoPE 的答案:不要 GPS 了,改成记录"你在我左边还是右边,离多远"——通过旋转操作把相对距离信息直接编码进 Attention 的点积里。

AI 助教
👋 对位置编码有疑问?比如"为什么不能用位置1/总长度这种简单方式?""Sinusoidal 的 sin/cos 频率是怎么选的?"

我会用具体数字帮你走一遍。
2
RoPE 怎么让"打"知道自己在"我打你"和"你打我"里位置不同?
提示:位置信息一定要"加"到词向量上吗?想一想,还有什么数学操作,会让同一个词在两个位置"看起来"不一样。
▾
知识背景
RoPE = Rotary Position Embedding(Su et al., 2021,追一科技)。通过旋转矩阵对 Q 和 K 向量施加位置相关的旋转,使 Q·K 点积天然包含相对位置信息(只依赖旋转角差)。替代了 Transformer 原始 Sinusoidal 和 BERT 可学习编码。Llama、Qwen、Mistral、Gemma 均使用 RoPE。

上一张卡片我们看清了绝对位置编码的死穴:只编码了"你在第几位"(绝对坐标),没编码"你离我多远"(相对距离)。RoPE 换了一种思路——不把位置信息当成"加法标签",而是把每个词的向量"旋转"一个角度,角度大小由位置决定。

核心数学:在二维平面里,把一个向量旋转 θ 角:x' = x·cosθ - y·sinθ,y' = y·cosθ + x·sinθ。关键性质:两个旋转后的向量做点积,结果里天然包含了它们的相对角度差——也就是相对位置。

打个比方:把每个词的向量想成钟表上的指针,位置决定它被拨到几点——两根指针的夹角只取决于"拨过的格数之差",而不取决于它们在表盘上的绝对位置。

注意这里的关键是「旋转」而不是「相加」:把位置向量直接加到词向量上,会同时改动这个向量的长度和方向——[0.8, 0.6] 加上位置编码 [0.84, 0.54] 后变成 [1.64, 1.14],模长从 1.00 变成约 2.00,方向也从 36.9° 偏到 34.8°,词本身的语义就被搅动了。旋转只改方向、不改长度([0.8, 0.6] 旋转 0.03 弧度后模长仍是 1.00),于是点积里就只剩「两个方向差多少」这一个变量,相对位置才干净地浮现。

🧮 完整走查:"打"在位置 1 vs 位置 3,旋转后模型自己就能看出区别
Step 1:两个位置的"打"向量 + 各自旋转角
原始向量(取前2维)旋转角(位置×角速度)
位置1的"打"[0.8, 0.6]θ₁ = 1×0.01 = 0.01 rad
位置3的"打"[0.8, 0.6]θ₃ = 3×0.01 = 0.03 rad
为便于手算,这里把角速度固定为 0.01 rad/step(位置差 100 步才转 1 rad,约 57°);真实 RoPE 是分维多频——每维角速度 ω_i = 1/10000^(2i/d),从高频≈1 到低频≈1/10000 不等(公式见本课 L2-C2)。此处单一 0.01 仅为示意。
Step 2:旋转计算(cos(0.01)≈0.99995, sin(0.01)≈0.0100)
x' = x·cosθ - y·sinθy' = y·cosθ + x·sinθ
位置1旋转后0.8×0.99995 - 0.6×0.0100 = 0.79996 - 0.006 = 0.7940.6×0.99995 + 0.8×0.0100 = 0.59997 + 0.008 = 0.608
位置3旋转后0.8×0.99955 - 0.6×0.0300 = 0.79964 - 0.018 = 0.7820.6×0.99955 + 0.8×0.0300 = 0.59973 + 0.024 = 0.624
cos(0.03)≈0.99955, sin(0.03)≈0.0300
Step 3:旋转后做点积——相对位置自动出现
对比旋转后点积旋转前点积
位置1的"打" × 位置3的"打"0.794×0.782 + 0.608×0.624 = 0.621 + 0.379 = 1.0000.8×0.8 + 0.6×0.6 = 1.000
都是同一个词"打"→旋转前点积=1.0,旋转后仍≈1.0。因为两个向量旋转方向相同、角度差很小(0.02 rad),几乎不影响相似度。
但如果位置差很大(比如位置1和位置100):角度差 = 99×0.01 = 0.99 rad ≈ 57° → 旋转后点积会从1.0降到约 cos(0.99)≈0.55——点积随着距离衰减,相对位置信息被自动编码进去了。
🎯 RoPE 为什么比绝对位置编码更利于外推?

标签表方式的死穴是"没见过位置257的标签"。旋转是连续数学操作——cos 和 sin 对任意角度都有定义,位置 257 的旋转角 257×0.01 = 2.57 rad 仍可算出。模型学会"旋转角越大、点积衰减越多"后,相对位置性质比绝对编码更好。但这不等于"天然支持任意长度"——超出训练长度后模型从未在这些角度上训练过,实测仍会退化,长文本外推需要 RoPE scaling(位置插值 / 调大 base)。

AI 助教
👋 对 RoPE 旋转编码有疑问?比如"为什么用旋转而不是平移?""角速度 0.01 怎么选的?"

我会用具体数字帮你走一遍。
3
生成第 5 个字时,前 4 个字的 Key 和 Value 不能扔——显存撑不住了怎么办?
提示:GPT 逐字生成,"吃苹果"的"果"时需要回忆"我爱吃"三个字——每次重算太慢,存着又太占地方
▾
知识背景
GQA = Grouped-Query Attention(Ainslie et al., Google, 2023)。介于 MHA(Multi-Head,每头独立 Q/K/V)和 MQA(Multi-Query,所有 Q 共享一个 KV)之间。将 Q 头分组共享 KV——如 32 个 Q 头分为 8 组 → 8 个 KV 头。KV Cache 显存降至原来的 25%。Llama 2 的 34B 与 70B、Llama 3、Gemma 使用 GQA(34B 已训练但未发布)。

上一张卡片我们用 RoPE 让"打"知道了自己的位置。但生成时还有一个瓶颈:GPT 逐 token 生成"我爱吃苹果",生成"果"时,需要看"我""爱""吃""苹"四个字的 Key 和 Value。每次都重算前 4 个字太慢——所以把它们的 K、V 缓存起来,这就是 KV Cache。

问题:KV Cache 占多少显存?

🧮 KV Cache 有多大?生成一个 1024 token 的回复
Step 1:一个 token 的 Key 或 Value 大小
假设:4 个头、每头 64 维、8 层
一个 token 的 Key = 4头 × 64维 = 256 个数(float16,每个 2 字节 = 512 字节)
一个 token 的 K+V = 1024 字节 ≈ 1 KB
Step 2:生成 1024 个 token 的最坏情况
阶段需要缓存多少 token 的 KV?显存
生成第 1 个字时0 个历史的 KV0
生成第 512 个字时511 个历史的 KV511 KB × 8层 = 4 MB
生成第 1024 个字时1023 个历史的 KV1023 KB × 8层 = 8 MB
Step 3:真实规模放大——32 头、128 维、32 层
配置KV Cache 总量(1024 token)
MHA:32 头独立 K、V32头 × 2(K+V) × 128维 × 1024token × 32层 × 2字节 = 512 MB
MQA:32 头共享 1 对 K、V1组 × 2 × 128 × 1024 × 32 × 2 = 16 MB(省了 97%)
GQA:32 头分 8 组共享 KV8组 × 2 × 128 × 1024 × 32 × 2 = 128 MB(省了 75%)
MQA 省了 97% 显存,但代价是所有头被强制用同一种注意力模式——Shazeer 2019 实测 PPL(困惑度,越小说明模型预测下一个词越准)从 29.9 升到 30.2(约 +1%,原文结论为"仅轻微退化"),生成质量有可感知下降。
Step 4:GQA——分 8 组,每组内共享 KV
方案KV Cache质量损失代表模型
MHA(全独立)512 MB0%原始 Transformer
MQA(全共享)16 MBPPL +1%(估算)PaLM
GQA(8组)128 MB接近 MHALlama 2/3
GQA 省了 75% KV Cache 显存,质量损失却很小(GQA 论文称接近 MHA)——几乎白捡。这就是为什么 Llama 选了它。
🎯 为什么不全部共享(MQA)?

不同注意力头会关注不同语义(示意:有的头偏向语法、有的偏向指代、有的偏向情感——具体分工因模型而异,来自事后探针分析,非固定编号)。全共享把所有头的视野压缩到一个 K/V 里,就像让所有医生共看同一张 X 光片——省胶片但丢诊断精度。GQA 给每组留下了独立的 K/V,保留了注意力多样性。

AI 助教
👋 对 GQA 和 KV Cache 有疑问?比如"为什么不分 16 组?""不用 KV Cache 有什么替代方案?"

我会用具体数字帮你算。
4
LayerNorm 的"减均值"和 ReLU 的"负值截断"——这两个操作能不能砍?怎么砍?
提示:先分别问一句——LayerNorm 的"减均值"和 ReLU 的"负值截断",各自究竟改变了向量的什么?少了这一步,谁受影响最大?
▾
知识背景
RMSNorm = Root Mean Square Layer Normalization(Zhang & Sennrich, 2019):砍掉 LayerNorm 的"减均值",只保留缩放。这里要说清一个常见误传:LayerNorm 是逐 token 在特征维上归约,本不需要跨卡通信——当年被批评"要跨 GPU 同步统计量"的其实是 BatchNorm(按批次维统计)。RMSNorm 的收益来自少算"减均值/中心化"这一步,论文实测运行时间降低 7%~64%。Llama、T5、PaLM 使用 RMSNorm。
SwiGLU = Swish-Gated Linear Unit(Shazeer, Google, 2020):用门控替代单激活——多出一个"阀门"分支,让每一维的信息通过量由数据自己决定(它的算式、以及它为什么让 FFN 的中间维变成 11008 这种"怪数字",放在第 5 课逐层解剖时算)。GLU 门控思想源自 Dauphin et al.(2017)。

上一张卡片我们用 GQA 省了 KV Cache 的 75% 显存。现在解决两个计算瓶颈:归一化层太慢、激活函数丢信息。

🧮 改造一:砍掉 LayerNorm 的"减均值",变为 RMSNorm
Step 1:LayerNorm 做了什么?(以 4 维向量 [1.2, -0.8, 0.5, -1.5] 为例)
步骤操作结果
① 减均值μ = (1.2-0.8+0.5-1.5)/4 = -0.15[1.2+0.15, -0.8+0.15, 0.5+0.15, -1.5+0.15] = [1.35, -0.65, 0.65, -1.35]
② 除标准差σ = √((1.35²+0.65²+0.65²+1.35²)/4) = √(1.1225) ≈ 1.06[1.27, -0.61, 0.61, -1.27]
③ 乘 γ可学习参数缩放最终输出
注意:这一步的"算均值"是对每个 token 自己的特征维做的归约,本不需要跨卡通信(真正需要跨 GPU 同步"批次统计量"的是 BatchNorm)。RMSNorm 省掉的是这一步中心化的计算量。
Step 2:RMSNorm——砍掉减均值,只保留缩放
步骤操作结果
① 算 RMSRMS = √(mean(x²)) = √((1.44+0.64+0.25+2.25)/4) = √(4.58/4) = 1.07—
② 直接除x / 1.07 × γ[1.2/1.07, -0.8/1.07, 0.5/1.07, -1.5/1.07] = [1.12, -0.75, 0.47, -1.40]
少算一次"减均值/中心化",计算量更小(同样不涉及跨卡统计)。RMSNorm 原文实测运行时间降低 7%~64%(规模越大越明显)。
🧮 改造二:ReLU → SwiGLU,给 FFN 加门控
Step 3:ReLU 的问题——负值全扔掉
输入向量 [0.6, -0.3, 0.9, -0.2] 经过 ReLU → [0.6, 0, 0.9, 0]
-0.3 和 -0.2 的信息全丢了。在深层网络中,负值里可能藏着有用的抑制信号。
Step 4:SwiGLU——让网络自己决定保留多少
组件作用类比
W_gate(门控权重)算出每个维度"该不该过"水龙头阀门
Swish(x·W_gate)Swish 门控值(Swish 即 SiLU,一种平滑激活)阀门开多大(关小≈0, 开大≈原值)
W_up·x信息本身水管里的水流
Swish(x·W_gate) × (x·W_up)门控值 × 信息阀门控制流量
关键差异:SwiGLU 对负值的抑制是"部分关阀门"(比如 0.3),不是 ReLU 的"直接截断为 0"。信息没有完全丢失。
Step 5:同等参数量下的效果对比
激活函数验证 PPL(示意)说明
ReLU9.7max(0,x):负值全截断,信息损失大
GELU8.9GELU(x)=x·Φ(x):负值被软抑制,不再硬截断,但所有维度共用同一处理
SwiGLU8.5门控机制 Swish(x·W_gate)⊙(x·W_up):示意 PPL 低于前两者
表中 PPL 为示意值,只用于展示三种激活的相对高低;不同论文的量级与口径(如 log-PPL)不同,此处不指向某一篇实测数字。(可核验的一手对照见 Shazeer 2020:C4 文段填充任务 524k 步的 log-PPL 为 SwiGLU 1.636、GELU 1.679、ReLU 1.677。)
🎯 不做这两个改进会怎样?

保留 LayerNorm 的减均值 → 每层都要多算一次中心化(每层每步都生效,累积起来是可观的算子开销)。保留 ReLU → 负半轴信息直接清零。两者叠加:同等训练预算下,训练运行时间可降 7%~64%(RMSNorm 原文口径),困惑度也有改善——对大规模训练,这是实打实的算力与成本。

AI 助教
👋 对 RMSNorm 和 SwiGLU 有疑问?比如"RMSNorm 不稳定的情况存在吗?""SwiGLU 的门控(Swish/SiLU)为什么不换成别的?"

我会用具体数字回答。
5
为什么 Post-Norm 难堆到几十层,而 Pre-Norm 能做到 80 层?Norm 放残差里面还是外面,天差地别?
提示:先想一件事——从 loss 一路回传到第 1 层,这条路上要经过哪些运算?把 Norm 挪个位置,这条路的"堵点"会变吗?
▾
知识背景
Pre-Norm(Pre-Layer Normalization)——把 LayerNorm 放在子层的输入侧,而不是输出侧。这里的子层指 Attention 与 FFN。GPT-2、T5 等 2019 年已采用;Xiong et al.(2020)给出梯度层面的理论解释(原文把 Pre-LN 称为 "recently proposed",说明该术语与做法在 2020 年前已在社区使用,并非该文首创命名)。原版 Post-Norm 在 ~24 层之后需要非常小心地调 warmup / 学习率才训得动,再深就难以为继(教学近似,经验值;BERT-large 本身就是 24 层,此处说的不是"24 层训不动"),Pre-Norm 让 80+ 层的训练成为可能。

上一张卡片我们砍掉了 LayerNorm 的减均值。现在看 Norm 的位置——放在残差连接的入口还是出口?这决定了模型能叠多少层,因为 Norm 挡在残差主干上时,梯度回传到浅层会被逐层压缩。

打个比方:它就是主水管上的减压阀——装在主管道上(Post-Norm),水压(梯度)要先过阀门才到上游;改装在支路上(Pre-Norm),主管道就是一根直通管,上游水压几乎不受影响。

🧮 反向传播走一遍:梯度每层衰减多少?
Step 1:Post-Norm(原始 Transformer)——Norm 在残差出口
x_{l+1} = Norm(x_l + F(x_l))——做完 Attention 再 Norm,Norm 堵在了梯度回传的路上。
层数梯度累积(示意:每层乘 0.5)
第 1 层0.5
第 4 层0.5⁴ = 0.0625
第 12 层(BERT)0.5¹² ≈ 0.00024
第 24 层0.5²⁴ ≈ 5.9 × 10⁻⁸(几乎为零)
第 32 层0.5³² ≈ 2.3 × 10⁻¹⁰(彻底为零,训不动)
这就是 BERT 做到 24 层就到头的根本原因——再深梯度就"饿死在半路"了。
Step 2:Pre-Norm(现代 Llama)——Norm 在残差入口
x_{l+1} = x_l + F(Norm(x_l))——先 Norm 再 Attention,梯度通过残差连接直通底层,不经过 Norm。
层数梯度累积
无论多少层1^N = 1(不变)
残差连接像高速公路,Norm 在匝道上而不在主路上——梯度畅通无阻。这就是 Llama 能堆到 80 层的原因。
Step 3:一句话对比
Post-Norm(BERT)Pre-Norm(Llama)
公式Norm(x + Sublayer(x))x + Sublayer(Norm(x))
梯度路径经过 Norm → 被压缩不经过 Norm → 直达
最大深度~24 层80+ 层
32 层时梯度0.5³² ≈ 2.3×10⁻¹⁰1³² = 1
🎯 为什么这件事如此关键?

Pre-Norm 是工程上最简单但影响最深远的改造——只换了一行代码的位置(Norm 放前面而不是后面),就让模型层数从 20+ 突破到 80+。没有 Pre-Norm,ChatGPT 级别的模型根本堆不起来。

AI 助教
👋 对 Pre-Norm 有疑问?比如"Pre-Norm 有缺点吗?""为什么 BERT 不用 Pre-Norm 重新训练一版?"

我会用具体例子回答。
6
四条硬约束各自逼出一个解——能不能同时装进同一个 Block?装进去之后收益到底有多大?
提示:和原始 Transformer 对比,你认出了几个改造?
▾
知识背景
LLaMA = Large Language Model Meta AI(Touvron et al., Meta, 2023)。较早把这些改造系统组合起来的开源模型之一。它把 Pre-Norm、RMSNorm 与 SwiGLU 拼进同一个 Block。位置信息则交给 RoPE。Llama 1(2023)展示了"小模型+多数据"的可比性。Llama 2(2023)新增 GQA。Llama 3(2024)扩展至 405B。催生了整个开源 LLM 生态(Alpaca、Vicuna 等)。

前面 5 张卡片我们走完了 RoPE 和 GQA 两步改造。后面三步是 RMSNorm、SwiGLU 与 Pre-Norm。这五条约束的解互不冲突,因此可以同时装进同一个 Block。打个比方:装修一套房子,水电、地板、门窗是不同工种的活,最后都装进同一个空间——但能这样拼装的根本原因,是它们各自作用在 Block 的不同环节,彼此不抢工位。把它们拼起来之后,从 2017 年的原始 Transformer 到 2024 年的 Llama,每一处改动的数值收益才能放在同一口径下算清。

🧮 走查:把五处改造放进同一个模型算一遍(32 头 / 128 维 / 32 层 / 1024 token / fp16)
Step 1:KV Cache 账——同一组配置,MHA 512 MB → GQA(8 组) 128 MB
方案KV 头数算式(字节)结果
MHA:每头独立 K、V3232 × 2 × 128 × 1024 × 32 × 2536,870,912 B = 512 MB
GQA:32 头分 8 组共享 KV88 × 2 × 128 × 1024 × 32 × 2134,217,728 B = 128 MB
式中第一个 2 是 K、V 两份,128 是每头维度,1024 是 token 数,32 是层数,末位 2 是 fp16 = 2 字节。与 Card 3 的 Step 3 和下方对比表同一口径(Card 3 的入门例子用小配置 4 头 / 64 维 / 8 层,口径不同已注明)。省下 (512−128)/512 = 75%。
Step 2:深度账——Post-Norm 的 0.5³²,Pre-Norm 的 1³²(示意:假设每层乘 0.5)
结构梯度逐层回传倍数32 层后
Post-Norm(原始 Transformer)0.5³²≈ 2.3 × 10⁻¹⁰(≈ 0)
Pre-Norm(Llama)1³²1
0.5³² = 2.3283 × 10⁻¹⁰ ≈ 2.3 × 10⁻¹⁰,与 Card 5 的 Step 1 / Step 3 同一「示意:假设每层乘 0.5」口径——Post-Norm 下第 1 层几乎收不到梯度,这就是它堆不过 ~24 层的原因。
Step 3:三类收益小结——每项都写明口径与置信度
改造量口径 / 置信度
GQAKV Cache 512 MB → 128 MB(−75%)按 32 头 / 128 维 / 32 层 / 1024 token / fp16 算得,可复算
Pre-Norm可堆层数 ~24 → 80+;32 层梯度 2.3×10⁻¹⁰ → 10.5 衰减为「示意」;层数上限 24 / 80+ 为经验值(估算)
RMSNorm + SwiGLU运行时间降低 7%~64%;SwiGLU 示意 PPL 8.5 vs ReLU 9.77%~64% 为 RMSNorm 论文实测口径;PPL 为示意值
三处数字都能从本课前面卡片导出:−75% 由 Step 1 算出,梯度由 Step 2 算出,7%~64% 与示意 PPL 与 Card 4 同源。
改造原始 Transformer (2017)现代 Llama (2023+)数值收益
位置编码Sinusoidal 绝对位置RoPE 旋转编码编码相对距离,外推性优于绝对位置编码,但超出训练长度仍需 RoPE scaling(位置插值 / 调大 base)
注意力MHA(全头独立 KV)GQA(分组共享 KV)KV Cache 512MB→128MB (-75%),质量损失很小(估算)
归一化LayerNormRMSNorm砍掉减均值,论文实测运行时间降低 7%~64%
激活函数ReLUSwiGLU门控机制替代一刀切(示意 PPL 8.5 vs 9.7)
Norm 位置Post-NormPre-Norm梯度不再衰减 (0.5³²→1³²),可堆 80 层 vs 24 层
注:表中 KV Cache 的 512MB→128MB 按「32 头 / 128 维 / 32 层 / 1024 token」口径计算;Card 3 的入门例子用小配置(4 头 / 64 维 / 8 层),两处基础量不同。
🎯 关键认知

这些改造不是"锦上添花"——GQA 没有的话 70B 模型推理根本跑不动显存,Pre-Norm 没有的话 32 层以上就训不动。它们是现代 LLM 的生存必需品,不是优化项。

AI 助教
👋 对这张卡片有疑问?我会用具体数字和计算例子来回答。