CS336: Language Models From Scratch (Spring 2025)
本节主要讲了模型的架构设计和超参数选择。
1.Architecture
1.1Norm
pre-norm, post-norm, ‘double’-norm
自从GPT之后大都采用pre-norm,把layernorm层放到FFN、MHA层之前。

prenorm和postnorm的效果一样好,而且不需要warm。更好的梯度反向传播,更少的spike。
现在有的模型还使用’double’-norm,即FFN、MHA层之前之后都有layernorm。
LayerNorm, RMSNorm
原始的transformer和早期模型使用LN,现在都改为使用RMSN。
LN:$y = \frac{x - \text{E}[x]}{\sqrt{\text{Var}[x] + \epsilon}} * \gamma + \beta$
其中 $\text{E}[x]$ 是均值,$\text{Var}[x]$ 是方差,$\epsilon$ 是防止分母为 0 的小量,$\gamma$(缩放因子)和 $\beta$(偏移因子)是可学习参数。
RMSN:$y = \frac{x}{\sqrt{\|x\|_2^2 + \epsilon}} * \gamma$ 其中 $\|x\|_2^2$ 是输入 x 的二范数平方,$\epsilon$ 是防止分母为 0 的小量,$\gamma$ 是可学习的缩放参数。
不减去均值,也不添加偏置项$\beta$。
RMSN效果和LN一样好,而且更快。操作更少(无需计算平均值),参数更少(没有偏置项)。

曾有研究表明,在模型运算中,矩阵乘法占用的flops达到99.8%,正则化的运算量只占到0.17%。从计算性能的角度看,norm没必要优化。但是内存开销也是一个重要的考量,该研究指出正则化所占的运行时间达到25.5%,在内存搬运上花了相当一部分时间,因此值得优化。
现有的大部分transformer模型都没有bias项,只进行矩阵乘法。reason:更稳定(原因未知)
1.2 Activations
ReLU、GeLU、SwiGLU、GeGLU
GLU(门控线性单元)现在得到广泛使用
ReLU(Rectified Linear Unit,修正线性单元)是深度学习中最常用的激活函数之一。
ReLU 的函数形式非常简单,数学定义为:$\text{ReLU}(x) = \max(0, x)$即:
- 当输入 $x \geq 0$ 时,输出等于输入本身 $x$;
- 当输入 $x < 0$ 时,输出为 0。

ReLU的优点
ReLU 之所以能取代 Sigmoid、Tanh 等传统激活函数,成为主流选择,核心原因在于以下几点:
- 计算效率极高:传统激活函数(如 Sigmoid 的 $\sigma(x)=1/(1+e^{-x})$)需要指数运算,计算成本高;而 ReLU 仅需一个简单的阈值判断(x 是否大于 0),几乎没有计算开销,能显著加速模型训练。
- 缓解梯度消失问题:Sigmoid 的导数在 $|x|$ 较大时趋近于 0,导致梯度难以传递到浅层。ReLU 的导数在 (x>0) 时为 1,梯度可以 “无损” 地反向传播,避免了深层网络中梯度被逐层衰减的问题。
- 稀疏激活特性:ReLU 会将所有负输入 “抑制” 为 0,只有正输入被保留,这种 “稀疏性” 使得神经网络在处理数 据时,仅激活部分神经元(类似生物大脑的工作模式),减少了冗余计算,同时增强了模型的泛化能力(避免过拟合)。
梯度消失
反向传播时,若梯度经过多层传递后逐渐衰减至接近 0,导致浅层参数几乎无法更新,称为梯度消失。
典型场景:使用 Sigmoid 激活函数时,其导数在输入绝对值较大时接近 0(最大值仅 0.25),多层相乘后梯度快速趋近于 0。
影响:深层网络的浅层参数难以优化,模型无法学习到有效特征。
梯度爆炸
反向传播时,若梯度经过多层传递后急剧增大至非常大的值,导致参数更新幅度过大,模型训练不稳定(如损失值跳变、NaN 等),称为梯度爆炸。
典型场景:循环神经网络(RNN)处理长序列时,梯度可能随时间步累积而指数级增长;或权重初始化过大,导致梯度被放大。
影响:参数更新失控,模型难以收敛。
ReLU 的缺点
尽管优势显著,ReLU 仍存在一些不容忽视的缺陷:
- 死亡 ReLU 问题(Dead ReLU Problem):当输入 (x < 0) 时,ReLU 的输出为 0,且导数也为 0。若在训练中,某个神经元的输入长期为负(例如,权重更新不当导致输入始终小于 0),该神经元的梯度将永远为 0,无法通过反向传播更新参数,最终 “永久死亡”,失去学习能力。
- 常见诱因:学习率过大(导致权重更新幅度过大,输入被 “推到” 负区间)、数据预处理不当(输入分布偏移)等。
- 输出非对称,均值偏移:ReLU 的输出范围是 $[0, +\infty)$,非零均值(输出均值偏向正方向)。这可能导致后续层的输入分布逐渐偏向正值,影响梯度下降的稳定性(例如,梯度方向可能被 “挤压” 到特定区域)。
swish
Swish 由 Google 提出,结合了 ReLU 和 Sigmoid 的特性,定义为:$\text{Swish}(x) = x \cdot \sigma(\beta x)$
其中 $\sigma$ 是 Sigmoid 函数,$\beta$是可学习参数(或固定为 1)。
- 优势:处处可导(无 ReLU 的 “折点”),负输入区域输出非零(避免死亡神经元),在深层网络(如 Transformer)中表现优异;
- 缺陷:计算复杂度略高于 ReLU。
SwigLU
SwigLU 是一种结合了 Swish 激活函数和 GLU(Gated Linear Unit)门控机制的激活函数。
SwigLU 数学定义为:$\text{SwigLU}(x) = \text{Swish}(x_1) \otimes x_2$
其中:
- x 是输入向量,被拆分为两个等长的子向量 $x_1$ 和 $x_2$(通常通过将输入维度切分或用两个线性层映射得到);
- $\text{Swish}(x_1) = x_1 \cdot \sigma(\beta x_1)$ 是激活函数($\sigma$ 为 Sigmoid,$\beta$ 为超参数,通常取 1),用于计算 “门控值”;
- $\otimes$ 表示逐元素相乘(Hadamard 乘积),即门控值与 $x_2$ 相乘,实现信息筛选。

