第3讲 · Stanford CS336:从零开始的语言建模:架构

本书为课堂口语记录的中文书面化重构,观点归讲师,编者补充均已明确标注。

视频来源 [Stanford CS336 Language Modeling from Scratch
课程主页与讲义(若有) 见文末「附:官方参考与延伸阅读」
本讲时长 约 89 分钟
讲述者 课程讲师(Percy Liang 的合讲者)

对应讲次:第3讲(Stanford CS336 语言建模)

制作模型:deepseek-flash(vb compose,据 run.log)


导览

一、这一讲老师想回答的核心问题

老师开篇就把这节课的标题定为 「一些你并不想知道的架构与超参数的一切」(Everything you didn't want to know about architectures and hyperparameters)。他用他自己提出的问题顺序来组织全讲:

  1. 现代语言模型的架构长什么样? 从一个"朴素 Transformer"(Attention Is All You Need,即 Vaswani 等人 2017 年的论文)出发,到今天各家模型实际采用的结构,中间到底变了什么?
  2. 哪些改动是所有人都会做的? 也就是说,哪些选择几乎是"铁律"(比如把 LayerNorm 移出残差流、用 RMSNorm、用门控激活)。
  3. 哪些改动是可以自由变动的? 哪些"看起来很明显很重要"的超参数,其实可以随便选而几乎不影响性能(老师反复强调的一句是:这些超参数"很宽容")。
  4. 为什么这些选择是这样子的? 它们背后是性能考虑、系统(GPU 吞吐)考虑,还是训练稳定性考虑?

二、难点在哪

老师自己说,这是他一向觉得最"玄"(inscrutable)的一块。难点不在单个部件的数学,而在:

三、读者可能带着什么疑问进来

① 老师真正提出的问题(本讲的问题): - 该怎么给一个语言模型"选配"各个组件? - 这些组件里哪些是关键、哪些是无所谓的?

② 编者预判的读者疑问(编者预判,非老师原话): - (a) "Pre-Norm 和 Post-Norm 到底差在哪,为什么几乎所有人都用 Pre-Norm?" - (b) "SwiGLU、GeGLU、ReGLU……这些名字到底是什么意思?"(老师表示自己年轻时"引以为豪地拒绝去搞懂 SwiGLU",后来发现不得不懂。) - (c) "RoPE 为什么这么流行?它和正弦位置编码有什么本质区别?" - (d) "为什么大家还在用权重衰减,明明不担心过拟合?"

四、本讲路线

老师给出的大致路线是:先讲"共识性的架构部件"(层归一化的位置、归一化方式、激活函数门控、并行/串行、位置编码)→ 再降到"超参数"层面(FF 维度比例、头维度、宽高比、词表大小)→ 最后讲"稳定性技巧"(因为它和架构选择深度绑定:输出 softmax 的处理、QK-Norm、logit 软上限)→ 以注意力层面的干预(GQA/MQA、滑动窗口注意力)收尾。下一讲会补上状态空间模型(SSM)与 MoE 等内容。

编者说明:本讲全部围绕"稠密注意力"(dense attention)这一范畴。老师明确表示,改变注意力本身的替代方案(线性注意力、状态空间模型)留到下一讲。


一、总览:现代 Transformer 到底改了什么

(参考时间: 00:00:05)

老师给这节课定了一个"生态位":训练自己的模型、试不同的架构,是学习架构最好的方式(这也是本课程的哲学);但没那么多算力去穷举整个设计空间,所以次优的事情是学习别人的经验——把这一年里所有新出的模型摊开来看,找出哪些参数在"有效架构"里是固定的,哪些是可以自由变动的。

课程开场,讲师在讲述本讲的目标与

📷 课程开场,讲师在讲述本讲的目标与"从零开始的语言建模"的路线
回到原视频 (00:00:12)

1.1 一段关于"追模型"的切身经历

(参考时间: 00:03:19)

老师讲了一段非常具体的过程,正是这套"综述式方法"的由来:

他把这些整理成一张小表(结尾会再回到它),表里按模型列出词表大小、用的层归一化类型、位置编码类型等等,趋势一眼可见。

编者说明:上面出现的具体模型清单(Qwen 2/3、Gemma 3/4、InternLM2、NeMo Tron 4、Percy 的 8B 模型等)是本讲的核心材料,也是老师"综述有据"的直接体现,故予保留。其中"Marine"疑为某个训练框架名,见文末「已知疑点」。

1.2 架构演化的三段论

(参考时间: 00:06:22)

老师把架构史概括为三段:

graph LR
    A["早期实验期<br/>从 Transformer 到 GPT-3<br/>各试各的,无统一标准"] --> B["LLaMA 2 时代<br/>大家都去模仿 LLaMA 2<br/>只做微小改动"]
    B --> C["去年<br/>转向提升训练稳定性<br/>的架构修改"]
    C --> D["今年<br/>转向支持更长上下文<br/>的架构修改"]

这条时间线是理解后续所有"共识"的关键背景。

1.3 一句话概括"架构的本质"

老师说,架构其实是一组复杂权衡,因为它同时要满足三个目标:

正因如此,很多架构选择才显得"不那么优雅"——它们是这三种需求妥协的产物。


二、层归一化:位置、类型与残差流的干净原则

(参考时间: 00:07:30)

画面回看(00:07:35)

📷 画面回看(00:07:35)
回到原视频 (00:07:35)

画面回看(00:14:45)

📷 画面回看(00:14:45)
回到原视频 (00:14:45)

老师说,架构上大家分歧很多,但有一件事几乎人人同意:Transformer 论文在"层归一化放在哪"这件事上做错了。

原始 Transformer 的 Post-Norm 结构与残差流示意

📷 原始 Transformer 的 Post-Norm 结构与残差流示意
回到原视频 (00:07:55)

2.1 Pre-Norm vs Post-Norm

在原始 Transformer 里,层归一化被放在残差路径(residual path)内部,也就是每次把某个部件的输出加回残差流之后再做一次归一化——老师称这种为 residual norm / post-norm。

替代方案是 Pre-Norm:把归一化放到残差流外面,在每个计算之前(多头注意力之前、FFN 之前)做归一化。

graph TD
    subgraph PostNorm["Post-Norm(原始做法)"]
        X1["x"] --> Attn1["注意力"] --> Add1["加回残差流"] --> LN1["LayerNorm 在残差流内"]
        LN1 --> Mlp1["FFN"] --> Add2["加回残差流"] --> LN2["LayerNorm 在残差流内"]
    end
    subgraph PreNorm["Pre-Norm(现代做法)"]
        X2["x"] --> LN1b["LayerNorm"] --> Attn2["注意力"]
        X2 --> Add1b
        Attn2 --> Add1b["加回残差流<br/>(残差流保持'干净')"]
        Add1b --> LN2b["LayerNorm"] --> Mlp2["FFN"]
        Add1b --> Add2b
        Mlp2 --> Add2b
    end

2.2 为什么大家一致地选 Pre-Norm

老师给出两条理由:

理由一(最初的动机):能否去掉 warm-up。 早期研究想解决"训练必须 warm-up"的问题,但很快发现去掉 warm-up 会严重影响稳定性与收敛。实验显示,Post-Norm 的收敛曲线(图中紫色虚线)明显不如 Pre-Norm;Pre-Norm 即便不做 warm-up 也能收敛得更好。

理由二(真正让它留下来):梯度衰减与保持残差流干净。 做架构的人常说的一句话是 "keep your residual stream clean"(保持残差流干净):

Pre-Norm 相对 Post-Norm 更平滑的收敛与梯度曲线

📷 Pre-Norm 相对 Post-Norm 更平滑的收敛与梯度曲线
回到原视频 (00:10:25)

这张对比图出自 Salazar 与 UN 的工作——据老师说是最早认真研究这一现象的人之一。老师认为,"稳定性 + 能训深"正是 Pre-Norm 能留存下来的根本原因。

编者说明:老师此处想强调的核心是——Pre-Norm 让梯度传播构成一条"恒等捷径",从而使深层网络既稳定又能训起来。这与残差连接本身的设计动机一脉相承。

2.3 归一化也可以放在计算之后

(参考时间: 00:12:45)

既然"把归一化移出残差流"才是关键,那它放在计算之前还是之后应该都行。事实也确实如此:

老师说了一句近乎"荒谬"却又被反复验证的结论:只要遇到稳定性问题,就往里加一个层归一化,往往就有用——"这听起来很怪,但每次都灵"。

2.4 LayerNorm 与 RMSNorm

(参考时间: 00:13:58)

原始 LayerNorm 的做法是:对激活 $x$ 做减均值、除方差,再缩放回去。

$$ \text{LN}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} \cdot \gamma + \beta, \quad \mu = \frac{1}{d}\sum_{i=1}^{d} x_i, \quad \sigma^2 = \frac{1}{d}\sum_{i=1}^{d} (x_i - \mu)^2 $$

而现代模型几乎都用 RMSNorm:不减均值、不加偏置,只做缩放:

$$ \text{RMSNorm}(x) = \frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2 + \epsilon}} \cdot \gamma $$

