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

14 KiB
Raw Permalink Blame History

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}}
# 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)

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

# 每个专家就是一个 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 的"思考"。

相关链接


最后更新:2026-08-05