1. Outline and goals

通过研究各种Transformer,分析其中的变与不变,通过这种类似于进化论的分析方法,找出最关键的设计要素

2. Starting Point : the Original Transformer

(老哥怎么一句话就带过去了)

2.1 RNN(Recurrent Neural Network)

原理:网络在处理当前输入时,会把"上一个时刻的隐藏状态"也作为输入之一,一起参与计算

三个问题:

1. 例如在翻译任务中,无法解决对齐问题。例如,翻译 " i love the cat " 为“ 我爱猫 ”的过程中,由四个输入到三个输出。

2.长距离依赖。前面的信息会随着时间步的增多而逐渐丢失。

3.不能并行计算,因为下一步的计算依赖于上一步隐藏层的计算结果,必须要一步一步来。

为了解决第一个问题,提出了编码器-解码器结构(Encoder & Deconder)。

编码器和解码器是两个独立的RNN,但通过一个"语义向量(Context Vector)"连接

step 1:Encoder 内部自己工作,计算隐藏层 h ,不再输出 y

step 2 :Encoder 读完最后一个词后,最终的隐藏状态 h_last( c,即上下文向量,也隐式地包含了输入 token 的位置信息)就被认为是整个输入序列的语义浓缩,捕获了输入句子的完整意思。

step 3:Encoder 的最终状态 c 被初始化为 Decoder 的第一个隐藏状态。

step 4:Decoder 逐步生成目标序列。第一步输入一个特殊的起始符(如 <SOS>),结合初始状态 c,预测第一个输出词;根据第一个输出词和当前隐藏状态,预测第二个词。(为了防止随着输出过程中最开始的语义信息被逐步稀释,可以在每步补充上下文向量 c)

(其实问题1也只是如解决,虽然看似解决了输入输出长度不相等的问题,但更深层次的语义对齐问题还是没解决:还是翻译 " i love the cat " 为“ 我爱猫 ”的过程中,所有时间步的输入在计算当前时刻输出时被同等对待,例如在翻译输出“ cat ” 时,应该更关注与它离得更近的词,而不是一视同仁只看 c,忽略了不同时间步对当前输出的重要性可能不一样。)

2.2 Attention 

为了解决如上问题,提出了 Attention 机制。

在 Decoder 中每个步骤输入的 c 不再是统一的结果,而是根据训练得到的权重,与 Encoder 中的各个隐藏层 h 加权计算得到的不同的上下文向量 c 。

这样就真正解决了问题1以及解决了问题2。

为了解决并行化的问题,提出了以自注意力机制(Self-Attention)为核心的 Transformer 。

2.3 Transformer

首先要明确,这张图里最下面的 Inputs 和 Outputs 都是模型的输入,模型实际输出是上面的 Output Probabilities。

①. 将自然语言文本转换成一个一维词向量(512维)

②. 由于是并行处理,不具备位置顺序信息,所以为每个向量赋予位置信息。传统 Transformer 使用“绝对位置编码”,利用正弦/余弦数学公式生成固定不变的位置向量,并直接加到词向量上

③.(先只看单头,忽略蓝色笔迹)

得到的结果 a1 为带上了上下文信息的 x1 新向量

对于其中 Q、K、V矩阵的认识:Q 矩阵负责提出(在某方面是否具有相关性)问题,K 矩阵给出对应的索引标签,两者的点乘就是相关性的权重。经过 d_k 放缩,再由归一化函数 softmax 将各权重处理成和为 1 的注意力权重分布,用这些权重对 V 矩阵中的所有位置进行加权求和。Q 和 K 负责“寻址”,而 V 负责“内容读取”。

由于语言中的关系是多维度的(语法、语义、指代、位置等),相较于单头,多头能让模型在不同子空间里并行捕捉多种关系,最后融合成更丰富的理解。

后续的残差连接和归一化,解决梯度消失问题,让模型训练更稳定

④.引入非线性关系,通过非线性变换学习到更复杂的特征

⑤. 同 ①

⑥. 同 ②

⑦. 具有掩码的多头注意力机制。在 Encoder 中编码器需要理解完整上下文,每个词能看到所有词(包括自己和后面的);而解码器在生成第t个词时,不能看到第t+1个词(否则就是作弊),所以每个词只能看到前面已经生成的词。

