03-大模型的非线性变化:从MLP到MOE,大模型2/3的参数都在这里

20 阅读7分钟

为什么需要非线性变化?

在前面的章节中,我们学习了注意力机制和位置编码。但如果仔细观察,你会发现一个问题:

注意力机制全是线性变换!

回顾注意力计算:

Q=XWQ(线性变换)K=XWK(线性变换)V=XWV(线性变换)Attention=softmax(QKTdk)V(softmax是非线性,但后面又是线性)\begin{aligned} Q &= X \cdot W_Q \quad \text{(线性变换)} \\ K &= X \cdot W_K \quad \text{(线性变换)} \\ V &= X \cdot W_V \quad \text{(线性变换)} \\ \text{Attention} &= \text{softmax}\left(\frac{Q \cdot K^T}{\sqrt{d_k}}\right) \cdot V \quad \text{(softmax是非线性,但后面又是线性)} \end{aligned}

虽然softmax提供了一些非线性,但整体来说,注意力机制主要是线性变换的组合

线性变换的局限性

一个众所周知的数学事实:多个线性变换的组合仍然是线性变换

如果 f(x)=W1x,g(x)=W2x\text{如果 } f(x) = W_1 x, \quad g(x) = W_2 x
那么 g(f(x))=W2(W1x)=(W2W1)x=W3x(仍是线性)\text{那么 } g(f(x)) = W_2 (W_1 x) = (W_2 W_1) x = W_3 x \quad \text{(仍是线性)}

这意味着:

  • 无论堆叠多少层注意力机制,如果只有线性变换,模型的表达能力都非常有限
  • 只能学习线性关系,无法捕捉复杂的非线性模式
  • 类比:如果只有线性函数,你无法拟合曲线,只能拟合直线

因此,Transformer需要引入强力的非线性变化层,这就是MLP(多层感知机)的作用。

MLP:前馈神经网络(Feed-Forward Network)

在Transformer的每一层中,注意力模块之后都会接一个MLP层(也叫FFN,Feed-Forward Network)。

MLP的结构

MLP层非常简单,由两个线性变换和一个激活函数组成:

MLP(x)=W2Activation(W1x+b1)+b2\text{MLP}(x) = W_2 \cdot \text{Activation}(W_1 \cdot x + b_1) + b_2

更详细的分解

步骤1(升维):h=W1x+b1步骤2(非线性):hact=Activation(h)步骤3(降维):y=W2hact+b2\begin{aligned} \text{步骤1(升维):} & \quad h = W_1 \cdot x + b_1 \\ \text{步骤2(非线性):} & \quad h_{\text{act}} = \text{Activation}(h) \\ \text{步骤3(降维):} & \quad y = W_2 \cdot h_{\text{act}} + b_2 \end{aligned}

参数解释

  • xRdmodelx \in \mathbb{R}^{d_{\text{model}}}:输入向量(从注意力层输出,比如768维)
  • W1Rdff×dmodelW_1 \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}}:第一层权重矩阵(升维)
  • b1Rdffb_1 \in \mathbb{R}^{d_{\text{ff}}}:第一层偏置
  • hRdffh \in \mathbb{R}^{d_{\text{ff}}}:中间隐藏层(通常 dff=4×dmodeld_{\text{ff}} = 4 \times d_{\text{model}}
  • Activation\text{Activation}:激活函数(引入非线性)
  • W2Rdmodel×dffW_2 \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}}:第二层权重矩阵(降维)
  • b2Rdmodelb_2 \in \mathbb{R}^{d_{\text{model}}}:第二层偏置
  • yRdmodely \in \mathbb{R}^{d_{\text{model}}}:输出向量(恢复原维度)

升维-非线性-降维的直觉

这个"升维-非线性-降维"的结构有很深的数学和实践意义:

1. 升维(dmodeldffd_{\text{model}} \to d_{\text{ff}}

  • 将表示投影到一个更高维的空间
  • 类比:在二维空间无法分离的数据,投影到三维空间后可能变得线性可分
  • 提供更大的表达容量,让模型有"空间"学习复杂模式

2. 非线性(Activation)

  • 激活函数引入非线性变换
  • 打破线性变换的限制,使模型能够学习复杂函数
  • 这是MLP的核心!没有激活函数,两个线性层等价于一个线性层

3. 降维(dffdmodeld_{\text{ff}} \to d_{\text{model}}

  • 将高维表示压缩回原始维度
  • 提取在高维空间学到的关键特征
  • 保持模型各层维度一致,方便堆叠

为什么 dff=4×dmodeld_{\text{ff}} = 4 \times d_{\text{model}}

这个4倍的比例并非随意选择,而是经过大量实验和理论分析得出的经验最优值。

1. 历史来源

原始Transformer论文(Vaswani et al., 2017)的设置:

  • dmodel=512d_{\text{model}} = 512
  • dff=2048=4×512d_{\text{ff}} = 2048 = 4 \times 512

作者通过实验发现,这个4倍的比例在效果和效率之间达到了最佳平衡。

2. 理论解释

信息瓶颈与表达容量

从信息论的角度,MLP层需要完成两个任务:

  1. 信息扩展:在高维空间中学习复杂的非线性变换
  2. 信息压缩:提取关键特征并投影回原始维度

4倍的扩展比例提供了足够的"工作空间":

容量增益=dffdmodel=4\text{容量增益} = \frac{d_{\text{ff}}}{d_{\text{model}}} = 4

这意味着:

  • 参数量:2×dmodel×dff=8×dmodel22 \times d_{\text{model}} \times d_{\text{ff}} = 8 \times d_{\text{model}}^2
  • 如果扩展太少(如2倍):表达能力不足,无法学习复杂模式
  • 如果扩展太多(如8倍):参数和计算成本暴增,但收益递减

实验验证

研究表明,在固定的参数预算下:

  • 2倍扩展:效果明显不如4倍
  • 4倍扩展:效果和效率的最佳平衡点
  • 8倍扩展:效果提升有限(约1-2%),但参数和计算量翻倍

3. 不同模型的选择

虽然4倍是标准,但不同模型有微调:

模型dmodeld_{\text{model}}dffd_{\text{ff}}扩展比例
BERT-Base76830724.0×
GPT-276830724.0×
GPT-312,28849,1524.0×
LLaMA-7B409611,0082.69×
LLaMA-13B512013,8242.70×

LLaMA的2.7倍

LLaMA使用约2.7倍而非4倍,这与SwiGLU激活函数的特殊结构直接相关。

关键点:SwiGLU需要两个升维矩阵!

先回顾标准MLP和SwiGLU的结构差异:

标准MLP(使用ReLU/GELU)

h=Activation(xW1)W1Rdmodel×dffy=hW2W2Rdff×dmodel\begin{aligned} h &= \text{Activation}(x W_1) \quad &W_1 \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}} \\ y &= h W_2 \quad &W_2 \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}} \end{aligned}
  • 参数量:(dmodel×dff)+(dff×dmodel)=2×dmodel×dff(d_{\text{model}} \times d_{\text{ff}}) + (d_{\text{ff}} \times d_{\text{model}}) = 2 \times d_{\text{model}} \times d_{\text{ff}}
  • 如果 dff=4×dmodeld_{\text{ff}} = 4 \times d_{\text{model}}:参数量 =8×dmodel2= 8 \times d_{\text{model}}^2