老师说:从表达能力看,LayerNorm 其实比 RMSNorm 更强,所以并非"必须"用 RMSNorm。但实践中:

2.5 为什么"更快"很重要:算术强度视角

(参考时间: 00:14:56)

这里老师把话题拉到系统层面,引入 Percy 在上一讲提过的算术强度(arithmetic intensity)概念,并给了一个非常具体的数字对比:

为什么?因为浮点运算量 ≠ 运行时。运行时取决于内存搬运。矩阵乘法(张量收缩)大部分工作是"乘",而统计归一化(层归一化)大部分工作是"内存搬运"——激活值可能很大,搬来搬去很慢。所以只要能删掉一个"算术强度很低、表达力也不多"的小操作,就是免费的优化。

算术强度对比图,LayerNorm 的算术强度极低,属于应尽量消除的操作

📷 算术强度对比图,LayerNorm 的算术强度极低,属于应尽量消除的操作
回到原视频 (00:16:37)

有学生问:"归一化的内存搬运为何相对算术如此不成比例?" 老师答:对张量收缩而言,大部分是乘法;对统计归一化而言,大部分是搬运,而搬运很慢——当"搬运"几乎占了全部开销时,代价就不小了。

有一篇 Google 2020 年的论文做了实证:把一个 2 亿参数的小 Transformer 换成 RMSNorm 后,"每秒步数"(第三种指标)提高了,而且性能甚至更好。老师强调:性能更好不是保证,但至少是免费的、白得的系统收益。

