Files
note_sync/大模型/FFN 前馈网络详解.md

411 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FFN 前馈网络详解
> Feed-Forward Network — Transformer 中参数量最大(~2/3)、负责知识存储和特征变换的核心组件
---
## 一、FFN 在 Transformer 中的角色
```
Transformer Block:
注意力层(信息混合) FFN 层(特征变换)
┌─────────────────┐ ┌─────────────────────┐
│ Token 之间交互 │ │ 每个 Token 独立处理 │
│ "谁跟谁有关?" │ → │ "这个 Token 是什么意思?"│
│ 参数量 ~1/3 │ │ 参数量 ~2/3 │
└─────────────────┘ └─────────────────────┘
```
| 组件 | 参数量占比 | 功能 |
|---|---|---|
| **注意力层** | ~1/3 | Token 间的信息路由与混合("连接信息") |
| **FFN 层** | ~2/3 | Token 内部的特征变换与知识存储("存储知识") |
> **直觉**:注意力层决定"从哪读信息",FFN 层存储"读到的信息怎么理解"。研究表明 FFN 的权重矩阵充当了**联想记忆(Associative Memory)**——W₁ 的每一行是一个"模式检测器",W₂ 的对应列是"该模式的输出"。
---
## 二、基础公式
### 标准 FFN(原始 Transformer)
```
FFN(x) = W₂ · ReLU(W₁ · x + b₁) + b₂
```
```
输入 x(d_model 维度)
│
▼
W₁: d_model → d_ff ← 升维(通常 d_ff = 4 × d_model)
│
▼
激活函数(ReLU/GELU/...) ← 引入非线性
│
▼
W₂: d_ff → d_model ← 降维回原维度
│
▼
输出(d_model 维度)
```
### 参数量计算
```
标准 FFN(无偏置):
params = d_model × d_ff + d_ff × d_model
= 2 × d_model × d_ff
= 2 × d_model × 4d_model (通常 d_ff = 4d)
= 8 × d_model²
```
| d_model | d_ff (4×) | FFN 参数量 | 一层总参数(含注意力) |
|---|---|---|---|
| 512 | 2048 | 2.1M | ~3M |
| 4096 (LLaMA 7B) | 11008 (约2.7×) | ~90M | ~135M |
| 8192 (LLaMA 70B) | 22016 | ~360M | ~540M |
> 在 LLaMA 等 SwiGLU 模型中,`d_ff` 通常设为约 `8/3 × d_model`(而非 4×),以补偿 GLU 变体引入的额外参数。
---
## 三、激活函数演进路线
```
2017 ─→ 2019 ────→ 2020 ──────→ 2025
ReLU GELU SwiGLU PolyGLU / 动态路由
│ │ │ │
│ │ │ └─ 每个神经元学自己的激活函数
│ │ └─ 门控线性单元,当前生产标准
│ └─ 平滑版 ReLU,不再死神经元
└─ 简单粗暴,负值直接清零
```
### 3.1 ReLU(原始 Transformer)
$$\text{ReLU}(x) = \max(0, x)$$
```
│
2 ┤ ╱
1 ┤ ╱
0 ┼════●──────── ← 负半轴完全为零
│ ╱
-1 ┤ ╱
└────────────
```
| 优点 | 缺点 |
|---|---|
| 计算极快,硬件原生支持 | **死神经元**:一旦输出为零,梯度为零,永不恢复 |
| 稀疏激活(约 50%) | 负值信息完全丢失 |
> **死神经元问题**:如果某个神经元的输出始终 ≤ 0(因为权重初始化或学习偏差),ReLU 的梯度永远为 0,这个神经元就"死了"——再也不会更新。
### 3.2 GELU(GPT-2 / BERT 时代)
$$\text{GELU}(x) = x \cdot \Phi(x) = x \cdot \frac{1}{2}\left[1 + \text{erf}\left(\frac{x}{\sqrt{2}}\right)\right]$$
```
│
2 ┤ ╱
1 ┤ ╱╱
0 ┼────●───────── ← 负半轴有小的非零输出
-1 ┤╱╱
└────────────
```
| 优点 | 缺点 |
|---|---|
| 平滑可微,梯度流更好 | 计算比 ReLU 慢(erf 函数) |
| 负值有概率性"门控"而非直接清零 | 所有输入用同一个函数处理 |
| 缓解死神经元问题 | |
> **GELU 的直觉**:把输入值 `x` 乘以它"来自正半轴的概率"——如果 x 很可能是负的,输出很小但不为零。
### 3.3 SiLU / Swish
$$\text{SiLU}(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}}$$
```
│
2 ┤ ╱
1 ┤ ╱╱
0 ┼─────●───────── ← 负半轴有"负沟"(non-monotonic)
-0.3┤ ╱ ╲ ← 这个沟是 SiLU 的独特特征
└────────────
```
| 关键特性 | 说明 |
|---|---|
| **自门控** | 每个值决定自己通过多少:`x · σ(x)` |
| **非单调** | 负半轴先下降再回升,能捕获复杂模式 |
| **无界正值** | 正半轴可以无限大(不像 sigmoid 有上限) |
| **平滑** | 处处可微,梯度流极好 |
> SiLU 和 Swish 是**同一个函数**的两个名字。Google 叫它 Swish,PyTorch 叫它 SiLU。
---
## 四、GLU 门控线性单元:范式转变
### 4.1 从"单路激活"到"双路门控"
```
标准 FFN: GLU FFN:
x x
│ ┌────┴────┐
W₁ W_gate W_up
│ │ │
Act σ │
│ │ │
W₂ └────┬────┘
│ ⊗ (逐元素相乘)
输出 │
W_down
│
输出
```
### 4.2 为什么乘法比加法强?
> 门控 = 逐元素乘法 = 引入**二阶交互项**
```
GLU(x) 包含 xᵢ · xⱼ 项(不同维度之间产生交互)
FFN(x) 只有 σ(Wx) 项(每个维度独立处理)
类比:
FFN = 每个特征独立打分
GLU = 特征之间可以"对话"——"我是形容词,如果前面是名词就激活我"
```
### 4.3 SwiGLU(2020→至今的生产标准)
$$\text{SwiGLU}(x) = \text{SiLU}(x W_{\text{gate}}) \otimes (x W_{\text{up}}) \cdot W_{\text{down}}$$
```python
# SwiGLU 的实际代码(也是 LLaMA、DeepSeek、Mistral 等主流模型的实现)
class SwiGLU(nn.Module):
def __init__(self, dim, hidden_dim):
super().__init__()
# 三个权重矩阵(不是标准的两个)
self.gate_proj = nn.Linear(dim, hidden_dim, bias=False) # 门控通路
self.up_proj = nn.Linear(dim, hidden_dim, bias=False) # 值通路
self.down_proj = nn.Linear(hidden_dim, dim, bias=False) # 降维
def forward(self, x):
# gate: 门控信号(SiLU 激活)
# up: 值信号(无激活,直接通过)
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
```
### 4.4 参数量补偿:为什么 d_ff 变小了
| 方案 | 矩阵数 | 参数量 | d_ff(等参) |
|---|---|---|---|
| 标准 FFN | W₁, W₂ (2个) | 2 × d × d_ff | 4d |
| SwiGLU | W_gate, W_up, W_down (3个) | 3 × d × d_ff | **8d/3 ≈ 2.67d** |
```
等参数量下:
标准 FFN:d_ff = 4d → 2 × d × 4d = 8d²
SwiGLU:d_ff = 8d/3 → 3 × d × 8d/3 = 8d²
↑
参数总量相同,但 SwiGLU 表达能力更强
```
> 实际中,即使参数相当,SwiGLU 的困惑度(Perplexity)也比 GELU 低约 **8.3%**(文本任务)——这是"免费的性能提升"。
---
## 五、GLU 变体对比
| 变体 | 门控激活函数 | 公式 | 特点 |
|---|---|---|---|
| **GLU(原始)** | Sigmoid | `σ(xW_g) ⊗ (xW_u)` | 门控有上限(0,1),表达受限 |
| **ReGLU** | ReLU | `ReLU(xW_g) ⊗ (xW_u)` | 最快,但仍可能死神经元 |
| **GeGLU** | GELU | `GELU(xW_g) ⊗ (xW_u)` | 视觉/多模态任务稍好 |
| **SwiGLU** | SiLU/Swish | `SiLU(xW_g) ⊗ (xW_u)` | **文本生成 SOTA,生产标准** |
### 选择指南
| 场景 | 推荐 | 原因 |
|---|---|---|
| LLM 文本生成 | **SwiGLU** | 困惑度最低,LLaMA/DeepSeek/Mistral 全部使用 |
| 视觉编码器 | GeGLU 或 GELU | 分类任务上略优(~2% Top-1 提升) |
| 延迟敏感 | ReGLU | Relu 计算成本最低,硬件原生支持 |
| 混合模态 | 编码器 GELU + 解码器 SwiGLU | 各取所长,推理可提速 ~15% |
---
## 六、FFN = 知识的"键值记忆"
> 这个视角将 FFN 从"无聊的全连接层"升级为"模型的知识存储系统"
### 联想记忆假说
```
FFN(x) = W₂ · Act(W₁ · x)
W₁ 的每一行 = 一个"键"(Key),检测特定模式
W₂ 的对应列 = 一个"值"(Value),存储该模式对应的输出
过程:
1. W₁·x → 将输入与所有"键"匹配(内积 = 相似度)
2. Act → 过滤:只保留匹配度高的模式
3. W₂· ... → 激活的模式"写出"对应的值
```
### 实验证据
| 发现 | 说明 |
|---|---|
| **知识定位** | FFN 的特定神经元可以对应具体的事实(如"埃菲尔铁塔在巴黎") |
| **可编辑性** | 修改 FFN 权重可以精确修改模型存储的事实(模型编辑) |
| **稀疏激活** | 对每个 Token,FFN 中只有少数神经元真正激活(和 MoE 思路一致) |
| **层间分工** | 浅层 FFN → 语法/局部模式、深层 FFN → 语义/全局知识 |
---
## 七、2025 前沿:激活函数本身也"可学习"
### 7.1 PolyGLU(2025年3月)
> **不再固定用一种激活函数,而是让每个神经元学习自己最适合的激活函数**
```
PolyGLU(x) = [ Σ g_k · σ_k(x·W_gate) ] ⊗ (x·W_up)
其中 σ_k 从候选池中选择:
{ ReLU, Tanh, SiLU, GELU }
g_k = 路由权重(每个神经元不同),训练中自动收敛
```
**训练后的层间分布(19K步训练)**:
```
层 0-2(浅层): GELU 35-40% | Tanh 15-25% | SiLU 15-20% | ReLU ~5%
层 3-14(中层): GELU 为主,逐步过渡
层 15-27(深层): Tanh 50-65% ← 深层网络偏好对称有界激活!
```
> 关键发现:深层网络的神经元**自发偏好 Tanh**(对称、有界),浅层偏好 GELU(概率性门控)。这暗示不同深度的层确实需要不同"类型"的非线性变换。
### 7.2 动态激活选择(LLaMA 4)
LLaMA 4 实验在 SwiGLU 和 GELU 之间做**输入级动态切换**:
- 用轻量门控网络根据输入内容选择激活函数
- 复杂推理任务 → 倾向于 SwiGLU(更强的特征交互)
- 简单模式匹配 → 倾向于 GELU(更快的计算)
---
## 八、DeepSeek 风格的实际实现
### 标准 SwiGLU(如 LLaMA)
```python
class LlamaMLP(nn.Module):
def __init__(self, config):
super().__init__()
self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
def forward(self, x):
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
```
### DeepSeek-V3 的 MoE + SwiGLU
```python
# 每个专家就是一个 SwiGLU FFN
class DeepSeekExpert(nn.Module):
def __init__(self, dim, moe_intermediate_size):
super().__init__()
self.gate_proj = nn.Linear(dim, moe_intermediate_size, bias=False)
self.up_proj = nn.Linear(dim, moe_intermediate_size, bias=False)
self.down_proj = nn.Linear(moe_intermediate_size, dim, bias=False)
def forward(self, x):
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
# MoE 层:N 个专家 + 路由器
class DeepSeekMoE(nn.Module):
def __init__(self, dim, n_routed_experts=256, n_shared_experts=2, top_k=8):
super().__init__()
self.shared_experts = nn.ModuleList([
DeepSeekExpert(dim, dim * 2) for _ in range(n_shared_experts)
])
self.routed_experts = nn.ModuleList([
DeepSeekExpert(dim, dim * 2) for _ in range(n_routed_experts)
])
self.gate = nn.Linear(dim, n_routed_experts, bias=False)
def forward(self, x):
# 共享专家(始终激活)
shared_out = sum(e(x) for e in self.shared_experts)
# 路由专家(稀疏激活 Top-K)
router_logits = self.gate(x)
topk_weights, topk_ids = torch.topk(router_logits.softmax(-1), self.top_k)
routed_out = 0
for i, expert_id in enumerate(topk_ids):
routed_out += topk_weights[i] * self.routed_experts[expert_id](x)
return shared_out + routed_out
```
> DeepSeek-V3 有 256 个路由专家 + 2 个共享专家,每个专家内部都是 SwiGLU FFN。详见 [[MoE 混合专家架构]]。
---
## 九、进化总览
```
标准 FFN GLU 系列
──────── ────────
非线性来源: 激活函数 门控乘法
通路数: 单路 双路(值 + 门控)
交互阶数: 一阶 二阶(xᵢ·xⱼ)
参数效率: 基准 显著更优
参数量: 2 × d × d_ff 3 × d × d_ff
生产标准: 已过时 SwiGLU(2025年)
未来方向
────────
激活函数选择: 可学习、每个神经元不同
实现: PolyGLU、动态路由
目标: 让模型自主发现最优非线性
```
---
## 十、一句话总结
```
FFN 不是"无聊的全连接层"——
它是 Transformer 中参数最密集、负责存储世界知识的核心组件。
ReLU → GELU → SiLU → SwiGLU → PolyGLU 的进化线,
本质上是"从固定非线性到可学习、动态、输入感知的门控机制"的演进。
你正在用的 LLaMA、DeepSeek、Mistral、Qwen 的每一层中,
SwiGLU 正在用乘法门控默默地做着模型 2/3 的"思考"。
```
---
## 相关链接
- [[Transformer 架构详解]] — FFN 在完整架构中的位置
- [[大模型架构基础]] — 各组件全景概览
- [[MoE 混合专家架构]] — FFN 的稀疏化扩展:用 N 个 SwiGLU 替代 1 个
---
*最后更新:2026-08-05*