SwiGLU MLP

Gate=Swish(xWgate)WgateRdmodel×dffUp=xWupWupRdmodel×dffh=GateUp(逐元素相乘)y=hWdownWdownRdff×dmodel\begin{aligned} \text{Gate} &= \text{Swish}(x W_{\text{gate}}) \quad &W_{\text{gate}} \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}} \\ \text{Up} &= x W_{\text{up}} \quad &W_{\text{up}} \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}} \\ h &= \text{Gate} \otimes \text{Up} \quad &\text{(逐元素相乘)} \\ y &= h W_{\text{down}} \quad &W_{\text{down}} \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}} \end{aligned}
  • 参数量:(dmodel×dff)+(dmodel×dff)+(dff×dmodel)(d_{\text{model}} \times d_{\text{ff}}) + (d_{\text{model}} \times d_{\text{ff}}) + (d_{\text{ff}} \times d_{\text{model}})
  • =3×dmodel×dff= 3 \times d_{\text{model}} \times d_{\text{ff}}
  • 比标准MLP多了50%的参数!(3个矩阵 vs 2个矩阵)

LLaMA的参数预算控制

如果LLaMA也用 dff=4×dmodeld_{\text{ff}} = 4 \times d_{\text{model}}

SwiGLU参数量=3×dmodel×(4×dmodel)=12×dmodel2\text{SwiGLU参数量} = 3 \times d_{\text{model}} \times (4 \times d_{\text{model}}) = 12 \times d_{\text{model}}^2
标准MLP参数量=2×dmodel×(4×dmodel)=8×dmodel2\text{标准MLP参数量} = 2 \times d_{\text{model}} \times (4 \times d_{\text{model}}) = 8 \times d_{\text{model}}^2

增加了 50%50\% 的参数!

为了控制参数量,LLaMA将 dffd_{\text{ff}} 降低到约 2.7×dmodel2.7 \times d_{\text{model}}

SwiGLU参数量=3×dmodel×(2.7×dmodel)=8.1×dmodel2\text{SwiGLU参数量} = 3 \times d_{\text{model}} \times (2.7 \times d_{\text{model}}) = 8.1 \times d_{\text{model}}^2

这样就接近了标准MLP的参数量 8×dmodel28 \times d_{\text{model}}^2

具体例子(LLaMA-7B):

  • dmodel=4096d_{\text{model}} = 4096
  • dff=110082.69×4096d_{\text{ff}} = 11008 \approx 2.69 \times 4096

参数量计算:

Wgate:4096×11008=45,088,768Wup:4096×11008=45,088,768Wdown:11008×4096=45,088,768总计:135,266,304135M 参数\begin{aligned} W_{\text{gate}} &: 4096 \times 11008 = 45{,}088{,}768 \\ W_{\text{up}} &: 4096 \times 11008 = 45{,}088{,}768 \\ W_{\text{down}} &: 11008 \times 4096 = 45{,}088{,}768 \\ \text{总计} &: 135{,}266{,}304 \approx 135M \text{ 参数} \end{aligned}

如果用标准MLP(dff=4×4096=16384d_{\text{ff}} = 4 \times 4096 = 16384):

W1:4096×16384=67,108,864W2:16384×4096=67,108,864总计:134,217,728134M 参数\begin{aligned} W_1 &: 4096 \times 16384 = 67{,}108{,}864 \\ W_2 &: 16384 \times 4096 = 67{,}108{,}864 \\ \text{总计} &: 134{,}217{,}728 \approx 134M \text{ 参数} \end{aligned}

参数量几乎相同!

为什么效果好?SwiGLU的优势

虽然 dffd_{\text{ff}} 只有2.7倍,但SwiGLU的门控机制提供了额外的表达能力:

  1. 双路径信息流

    • Gate路径:学习"哪些特征应该被激活"(选择性)
    • Up路径:学习"特征的表示"(内容)
    • 两者逐元素相乘,实现动态的特征选择
  2. 有效容量更大

    • 虽然维度是2.7倍,但两个独立的升维矩阵提供了更丰富的变换空间
    • 类似于"两个小专家合作"比"一个大专家独立工作"更灵活
  3. 平滑的非线性

    • Swish激活 xσ(x)x \cdot \sigma(x) 比ReLU更平滑
    • 门控乘法提供了额外的非线性

计算量对比

虽然参数量相近,但SwiGLU的计算量确实更大:

标准MLP升维:dmodel×dff=dmodel×(4×dmodel)=4×dmodel2SwiGLU升维:2×(dmodel×dff)(两个矩阵乘法)=2×dmodel×(2.7×dmodel)=5.4×dmodel2\begin{aligned} \text{标准MLP升维} &: d_{\text{model}} \times d_{\text{ff}} = d_{\text{model}} \times (4 \times d_{\text{model}}) = 4 \times d_{\text{model}}^2 \\ \\ \text{SwiGLU升维} &: 2 \times (d_{\text{model}} \times d_{\text{ff}}) \quad \text{(两个矩阵乘法)} \\ &= 2 \times d_{\text{model}} \times (2.7 \times d_{\text{model}}) \\ &= 5.4 \times d_{\text{model}}^2 \end{aligned}

SwiGLU的升维阶段计算量约为标准MLP的 5.4/4=1.355.4 / 4 = 1.35 倍。

总结:为什么2.7倍配合SwiGLU效果好?

方面标准MLP (4倍)SwiGLU (2.7倍)
矩阵数量2个3个
参数量8×dmodel28 \times d_{\text{model}}^28.1×dmodel28.1 \times d_{\text{model}}^2
计算量(升维)4×dmodel24 \times d_{\text{model}}^25.4×dmodel25.4 \times d_{\text{model}}^2
表达能力单路径双路径(门控+内容)
非线性ReLU/GELUSwish + 门控乘法

结论

  • SwiGLU通过门控机制双路径结构,在相近的参数量下提供了更强的表达能力
  • 2.7倍的扩展比例是为了匹配标准MLP的参数预算
  • 实践证明,SwiGLU (2.7倍) 的效果优于标准MLP (4倍),这就是为什么LLaMA和其他现代大模型都采用这个组合

4. 扩展比例的权衡

较小的扩展比例(如2倍):

  • ✅ 参数少、计算快
  • ❌ 表达能力有限
  • 适用场景:资源受限的小模型

标准的4倍扩展

  • ✅ 效果好、经验证的最佳实践
  • ✅ 参数-效果平衡
  • 适用场景:绝大多数模型

更大的扩展比例(如8倍):

  • ✅ 理论上表达能力更强
  • ❌ 参数和计算成本过高
  • ❌ 收益递减明显
  • 适用场景:几乎不使用

偏置 b1b_1b2b_2 的初始化

偏置向量在MLP中起到"基准调整"的作用,它们的初始化策略很重要。

标准初始化方式

最常见的做法:零初始化

b1=0Rdff(全为0)b2=0Rdmodel(全为0)\begin{aligned} b_1 &= \mathbf{0} \in \mathbb{R}^{d_{\text{ff}}} \quad \text{(全为0)} \\ b_2 &= \mathbf{0} \in \mathbb{R}^{d_{\text{model}}} \quad \text{(全为0)} \end{aligned}

重要澄清

  • ⚠️ 零初始化 ≠ 不训练
  • b1b_1b2b_2可学习参数,会在训练过程中通过梯度下降更新
  • "零初始化"只是指训练开始前的初始值,训练后会学到有意义的值

为什么初始化为0?

  1. 对称性破缺靠权重矩阵W1W_1W2W_2 已经通过随机初始化打破对称性
  2. 训练初期稳定:零偏置让模型从"中性"状态开始学习
  3. 简单有效:绝大多数深度学习库(PyTorch、TensorFlow)的默认行为

偏置参数的训练过程

让我们看看 b1b_1b2b_2 在训练中如何更新:

1. 前向传播

h=W1x+b1(b₁参与计算)hact=Activation(h)y=W2hact+b2(b₂参与计算)\begin{aligned} h &= W_1 \cdot x + b_1 \quad \text{(b₁参与计算)} \\ h_{\text{act}} &= \text{Activation}(h) \\ y &= W_2 \cdot h_{\text{act}} + b_2 \quad \text{(b₂参与计算)} \end{aligned}

2. 反向传播

梯度通过链式法则传到偏置:

Lb2=Lyyb2=Ly1=LyLb1=Lhhb1=Lh1=Lh\begin{aligned} \frac{\partial L}{\partial b_2} &= \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial b_2} = \frac{\partial L}{\partial y} \cdot 1 = \frac{\partial L}{\partial y} \\ \\ \frac{\partial L}{\partial b_1} &= \frac{\partial L}{\partial h} \cdot \frac{\partial h}{\partial b_1} = \frac{\partial L}{\partial h} \cdot 1 = \frac{\partial L}{\partial h} \end{aligned}

偏置的梯度就是下游传来的梯度(因为 bb=1\frac{\partial b}{\partial b} = 1)!

3. 参数更新

使用优化器(如AdamW)更新:

b1b1ηLb1b2b2ηLb2\begin{aligned} b_1 &\leftarrow b_1 - \eta \cdot \frac{\partial L}{\partial b_1} \\ b_2 &\leftarrow b_2 - \eta \cdot \frac{\partial L}{\partial b_2} \end{aligned}

其中 η\eta 是学习率。

4. 训练后的偏置值

经过训练,偏置会学到有意义的值。例如(LLaMA-7B的某一层):

b1[0.23,0.15,0.08,0.41,...,0.19](不再是全0!)b2[0.02,0.17,0.31,0.09,...,0.26](不再是全0!)\begin{aligned} b_1 &\approx [0.23, -0.15, 0.08, 0.41, ..., -0.19] \quad \text{(不再是全0!)} \\ b_2 &\approx [-0.02, 0.17, -0.31, 0.09, ..., 0.26] \quad \text{(不再是全0!)} \end{aligned}

5. 偏置的作用

训练后的偏置起到什么作用?

  • b1b_1:调整每个隐藏神经元的"激活阈值"

    • 如果 b1[i]b_1[i] 是正值:该神经元更容易被激活
    • 如果 b1[i]b_1[i] 是负值:该神经元更难被激活
  • b2b_2:调整输出的"基准偏移"

    • 为每个输出维度添加一个常数偏移

举例:假设使用ReLU激活函数

hact[i]=ReLU(W1[i]x+b1[i])h_{\text{act}}[i] = \text{ReLU}(W_1[i] \cdot x + b_1[i])
  • 如果 b1[i]=0.5b_1[i] = 0.5:即使 W1[i]x=0.3W_1[i] \cdot x = -0.3,仍然有 0.3+0.5=0.2>0-0.3 + 0.5 = 0.2 > 0,神经元被激活
  • 如果 b1[i]=0.5b_1[i] = -0.5:只有当 W1[i]x>0.5W_1[i] \cdot x > 0.5 时,神经元才被激活(提高了阈值)