2.6 顺带删掉 bias 项

(参考时间: 00:18:02)

更一般地,Transformer 和神经网络里的偏置项(bias)通常没什么用。原版 Transformer 的线性层都有 bias,但大多数实现直接删掉了。

老师说,这一类结论("LayerNorm 放外面 + 删 bias + 用 RMSNorm")无法先验推理,只能靠大量实验和集体经验得到。他还特别点名一个"好笑的例外":OPT-350M —— 为什么只有这一个模型在残差流里放了 post layer norm?他也不知道,"OPT 这一系列本来就是一锅粥"。


三、激活函数:从 ReLU 到门控线性单元(GLU)

(参考时间: 00:20:16)

画面回看(00:21:00)

📷 画面回看(00:21:00)
回到原视频 (00:21:00)

画面回看(00:25:00)

📷 画面回看(00:25:00)
回到原视频 (00:25:00)

老师说,激活函数是一个"动物园":ReLU、GELU、Swish、ELU、GeGLU、SeLU、SwiGLU、LiGLU……他年轻时"以从未搞懂 SwiGLU 为荣",如今不得不好好理解——关键是搞清这些名字里哪一部分真正影响性能。

3.1 演进脉络

3.2 什么是门控线性单元(GLU)

普通 ReLU 前馈层:

$$ \text{FFN}(x) = W_2 \cdot \text{ReLU}(W_1 x) $$

"门控"的想法是:再拿一个向量去逐元素调节(modulate)ReLU 的输出。具体是在 ReLU 之外再引入一个门:

$$ \text{GLU}(x) = W_2 \Big( \text{ReLU}(W_1 x) \odot \sigma(V x) \Big) $$

其命名规则是"把第一个激活的名字 + GLU":

编者拓展|为什么"门控"有效:门控的思想来自 LSTM 等门控循环结构——用一个信号去调节另一个信号的通过程度。它相当于在前馈层里加入一个乘性交互路径,让网络有能力表达"根据上下文按需开启/关闭某个特征"的行为。这种乘性结构比单纯的逐元素非线性具有更强的表达力,因此在同样的参数预算下往往表现更好。

3.3 门控带来的参数问题与 2/3 规则

(参考时间: 00:24:10)

门控让前馈层多了一个矩阵 $V$(3 个矩阵而非 2 个),参数变多了。为保持参数量与原 MLP 相同,通常把前馈维度缩小到原来的约 2/3。

门控线性单元相对非门控变体的性能对比(带误差棒)

📷 门控线性单元相对非门控变体的性能对比(带误差棒)
回到原视频 (00:25:35)

3.4 谁用哪个?

老师说,在门控变体之间选哪个其实关系不大,真正重要的单一维度是:对这些非线性做门控,它能以很低的计算开销带来不错的增益。

当然,门控并非必须:GPT-3 就没用门控;NeMo Tron 340B 甚至用了"平方 ReLU"(一个有点疯狂的选择)也照样能训。但如今见不到几个不用门控的模型了。


四、并行层 vs 串行层

(参考时间: 00:27:26)

画面回看(00:28:00)

📷 画面回看(00:28:00)
回到原视频 (00:28:00)