⑧.交叉注意力。这一步计算得到的 QKV 矩阵,Q 是由 Decoder 的 Masked Multi-Head Atten 层得到的语义信息乘以 W_Q 得到的,而 K 和 V 是由 Encoder 的 Atten 层得到的语义信息分别乘以 W_K 和 W_V 得到的。此外由于 Inputs和 Ouputs 文本长度不同,Q 和 KV 的大小可能不一样。(这里真是有点绕)

⑨. 同 ④

⑩.最终输出是一个和为一的概率分布向量,长度等于词典大小,告诉模型"词典里的每个词成为下一个词的概率分别是多少"。这之后进行梯度计算,反向传播,更新参数。

模型中一共有18(6 层 x 每层 3 个)个注意力机制网络,18 个网络会各自训练得到一组 W_Q、W_K、W_V 参数。

(还是得看代码啊)

2.4 Advanced Transformer

作业中要求实现的并非原论文的标准 Transformer 架构:层归一化(LayerNorm)被置于残差连接中每个模块的前端,实现旋转位置编码(RoPE),前馈网络采用了 SwiGLU 激活函数,并且线性层不会输出偏置项。

3 Architecture Variations

3.1 pre_norm & post_norm

post_norm: output = LayerNorm( x + F(x) )

pre_norm:   output = x + F( LayerNorm(x) )

origianl Transformer 模型采取的是左侧所示的 post_norm 方式。但随着模型架构研究的深入,研究者们发现:若将层归一化前置,置于非残差部分的前端,采用 pre_norm 方式(右侧所示)时在多项指标上表现更优。

相较于 post_norm,pre_norm 有以下优点:

1. 梯度流动更顺畅(解决梯度消失)

post_norm 在反向传播时,梯度要穿过 LayerNorm,再穿过残差。当层数很深时,梯度在逐层传递中会被 LayerNorm 的缩放因子反复压缩,传到浅层时已经微乎其微。而 pre_norm 反向传播时,梯度可以从输出直接走"残差捷径"原封不动地传回输入。

2. 训练更稳定,允许使用更大的学习率

post_norm 在训练早期,参数还是随机数时,输出 F(x) 的方差很大,和 x 相加后再归一化,可能导致数值不稳定,Loss 突然飙升(甚至变成NaN)。

而 pre_norm 先归一化再进子层,意味着子层的输入永远是"规规矩矩"的(均值0,方差1),不管前面参数怎么变,子层接收到的数据分布都是稳定的。这让训练过程更平滑,可以用更大的学习率,收敛更快。若采用 pre_norm 配合其他稳定训练的技巧就能省去预热环节,其表现完全不逊色于需要精心设计预热方案的 post-norm 方法,甚至往往更胜一筹,这个现象在机器翻译场景中尤其明显。

预热(Warm-up):在训练刚开始的若干步(比如前4000步),把学习率从一个极小的值(如0),逐渐线性增长到一个预设的最大值(如5e-4),之后再开始正常的衰减。

post_norm 在训练初期极其“脆弱”,稍有不慎梯度就会爆炸(Loss 变成 NaN);而 pre_norm 因为梯度流动更顺畅,不需要这种“小心翼翼的起步”,可以直接用较大的学习率从头训到尾。

3. 更容易扩展到深层(百层以上)

原始 Transformer 论文用的是6层,勉强能用 post_norm。但当层数增加到12层、24层、甚至96层(如 GPT-3 )时梯度不稳定,几乎无法收敛。

double_norm : 在残差流中直接插入层归一化效果不好(post_norm)。pre_norm 在注意力机制前加了一层归一化 ,研究者们尝试直接在模块末端追加层归一化操作(右侧)

3.2 LayerNorm & RMSNorm

两者表现相当,而 RMSNorm 减少了计算量,效率更高。

问题:在之前的课程中,如 Lecture 02 中提到过 “在深度学习中,对于足够大的矩阵,矩阵乘法是所有操作中最耗时的”(标题 2.2.4)。这一步并不是矩阵乘法运算,那优化的必要性在哪?

该表格记录了 Transformer 模型中各步骤的 flops 数以及运行时长。虽然张量收缩计算(矩阵乘法)占据了 Transformer 中 99.8% 的浮点运算量,而像 softmax 以及层归一化等操作仅占 0.17%,但它们实际上占据了 25% 的运行时长。这其中巨大的差距来自于这些归一化操作会带来巨大的内存搬运开销。性能评估不仅要看浮点运算能力(flop),还要考虑内存访问效率。