PyTorch中的实现

import torch
import torch.nn as nn

# 定义MLP
mlp = nn.Sequential(
    nn.Linear(768, 3072),  # W1和b1
    nn.GELU(),
    nn.Linear(3072, 768)   # W2和b2
)

# 查看初始化后的偏置值
print("初始 b1:", mlp[0].bias[:5])  # 前5个值
print("初始 b2:", mlp[2].bias[:5])

# 输出类似:
# 初始 b1: tensor([-0.0002,  0.0001, -0.0001,  0.0002, -0.0001])
# 初始 b2: tensor([ 0.0001, -0.0002,  0.0001, -0.0001,  0.0002])
# 注意:不是精确的0,PyTorch默认用小的均匀分布初始化

# 显式设置为0
mlp[0].bias.data.zero_()
mlp[2].bias.data.zero_()
print("\n设置为0后 b1:", mlp[0].bias[:5])
print("设置为0后 b2:", mlp[2].bias[:5])
# 输出:tensor([0., 0., 0., 0., 0.])

# 训练后(假设经过1000步训练)
# b1和b2的值会显著改变
print("\n训练后 b1:", mlp[0].bias[:5])
print("训练后 b2:", mlp[2].bias[:5])
# 输出类似:
# 训练后 b1: tensor([ 0.2341, -0.1523,  0.0876,  0.4102, -0.1891])
# 训练后 b2: tensor([-0.0234,  0.1782, -0.3145,  0.0923,  0.2567])

与权重矩阵的对比

让我们对比一下偏置和权重矩阵的训练:

特性权重矩阵 W1,W2W_1, W_2偏置 b1,b2b_1, b_2
是否可学习✅ 是✅ 是
初始化方式He/Xavier随机初始化零初始化(或小随机)
训练过程梯度下降更新梯度下降更新
最终值学到的复杂模式学到的偏移/阈值
参数量占比99.9%+<0.1%
重要性核心参数辅助参数(但不可少)

关键点

  • 偏置虽然参数少(dff+dmodel3840d_{\text{ff}} + d_{\text{model}} \approx 3840 vs 权重的 2×768×30724.7M2 \times 768 \times 3072 \approx 4.7M
  • 但它们是必要的可学习参数,不是常数
  • 训练后会学到有意义的值,帮助模型更好地拟合数据

没有偏置会怎样?

有些模型选择不使用偏置bias=False),例如LLaMA:

# LLaMA的MLP没有偏置
self.gate_proj = nn.Linear(d_model, d_ff, bias=False)
self.up_proj = nn.Linear(d_model, d_ff, bias=False)
self.down_proj = nn.Linear(d_ff, d_model, bias=False)

原因

  1. LayerNorm已经提供了偏移

    • LayerNorm的 β\beta 参数已经提供了加性偏移
    • 偏置 bb 的作用被部分替代
  2. 减少参数量

    • 虽然偏置只占0.1%,但在数百亿参数的模型中,积少成多
    • 去掉偏置可以节省数百MB内存
  3. 训练稳定性

    • 有研究表明,去掉偏置在某些情况下训练更稳定

但传统模型(BERT、GPT-2等)都保留了偏置,因为它们确实有用。

权重矩阵的初始化

偏置虽然初始化为0,但权重矩阵需要仔细初始化:

Xavier/Glorot 初始化(对称激活函数,如tanh):

WU(6din+dout,6din+dout)W \sim \mathcal{U}\left(-\sqrt{\frac{6}{d_{\text{in}} + d_{\text{out}}}}, \sqrt{\frac{6}{d_{\text{in}} + d_{\text{out}}}}\right)

He 初始化(ReLU类激活函数):

WN(0,2din)W \sim \mathcal{N}\left(0, \sqrt{\frac{2}{d_{\text{in}}}}\right)

例子:对于 W1R3072×768W_1 \in \mathbb{R}^{3072 \times 768}(升维):

W1N(0,2768)=N(0,0.051)W_1 \sim \mathcal{N}\left(0, \sqrt{\frac{2}{768}}\right) = \mathcal{N}(0, 0.051)

每个元素从均值0、标准差0.051的正态分布中采样。

为什么不随机初始化偏置?

对比实验

  1. 偏置零初始化 vs 偏置随机初始化

    • 随机初始化偏置可能导致训练初期激活值过大或过小
    • 零初始化让激活值的分布更稳定
  2. 举例:使用ReLU激活

    h=ReLU(W1x+b1)h = \text{ReLU}(W_1 \cdot x + b_1)
    • 如果 b1b_1 过大且为正:太多神经元被激活,梯度可能爆炸
    • 如果 b1b_1 过大且为负:太多神经元被抑制(Dead ReLU)
    • 如果 b1=0b_1 = 0:激活与否完全由 W1xW_1 \cdot x 决定,训练平稳

特殊情况:可学习的偏置缩放

在一些高级模型中,偏置可能会在训练后期学习到有意义的值:

  • b1b_1:学习到每个隐藏神经元的"激活阈值"
  • b2b_2:学习到输出的"基准偏移"

但初始时仍然设为0。

实际代码示例

import torch
import torch.nn as nn

class MLP(nn.Module):
    def __init__(self, d_model=768, d_ff=3072):
        super().__init__()
        # 第一层:升维
        self.fc1 = nn.Linear(d_model, d_ff)
        # 激活函数
        self.activation = nn.GELU()
        # 第二层:降维
        self.fc2 = nn.Linear(d_ff, d_model)

        # 查看初始化(PyTorch默认行为)
        print(f"W1 初始化方式: Kaiming/He uniform")
        print(f"b1 初始值: {self.fc1.bias[:5]}...")  # 前5个值
        print(f"b1 全为0? {torch.allclose(self.fc1.bias, torch.zeros_like(self.fc1.bias))}")

    def forward(self, x):
        # x: (batch, seq_len, d_model)
        h = self.fc1(x)           # (batch, seq_len, d_ff)
        h = self.activation(h)     # (batch, seq_len, d_ff)
        y = self.fc2(h)           # (batch, seq_len, d_model)
        return y

