# 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*