除此之外,目前的 Transformer 架构大多都舍去了偏置项。原因同上,效率相似,减少计算。

original Transformer 中,前馈网络计算过程为(ReLU):

                                          FFN(x) = max( 0, xW_{1} + b_{1}) W_{2} + b_{2}

但目前大部分结构(非门控单元结构)都舍弃了偏置项,采用了下列结构:

                                                  FFN(x) = \sigma (xW_{1})W_{2}

对于 RMSNorm,由于计算过程中没有减去均值,它保留了输入向量的原始均值信息。如果再加上一个可学习的偏置 b,RMSNorm保留了上一层传递下来的均值,偏置又强行加了一个新基线,两者会产生不必要的耦合干扰。

3.3 Activation

a. ReLU

original Transformer等早期模型使用,存在“神经元死亡”问题(输出为0的神经元可能永久失活)

                                       FF(x) = max(0,xW_{1})W_{2}   

                               (上式省略了偏置项,W_1、W_2为权重矩阵)

b. GeLU (Gaussian Error Linear Unit,高斯误差线性单元)

 GPT 系列模型包括 GPT1/2/3 以及 GPT-J 等采用了 GeLU 激活函数

                                          FF(x) = GeLU(xW_{1})W_{2}

                                                GeLU(x) := x\Phi (x)

                                   Φ(x) 是标准正态分布的累积分布函数(CDF)

c. 门控线性单元(Gated Linear Unit,GLU)

2023年之后的模型基本上都采用了门控线性单元

ReGLU:(T5v1.1、Gemma2、Gemma3 等)

                           FF_{ReGLU}(x) = (max(0,xW_{1})\otimes xV )W_{2}

                                                        ⊗ 代表逐元素相乘

SwiGLU:(LLaMA 系列、PaLM 和 OLMo 等)

                        FFN_{SwiGLU} (x,W_{1},V,W_{2}) = (Swish(xW_{1})\otimes xV )W_{2}

GLU 对比 ReLu 等的优点在于:

1. GLU 使得模型拥有动态选择哪些特征该保留的能力;

2. ReLU 存在神经元死亡的问题,负数区域梯度为 0;而 GLU 全区域梯度不为 0;

3. ReLU 为线性分段函数,而 GLU 为非线性乘法,更能拟合复杂函数。

相较于 ReLU 和 GeLU只需要 W_1、W_2 两个矩阵,门控线性单元引入了三个矩阵。为了在提升性能的同时不让计算量过大,做法是将输出层维度设置为原本的2/3

 3.4 Serial& Parallel Layers

original Transformer 同时采用了并行和串行思想,体现在不同方面。

并行体现在:

1.自注意力中:所有词同时计算自己的 Q、K、V矩阵以及后续计算;

2.多头之间:各个注意力头独立并行计算;

串行体现在:

1.层与层之间:original Transformer 中 Encoder 和 Decoder 中各有六层注意力层和前馈网络。

2.每层内也是先算注意力层,再由其结果计算前馈网络。

此小节主要探究的是层内串行,部分模型(例如 GPT-J、PaLM 等)采用了并行层(Parallel layers)架构,并行处理注意力层和前馈网络。从上一层获取输入后并行计算注意力机制和前馈网络,最后将两者相加合并到残差流,得到最终输出。

最近一年来该架构已不再流行,主流还是串行结构。

3.5 Position Embeddings

这一小节老哥叽里呱啦讲了啥完全没搞懂,自己去看了些其他的资料。

a. original Transformer:绝对位置编码  正余弦位置编码 

pos:单词在序列中的位置

i :词向量中的维度索引,范围在 [ 0,d_model / 2 - 1 ]

d_model:词向量的维度(固定值),original Transformer 中为 512

相之所以使用正余弦函数,有以下几点原因:

1.位置编码有界且稳定:值域周期性固定在 [-1, 1],过大的位置编码会让训练无法收敛;

2.模型能外推到更长的序列:若训练时只见过最大长度为 L 的句子,遇到比 L 更长的句子时,就没有对应的编码可用。而正余弦公式没有长度限制,理论上可以处理任意长度的序列

3.模型能轻松学到“相对位置”

任意位置的编码,都可以由它前面的某个位置的编码通过一个固定的线性变换得到。这个变换矩阵只依赖于 k(相对距离),而不依赖于 pos(绝对位置)