# 创建MLP
mlp = MLP(d_model=768, d_ff=3072)

# 实际运行会看到:
# W1 初始化方式: Kaiming/He uniform
# b1 初始值: tensor([0., 0., 0., 0., 0.], grad_fn=<SliceBackward0>)...
# b1 全为0? False  # PyTorch的Linear层默认会用uniform初始化偏置

注意:PyTorch的 nn.Linear 默认会用小的均匀分布初始化偏置:

bU(1din,1din)b \sim \mathcal{U}\left(-\frac{1}{\sqrt{d_{\text{in}}}}, \frac{1}{\sqrt{d_{\text{in}}}}\right)

但这个范围非常小,实际上接近于0。许多实现会显式地将偏置设为0:

# 显式设置偏置为0
self.fc1.bias.data.zero_()
self.fc2.bias.data.zero_()

总结:偏置的作用

参数维度初始化作用
W1W_1(dff,dmodel)(d_{\text{ff}}, d_{\text{model}})He/Xavier升维变换,主要参数
b1b_1(dff,)(d_{\text{ff}},)零或小随机激活阈值,辅助参数
W2W_2(dmodel,dff)(d_{\text{model}}, d_{\text{ff}})He/Xavier降维变换,主要参数
b2b_2(dmodel,)(d_{\text{model}},)零或小随机输出偏移,辅助参数

偏置的参数量相对很小(dff+dmodeld_{\text{ff}} + d_{\text{model}}),在总参数中占比不到0.1%,但它们在训练过程中会学习到有意义的值,帮助模型更好地拟合数据。

具体例子

假设 dmodel=768d_{\text{model}} = 768dff=3072d_{\text{ff}} = 3072(4倍关系):

输入:xR768升维:h=W1x+b1,W1R3072×768hR3072(维度扩大4倍)非线性:hact=ReLU(h)=max(0,h)hactR3072(维度不变,但引入非线性)降维:y=W2hact+b2,W2R768×3072yR768(恢复原始维度)\begin{aligned} &\text{输入:} x \in \mathbb{R}^{768} \\ \\ &\text{升维:} h = W_1 \cdot x + b_1, \quad W_1 \in \mathbb{R}^{3072 \times 768} \\ &\quad \Rightarrow h \in \mathbb{R}^{3072} \quad \text{(维度扩大4倍)} \\ \\ &\text{非线性:} h_{\text{act}} = \text{ReLU}(h) = \max(0, h) \\ &\quad \Rightarrow h_{\text{act}} \in \mathbb{R}^{3072} \quad \text{(维度不变,但引入非线性)} \\ \\ &\text{降维:} y = W_2 \cdot h_{\text{act}} + b_2, \quad W_2 \in \mathbb{R}^{768 \times 3072} \\ &\quad \Rightarrow y \in \mathbb{R}^{768} \quad \text{(恢复原始维度)} \end{aligned}

维度变化7683072768768 \to 3072 \to 768

激活函数的选择

激活函数是MLP的"灵魂",不同的大模型使用不同的激活函数。

1. ReLU(Rectified Linear Unit)

最简单的激活函数:

ReLU(x)=max(0,x)={xif x>00if x0\text{ReLU}(x) = \max(0, x) = \begin{cases} x & \text{if } x > 0 \\ 0 & \text{if } x \leq 0 \end{cases}

优点

  • 计算简单高效
  • 缓解梯度消失问题
  • 稀疏激活(约50%的神经元被激活)

缺点

  • "Dead ReLU"问题:负值区域梯度为0,某些神经元可能永远不被激活
  • 非零中心(输出总是≥0)

使用:早期Transformer模型(如原始论文)

2. GELU(Gaussian Error Linear Unit)

更平滑的激活函数,引入了概率思想:

GELU(x)=xΦ(x)\text{GELU}(x) = x \cdot \Phi(x)

其中 Φ(x)\Phi(x) 是标准正态分布的累积分布函数:

Φ(x)=12[1+erf(x2)]\Phi(x) = \frac{1}{2}\left[1 + \text{erf}\left(\frac{x}{\sqrt{2}}\right)\right]

近似计算(更快):

GELU(x)0.5x(1+tanh[2π(x+0.044715x3)])\text{GELU}(x) \approx 0.5 \cdot x \cdot \left(1 + \tanh\left[\sqrt{\frac{2}{\pi}} \cdot (x + 0.044715 \cdot x^3)\right]\right)

直觉

  • 不是硬截断(像ReLU),而是平滑过渡
  • 对于较大的正值,几乎完全保留;对于较大的负值,几乎完全抑制
  • 在0附近是一个平滑的曲线

优点

  • 平滑可导,梯度性质更好
  • 非单调性(在负值区域有小的正梯度)
  • 实践中效果通常优于ReLU

使用:BERT、GPT-2、GPT-3等主流模型

3. SwiGLU(Swish-Gated Linear Unit)

目前最先进的激活函数之一,被LLaMA、PaLM等最新大模型采用:

SwiGLU(x,W,V,b,c)=Swish(xW+b)(xV+c)\text{SwiGLU}(x, W, V, b, c) = \text{Swish}(xW + b) \otimes (xV + c)

其中:

  • Swish(x)=xσ(x)=x11+ex\text{Swish}(x) = x \cdot \sigma(x) = x \cdot \frac{1}{1 + e^{-x}}(Swish激活函数)
  • \otimes 表示逐元素乘法(Hadamard积)
  • WWVV 是两个独立的权重矩阵

更详细的MLP结构(使用SwiGLU)

Gate=Swish(xWgate)=(xWgate)σ(xWgate)Up=xWuph=GateUpy=hWdown\begin{aligned} \text{Gate} &= \text{Swish}(x W_{\text{gate}}) = (x W_{\text{gate}}) \cdot \sigma(x W_{\text{gate}}) \\ \text{Up} &= x W_{\text{up}} \\ h &= \text{Gate} \otimes \text{Up} \\ y &= h W_{\text{down}} \end{aligned}

参数解释

  • WgateRdmodel×dffW_{\text{gate}} \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}}:门控权重矩阵
  • WupRdmodel×dffW_{\text{up}} \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}}:内容升维权重矩阵
  • WdownRdff×dmodelW_{\text{down}} \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}}:降维权重矩阵
  • σ(x)=11+ex\sigma(x) = \frac{1}{1 + e^{-x}}:Sigmoid函数
  • \otimes:逐元素相乘(Hadamard积)

