翻开任何一篇介绍Transformer的文章,注意力机制总是占据C位。“Attention Is All You Need”——这篇2017年的论文标题本身就暗示着注意力是主角。但如果把目光从聚光灯下移开,看向模型的参数分布,会发现一个反直觉的事实:在GPT-3的1750亿参数中,前馈网络(Feed-Forward Network,FFN)占据了约三分之二

这不是设计上的疏忽,也不是参数的浪费。FFN承载着Transformer模型中最核心的功能之一:知识的存储与检索。理解FFN,就是理解大模型"知道什么"以及"如何知道"的关键。

一个看似简单的设计

FFN的结构可以用一行数学公式描述:

$$\text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2$$

其中$W_1 \in \mathbb{R}^{d_{model} \times d_{ff}}$,$W_2 \in \mathbb{R}^{d_{ff} \times d_{model}}$。在原始Transformer中,$d_{ff} = 4 \times d_{model}$——输入向量先被"升维"到4倍大小,经过非线性激活后再"降维"回来。

graph LR
    subgraph FFN层
        Input[输入 x<br/>d_model维] --> W1[线性变换 W1<br/>升维至 d_ff]
        W1 --> Act[激活函数<br/>ReLU/GELU/SiLU]
        Act --> W2[线性变换 W2<br/>降维至 d_model]
        W2 --> Output[输出<br/>d_model维]
    end

用代码实现也极其简洁:

class FFN(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff)
        self.w2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)
    
    def forward(self, x):
        return self.w2(self.dropout(F.relu(self.w1(x))))

两层线性变换,中间夹一个激活函数。每个位置独立处理,参数在所有位置共享。如此简单的设计,凭什么占据模型三分之二的参数量?

参数分布:FFN的"体量"

答案藏在维度的选择里。以GPT-3为例,$d_{model} = 12288$,$d_{ff} = 49152$。单层FFN的参数量为:

$$2 \times d_{model} \times d_{ff} = 2 \times 12288 \times 49152 \approx 12亿$$

GPT-3有96层,FFN总参数约$12亿 \times 96 \approx 1150亿$,占总参数的约66%。

pie title GPT-3 175B 参数分布
    "FFN层" : 66
    "注意力层" : 33
    "嵌入层" : 1

相比之下,注意力层的参数量为$4 \times d_{model}^2$(Q、K、V、O四个投影矩阵),约60亿,仅占约3%。每层的参数分配约为2:1,FFN占大头。

graph TD
    subgraph 单层Transformer Block
        Input[输入] --> Attn[多头注意力<br/>约1/3参数]
        Attn --> AddNorm1[残差+LayerNorm]
        AddNorm1 --> FFN[前馈网络<br/>约2/3参数]
        FFN --> AddNorm2[残差+LayerNorm]
        AddNorm2 --> Output[输出]
    end

为什么需要FFN?

如果注意力机制已经能够捕捉序列中任意位置之间的依赖关系,为什么还需要FFN?

防止"表示退化"

2021年,Google的研究人员发现了一个惊人的现象:没有FFN和残差连接的纯注意力网络,token表示会以双倍指数速度退化为秩-1矩阵

这意味着什么?假设输入是一个句子"苹果很好吃",经过多层纯注意力处理后,三个词的表示会逐渐趋同,最终变成几乎相同的向量。模型失去了区分不同位置信息的能力。

graph LR
    subgraph 有FFN
        A1[苹果] --> B1[很好]
        B1 --> C1[好吃]
        C1 --> D1[保持区分]
    end
    
    subgraph 无FFN
        A2[苹果] --> B2[趋同]
        B2 --> C2[趋同]
        C2 --> D2[信息丢失]
    end

FFN的作用之一,就是防止这种"同质化"。研究发现,FFN通过非线性变换,维持了token表示之间的"各向同性"(Isotropy)——让不同位置的向量在表示空间中保持足够的距离,从而保留各自的语义信息。

这是一个精妙的平衡:注意力负责让不同位置"交流",FFN负责让它们"保持自我"。

知识的存储介质

2020年,一篇题为"Transformer Feed-Forward Layers Are Key-Value Memories"的论文揭示了FFN更深层的作用。

研究者发现,FFN的第一层($W_1$)可以看作"键"(Key),第二层($W_2$)可以看作"值"(Value)。输入向量$x$与$W_1$的每一行计算相似度(通过激活函数),然后根据这个相似度从$W_2$的对应列中"检索"信息。