标准 Transformer block 是串行的:先算注意力,再算 MLP,一个接一个。"系统思维强"的人会说这是个瓶颈——必须等一个算完才能算另一个。于是有人提出:能不能把 Transformer block 并行化? 让 MLP 和注意力的输出相加后一起加回残差流。

这个想法最早出现在 GPT-J(GPT-3 的开源复现尝试),PaLM 报告里也有清晰描述。如果实现得当,还能共享层归一化、融合矩阵乘法,从而获得额外系统优化。

graph TD
    subgraph Serial["串行(主流)"]
        Xs["x"] --> Attns["注意力"] --> Adds["加"] --> Mlps["MLP"] --> Adds2["加"]
    end
    subgraph Parallel["并行(PaLM/GPT-J 系)"]
        Xp["x"] --> Attnp["注意力"]
        Xp --> Mlp2["MLP"]
        Attnp --> Addp["相加加回残差流"]
        Mlp2 --> Addp
    end

编者说明:老师把并行层描述为"用一半的深度换取系统效率",这是判断是否采用它的核心权衡。

学生在提问环节问:"并行的精度损失到底有多大?"老师回答:"这其实很混乱——PaLM 原文很自信地说'无性能损失、系统利用率提升 15%',但后来许多 Google 自己的模型不再用并行,这可以看作一个隐含信号:可能确有损失。只是据他所知,没有做过受控的、干净的并行 vs 串行对照实验。"


五、位置编码:从正弦到 RoPE

(参考时间: 00:31:04)

画面回看(00:34:00)

📷 画面回看(00:34:00)
回到原视频 (00:34:00)

画面回看(00:39:40)

📷 画面回看(00:39:40)
回到原视频 (00:39:40)

老师指出,位置编码是各实现差异最大、最不统一的地方,也是"信息如何跨位置传播"的核心。

首先复习一个前提:注意力本身与位置无关——如果没有位置编码,注意力只是一堆内积,你把词打乱,结果一样。

5.1 早期的几种方案

5.2 RoPE:真正成为主流的方案

(参考时间: 00:32:34)

RoPE(旋转位置编码) 是如今的主流——2024 年之后的大多数模型都用它。老师说它"某种程度上是从天而降",最初也来自 GPT-J(据一篇不太知名的博文 + 论文,作者来自中国)。

5.2.1 动机:我们只关心相对位置

老师主张一个立场鲜明的观点:"不应该关心任何词在句中的绝对位置。如果两个词一起出现,无论出现在句首还是句尾,都应该得到相同的结果。"

形式化地:记 $\langle f(x_i, i), f(y_j, j) \rangle$ 为两个位置嵌入的内积($x, y$ 为词的语义向量,$i, j$ 为绝对位置),我们希望它只依赖相对差值 $i - j$:

$$ \langle f(x_i, i),\, f(y_j, j) \rangle = g(x, y,\, i - j) $$

老师逐一检查旧方案为什么满足不了这个等式:

5.2.2 RoPE 的几何直觉:旋转

那么,怎样才能真正得到一个相对嵌入呢?答案很漂亮:利用内积对旋转的不变性。

举一个二维例子:

graph LR
    A["词的语义向量<br/>(与位置无关)"] --> B["按位置 i 旋转角度 i·θ"]
    B --> C["得到位置编码后的向量"]
    C --> D["两两内积<br/>只依赖相对距离 (i−j)"]

5.2.3 从二维到高维

在二维上只有顺/逆时针两个选择;但高维里有无限多种旋转方式。RoPE 用最简单的办法:把 $D$ 维向量切成两两一组,对每一对做二维旋转,且各组旋转的 $\theta$ 频率不同:

本质上,它把高维旋转反复归约为二维情形。(论文里对复数有一段很复杂的动机说明,但老师说,用"归约到二维 + 逐对旋转"这个几何直觉来理解最省力。)

编者说明:老师此处提到 Gemma 4 采用了一种叫 比例 RoPE / P-RoPE 的做法——它其实只旋转前两个坐标。"这是个奇怪的说法,但也是一种可行做法。"这说明 RoPE 空间里还有很多变体在起作用。

5.2.4 实现方式与"无交叉项"的关键

实现上,用正弦和余弦做一次稀疏乘法即可:

$$ \text{embedding} = (X W) \cdot R, $$

其中 $R$ 是由正弦/余弦构成的旋转矩阵。RoPE 在注意力层面施加(而非最底层),对 Queries 和 Keys 都施加正弦/余弦旋转。

老师说,这里有个关键:RoPE 看起来像正弦编码,但本质不同——它是乘上去的(而非当作嵌入加进去),因此不产生交叉项,内积纯粹是相对的,不会泄露绝对位置。

