2、Transformer基础回顾:多头注意力机制、前馈神经网络、层归一化与残差连接
好,咱们正式开始。在动手剪枝之前,我强烈建议先把Transformer的“骨架”摸清楚。你想想看,如果连模型哪部分最耗算力、哪部分容易冗余都不知道,剪枝就成瞎剪了。
这一节,我会带你快速过一遍Transformer的四个核心组件:多头注意力机制、前馈神经网络、层归一化和残差连接。嗯,都是老面孔了,但咱们这次换个角度——从“哪里能剪”的视角来审视它们。
2.1 多头注意力机制:模型的核心“搜索引擎”
说白了,注意力机制就是让模型学会“看哪里”。它给输入序列里的每个位置分配一个权重,权重越高,说明这个位置对当前输出越重要。
公式长这样:
Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V
Q是查询,K是键,V是值。d_k是键的维度,除以它的平方根是为了防止内积过大,把softmax推向梯度饱和区。我在项目中遇到过,如果不做这个缩放,训练初期loss直接炸掉,梯度全变成0。
多头注意力呢?就是把Q、K、V分别投影到h个不同的子空间,每个子空间独立做注意力计算,最后再拼起来。这样做的好处是:模型可以在不同的表示子空间里学到不同的关注模式。
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) * W_O
其中 head_i = Attention(Q * W_Q_i, K * W_K_i, V * W_V_i)
这里W_Q_i、W_K_i、W_V_i是投影矩阵,W_O是输出投影矩阵。每个头的维度通常是d_model / h。
剪枝视角:多头注意力是剪枝的“富矿”。每个头学到的模式往往有冗余,很多头其实贡献很小。我曾经在BERT上做过实验,直接剪掉30%的头,下游任务指标几乎不掉。所以,头剪枝是入门级操作。
我的习惯:在剪枝前,我会先跑一个“头重要性评估”。方法是:随机mask掉一个头,看loss变化有多大。变化小的,优先剪掉。
2.2 前馈神经网络:非线性变换的“加工厂”
注意力层负责“聚合信息”,FFN层负责“加工信息”。每个Transformer块里,注意力后面都跟着一个FFN。
标准的FFN包含两个线性变换,中间夹一个ReLU激活:
FFN(x) = max(0, x * W_1 + b_1) * W_2 + b_2
中间隐藏层的维度d_ff通常是d_model的4倍。比如BERT-base里d_model=768,d_ff=3072。你看,FFN的参数占了模型总参数的大头,大概2/3左右。
为什么会这样?因为注意力层的参数跟序列长度无关,而FFN的参数跟d_model和d_ff直接挂钩。d_ff一放大,参数量就上去了。
剪枝视角:FFN是剪枝的“主战场”。参数多,冗余也大。我常用的做法是:对W_1和W_2做结构化剪枝,直接剪掉整行或整列。比如把d_ff从3072剪到1536,参数量直接减半。
注意:剪FFN时,要同时剪W_1的行和W_2的列,保持维度匹配。我曾经犯过这个错,只剪了W_1没剪W_2,结果矩阵乘法报错,排查了半天才发现。
2.3 层归一化:训练的“稳定器”
层归一化,简称LayerNorm。它的作用是对每个样本的所有特征做归一化,让数据分布更稳定。
公式如下:
LayerNorm(x) = gamma * (x - mu) / sqrt(sigma^2 + epsilon) + beta
mu和sigma是均值和标准差,gamma和beta是可学习的缩放和平移参数。epsilon是个小常数,防止除零。
跟BatchNorm不同,LayerNorm不依赖batch size,所以在NLP任务里更常用。你想想看,文本序列长度不一,用BatchNorm会出问题。
剪枝视角:LayerNorm本身参数很少(就gamma和beta两个向量),剪枝收益不大。但它的位置很关键——通常在注意力层和FFN层之前(Pre-LN)或之后(Post-LN)。我个人习惯用Pre-LN,训练更稳定,剪枝后微调也更容易收敛。
避坑指南:我曾经在剪枝后忘记重新计算LayerNorm的统计量,结果推理时输出全是NaN。记住,剪枝改变了特征分布,一定要重新跑一下forward,更新mu和sigma。
2.4 残差连接:梯度流动的“高速公路”
残差连接,就是让输入直接加到输出上。公式简单:
output = LayerNorm(x + Sublayer(x))
这里Sublayer可以是注意力层,也可以是FFN层。残差连接解决了深层网络的梯度消失问题。没有它,Transformer根本堆不到12层、24层。
为什么有效?因为梯度可以直接通过残差路径回传,绕过了Sublayer里的复杂变换。即使Sublayer的梯度很小,残差路径也能保证梯度不消失。
剪枝视角:残差连接本身没有参数,不需要剪。但剪枝后,残差路径的“信号质量”会受影响。如果剪掉太多头或神经元,残差路径里传的可能就是噪声了。所以,剪枝后一定要做微调,让模型重新适应。
好,四个组件都过了一遍。总结一下:
| 组件 | 参数量占比 | 剪枝潜力 | 我的建议 |
|---|---|---|---|
| 多头注意力 | 约30% | 高(头剪枝) | 先做头重要性评估,再剪 |
| 前馈神经网络 | 约65% | 非常高(神经元剪枝) | 结构化剪枝,同时剪W_1和W_2 |
| 层归一化 | 约1% | 低 | 保留,但需重新计算统计量 |
| 残差连接 | 0% | 无 | 保留,剪枝后微调 |
嗯,基础就复习到这儿。下一节,咱们会正式进入剪枝方法,从最简单的“权重剪枝”开始。到时候我会拿实际代码演示,你准备好IDE就行。