graph LR
    subgraph FFN作为Key-Value记忆
        Input[输入向量 x] --> Key[Key矩阵 W1<br/>匹配文本模式]
        Key --> Score[激活分数<br/>匹配程度]
        Score --> Value[Value矩阵 W2<br/>输出分布]
        Value --> Combine[加权组合]
        Combine --> Output[输出向量]
    end

换句话说,FFN本质上是一个巨大的键值记忆网络。训练数据中的语言模式被编码进这些键值对中,推理时根据输入激活相应的记忆。

更有意思的是,研究者发现这种记忆具有层次性:

  • 底层FFN学习的是浅层模式:标点、常见词组合、基本语法结构
  • 高层FFN学习的是语义模式:概念关系、推理模式、领域知识
graph TD
    subgraph 高层FFN
        H1[概念关系<br/>推理模式]
        H2[领域知识<br/>上下文语义]
    end
    
    subgraph 中层FFN
        M1[短语结构<br/>语义组合]
        M2[句法关系<br/>搭配模式]
    end
    
    subgraph 底层FFN
        L1[标点符号<br/>字符模式]
        L2[常见词组<br/>基本语法]
    end
    
    Input[输入Token] --> L1
    Input --> L2
    L1 --> M1
    L2 --> M2
    M1 --> H1
    M2 --> H2
    H1 --> Output[输出表示]
    H2 --> Output

这解释了为什么FFN需要那么多参数:知识需要存储空间。一个千亿参数的模型,其中大部分"知识"都存放在FFN里。

两层结构的设计哲学

为什么FFN是两层,而不是一层或三层?

单层的局限

如果只有一层线性变换,FFN就退化为:

$$y = xW + b$$

无论叠加多少层,线性变换的组合仍然是线性的。模型失去了最关键的能力:拟合任意复杂函数。非线性激活函数的引入,让神经网络具备了"通用函数逼近器"的能力。

graph LR
    subgraph 单层线性
        S1[输入] --> S2[线性变换]
        S2 --> S3[输出]
        S3 --> S4[❌ 只能学习线性关系]
    end

两层的"刚刚好"

两层结构是最小的非线性网络配置。理论上,一个具有足够宽度的两层网络就能逼近任何连续函数。Transformer选择这个最简配置,体现了"够用就好"的设计哲学。

graph LR
    subgraph 两层FFN
        T1[输入] --> T2[线性+激活]
        T2 --> T3[线性变换]
        T3 --> T4[输出]
        T4 --> T5[✓ 可逼近任意连续函数]
    end

当然,也有研究者探索过更深的FFN。2025年的一篇论文发现,使用三层FFN(配合更少的层数)可以在相同参数量下获得更好的性能。但这会增加推理时的串行计算深度,在延迟敏感的场景中不一定划算。

扩展比:4倍还是更少?

原始Transformer选择$d_{ff} = 4 \times d_{model}$。这个"4倍扩展比"在很长一段时间内是标准配置。

但现代模型开始调整这个比例。LLaMA系列使用约3.5倍,某些高效模型甚至降至2倍。为什么?

graph LR
    subgraph 大扩展比 4x
        L1[最大表示能力] --> L2[高计算/内存开销]
    end
    
    subgraph 小扩展比 2-3x
        S1[推理效率高] --> S2[表示能力受限]
    end

计算效率的考量:扩展比越大,FFN的计算量越大。在推理时,FFN往往是内存带宽瓶颈的主要来源。降低扩展比可以减少内存访问量,提高推理速度。

参数效率的权衡:研究发现,过大的扩展比并不总是带来对应的性能提升。存在一个"边际收益递减"的临界点。

模型 隐藏维度 FFN维度 扩展比 设计考量
GPT-3 12288 49152 4x 标准配置
LLaMA 2 70B 8192 28672 3.5x 平衡效率与性能
LLaMA 3 8B 4096 14336 3.5x 一脉相承
DeepSeek V3 7168 约2048/expert 10/3x MoE架构

激活函数的演进

FFN中间的激活函数虽小,却深刻影响着模型的训练动态和最终性能。

ReLU:简单但有问题

原始Transformer使用ReLU:

$$\text{ReLU}(x) = \max(0, x)$$

计算极其高效,但存在"死亡神经元"问题:一旦某个神经元的输入持续为负,它就永远输出0,梯度也为0,参数再也无法更新。这个问题在深层网络中尤其严重。

graph TD
    subgraph ReLU的问题
        R1[负输入] --> R2[输出=0]
        R2 --> R3[梯度=0]
        R3 --> R4[神经元"死亡"]
        R4 --> R5[参数无法更新]
    end