5.3 课堂问答


六、超参数:比例、头维度、宽高比、词表

(参考时间: 00:41:00)

画面回看(00:48:00)

📷 画面回看(00:48:00)
回到原视频 (00:48:00)

画面回看(00:54:30)

📷 画面回看(00:54:30)
回到原视频 (00:54:30)

老师转入超参数,并说这是个"一旦真要训模型就会碰到"的问题。没有知识时会觉得"要搜一个非常大的高维空间,很可怕",但人们实际尝试的空间其实很小。

6.1 前馈层比例(FF ratio)

共识超参数:前馈维度 / 模型维度。

例外一:门控线性单元。 GLU 多了矩阵 $V$,保持参数量不变就要缩到 2/3,于是比例落到 约 2.67(图中 2.67~2.5 这一段)。

例外二:LLaMA 的"任意选择"。 LLaMA 2 有一批高效的注意力头(用了 MQA,"后面会讲"),于是他们把比例再乘一个任意的 1.33,得到 约 3.5,本质上稍微加重了 MLP 的权重。

所以文献里常见:非 GLU 用 4,GLU 用 2.6 左右或 3.5。

例外三:T5 的 64×。 老师说他很喜欢这个例子——Google 有时非常大胆。T5 把这个倍数设成 64×,理由是系统性的:矩阵乘法越大,硬件利用率越高。Gemma 2 也试着往上推,但 64 仍属"惊人"。有意思的是 T5 的改进版 T5 v1.1 又回到了标准的 2.5 倍——虽未明说,但显然是"试着改回去"。

Kaplan 2020 的实证。 经典神经缩放定律论文里有一次对 FF 比例的扫描(针对很小的模型),发现存在一个"盆地":从约 1 到约 10 的一段,性能都很平(相对最优损失只差一点);超过 10 到 100 会二次地急剧变差。所以 2.6~4 之间都安全。

6.2 头维度(head dim)

(参考时间: 00:49:53)

多头注意力里,通常让头维度 × 头数 = 模型维度,即头维度 $d_{head} = D / H$,使得整体"相当于一个单头 Transformer 的规模"。并非必须如此,可以任意调整,但大多数模型都遵循,且效果不错。T5、以及另一个 Google 模型 Lambda 是显著例外,但大家都围绕在 1 附近。这又是一个很宽容的超参数,围绕 1 有很宽的盆地。

6.3 宽高比(aspect ratio)——概念上最有趣的超参数

(参考时间: 00:51:31)

放大或缩小模型时,通常固定一个宽高比(模型宽度 vs 深度),再整体变大。因此宽高比控制着整个深度-宽度权衡。人们会问:模型该多深?有没有最优?

答案:变化确实比别的超参数大,但存在相当清晰的甜点区——大多数现代模型落在 $D\_model / N\_layers \approx 100$,即每层约 100 的宽度。GPT-3、LLaMA 等都符合。

这背后的权衡是表达力 vs 硬件:

graph TD
    A["选择宽高比 D_model / N_layers"] --> B{太深?}
    B -->|是| C["需按层切分<br/>流水线并行<br/>工程复杂"]
    B -->|否| D{太宽?}
    D -->|是| E["切分容易<br/>张量并行<br/>但表达力存疑"]
    D -->|否| F["D/N ≈ 100<br/>甜点区"]
    C --> F
    E --> F

于是"系统上倾向宽,表达力上倾向深",最终落到约 100。Kaplan 等人还做了不同尺寸模型下的扫描:不论模型多大,最优宽高比都差不多,都在 100 附近。

另一个重要结论(来自老师称为"ETA 等"的工作):做各种架构变体实验后,发现真正起主要作用的其实是 FLOPs——FLOPs 越大模型越好,宽高比本身反而次要。

于是老师总结出一个贯穿全讲的洞见:存在一个宽容的超参数带,人们基本都在这个带里选;真正该操心的是"系统利用率",而非难以推理的"表达力"。

6.4 词表大小(vocab size)

(参考时间: 00:55:14)

清晰的二分格局:

Google 系词表普遍更大;LLaMA 系约 10 万;单语模型约 3 万。

原因:多语言模型确实需要大得多的词表;右边这些模型整体也更大,而缩放定律显示"模型越大,能应付的词表越大"——现在也没人再训大型单语模型了。

课堂问答(关于 bits-per-byte):

一位学生问:用不同的分词器比较 bits-per-byte 是否有效?

老师先给正确的思维框架:语言建模是生成建模任务,建模序列的概率。只要序列固定、并且对所有可能字符串都给出了概率,那么在任何层面比较都是合法的。具体到 bits-per-byte:

老师说 perplexity 与 BPD 互为对偶。后面还有一个关于"改变切分方式后比较 perplexity"的追问,老师表示当面没完全听清,留待日后讨论——但认可这是个有意思的问题。

编者说明:这里的要点是——比较的合法性取决于①序列是否被无损保留、②归一化因子是否一致。这两条都满足时,跨分词器的比较就是公平的。


七、正则化:一个反直觉的世界

(参考时间: 00:59:20)

画面回看(01:02:00)

📷 画面回看(01:02:00)
回到原视频 (01:02:00)

这是老师认为"很反直觉、比 ML 101 直觉更有意思"的一节。

7.1 标准论证:我们几乎不会过拟合

老师们甚至有人只看训练损失,就因为坚信单遍 SGD 不会过拟合。

7.2 那为什么大家还用过 dropout / 权重衰减?

老师说,很多近期模型根本不提这些——它比技术报告愿意披露的层级还要底层。但真正翻看会发现:很多模型两个都用,尤其权重衰减在现代高性能语言模型里依然相当普遍。这非常令人费解。

7.3 权重衰减可能不是正则化,而是优化干预

(参考时间: 01:01:40)

有论文论证并给出证据:权重衰减有时根本不是正则化器,而是与优化器交互、让优化变好。

所以老师再次强调:架构/训练的超参数在很多情况下很难先验推理,必须动手实验——你可能会发现"权重衰减其实是一个优化干预,而非正则化干预"。

编者拓展|权重衰减为什么可能"帮助优化":在自适应优化器(如 AdamW)中,权重衰减相当于每步对参数做轻微"收缩到零"(shrinkage to zero),这可以限制有效步长、抑制参数无限增长。当它与学习率衰减配合时,往往能让模型在后期进入更平坦、更优的极小值区域。这也解释了为什么它在"没有过拟合"的语言建模里仍然有用。

7.4 课堂问答

7.5 超参数总纲

(参考时间: 01:03:26)

老师把超参数部分收成一句话:


八、训练稳定性:三个关键干预

(参考时间: 01:05:01)

画面回看(01:08:00)

📷 画面回看(01:08:00)
回到原视频 (01:08:00)

画面回看(01:13:20)

📷 画面回看(01:13:20)
回到原视频 (01:13:20)

老师说,近几年重点不在性能,而在稳定性。模型越贵,稳定性越关键。

graph TD
    A["训练中不稳定:梯度尖峰"] --> B["定位嫌疑点"]
    B --> C["softmax 的两个危险点"]
    C --> C1["输出端 softmax<br/>→ 归一化因子 Z 可能爆炸"]
    C --> C2["注意力 softmax<br/>→ 输入 logits 尺度失控"]
    C1 --> D["干预一:Z loss(正则 log Z)"]
    C2 --> E["干预二:QK-Norm"]
    C2 --> F["干预三:Logit 软上限(soft cap)"]

不稳定的"惯犯"是 softmax,它有两个危险因素:指数(很容易爆炸)和除法(两个数相除也很危险)。语言模型里有两处 softmax:输出端和注意力。

8.1 输出端 softmax 与 Z loss

(参考时间: 01:07:05)

我们要计算 log 概率以算损失。log 概率为:

$$ \log P = u - \log Z, \quad Z = \sum_i \exp(u_i) $$

理想情况下希望 $Z$ 接近 1,即 $\log Z$ 接近 0。

洞察:softmax 是过参数化的——给 $u$ 加一个常数,可在不影响输出的情况下改变 $Z$。因此可以加一个正则项惩罚 $\log Z$ 偏离 0:

$$ \mathcal{L}_{Z} = \alpha \cdot (\log Z)^2 $$

当 $\log Z$ 接近 0 时,整个表达式数值上很稳定。这就是 Z loss(Z 损失技巧),源自 Jacob Devlin 2014 年的论文,后来经若干开源模型(Baichuan 是第一个用的开源模型,之后 DCLM、OLMo 等也用了)重新流行。老师说,"这是个相当有效的做法"。

8.2 注意力 stabilit:QK-Norm

(参考时间: 01:09:35)

注意力是"退化(degeneracy)大量发生"的地方。老师给出一个总的 设计哲学:遇到不稳定,就想办法往里塞一个层归一化。

标准注意力流程:对输入做 pre-norm → 乘 QKV → 得到 $Q, K$ → 矩阵相乘 → softmax → 与 $V$ 相乘得到加权平均 → 输出。

QK-Norm 的做法:在 $Q$ 与 $K$ 相乘之前再加一个(RMS)层归一化。这样 $Q$、$K$ 的尺度都被归一化到大约 1,于是 softmax 的输入尺度受控,注意力 softmax 变得稳定。

