翻开任何一篇介绍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,就是理解大模型的"大脑"在哪里。下次再看到注意力机制的光环时,别忘了,在聚光灯之外,还有三层巨量的矩阵,静静存储着模型对世界的理解。
参考文献
- Vaswani, A., et al. “Attention Is All You Need.” NeurIPS 2017.
- Geva, M., et al. “Transformer Feed-Forward Layers Are Key-Value Memories.” EMNLP 2021.
- Dong, Y., et al. “Attention is Not All You Need: Pure Attention Loses Rank Doubly Exponentially with Depth.” ICML 2021.
- Shazeer, N. “GLU Variants Improve Transformer.” arXiv 2020.
- Hendrycks, D., & Gimpel, K. “Gaussian Error Linear Units (GELUs).” arXiv 2016.
- Ramachandran, P., et al. “Searching for Activation Functions.” arXiv 2017.
- Brown, T., et al. “Language Models are Few-Shot Learners.” NeurIPS 2020.
- Touvron, H., et al. “Llama 2: Open Foundation and Fine-Tuned Chat Models.” arXiv 2023.
- Fedus, W., et al. “Switch Transformers: Scaling to Trillion Parameter Models.” JMLR 2022.
- DeepSeek-AI. “DeepSeek-V3 Technical Report.” arXiv 2024.