GELU:平滑的概率门控

BERT引入了GELU(Gaussian Error Linear Unit):

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

其中$\Phi(x)$是标准正态分布的累积分布函数。GELU可以理解为一种"软门控":对于确定的正值,几乎完全通过;对于确定的负值,几乎完全阻断;对于接近零的值,则进行部分衰减。

GELU的优势在于处处可导,梯度流动更平滑。这在大规模训练中带来了更好的收敛性。

SiLU/Swish:解码器模型的新宠

LLaMA、Mistral等现代解码器模型选择了SiLU(也称Swish):

$$\text{SiLU}(x) = x \cdot \sigma(x)$$

其中$\sigma(x)$是sigmoid函数。与GELU相比,SiLU的计算更简单(不需要误差函数),且有独特的性质:它的导数可以大于1,在某些输入区域会"放大"梯度,可能有助于深层网络的训练。

SwiGLU:现代标准

2020年,Noam Shazeer提出用门控线性单元(GLU)替代传统激活函数。SwiGLU的公式为:

$$\text{SwiGLU}(x) = \text{SiLU}(xW_{gate}) \odot (xW_{up})$$

其中$\odot$表示逐元素乘法。这相当于把输入分成两路,一路经过sigmoid"门控",另一路直接传递,最后相乘。

graph LR
    Input[x] --> W1[W_gate]
    Input --> W2[W_up]
    W1 --> SiLU[SiLU激活]
    W2 --> ID[恒等映射]
    SiLU --> Mul[逐元素乘法 ⊙]
    ID --> Mul
    Mul --> W3[W_down]
    W3 --> Output[输出]

SwiGLU在参数量增加约50%的情况下,带来了约1-2%的性能提升。LLaMA、PaLM等顶级模型都采用了这一设计。

需要注意,SwiGLU改变了FFN的参数结构:不再是$2 \times d_{model} \times d_{ff}$,而是$3 \times d_{model} \times d_{ff}$(三个矩阵$W_{gate}$、$W_{up}$、$W_{down}$)。为了保持总参数量相近,使用SwiGLU的模型通常会相应减小$d_{ff}$。

稀疏激活:MoE的思路

传统FFN是"稠密"的:每个token都会激活所有神经元。但研究发现,在训练好的FFN中,大部分神经元对大部分输入的响应都很弱

这引出了一个自然的想法:能不能只激活"相关"的神经元?

从稠密到稀疏

Mixture of Experts(MoE)将FFN拆分为多个"专家",每个专家是一个小型FFN。路由网络决定每个token由哪些专家处理:

graph TD
    subgraph MoE架构
        Input[输入Token] --> Router[路由网络]
        Router --> E1[专家1]
        Router --> E2[专家2]
        Router --> E3[专家3]
        Router --> En[专家N]
        E1 --> Agg[加权聚合]
        E2 --> Agg
        E3 --> Agg
        En --> Agg
        Agg --> Output[输出]
    end
class MoEFFN(nn.Module):
    def __init__(self, d_model, d_ff, n_experts, top_k):
        super().__init__()
        self.experts = nn.ModuleList([
            FFN(d_model, d_ff) for _ in range(n_experts)
        ])
        self.router = nn.Linear(d_model, n_experts)
        self.top_k = top_k
    
    def forward(self, x):
        # 计算路由分数
        router_logits = self.router(x)
        # 选择top-k专家
        top_k_weights, top_k_indices = torch.topk(
            router_logits, self.top_k, dim=-1
        )
        top_k_weights = F.softmax(top_k_weights, dim=-1)
        # 只计算选中专家的输出
        # ... 实现省略

MoE的效率收益

DeepSeek V3是一个典型的例子:

  • 总参数:6710亿
  • 每个token激活的参数:370亿(仅5.5%)
  • 等效于一个70亿参数的稠密模型的计算量
graph LR
    subgraph 稠密模型
        D1[全部参数] --> D2[全部激活]
        D2 --> D3[计算量大]
    end
    
    subgraph MoE模型
        M1[全部参数] --> M2[部分激活]
        M2 --> M3[计算量小]
    end

这种"大容量、小计算"的特性,让MoE在追求极致性能的场景中大放异彩。

但MoE也有代价:训练时的负载均衡问题、推理时的专家调度开销、部署时的内存压力。对于资源受限的场景,稠密FFN仍然是务实的选择。

推理时的计算特性