graph LR
    X["x"] --> LN["Pre-Norm"] --> QKV["× W_qkv"]
    QKV --> Q["Q"]
    QKV --> K["K"]
    QKV --> V["V"]
    Q["Q"] --> QKN["RMSNorm"]
    K["K"] --> QKN["RMSNorm"]
    QKN --> MM["Q·Kᵀ"] --> SM["softmax"] --> WV["× V"] --> Out["输出"]

老师还感慨地描述了这一"层层递进"的过程:一开始层归一化只在 pre-norm;后来加到了每个 block 的非线性之后;现在又塞进了 Q 和 K。 这是这一领域稳定化技巧的演进方式。

8.3 Logit 软上限(logit soft cap)

(参考时间: 01:12:20)

这是较不流行、更偏 Google 特有的做法。

Gemma 系列(Gemma 2、3、4)都用了这一技巧:把注意力层所有 logits 都做软上限。

代价:Nvidia 有人做过系统的稳定性干预对比,发现:

所以它是一把双刃剑:非常安全,但有负面后果。


九、注意力层面的干预:GQA/MQA 与滑动窗口注意力

(参考时间: 01:14:08)

画面回看(01:21:30)

📷 画面回看(01:21:30)
回到原视频 (01:21:30)

老师只说"今天只讲稠密注意力下的干预",关于状态空间模型和线性时间注意力留到下一讲。今天要讲的三个高频干预是:GQA/MQA、滑动窗口注意力,以及对位置嵌入的形式的改动。

9.1 从"训练/预填充"到"部署/解码"

(参考时间: 01:15:55)

老师先设定动机:训练好模型后要服务大量用户,这要花两种资源的钱:

训练 / 预填充阶段(给定 prompt 时):

算术强度较好,约 $\frac{1}{K}$($K$ 为头维度)和 $\frac{1}{BN}$——只要头维够大、序列或批量够长,GPU 就能被充分利用。

生成 / 解码阶段:不能并行——一次生成一个 token,再条件于它生成下一个(自回归的"诅咒")。高效做法是维护 KV cache:缓存过去所有 keys 和 queries,只计算必须的新 query-key 交互,所以能省很多计算。

但代价是算术强度变差:读取和写出的参数始终在读写。总算术量不变(还是同样的矩阵乘法,只是增量做),但内存访问模式中出现了 序列长度 × 隐藏维平方 这一项,不再是单纯的 $D^2$。算术强度变成:

$$ \frac{N}{D} + \frac{1}{B} $$

结论:要高效推理小模型,需要大批量 + 短序列,或很大的模型维度。而 $\frac{N}{D}$(序列长度 / 隐藏维)这一项在增量计算下极难降低。

9.2 MQA → GQA 的演进

(参考时间: 01:19:32)

问题:KV cache 让内存访问随序列长度增长,效率变差。

MQA(Multi-Query Attention,多查询注意力):让所有头共享同一组 keys 和 values,只有 queries 因头不同而不同。这样 KV cache 显著变小(所有头共享),总内存访问显著降低,算术强度中多了个 $H$(头数)的因子,大幅提升。

graph TD
    subgraph MHA["MHA 多头注意力"]
        Qh["每个头各自有 Q、K、V"]
    end
    subgraph MQA["MQA 多查询注意力"]
        Qm["每个头有独立 Q"] --> Kv["所有头共享 1 组 K、V"]
    end
    subgraph GQA["GQA 分组查询注意力"]
        Qg["每个头有独立 Q"] --> Kg["按组共享 K、V<br/>组数可调"]
    end
    MHA -->|"效率低、表达力高"| MQA
    MQA -->|"效率极高、表达力低"| GQA
    GQA -->|"效率与表达力折中"| Sweet["甜点区"]

MQA 的代价:所有 queries 共享一对 K、V,损失了不少表达力。

GQA(Grouped-Query Attention,分组查询注意力):折中方案——减少 K/V 的数量,但保留更多的 queries。引入一个"KV 头数 / query 头数"的比例来调节:

GQA 的实测效果:在"下游性能 vs 每样本耗时"的图上:

而且只要稍稍减少 KV 头数,就能拿到大部分效率收益,同时保留大部分表达力。

老师说,DeepSeek-V2 的"多 Head 潜在注意力(MLA)"是另一类不同分解、不同权衡的方案,下一讲会略提。但 GQA 的优势在于实践中的权衡非常有利——因此今天的模型几乎都采用 GQA 结构,用很小的表达力代价换来关键的推理成本降低。

课堂问答:

9.3 滑动窗口注意力与混合结构