4.随着 i 增大,对应正余弦周期变大。即对于同一个位置 pos,维度 i 小的周期短,可用来学习局部信息;维度 i 大的周期大,用来学习全局信息

b.RoPE(Rotary Position Embeddings)

位置信息直接加语义中会破坏原本的语义信息,故引出了RoPE

通过旋转矩阵,将“绝对位置”信息编码到词向量中,但最终让注意力机制“读出”的却是“相对位置”

与绝对位置编码或传统正弦余弦编码不同之处在于,旋转位置编码直接在注意力层进行运算,并非在模型底层进行叠加

4. Hyperparameters

4.1 Feed-forward :Model DImension Ratio

采用ReLU等架构:

                                                                d_{ff} = 4 \times d_{model}

采用门控线性单元件架构(如SwiGLU)由于多一个训练模型,为保证参数数量相等,数量关系如下(推导过程见上):

                                                                 d_{ff} = \frac{8}{3} \times d_{model}

但要关注一些特例:

模型 T5,参数数量:11B,d_ff / d_model =64

                                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        d_{model} = 1024         

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        d_{ff} = 65536

4.2 Model DImension & Head DImension * Numbers of Heads 

4.3 Aspect Ratio(d_model / n_layer)

业界广泛规律:       

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        d_{model} / n_{layer} = 128

模型宽深比的考量至关重要,这些参数直接决定了模型可实现的并行计算能力。

当采用流水线并行(pipeline parallel)技术时,标准操作流程是对神经网络各层进行切割划分,然后分配到不同计算设备或设备计算集群上,这时因为每层内部同样可以实现并行计算,因此在模型设计时必然存在特定的架构约束条件。

对于宽度较大的模型,可以采用张量并行技术—通过矩阵切片的方式将计算任务分配到多个 GPU 上执行。

张量并行技术需要极高的网络带宽,流水线并行对网络延迟的容忍度相对较高,在网络延迟较高的情况下仍可保持运行,因此网络性能参数将直接决定模型在宽度与深度之间的取舍。

上面这段听不懂,直接摘了下来。看看标题大概要到 Lecture 7、8 才能讲到。

4.4 Vocabulary Size

4.5 Dropout and Other Regularization

虽然这部分不属于具体超参数范畴,但在启动模型运行前还需完成两项关键配置:dropout 及其他正则化策略的设置。

正则化 = 所有以“抑制模型复杂度、提升泛化能力”为目的的辅助手段,防止模型将把训练数据里的噪音和特例都记下来(高方差)

常见的正则化方法:

1. L1 / L2 正则化(权重衰减):在每次更新参数时,除了常规的梯度下降,额外给权重矩阵“打个折”,让它缩小一点点。参数越大,缩小得越多。这迫使模型不能把参数调得太大,只能用更小的权重组合去拟合数据。其中 L1 是让权重稀疏(很多变成0),L2 是让权重均匀变小

2. Dropout:训练时随机“关闭”一部分神经元,强迫网络不能只依赖少数几个“精英”神经元。

3. 数据增强:给模型提供更“模糊”或更“多样”的输入,让它无法轻易记住每个样本的细节。

其中对于权重衰减,不同的衰减强度并不会显著改变训练损失与验证损失之间的比例关系。只要训练时间足够长,或是恰当地调整超参数,最终得到的训练损失与验证损失之间的差距将会趋于一致即便完全不使用权重衰减,过拟合现象也并未发生实质性变化。但权重衰减与优化器的学习率存在高度复杂的参数交互。如今权重衰减的作用已从其设计初衷:正则化,转变成了通过优化训练函数值来提升性能。

5.Stability Tricks

softmax函数原理为:

对于一个向量 z = [ z1, z2 , z3, ... , zK]

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        Softmax(z_{i}) = \frac{e^{z_{i}}}{\sum e^{z_{j}}}

由于有指数运算以及除零问题,该过程对模型收敛有很大的影响。而在 Transformer 模型中,有两处使用了 Softmax 函数,分别在自注意力机制和最后的输出。

5.1 Output Softmax Stability ---- the z - loss

好的又是完全没听懂,还是找其他资料

z_loss 是在计算 Softmax 交叉熵损失时,额外增加的一项辅助损失。它的作用是惩罚 Softmax 的输入(Logits)过大,从而有效防止训练过程中的数值溢出(Overflow)和损失震荡。

主要问题:

1. 数值溢出:训练初期,由于参数是随机的,Logits 很容易产生较大的数值(比如几百)。当 z 很大时,exp(z) 在计算机中会直接溢出(变成 Inf),导致训练直接崩溃。

2. One_hot 问题:当某个类别的 Logits 远大于其他类别时,Softmax 输出会趋近于 One-hot 向量,导致梯度变得极小,模型无法继续优化。

z_loss 的定义直接惩罚 Logits 的对数-求和-指数(Log-Sum-Exp, LSE)。计算顺序:求和、对数、指数。(我也不知道为啥叫 LSE )

最终的总损失函数为交叉熵损失与 z_loss 的和:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        L_{total} = L_{CE} + z_{loss}

5.2 Attention Softmax Stability --- the ' QK norm '  

QK norm :在计算 softmax 所需的点积之前,先让 Q 和 K 通过层归一化 LayerNorm 

5.3 Logit  Soft - Capping

将 z 传入 softmax 函数前,先对 z 进行处理:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        z = soft\_cap \times tanh(\frac{z}{soft\_cap})

当 z >> soft_cap 时,tanh 函数能将输入截断至 1,确保输入最大值不超过 soft_cap

6. Attention Heads

这一小节更是全是天书,自己找资料

6.1 GQA & MQA

GQA 和 MQA 对模型训练阶段影响不大,但对理解推理阶段的成本和性能表现很关键。

一个(B, K)矩阵乘以一个(K, N)矩阵,FLOPs = 2 * B * K * N (计算一个结果就需要 K 次乘法以及 K - 1 次加法,然后结果矩阵中一共有 B * N 个数)

Transformer 过程中的 FLOPs 为:

上式中,b 代表 batch 维度,n 代表序列长度,而 d 为模型维度

而整个过程的内存访问量 MAC 为:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        memory\_access = bnd + bhn^{2} + d^{2}

注意 Softmax 项,自己一开始没转过来为什么会和 h 有关,因为自己觉得在计算过程中把固然引入了多头数量 h,但参数 d 也变成了 d / 8 。其实这里和 d 根本没关系,每个头都要独立计算自己的注意力矩阵

Arithmetic Intensity = FLOPs / MAC,即每次内存访问能进行的计算操作数。这是因为在 GPU 上,内存访问的相对开销非常高昂,而计算操作则相对廉价。

所以在训练过程中,当注意力头数量、批处理规模和序列长度都足够大时,这些参数都将成为显著提升性能的关键因子。

而对于推理过程,模型必须在得到某个 token 的输出后,才能串行计算下一个位置的 token。整个过程以自回归方式逐 token 推进。所以,文本生成过程无法实现并行化,每个 token 都必须逐步生成,在此过程中需要增量式计算注意力机制,这即 KV Cache 技术。(不然的话,每计算出一个新 token,都要重新全部计算一下新的自注意力,消耗太大)

KV Cache:把历史 token 的 Key 和 Value 缓存起来,每步只计算新 token 的 Key、Value 和 Query,然后用新 Query 去匹配所有缓存的 Key。核心原因是:注意力只看过去,不看未来

典型的空间换时间思想,引入了额外的显存消耗,Arithmetic Intensity 下降。

MQA(Multi Query Attention)和 GQA (Group Query Attention)

MQA:所有注意力头共享同一份 K 和 V,但每个头保留自己的 Q

GQA:将 HH 个 Q 头分成 GG 组,每组内的 Q 头共享同一份 K 和 V

代价很明显,模型性能有所下降。

6.2 Sparse / Sliding Window Attention

(Sliding Window Attention 是 Sparse 其中的一种经典模式)

标准 Transformer 的注意力机制让每个 token 与所有 token 交互:复杂度为 O(n^2)。

而 Sparse Attention 让每个 token 只关注预先定义的稀疏模式 中的 token,例如:

a. Sliding Window Attention:只能关注前后固定大小窗口(例如前后2000个token)内的 token

b. 固定步长:每隔 k 个位置关注一个

c. 随机稀疏

6.3 Current Standard Trick

组合设计:每四个 Transformer 为一组。最底层的 Transformer 不添加任何位置编码,但采用完整的自注意力机制;而上方三个模块则采用带旋转位置编码的滑动窗口注意力机制。

一方面既有效控制了系统资源开销又通过交替使用滑动窗口注意力(带 RoPE)维持了位置感知能力,另一方面模型能够进行及其激进的长度外推。

更多推荐