理解FFN在推理时的行为,对优化部署至关重要。

内存带宽瓶颈

在大模型推理中,FFN往往是**内存受限(memory-bound)**而非计算受限(compute-bound)的。

graph LR
    subgraph 推理瓶颈
        Load[加载权重] --> Compute[计算]
        Load -->|耗时长| Bottleneck[瓶颈]
        Compute -->|耗时短| Fast[快速]
    end

原因是显存带宽的限制。以A100为例,理论计算能力是312 TFLOPS(FP16),但显存带宽只有2TB/s。对于一个小batch的推理请求,从显存加载权重的时间远超过实际计算的时间。

FFN的两个大矩阵(每个约$d_{model} \times d_{ff}$)是主要的带宽消费者。当$d_{ff} = 4 \times d_{model}$时,FFN的权重加载量约占总量的三分之二。

优化策略

量化:将FP16权重压缩为INT8甚至INT4,直接减少内存访问量。研究表明,4-bit量化对模型质量的影响很小,但可以将带宽需求减少4倍。

稀疏激活:利用ReLU/SwiGLU产生的稀疏性,跳过激活值为0的神经元。虽然现代硬件对稀疏计算的支持有限,但这是一个活跃的研究方向。

投机解码:用一个快速的小模型预测多个token,再用大模型验证。FFN的计算可以在验证阶段并行处理多个位置。

graph LR
    subgraph 优化策略
        Q[量化<br/>FP16→INT4] --> Q1[带宽减少4x]
        S[稀疏激活] --> S1[跳过零神经元]
        D[投机解码] --> D1[并行处理]
    end

设计权衡的全景图

FFN的设计没有银弹,每个选择都伴随着权衡:

设计选择 优势 劣势 适用场景
大扩展比(4x) 最大表示能力 计算和内存开销大 追求极致性能
小扩展比(2-3x) 推理效率高 表示能力受限 资源敏感部署
ReLU 计算最快 死亡神经元问题 快速原型
GELU 平滑梯度 计算稍慢 编码器模型
SiLU/SwiGLU 性能最优 参数增加50% 现代解码器
MoE 计算效率高 部署复杂 超大规模模型
graph TD
    subgraph 设计决策树
        Start[开始设计] --> Q1{追求极致性能?}
        Q1 -->|是| MoE[MoE + SwiGLU]
        Q1 -->|否| Q2{资源受限?}
        Q2 -->|是| Small[小扩展比 + ReLU]
        Q2 -->|否| Standard[标准配置 4x + GELU]
    end

关键洞察:FFN的设计选择应该与模型的整体目标一致。如果追求最快的推理速度,小扩展比+ReLU可能是最佳选择;如果追求最佳性能,SwiGLU+MoE的组合则更合适。

从配角到主角

回到最初的问题:为什么FFN占据了模型三分之二的参数?

答案现在清晰了。FFN不是"配角",它是Transformer中知识存储的主要载体。注意力机制负责"在哪里找",FFN负责"知道什么"。

当我们说一个模型"学会了"某些知识时,这些知识主要编码在FFN的权重里。当我们说一个模型有"强大的推理能力"时,这种能力很大程度上源于FFN学到的模式。

理解FFN,就是理解大模型的"大脑"在哪里。下次再看到注意力机制的光环时,别忘了,在聚光灯之外,还有三层巨量的矩阵,静静存储着模型对世界的理解。


参考文献

  1. Vaswani, A., et al. “Attention Is All You Need.” NeurIPS 2017.
  2. Geva, M., et al. “Transformer Feed-Forward Layers Are Key-Value Memories.” EMNLP 2021.
  3. Dong, Y., et al. “Attention is Not All You Need: Pure Attention Loses Rank Doubly Exponentially with Depth.” ICML 2021.
  4. Shazeer, N. “GLU Variants Improve Transformer.” arXiv 2020.
  5. Hendrycks, D., & Gimpel, K. “Gaussian Error Linear Units (GELUs).” arXiv 2016.
  6. Ramachandran, P., et al. “Searching for Activation Functions.” arXiv 2017.
  7. Brown, T., et al. “Language Models are Few-Shot Learners.” NeurIPS 2020.
  8. Touvron, H., et al. “Llama 2: Open Foundation and Fine-Tuned Chat Models.” arXiv 2023.
  9. Fedus, W., et al. “Switch Transformers: Scaling to Trillion Parameter Models.” JMLR 2022.
  10. DeepSeek-AI. “DeepSeek-V3 Technical Report.” arXiv 2024.