为什么需要两个升维矩阵?

这是SwiGLU的核心设计!两个矩阵分工明确:

  1. WgateW_{\text{gate}} - 门控路径(决定"选什么")

    • 通过Swish激活后,输出一个"门控信号"
    • 作用:学习"哪些维度/特征应该被激活"
    • 类比:相当于一个智能开关,决定信息能否通过
  2. WupW_{\text{up}} - 内容路径(决定"传什么")

    • 不经过激活函数,直接线性变换
    • 作用:学习"特征的实际表示/内容"
    • 类比:相当于信息本身
  3. GateUp\text{Gate} \otimes \text{Up} - 门控乘法

    • 两个路径的输出逐元素相乘
    • Gate控制Up中每个维度的"通过程度"
    • 实现动态的、细粒度的特征选择

直观理解

想象你在看一本书的一页:

  • Up路径:这页上所有的文字(所有信息)
  • Gate路径:你的注意力/荧光笔(决定标记哪些内容)
  • 最终输出:只有被标记(门控激活)的内容才会被传递

对比标准MLP

类型结构矩阵数量信息流
标准MLPReLU(xW1)W2\text{ReLU}(xW_1) W_22个单路径,全局激活
SwiGLU[Swish(xWgate)(xWup)]Wdown[\text{Swish}(xW_{\text{gate}}) \otimes (xW_{\text{up}})] W_{\text{down}}3个双路径,动态选择

参数量计算(以 dmodel=768d_{\text{model}}=768, dff=3072d_{\text{ff}}=3072 为例):

标准MLP:W1:768×3072=2.36MW2:3072×768=2.36M总计:4.72M 参数SwiGLU:Wgate:768×3072=2.36MWup:768×3072=2.36MWdown:3072×768=2.36M总计:7.08M 参数(1.5倍)\begin{aligned} \text{标准MLP:} \quad &W_1: 768 \times 3072 = 2.36M \\ &W_2: 3072 \times 768 = 2.36M \\ &\text{总计:} 4.72M \text{ 参数} \\ \\ \text{SwiGLU:} \quad &W_{\text{gate}}: 768 \times 3072 = 2.36M \\ &W_{\text{up}}: 768 \times 3072 = 2.36M \\ &W_{\text{down}}: 3072 \times 768 = 2.36M \\ &\text{总计:} 7.08M \text{ 参数(1.5倍)} \end{aligned}

为什么1.5倍参数却效果更好?

  1. 更强的表达能力

    • 两个独立的升维矩阵提供了不同的变换空间
    • 门控机制实现了输入依赖的动态激活
  2. 更好的梯度流

    • Swish比ReLU更平滑,梯度更稳定
    • 门控乘法提供了多条梯度路径
  3. 稀疏激活

    • 门控可以学习到某些维度在某些输入下完全关闭
    • 提供了一种"软"的稀疏性

使用:LLaMA、LLaMA-2、PaLM等最新大模型

实现示例

class SwiGLU(nn.Module):
    def __init__(self, d_model=768, d_ff=3072):
        super().__init__()
        # 两个升维矩阵
        self.W_gate = nn.Linear(d_model, d_ff, bias=False)
        self.W_up = nn.Linear(d_model, d_ff, bias=False)
        # 一个降维矩阵
        self.W_down = nn.Linear(d_ff, d_model, bias=False)

    def forward(self, x):
        # 门控路径:Swish激活
        gate = self.W_gate(x)
        gate = gate * torch.sigmoid(gate)  # Swish(x) = x * σ(x)

        # 内容路径:直接线性变换
        up = self.W_up(x)

        # 门控乘法:动态选择
        h = gate * up  # 逐元素相乘

        # 降维
        y = self.W_down(h)
        return y

激活函数对比

激活函数公式参数量计算量效果使用模型
ReLUmax(0,x)\max(0, x)标准最低一般早期Transformer
GELUxΦ(x)x \cdot \Phi(x)标准中等BERT, GPT-2/3
SwiGLUSwish(xW)(xV)\text{Swish}(xW) \otimes (xV)1.5倍较高最好LLaMA, PaLM

MLP的参数量和计算量

MLP层是Transformer中参数量和计算量的主要来源

参数量计算

dmodel=768d_{\text{model}} = 768dff=3072d_{\text{ff}} = 3072 为例:

标准MLP(使用ReLU或GELU)

W1:768×3072=2,359,296 参数b1:3072 参数W2:3072×768=2,359,296 参数b2:768 参数总计:4,722,6884.7M 参数\begin{aligned} W_1 &: 768 \times 3072 = 2{,}359{,}296 \text{ 参数} \\ b_1 &: 3072 \text{ 参数} \\ W_2 &: 3072 \times 768 = 2{,}359{,}296 \text{ 参数} \\ b_2 &: 768 \text{ 参数} \\ \\ \text{总计} &: 4{,}722{,}688 \approx 4.7M \text{ 参数} \end{aligned}

对比注意力层(假设12头,每头维度64):

WQ,WK,WV:3×(768×768)=1,769,472 参数WO:768×768=589,824 参数总计:2,359,2962.4M 参数\begin{aligned} W_Q, W_K, W_V &: 3 \times (768 \times 768) = 1{,}769{,}472 \text{ 参数} \\ W_O &: 768 \times 768 = 589{,}824 \text{ 参数} \\ \\ \text{总计} &: 2{,}359{,}296 \approx 2.4M \text{ 参数} \end{aligned}