1.3 serial、parallel layers
经典的transformer是serial串行结构。
公式:$y = x + \text{MLP}(\text{LayerNorm}(x + \text{Attention}(\text{LayerNorm}(x))))$
parallel并行结构最早在GPT-J中提出.
公式:$y = x + \text{MLP}(\text{LayerNorm}(x)) + \text{Attention}(\text{LayerNorm}(x))$
输入x经过层归一化后,同时送入MLP和注意力模块,实现并行。
现在大多数采用serial结构。
1.4 Position Embedding
transformer的自注意力机制无法感知序列顺序,不能处理文本序列等需要考虑位置的问题。所以使用位置编码告诉模型输入之间的先后顺序。
transformer中位置编码位于embedding层之后。

位置编码为每个位置生成独特的编码向量,将其与输入的词嵌入向量相加,让模型学习到每个元素在序列中的位置。

sine embeddings(正弦位置嵌入)
sine Embeddings 是一种经典的绝对位置嵌入方法,在论文《Attention Is All You Need》中提出,用于 Transformer 模型中。利用不同频率的正弦和余弦函数为每个位置生成一个独特的向量。
$$PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$$$PE_{(pos, 2i + 1)} = \cos\left(\frac{pos}{10000^{2i+1/d_{model}}}\right)$$
- pos 是 token 在序列中的位置索引(从0开始)
- i 是维度索引(不是维度),取值范围:$0 ≤ i < d_{model} / 2 - 1$
- $d_{\text{model}}$ 是模型的嵌入维度
- $PE_{(pos, j)}$表示位置 pos 的编码向量中第 j 维的值
正弦余弦交替使用 对于每对相邻维度,偶数维度使用正弦函数,奇数维度使用余弦函数。
波长的递增序列 指数$10000^{2i/d_{model}}$为不同维度创建了不同的波长:
- 当$i = 0$时:$10000^{0}=1$,波长为$2\pi$,波长短,函数值变化快。有助于捕捉近距离关系,具有高敏感性。
- 当$i = d_{model}/2 - 1$时:$10000^{(d_{model}-2)/d_{model}}\approx10000$,波长为$10000\cdot2\pi$,在很长的序列范围内,高维度的编码值几乎恒定,有助于学习长距离依赖关系,提供稳定的、粗粒度的 “区域” 或 “上下文” 信号。
为什么选择10000? 波长依据维度形成从$2\pi$到$10000\cdot2\pi$的几何级数,保证位置编码的频率覆盖范围足够大,同时兼顾长序列与短序列。
sine embeddings的特性
唯一性和有界性
- 各位置的编码向量互不相同
- 所有值都在[-1, 1]范围内
- 显示引入位置信息,不需要学习
无限外推能力
- sin 和 cos 对于任何实数都有定义,可以对任意位置生成编码,不受训练数据长度限制
- 编码模式沿着序列位置平滑变化,没有突变点
线性表示相对位置
任意固定位置偏移的相对位置关系可以被表达为线性变换。
对于任意固定偏移k,$PE_{pos + k}$可以表示为$PE_{pos}$的线性函数。
对于任意位置$pos$和偏移$k$,考虑特定维度$i$:
令$\alpha=\frac{pos}{10000^{2i/d_{model}}}$和$\beta=\frac{k}{10000^{2i/d_{model}}}$,则
$PE_{(pos,2i)}=\sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)=\sin\alpha$,$PE_{(pos,2i + 1)}=\cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)=\cos\alpha$
通过积化和差公式:
$PE_{(pos + k,2i)}=\sin(\alpha + \beta)=\sin(\alpha)\cos(\beta)+\cos(\alpha)\sin(\beta)$
$PE_{(pos + k,2i + 1)}=\cos(\alpha + \beta)=\cos(\alpha)\cos(\beta)-\sin(\alpha)\sin(\beta)$
这表明$PE_{pos + k}$可以通过$PE_{pos}$的线性组合得到,线性系数仅与偏移$k$相关,独立于位置$pos$。
能高效推断相对关系,但编码的仍然是每个 token 的绝对位置。有没有直接编码相对位置关系的方式呢?
参考:
【Transformer】9分钟认识位置编码 | 数学解析 | 旋转位置编码_哔哩哔哩_bilibili
Rope embeddings(旋转位置嵌入)
简单理解:我们需要一种表示相对位置的算法。RoPE在计算注意力时,通过将qk都乘以旋转矩阵,来把注意力运算结果中加入位置信息。
特性:
- 相对距离决定注意力:两个 token 之间的注意力分数只依赖于它们的相对位置差,而不是绝对位置
- 平移不变性:序列整体平移不会改变内部的注意力模式
- 保留长度外推能力:随着相对距离增加,注意力分数自然衰减。这符合靠的近的token注意力高,离得远的token注意力低的要求
原理解析参考,这两个已经解释的较为清楚了:
2.Hyperparameters
经过这些年来大模型的迭代发展,人们在一些超参数的选择上逐渐形成了共识。
2.1 Feedforward - model dimension ratio
一般模型: $d_{ff}=4d_{model}$
如果使用GLU及其变体作为激活函数,需要额外引入一个权重矩阵。为了保持与原始 FFN 相同的计算成本和参数规模,GLU 变体普遍将隐藏层维度 $d_{ff}$ 缩小为原来的大约 2/3。所以 $d_{ff}=\frac{2}{3}\times(4d_{model})=\frac{8}{3}d_{model}$