(参考时间: 01:25:11)

滑动窗口注意力(sliding window attention) 是很老的想法:GPT-3 就已经用——交替使用全注意力(每个位置可以看到过去所有位置) 和 带状矩阵式注意力(只能看到固定窗口内的位置)。OpenAI 早期也有关于不同注意力模式的工作。

但这一做法在过去一年里变得非常流行:交替"全局 + 局部"注意力,是在"管理长上下文性能"和"不付太多推理成本"之间的甜点区。

graph TD
    A["层结构(每 4 层)"] --> B["第 1 层:全注意力<br/>可看所有历史"]
    B --> C["第 2、3、4 层:滑动窗口注意力<br/>只看局部"]
    C --> D["下一次全注意力<br/>聚合前面所有局部信息"]
    D --> E["以此类推<br/>局部信息逐层汇聚为全局"]

具体例子:

还有一些创新是对长距离信息的嵌入形式动手——比如去掉 RoPE,让长距离部分根本没有位置嵌入(近乎"词袋"),而短距离部分仍保留位置信息。这类改动涉及嵌入 + 局部/全局交替结构两方面。

老师说,这仍是活跃的研究领域,也是架构工作最多、变化最大的地方。

采用者:

老师说,这就是过去一年出现的新主题:开放模型在认真解决长上下文性能,而目前的做法是"混合模型"——既不是纯全局注意力,也不是纯廉价注意力,而是两者的混合,目前看效果很好。


十、收束

(参考时间: 01:28:37)

老师总结:把这些模型横着看,就能看出很多模式和共识,从而形成一种"什么可以做、什么对大家是好的"的一般认识。同时也会看到不少差异——如何处理上下文、如何处理位置嵌入,乃至分词都各不相同。

但共性是存在的。希望这些共性,能在同学们做作业、刷排行榜(leaderboard)时,提供一些直觉。


已知疑点

本节罗列本讲中不确定、可能听错、或与常识/官方资料冲突的地方,并注明依据或时间点。凡拿不准处,我们宁可标注,不替讲师做决定。

  1. 模型名称拼写(多处):
  2. (00:03:27) "InternLM2" 与 "Gemma 3"、"Qwen 2" 并列,判断为公开的 InternLM 系列,但字幕未给出确切版本号。
  3. (00:03:39) "NeMo Tron 4" 疑为 Nemotron 4(NVIDIA 系列),字幕可能的听写误差,正文按"NeMo Tron 340B"保留讲师口述。
  4. (00:03:59) "Percy's own 8B model trained with Marine" —— "Marine" 疑为某个训练框架/项目的名(可能是 Marin),字幕不确定,正文已标注。
  5. 人名/论文名 (00:10:25):"Salazar 与 UN" —— 字幕中第二个人名被截断,可能是 Salazar and ... 的某位合著者。正文按字幕保留,标注不确定。
  6. "Gemma 4"/"Qwen 3.5" 等未来时间点:本讲属于 Spring 2026 课程,讲师多次提到"上周四刚出的 Gemma 4",以及"Qwen 3.5"。这些在一般公开资料中属于较新的发布,编者未逐一核实版本号,正文按讲师原述保留。
  7. "Eagle" 位置编码 (00:41:27):学生提问中出现的 "Eagle",与正文讨论的 RoPE 变体相关;讲师回答说"区别只在于旋转了哪些坐标"。编者未在该范畴中确认一个公认名为 "Eagle" 的位置编码,可能是 ASR 误差或某个非主流做法,正文照讲师口述处理。
  8. Z loss 年份 (01:08:35):讲师先说 "Jacob Devlin's paper 2024",随即自我更正为 2014。正文采用 2014。
  9. 正则化与优化器交互的表述 (01:04:45):讲师关于"权重衰减与 AdamW 类优化器交互"的说明较口语化,编者按"它可能允许用更大学习率或更快衰减"复述;具体的耦合机制、以及"是否所有自适应优化器都如此"在讲中并未展开。
  10. 并行层的精度损失 (00:41:00 附近问答):PaLM 原文声称"无性能损失",但讲师指出后续 Google 模型多不再采用,这可能暗示存在损失;但据讲师说没有受控实验,故正文明确标注"此为隐含信号,而非定论"。
  11. bits-per-byte 问答的完整结论 (00:57:11 起):学生对"改变切分方式后比较 perplexity"的追问,讲师表示"没完全听懂,留待日后讨论",故本节对该问题不作定论。

若本讲中还有你注意到、但此处未列出的疑点,欢迎在课程论坛补充。本节的目的是让读者清楚"哪些是已定论,哪些是待定"。


附:参考与延伸阅读

编者补充