结论:MLP层的参数量约为注意力层的2倍

一个Transformer层的参数分布

对于GPT-3规模的模型(dmodel=12,288d_{\text{model}} = 12{,}288dff=49,152d_{\text{ff}} = 49{,}152):

注意力层:600M 参数MLP层:1200M 参数比例:注意力:MLP=1:2\begin{aligned} \text{注意力层} &: \approx 600M \text{ 参数} \\ \text{MLP层} &: \approx 1200M \text{ 参数} \\ \\ \text{比例} &: \text{注意力} : \text{MLP} = 1 : 2 \end{aligned}

这意味着:在大模型中,约2/3的参数都在MLP层!

为什么需要这么多参数?

  1. 表达能力:MLP负责学习复杂的非线性变换,需要足够的参数容量
  2. 知识存储:研究表明,MLP层类似于"知识库",存储了大量事实性知识
  3. 特征提取:升维后的高维空间提供了丰富的特征表示能力

但这也带来了问题:计算成本太高!

从MLP到MOE:计算效率的困境

问题的根源

随着模型规模增大,MLP的计算成本呈爆炸式增长:

计算量=2×dmodel×dff×ntokens=2×dmodel×(4×dmodel)×ntokens=8×dmodel2×ntokens\begin{aligned} \text{计算量} &= 2 \times d_{\text{model}} \times d_{\text{ff}} \times n_{\text{tokens}} \\ &= 2 \times d_{\text{model}} \times (4 \times d_{\text{model}}) \times n_{\text{tokens}} \\ &= 8 \times d_{\text{model}}^2 \times n_{\text{tokens}} \end{aligned}

举例

  • GPT-3 (175B):dmodel=12,288d_{\text{model}} = 12{,}288,每个Token需要约 1.21.2 万亿次浮点运算
  • 处理一个长度2048的序列:约 2.52.5 千万亿次运算

困境

  • 想要更强的模型 → 需要更多参数 → MLP层变得巨大 → 计算成本爆炸
  • 但是,每次推理时,我们真的需要激活所有的参数吗?

关键观察:稀疏性

研究人员发现:

  1. 不是所有参数对所有输入都重要:对于特定的输入,只有部分参数是关键的
  2. 专家分工:不同的"专家"可以专注处理不同类型的输入
  3. 条件计算:根据输入动态选择激活哪些参数

这启发了一个革命性的想法:混合专家模型(Mixture of Experts, MoE)

MOE:混合专家模型

核心思想

将一个大的MLP层拆分成多个小的"专家"MLP,每次只激活其中的几个:

保持参数量(甚至增加),但只激活一小部分,从而减少计算量

MOE的结构

标准MLP:

y=MLP(x)=W2Activation(W1x)y = \text{MLP}(x) = W_2 \cdot \text{Activation}(W_1 \cdot x)

MOE:

y=i=1NG(x)iEi(x)y = \sum_{i=1}^{N} G(x)_i \cdot E_i(x)

参数解释

  • NN:专家的总数(比如8个、64个、甚至128个)
  • Ei(x)E_i(x):第ii个专家(就是一个小的MLP)
  • G(x)G(x):门控网络(Router),输出每个专家的权重
  • G(x)iG(x)_i:输入xx应该分配给第ii个专家的权重

MOE的三个关键组件

1. 专家网络(Experts)

每个专家 EiE_i 是一个独立的MLP:

Ei(x)=W2,iActivation(W1,ix+b1,i)+b2,iE_i(x) = W_{2,i} \cdot \text{Activation}(W_{1,i} \cdot x + b_{1,i}) + b_{2,i}
  • 每个专家的结构与标准MLP相同
  • 但参数完全独立,可以学习不同的模式
  • 专家数量 NN 通常为 81288 \sim 128

2. 门控网络(Router/Gating Network)

门控网络决定每个输入应该路由到哪些专家:

G(x)=Softmax(TopK(xWg,k))G(x) = \text{Softmax}(\text{TopK}(x \cdot W_g, k))

详细步骤

步骤1:计算每个专家的得分s=xWgRN步骤2:选择Top-K专家选出得分最高的k个专家步骤3:归一化G(x)=Softmax(选中的专家得分)\begin{aligned} \text{步骤1:计算每个专家的得分} \quad & s = x \cdot W_g \in \mathbb{R}^N \\ \text{步骤2:选择Top-K专家} \quad & \text{选出得分最高的}k\text{个专家} \\ \text{步骤3:归一化} \quad & G(x) = \text{Softmax}(\text{选中的专家得分}) \end{aligned}