2.2 Head-dim*num-heads to model-dim ratio
Head-dim*num-heads和model-dim的比例基本为1。

2.3 Aspect ratios
模型应该更宽还是更深?

一般 $d_{model}/n_{layer}=128$
如下图所示,不同参数量的模型基本都在比率100左右表现最好。

2.4 Vocabulary sizes
早期的单语言的模型的词表大小在30-50k。现在的多语言模型在100-250k左右。

2.5 Dropout&Regularization
在预训练时,由于数据量非常大,只需要在数据集上训练一遍,不大会出现过拟合。
早期模型会在预训练时采用dropout。现在大多只使用weight decay。
一般来说,使用weight decay是用来防止过拟合的。但实验发现weight decay并不会影响 val loss和train loss(下图左)。weight decay会与cosine模式的optimizer相作用,当学习率减小时,train loss会有效下降(下图中)。

3.stability
我们希望模型在训练时,loss稳定下降,而不要波动过大,出现过多spike。
transformer的不稳定成因主要来自softmax:$\sigma(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}$
其中的指数运算易出现数值不稳定。其中的除法运算可能会除0错误。
softmax出现在模型的最后和注意力层中,以下分别从这两个位置进行优化。
3.1 z-loss

增加一个惩罚项,使Z的值稳定在1。
3.2 QK norm
看不懂,跳过
3.3 Soft-capping
看不懂,跳过
4.Attention
4.1 GQA/MQA
当我们生成文字时,需要不断地预测下一个token,这一过程不能并行。
通过KV cache可以减少开销。
KV cache
KV Cache 是大语言模型(LLM)推理阶段的核心优化技术,通过缓存注意力机制中重复计算的键(Key)和值(Value),显著提升生成效率。
在 Transformer 解码器(如 GPT 系列模型)中,每一步生成新 token 时,都需要对已生成序列和当前输入计算自注意力(Self-Attention)。假设已生成序列长度为 n,新生成 token 为第 (n+1) 个,则自注意力的核心公式为:
(\text{Attention}(Q, K, V) = \text{softmax}\left( \frac{QK^T}{\sqrt{d_k}} \right) V)
其中:
- Q(Query):当前输入的查询向量(维度 (d_k));
- K(Key)、V(Value):已生成序列中所有 token 的键和值向量(维度均为 (d_k))。
未优化时的问题:生成第 (n+1) 个 token 时,需要重新计算前 n 个 token 的 K 和 V,导致计算量随序列长度增长呈 (O(n^2)) 增长,推理速度极慢。
原理:
KV Cache 的本质是空间换时间:将已生成序列的 K 和 V 向量缓存起来,避免重复计算。
- 首次计算(生成第 1 个 token):
- 输入:初始序列(如提示词);
- 计算:为每个 token 生成对应的 (K_1, V_1),并缓存到内存中。
- 后续计算(生成第 t 个 token,(t > 1)):
- 输入:前 (t-1) 个 token 生成的序列 + 新输入(第 t 个 token 的 Query);
- 计算:仅需为第 t 个 token 生成 (Q_t),直接复用缓存中的 (K_1, K_2, …, K_{t-1}) 和 (V_1, V_2, …, V_{t-1});
- 更新缓存:将新生成的 (K_t, V_t) 追加到缓存中,供下一步使用。
通过这一机制,每次生成新 token 时,仅需计算当前 token 的 Q 以及与历史 (K/V) 的注意力得分,计算量从 (O(n^2)) 降至 (O(n))。
还讲了GQA、MQA、sparse attn、sliding window attn、interleave’full’ and ‘LR’ attn
慢慢补吧……