参数解释

  • WgRdmodel×NW_g \in \mathbb{R}^{d_{\text{model}} \times N}:门控权重矩阵
  • si=xWg[:,i]s_i = x \cdot W_g[:, i]:输入xx对专家ii的"亲和度"
  • kk:每次激活的专家数量(通常 k=1k=1k=2k=2
  • TopK:只保留得分最高的kk个专家,其余设为-\infty(softmax后为0)

3. 稀疏激活(Sparse Activation)

关键:每个Token只路由到kk个专家,其余专家不参与计算

y=iTopKG(x)iEi(x)y = \sum_{i \in \text{TopK}} G(x)_i \cdot E_i(x)
  • 如果 k=2k=2N=8N=8,则只有 2/8=25%2/8 = 25\% 的专家被激活
  • 其余 66 个专家完全跳过,节省计算

MOE完整计算流程

假设有8个专家,每次激活Top-2:

步骤1:计算门控得分

s=xWg=[s1,s2,s3,s4,s5,s6,s7,s8]s = x \cdot W_g = [s_1, s_2, s_3, s_4, s_5, s_6, s_7, s_8]

假设得到:s=[0.3,0.8,0.1,0.5,0.9,0.2,0.4,0.6]s = [0.3, 0.8, 0.1, 0.5, 0.9, 0.2, 0.4, 0.6]

步骤2:选择Top-2专家

  • 得分最高的2个:专家5(0.9)和专家2(0.8)
  • 其余6个专家被屏蔽

步骤3:归一化权重

G(x)2=e0.8e0.8+e0.90.47G(x)5=e0.9e0.8+e0.90.53\begin{aligned} G(x)_2 &= \frac{e^{0.8}}{e^{0.8} + e^{0.9}} \approx 0.47 \\ G(x)_5 &= \frac{e^{0.9}}{e^{0.8} + e^{0.9}} \approx 0.53 \end{aligned}

步骤4:计算输出

y=G(x)2E2(x)+G(x)5E5(x)=0.47E2(x)+0.53E5(x)\begin{aligned} y &= G(x)_2 \cdot E_2(x) + G(x)_5 \cdot E_5(x) \\ &= 0.47 \cdot E_2(x) + 0.53 \cdot E_5(x) \end{aligned}

计算量对比

  • 标准MLP:计算1个大MLP
  • MOE(8专家,Top-2):计算2个小MLP + 门控网络
  • 如果每个专家大小为标准MLP的1/8,则计算量约为 (2/8)25%(2/8) \approx 25\%

MOE的参数量和计算量

参数量

标准MLP:2×dmodel×dffMOE:N×(2×dmodel×dffN)+dmodel×N=2×dmodel×dff+dmodel×N\begin{aligned} \text{标准MLP} &: 2 \times d_{\text{model}} \times d_{\text{ff}} \\ \\ \text{MOE} &: N \times (2 \times d_{\text{model}} \times \frac{d_{\text{ff}}}{N}) + d_{\text{model}} \times N \\ &= 2 \times d_{\text{model}} \times d_{\text{ff}} + d_{\text{model}} \times N \end{aligned}
  • 专家参数:与标准MLP相当(假设每个专家是原MLP的1/N1/N
  • 门控参数:dmodel×Nd_{\text{model}} \times N(通常很小)
  • 总参数量:如果专家数 N=8N=8,参数可以保持不变,甚至显著增加

计算量(每个Token):

标准MLP:2×dmodel×dffMOE:k×(2×dmodel×dffN)+dmodel×N2kN×dmodel×dff(门控计算很小,忽略)\begin{aligned} \text{标准MLP} &: 2 \times d_{\text{model}} \times d_{\text{ff}} \\ \\ \text{MOE} &: k \times (2 \times d_{\text{model}} \times \frac{d_{\text{ff}}}{N}) + d_{\text{model}} \times N \\ &\approx \frac{2k}{N} \times d_{\text{model}} \times d_{\text{ff}} \quad \text{(门控计算很小,忽略)} \end{aligned}
  • 如果 N=8N=8k=2k=2:计算量减少到 2/8=25%2/8 = 25\%
  • 如果 N=64N=64k=2k=2:计算量减少到 2/643%2/64 \approx 3\%

MOE的优势

  1. 参数-计算解耦

    • 可以有海量参数(增强容量)
    • 但每次只激活一小部分(保持效率)
  2. 专家专业化

    • 不同专家自动学习处理不同类型的输入
    • 类似"分工合作",每个专家专注自己擅长的领域
  3. 可扩展性强

    • 容易扩展到超大规模(如Switch Transformer有1.6万亿参数)
    • 推理时计算量增长缓慢

MOE的挑战

  1. 负载不均衡(Load Imbalance)

    • 某些专家可能被频繁选中,其他专家几乎不被使用
    • 导致计算资源浪费和训练不充分
    • 解决方案:添加负载均衡损失函数
    Lbalance=αCV(xbatchG(x))L_{\text{balance}} = \alpha \cdot \text{CV}\left(\sum_{x \in \text{batch}} G(x)\right)

    其中 CV\text{CV} 是变异系数(coefficient of variation),鼓励专家使用均匀分布。

  2. 训练不稳定

    • 门控网络训练困难,容易收敛到次优解
    • 需要仔细调整学习率和初始化策略
  3. 通信开销(分布式训练):

    • 在多GPU训练时,需要在GPU间传输Token到对应专家
    • 通信成本可能抵消计算节省
    • 解决方案:专家并行(Expert Parallelism)策略
  4. 推理复杂度

    • 需要动态路由和条件计算
    • 实现复杂度高于标准MLP

MOE的实际应用

Switch Transformer

Google的Switch Transformer(2021)是MOE的成功案例:

  • 规模:1.6万亿参数(当时最大)
  • 专家数:每层128个专家
  • 激活:每个Token只路由到1个专家(k=1k=1
  • 效果:比同等计算量的稠密模型快4倍,效果更好

关键创新

  • 简化路由:只用Top-1专家(k=1k=1
  • 专家级负载均衡
  • 选择性精度:专家用FP32,其余用FP16

DeepSeek-MoE 和 Mixtral

更近期的MOE模型:

  • Mixtral-8x7B(2023):8个专家,每个7B参数,Top-2激活,总共56B参数但只有13B激活
  • DeepSeek-MoE(2024):细粒度专家分割,进一步提升效率

这些模型证明:MOE是扩展到超大规模的有效路径

小结

  1. 非线性的必要性

    • 注意力机制主要是线性变换
    • MLP层通过激活函数引入强力的非线性
    • 这是模型学习复杂模式的关键
  2. MLP结构

    • 升维-非线性-降维(dmodeldffdmodeld_{\text{model}} \to d_{\text{ff}} \to d_{\text{model}}
    • 通常 dff=4×dmodeld_{\text{ff}} = 4 \times d_{\text{model}}
    • 占据Transformer约2/3的参数量
  3. 激活函数演进

    • ReLU → GELU → SwiGLU
    • 从简单到复杂,效果逐步提升
    • SwiGLU是目前最先进的选择
  4. 从MLP到MOE

    • MLP的计算成本随模型规模爆炸式增长
    • MOE通过稀疏激活解耦参数量和计算量
    • 核心思想:多个专家分工,每次只激活少数几个
  5. MOE的权衡

    • ✅ 优势:参数多、计算少、可扩展
    • ❌ 挑战:负载均衡、训练难度、实现复杂

未来趋势:MOE正在成为超大规模模型的标配架构,允许我们在保持推理效率的同时,不断扩大模型容量。