土法炼钢兴趣小组的算法知识备份

【Transformer 与注意力机制】26|前馈网络:那个看似平平无奇的两层 MLP,其实是「记忆」所在

文章导航

分类入口
transformer
标签入口
#transformer#ffn#mlp#swiglu#moe

目录

如果你把 Transformer block 拆开来看,多数注意力都给了「attention」那部分——QKᵀ、softmax、多头、causal mask、KV cache,每一个都能讲一整篇。

但 block 里还有另外一半,叫「前馈网络(Feed-Forward Network,FFN)」,看起来就是一个最普通的两层 MLP(Multi-Layer Perceptron),先把维度从 d 升到 4d,过一个 ReLU,再压回 d。代码三行写完。

很多教程到这里就一句带过:「然后是一个 position-wise feed-forward。」

这是个很大的浪费。

第一,FFN 占整个 Transformer 总参数量的大约三分之二——它不是边角料,是大头。

第二,从 2021 年开始,一系列可解释性研究(Geva、Anthropic、Olah 等)把 FFN 重新解读成「键值记忆(Key-Value Memory)」——也就是说,attention 是「检索接口」,FFN 才是模型存储事实和模式的地方。

第三,现代 LLM(LLaMA、PaLM、Qwen、Mistral、DeepSeek)几乎全部把原版的 ReLU FFN 换成了 SwiGLU——这是 2017 年到现在 Transformer 主结构上变化最大的一处。

第四,混合专家(Mixture of Experts,MoE)本质上就是把单个 FFN 换成 N 个 FFN 加路由,连 attention 都没碰过。后面我们专门讲 MoE 的那一篇(44 篇),所有改动都在 FFN 这块。

第五,做推理工程的人都知道,量化时 attention 还好说,FFN 才是真正难处理的——4d 这一层的激活值分布往往非常厚尾,INT8 一不小心就把模型搞坏。

所以这一篇我想做的事情是:把 FFN 当成一个值得写一万字的对象,而不是一行注释。

读完之后,你应该能做到:

先把主公式贴出来,然后我们一节一节地拆它:

FFN(x) = max(0, x W₁ + b₁) W₂ + b₂

其中:

这是 2017 年原论文的写法。它有三个设计选择,每一个我们都要单独讲:两层而不是更多4 倍扩张而不是其它倍数逐位置(position-wise)而不是跨位置


一、把 FFN 放回 block 里看

1.1 一个 Transformer block 长什么样

先回到上下文里。Transformer encoder 的一个 block 长这样(pre-LN 写法,现代主流):

x' = x + Attention(LN(x))
y  = x' + FFN(LN(x'))

post-LN 写法(原论文):

x' = LN(x + Attention(x))
y  = LN(x' + FFN(x'))

无论 LN 放哪儿,结构上有两块「子层」:先 attention,再 FFN,之间各夹一个残差连接(Residual Connection)和层归一化(Layer Normalization)。

如果你只看这两个子层做了什么事,会发现一个很清晰的分工:

写成大白话:attention 负责「token 之间通信」,FFN 负责「token 内部计算」。

这个分工乍一看好像 attention 是主力、FFN 是辅助,因为新闻里关注的都是 attention。但前面已经说过:FFN 占参数量的三分之二,attention 只占三分之一。从「容量」角度看,FFN 才是模型的主力。

1.2 为什么 attention 之后还要 FFN

这是个值得停下来想一想的问题。

理论上,self-attention 已经是一个「带参数的混合操作」——它有 W_Q、W_K、W_V、W_O 四个矩阵,足以做相当复杂的事情。为什么不直接堆叠 attention 层就行?为什么每层之后非要再夹一个 MLP?

答案藏在 attention 的结构里。

attention 的公式是 softmax(QKᵀ/√d_k) V。这里 Q、K、V 都是输入 x 的线性投影。也就是说,attention 输出的每一个分量,都是输入 x 各位置的线性组合——加权系数虽然是非线性算出来的,但 V 进入输出的方式是纯线性。

如果你只堆 attention 层,每一层输出都是上一层输入的「加权线性组合」。多层堆起来,仍然只是一个比较复杂的线性变换(再加上 softmax 那点非线性,主要影响的是混合权重,不影响表示空间的维度结构)。

要让模型学到真正的非线性变换——比如「这个表示要先映射到一个高维空间,在那里某些方向被压缩、某些方向被放大、某些方向被砍掉,再压回来」——你需要一个纯粹做非线性的子层。这就是 FFN 的角色。

它升维(拉到 4d)、过非线性(ReLU 或后来的 Swish)、再降维(压回 d)。这个「升-非线性-降」的三明治,是经典 MLP 的标准动作,被各种深度学习模型反复证明有效。

换个角度说:attention 是在表示空间内部做线性混合,FFN 是在表示空间和一个更高维空间之间来回。两者结合,才能既看到上下文,又能做复杂的非线性计算。

1.3 FFN 是 token 间无关的

再强调一次这条性质,因为后面会反复用到:FFN 是逐位置(position-wise)的。

意思是:对 batch 里的每一个 token,FFN 都用同一组参数 W₁、W₂、b₁、b₂ 独立做一次计算。token A 的 FFN 计算和 token B 的 FFN 计算之间没有任何信息交换。

这不是巧合,是有意设计。

第一,它和 attention 的「跨位置混合」形成互补。

第二,它让 FFN 的计算可以完美并行——每个 token 一份独立的矩阵向量乘,一万个 token 就是一万次相同形状的乘法,GPU 很喜欢这种结构。

第三,参数量与序列长度无关——FFN 的参数 d × 4d + 4d × d = 8d²,和 token 数 T 没关系。

第四,这种「位置不敏感」的属性,让 FFN 像一个「纯粹的查表函数」:你给它一个 d 维向量 x,它返回一个 d 维向量 y,不需要任何上下文。下面讲键值记忆视角时,这个属性是关键。

FFN 计算图

图里把整条流程画清楚了:输入 [B, T, d] → 升维到 [B, T, 4d] → ReLU → 降维到 [B, T, d]。每个位置独立处理,没有跨位置的算子。


二、为什么是「两层」与「4 倍扩张」

这是 FFN 最常被略过、却最值得讨论的两个超参数。

2.1 两层是经验上的甜蜜点

为什么不是一层 MLP?因为一层带激活的 MLP 等价于「一个非线性核函数」,表达能力有限——具体说,一层的隐层维度即便很大,也仍然只是「线性 → 非线性 → 线性输出」三步合一,没有非线性的复合。

为什么不是三层、四层?因为再加层,参数量翻倍,但实测增益边际很小。原论文当时尝试了几种方案,最终选 2 层。后来 GPT-2、GPT-3、LLaMA 都沿用 2 层,没人换过。

更深的原因可能与残差连接(Residual Connection)有关:在残差网络里,每个 block 学的是一个「小修正」,不需要每个子层本身做特别复杂的变换,深度由层数堆叠提供,而不是由单个 FFN 内部提供

如果 FFN 内部已经是 3 层,再加上残差和 LN,每个 block 就有 5-6 个非线性层,反而可能让训练变难。

2.2 4 倍扩张比:原论文的消融

「为什么 d_ff = 4d?」这是个面试常问题,但真正讲到位的答案不多。

原论文 Attention Is All You Need(Vaswani et al., 2017)在 §5.4 的消融表里,对 d_ff 做过几组对照:

也就是说,2 倍不够,8 倍边际收益太小,4 倍是性价比最高的点

这个 4 倍扩张比的合理性,可以从另一个角度理解:FFN 第一层把 d 维向量映射到 4d 维空间。如果你把 4d 看成「特征字典」,每个字典条目对应一个被 ReLU 选择性激活的特征——4d 个条目大致够用,再多就稀疏到学不动。

但要注意:这个 4 倍是 2017 年在 d=512 的尺度上调出来的。后来不同尺度的模型,d_ff/d 这个比例其实有变动:

模型 d_model d_ff 比值
Transformer base (2017) 512 2048 4.0
Transformer big 1024 4096 4.0
BERT-base 768 3072 4.0
GPT-2 small 768 3072 4.0
GPT-3 175B 12288 49152 4.0
LLaMA-7B(SwiGLU) 4096 11008 2.69(≈ 8/3)
LLaMA-65B(SwiGLU) 8192 22016 2.69
PaLM 540B(SwiGLU) 18432 49152 2.67

ReLU FFN 一直是 4 倍。SwiGLU 改成 8/3 倍——但这不是「真的更窄」,而是因为 SwiGLU 多了一个矩阵,要把总参数量对齐到 8 d²。下面讲变体时会详细说。

2.3 d_ff 的选择不是孤立的

最后一个值得强调的点:d_ff 不能孤立看,要和 d_model、num_layers、num_heads 一起看。

总参数量 ≈ n_layers × (4 d² (attention) + 8 d² (FFN)) = 12 n_layers d²

这是一个非常好用的估计公式。比如:

记住这个 12 d² 的估计,对评估「这个模型有多大」「我能不能跑得起来」非常有用。


三、视角一:FFN 是逐位置的标量函数

我们已经反复说 FFN 是 position-wise 的,现在把这件事的形式化结果提出来。

把 FFN 看成一个函数 f: ℝ^d → ℝ^d,输入一个 d 维向量、返回一个 d 维向量。整个 FFN 子层做的事情是:

y[b, t, :] = f(x[b, t, :])    # 对每个 (b, t) 独立调用

注意 b 和 t 都不出现在 f 的定义里——f 只依赖参数 W₁、W₂、b₁、b₂,这些参数在所有位置共享。

这有几个有意思的推论:

推论 1:FFN 的输入分布是「每个 token 的隐状态」

这点很容易被忽视。你训练 FFN 时,看到的不是「整句话」,而是大量被 attention 混合过的、独立的 d 维向量。两个不同句子在某个位置上的隐状态,对 FFN 来说是同一类输入——只要它们的 d 维表示落在相似的区域。

推论 2:FFN 学到的是「隐状态空间到隐状态空间」的映射

这种映射的复杂度由 4d 这个隐藏层决定。如果 d=512、d_ff=2048,FFN 在内部有 2048 个「特征探测器」,每个探测器决定输入向量是否落在它响应的方向上。

推论 3:因为 FFN 不混 token,它对序列长度完全不敏感

无论你输入 512 个 token 还是 32k 个 token,FFN 子层的参数和单 token 计算量完全一样。所以长序列推理的瓶颈通常不在 FFN,而在 attention(它的复杂度是 O(T²))。这是后面讲长上下文优化(41-43 篇)的一个关键背景。

推论 4:FFN 的计算可以无成本地并行

GPU 可以把所有 (b, t) 的 FFN 计算合并成一次大的批量矩阵乘——[B*T, d] × [d, 4d] → [B*T, 4d],再 × [4d, d]。这是为什么 FFN 在工程上特别友好的原因。

3.1 一个直观的具象类比

我喜欢把 FFN 想象成一台「词向量加工厂」。

attention 子层把「上下文」装进了当前 token 的隐状态——它现在不再只是 “cat” 这个词的初始嵌入,而是「这句话里、在这个位置上、被前后文影响过的 cat 表示」。

FFN 接过这个加工过的表示,做一次复杂的非线性变换:「让这个向量在隐空间里走一段路」。这一步可能是把一个表示「澄清」(让它更接近某个概念)、可能是「转换」(把动词形态改了)、也可能是「事实查询」(输入 “Paris is the capital of”,输出方向上加上 “France” 的成分)。

具体它做了什么,要看下一节的「键值记忆」视角。


四、视角二:FFN 是关联记忆(Geva et al. 2021)

如果说前一节是「逐位置标量函数」的视角——把 FFN 当黑盒,问它的形状和性质——那么这一节要换一个完全不同的视角,把它打开看里面在干什么。

4.1 把 FFN 重写成内积+加权和

先做一点纯代数的重写。原公式:

FFN(x) = ReLU(x W₁ + b₁) W₂ + b₂

把 W₁ 看作 d_ff 个 d 维列向量(严格说是 W₁ 的列向量,或者把 W₁ 写成 [k₁ | k₂ | … | k_{d_ff}],每个 k_i 是 d 维):

W₁ = [k₁, k₂, ..., k_{d_ff}]    形状 d × d_ff

那么 x W₁ 的第 i 个分量就是 x · k_i——也就是输入 x 与 k_i 的内积。

加上 ReLU 和偏置:

a_i = ReLU(x · k_i + b_{1,i})

a_i 是一个标量,可以理解成「k_i 这个方向,被 x 激活了多少」。

再看 W₂。把 W₂ 拆成 d_ff 个 d 维行向量(这次是行):

W₂ = [v₁; v₂; …; v_{d_ff}]    形状 d_ff × d

那么:

y = a · W₂ + b₂ = Σ_i a_i v_i + b₂

也就是说,FFN 的输出,是 d_ff 个向量 v_i 的加权和,权重是 a_i

这个写法立刻让人联想到一个东西:注意力公式

Attention(Q, K, V) = softmax(QKᵀ/√d_k) V

attention 也是「先用 Q 和每个 K 算相似度,得到权重,再用权重加权 V」。

唯一的区别是:

attention 的相似度用 softmax 归一化,FFN 用 ReLU 不归一化(多个激活可以同时打开)。

但形式上的对应关系非常清楚:FFN 是「带固定 K/V 的 attention」,或者反过来说,attention 是「带动态 K/V 的 FFN」

4.2 Geva 等人的实证

这个视角不是凭空想出来的。Geva、Schuster、Berant、Levy 在 EMNLP 2021 的论文 Transformer Feed-Forward Layers Are Key-Value Memories 里,对 BERT 的每个 FFN 神经元做了系统的可视化研究。

他们做了什么呢?大致是:拿一个训练好的 BERT,把每一层 FFN 的 4d 个神经元逐个拿出来,看每个神经元在哪些输入下被强烈激活——也就是找出能把 a_i 激活到最大的那些训练样本。

结果非常震撼:

也就是说,FFN 的每个神经元,确实在做某种「检索」——它有一个偏好的输入模式(k_i 编码),当输入 x 的 d 维表示与 k_i 方向接近时,a_i 被打开,对应的 v_i 被加到输出里。

4.3 这个视角有什么用

很多个用处。我挑几个最直接的。

第一,它解释了「事实回忆」是从哪儿来的

一个语言模型能正确回答 “The capital of France is _” 是因为它在某个位置、某层 FFN 里,有一个神经元的 k 编码了「the capital of 」这个模式,对应的 v 在 vocab 投影后倾向 “Paris” 这个词的方向。Geva 的后续工作(Dissecting Recall of Factual Associations in Auto-Regressive Language Models,EMNLP 2023)甚至能定位到具体哪一层、哪些神经元负责一个特定事实,并通过修改这些神经元来改变模型记住的事实。

这是 ROME(Locating and Editing Factual Associations in GPT,Meng et al., NeurIPS 2022)和 MEMIT 等模型编辑工作的理论基础。

第二,它解释了为什么 MoE 有效

如果 FFN 是关联记忆,那一个大 FFN 就像一个大字典。当你想让模型容量翻倍时,可以扩大 d_ff——但 d_ff 已经很大了(数万),每个 token 都要遍历所有 d_ff 个 key 浪费太多。

MoE(Mixture of Experts)的想法是:做几个独立的 FFN(专家),每次只激活最相关的几个。这样总参数量可以放大几十倍,但单次计算只动用一小部分。后面 44 篇会详细讲。

第三,它解释了为什么 attention 量化容易、FFN 量化难

attention 的 K/V 是动态生成的,分布相对均匀;FFN 的 W₁ 行向量(也就是 k_i)有非常明显的「明星神经元」——少数 k_i 在大量样本上被强烈激活,对应的激活值有长尾。INT8 量化的难点就在这里:用 256 个量化级覆盖一个长尾分布会丢精度。后面讲量化(48-50 篇)时这点会具体展开。

键值记忆

图里把 W₁ 的列当成 keys、W₂ 的行当成 values 画出来,并对照了 attention 公式的各项。直观上很容易看出两者的同构关系。

4.4 一个反思:FFN 真的「存」事实吗

需要泼一点冷水。

「FFN 是事实存储」是一个很流行的隐喻,但严格说,这个说法有它的边界。

第一,FFN 单个神经元不是「一个事实对应一个神经元」。Anthropic 在 Toy Models of Superposition(Elhage et al., 2022)中指出,神经网络往往把多个特征「叠加」(superposition)到同一个神经元上——尤其是当特征数量超过维度数时。所以你看一个神经元被「激活」,它可能同时编码三个不相关的概念。

第二,事实的回忆经常需要 attention 和 FFN 配合——attention 把相关上下文搬到当前位置,FFN 才能在该位置做 lookup。两者拆开看意义有限。

第三,模型的「事实」不是离散存储,而是分布式的——同一个事实可能被多个神经元、多层一起共同编码。改一个神经元只会让模型对这个事实的回忆稍稍偏移,不会完全忘掉。

但这些 caveat 不否定主结论:FFN 是模型存储「学到的内容」的地方,attention 是「调度逻辑」。


五、参数量的会计

我们前面已经多次提到「FFN 占总参数 2/3」。这一节把它算清楚。

5.1 单层参数

把一个 Transformer block 的可训练参数列出来(忽略 LN 和偏置,它们参数量极小):

Attention 子层

FFN 子层

单 block 合计:12 d²。其中 attention 占 4/12 = 33%,FFN 占 8/12 = 67%。

5.2 整个模型

把 block 重复 n_layers 次,再加 embedding:

总参数 ≈ n_layers × 12 d² + V × d

其中 V 是词表大小,V × d 是 token embedding 的参数量(output 的 LM head 通常和 input embedding 共享权重)。

代入几个具体模型验算:

Transformer base (2017):n_layers=6, d=512, V=37000 - block:6 × 12 × 512² = 18.9M - embedding:37000 × 512 = 18.9M - 合计:≈ 37.8M(论文公开值 65M——差额来自 encoder + decoder 各 6 层、cross-attention、各种偏置)

GPT-2 small:n_layers=12, d=768, V=50257 - block:12 × 12 × 768² = 85M - embedding:50257 × 768 = 38.6M - 合计:≈ 124M(与公开值 124M 完全吻合)

LLaMA-7B:n_layers=32, d=4096, V=32000,但用 SwiGLU(d_ff=11008,3 个矩阵) - attention:32 × 4 × 4096² = 2.1B - FFN:32 × 3 × 4096 × 11008 = 4.3B - embedding:32000 × 4096 = 0.13B - 合计:≈ 6.6B(公开值 6.7B,差额来自 RMSNorm 等少量参数)

注意 LLaMA 因为用 SwiGLU,FFN 参数比例更夸张:4.3 / 6.6 ≈ 65%——和原版相当,但绝对值大得多。

参数饼图

图里把单 block 参数分布画成饼图:FFN 那一块是 attention 的两倍。

5.3 这意味着什么

第一,模型瘦身的主要目标是 FFN

剪枝(pruning)、低秩分解(low-rank factorization)、量化(quantization)、MoE,绝大多数是冲着 FFN 去的,因为它「肉最多」。

第二,FFN 也是显存大头

训练时,激活值需要保存在显存里以便反传。FFN 的激活是 [B, T, 4d],比 attention 的 [B, T, d] 大 4 倍。所以 gradient checkpointing(重计算)经常优先重算 FFN。

第三,推理时 FFN 不能「缓存」

KV cache 缓存的是 attention 的 K 和 V——它们随历史生成而累积,下一步只需要追加新 token 的 K/V,旧的复用。

但 FFN 是逐位置的,每生成一个新 token,都要把它过一遍完整的 FFN。所以 decode 阶段每步的计算量主要由 FFN 决定,这也是为什么 MoE 在 decode 时格外有意义——它把 FFN 的「每步必算」成本砍下来了。


六、现代变体:GLU、SwiGLU、GeGLU

6.1 ReLU 的弱点

原版 FFN 用 ReLU,公式干净、计算便宜。但它有一些工程上的麻烦:

针对这些问题,2016 年前后出现了几个替代品:GELU(Gaussian Error Linear Unit)、Swish(也叫 SiLU)、Mish 等。它们都是「平滑版的 ReLU」,在大模型上常见到。

GELU 的形式(精确版):

GELU(x) = x · Φ(x)

其中 Φ 是标准正态的累积分布函数。直观上,它把 ReLU 的「0/1 硬开关」换成了「按 x 的标准正态尾概率加权」。BERT、GPT-2、GPT-3 都用 GELU。

Swish(SiLU):

Swish(x) = x · sigmoid(x)

形式更简单,性质和 GELU 几乎一样,LLaMA 系列用的就是这个。

但更大的变化是引入「门控」结构。

6.2 GLU:门控线性单元

GLU(Gated Linear Unit)由 Dauphin 等人在 2017 年的 Language Modeling with Gated Convolutional Networks 中提出。后来 Shazeer 在 2020 年的短论文 GLU Variants Improve Transformer 中专门做了在 Transformer FFN 上的对照。

GLU 把 FFN 第一层从「一个矩阵乘 + 非线性」改成「两个矩阵乘相乘」:

FFN_GLU(x) = (x W₁ ⊙ σ(x V)) W₂

其中:

直觉上:x W₁ 是「候选信息」,σ(x V) 是「这个信息要打开多少」。两者相乘,得到「按门控加权的信息」。

这比 ReLU 灵活——它可以学到「打开 30%」「打开 80%」这种连续的门控,而不是「打开 / 不打开」二选一。

6.3 SwiGLU:把 sigmoid 换成 Swish

Shazeer 的论文里给出了一组变体:

实测上 SwiGLU 和 GeGLU 表现最好,比 ReLU FFN 在同等参数量下 PPL 略低。LLaMA、PaLM、Qwen、DeepSeek、Mistral 等主流大模型基本都选了 SwiGLU。

SwiGLU 公式:

FFN_SwiGLU(x) = (Swish(x W_gate) ⊙ x W_up) W_down

注意这里有三个矩阵:W_gate、W_up、W_down(在 LLaMA 实现里就是这个命名)。

6.4 为什么 SwiGLU 的 d_ff 是 8d/3 而不是 4d

这是个常被问到、答得到位的人不多的问题。

ReLU FFN 的参数量是 d × 4d + 4d × d = 8 d²

SwiGLU 有三个矩阵,每个是 d × d_ff(W_gate 和 W_up)或 d_ff × d(W_down),合计 3 × d × d_ff

为了让 SwiGLU 和 ReLU FFN「同等参数量」做对比,需要 3 × d × d_ff = 8 d²,解得:

d_ff = 8d / 3 ≈ 2.667 d

所以 LLaMA-7B 的 d=4096,d_ff = 4096 × 8/3 ≈ 10923——实际工程取了 11008(向上取到 256 的整数倍,便于 GPU 算子对齐)。

如果你在论文或博客里看到「LLaMA 的 FFN 比 4 倍小」,那是因为没意识到 SwiGLU 多了一个矩阵——参数总量是没变的。

6.5 SwiGLU 真的更好吗

Shazeer 的原文里给了几条小实验,结论是 SwiGLU 在多个任务上比 ReLU FFN 好一点点(PPL 低 0.01-0.1 量级)。后来大模型的实测也支持这个结论。

但 Shazeer 自己写了一句很诚实的话:「我们不能给出一个清晰的解释,为什么这些变体有效。或许是 divine benevolence。」(divine benevolence = 神的眷顾,他原话开玩笑)

也就是说,现在没有一个清楚的理论解释 SwiGLU 为什么更好——只是工程上稳定地有效。这种「不知道为什么但有效」在深度学习里很常见。

SwiGLU vs ReLU

图里把三种变体的公式、参数量、d_ff 取值放在一起对比。要点是:同等参数量下,SwiGLU 比 ReLU FFN 好;但代价是多一个矩阵(实现复杂、算子要适配)


七、MoE:把 FFN 切成多个专家

混合专家(Mixture of Experts,MoE)整整一篇值得讲(44 篇会详讲),这里只点出它和 FFN 的关系。

7.1 单个 FFN → 多个专家 + 路由

标准 FFN:

y = FFN(x)    # 单一参数共享给所有 token

MoE FFN:

gate_scores = router(x)             # 给每个专家打分
top_k_experts = top_k(gate_scores)  # 选出 k 个(通常 k=2)
y = Σ_{i in top_k} gate_scores[i] · FFN_i(x)

也就是说,MoE 用一个轻量的「路由网络」给每个 token 选 2 个(或 k 个)专家,只让这 2 个专家算 FFN,其它的不算。

如果有 8 个专家、k=2,那么 8 个专家的总参数量是单 FFN 的 8 倍,但每次推理只用其中 2/8 = 1/4 的计算(再加一点路由开销)。模型容量翻倍但计算不翻倍——这是 MoE 的核心卖点。

7.2 为什么 MoE 选 FFN 而不是 attention

理论上你可以做 MoE-attention(确实有论文尝试过),但实际几乎所有 MoE 工作都只对 FFN 做切分。原因:

7.3 现代 MoE 模型

这些细节 44 篇会展开。这里只要记住:MoE = FFN 的稀疏化版本。它没改 attention,只改 FFN。


八、可解释性:单个神经元在做什么

这一节把前面「键值记忆」的视角再往深推一层,进入可解释性研究的领域。

8.1 神经元可视化的早期工作

2018 年 Karpathy 的博客 The Unreasonable Effectiveness of Recurrent Neural Networks 里展示过:训练一个 LSTM 字符级语言模型,会有一个神经元专门追踪「我是不是在引号里」、另一个追踪「我是不是在 if 语句的条件部分」。这种「单个神经元学到一个具体功能」的现象,在 RNN 上就有人观察到了。

到了 Transformer 时代,OpenAI 在 2019 年的 GPT-2 微观分析(早期博客)和 Anthropic 后来的一系列工作(Olah、Elhage 等)把这件事推到了系统化研究。

8.2 Anthropic 的多 polysemantic 神经元

但实际研究中,很快发现一个问题:很多神经元是「多义的」(polysemantic)——你看它在某些样本上激活,会同时响应几个不相关的概念。

比如在一个 GPT-2 模型里,你可能找到一个神经元,它同时对「关于猫的句子」「金融术语」「动词过去时」都强烈激活。

这违反了「一个神经元对应一个概念」的简单假设。

Anthropic 在 Toy Models of Superposition(Elhage et al., 2022)中给出了一个解释:当模型要表示的特征数量超过维度数时,它会把多个特征「叠加」到同一个方向上——不是 d_ff 个特征用 d_ff 个独立方向,而是用稀疏结构(只有少数特征同时激活),把更多特征压缩到 d_ff 维空间里。

这种「叠加」(superposition)让单个神经元变成多义的,但也让模型容量变大——它实际能编码的特征数远超 d_ff。

8.3 字典学习与稀疏特征

Anthropic 后来的 Towards Monosemanticity(Bricken et al., 2023)和 Scaling Monosemanticity(Templeton et al., 2024)用稀疏自编码器(Sparse Autoencoder, SAE)从叠加表示中「解出」单义特征。

做法大致是:训练一个超宽的自编码器(比如 d=512 解到 32k 或 256k 维),强制激活稀疏。每个解出的特征更接近「一个清晰的概念」。

在 Claude 3 Sonnet 上的实验里,他们解出了几百万个单义特征,包括「金门大桥」「Python 列表推导式」「关于死亡的诗歌」等具体得吓人的概念。

这些研究的结论是:FFN 内部确实在做某种「特征字典」的工作,但字典条目和神经元不是一一对应——神经元是叠加表示,需要专门的工具才能解出

8.4 这对工程意味着什么

可解释性目前还没有直接落到工程实践(除了 ROME 这类编辑工作)。但有几个值得关注的方向:


九、Dropout、初始化与训练细节

9.1 dropout 放在哪里

原论文在两个位置加了 dropout(rate=0.1):

  1. 每个子层的输出(在加到残差之前);
  2. embedding + 位置编码之后。

具体到 FFN:

h = ReLU(x W₁ + b₁)
h = dropout(h)        # 注:这一步在很多实现里有
y = h W₂ + b₂
y = dropout(y)        # 子层输出 dropout
return x + y

不同实现的 dropout 位置略有差异——有的在 ReLU 之后再 dropout,有的不。fairseq、Tensor2Tensor、HuggingFace 各有微小差异,但总体效果相近。

现代大模型(GPT-3、LLaMA)已经基本不用 dropout 了——因为数据足够大,过拟合不再是主要矛盾。

9.2 初始化

Xavier/Glorot 初始化是默认选择:W 用 N(0, 2/(fan_in + fan_out)) 或类似分布。但 W₂ 因为是输出回 d,常常额外乘一个 1/√(2 n_layers) 的缩放因子(来自 GPT-2 / LLaMA 的实现),目的是让深层残差累积时方差不爆炸。

9.3 训练时 FFN 的常见踩坑

坑 1:bf16 下的数值不稳

FFN 的 4d 中间激活在 ReLU 后某些值会非常大(极端值),bf16 表示范围窄,可能溢出或下溢。LLaMA 的训练日志里能看到「ffn_norm」这种额外的 LN 就是为了抑制 FFN 的激活范围。

坑 2:SwiGLU 的实现错误

SwiGLU 公式是 Swish(x W_gate) ⊙ x W_up,注意 Swish 只作用于 W_gate 那一支。一个常见 bug 是把 Swish 错放到 W_up 上、或者两支都过 Swish——结果模型能训但效果略差。

坑 3:在量化时 FFN 是瓶颈

INT8 / INT4 量化通常先量化 attention,最后才动 FFN。FFN 的 W₁ 和 W₂ 都是大矩阵,权重分布有重尾,量化误差容易累积。一些方案(GPTQ、AWQ)专门针对 FFN 做了校准。


十、推理时的工程考量

FFN 在推理阶段的特性,和训练时有些不一样,值得单独提。

10.1 不能缓存

KV cache 缓存的是 attention 的 K/V——它们一旦算出来,对未来步骤永远有效,可以累积存储,下一步只追加新 token 的 K/V、复用历史。

FFN 不一样。每生成一个新 token:

  1. 取这个 token 的隐状态(d 维);
  2. 过 W₁ → 4d;
  3. 过激活;
  4. 过 W₂ → d。

历史 token 的 FFN 计算结果对当前生成完全没用——它们已经写进了那些 token 的隐状态里,也已经传递过 attention,下一步 decode 不再需要它们的 FFN 输出。

所以 decode 时每一步都要完整跑一遍 FFN,没有 cache 可以复用。

10.2 decode 是带宽瓶颈

decode 阶段(生成新 token),每步只处理 1 个 token,但要把 W₁ 和 W₂ 这两个矩阵从 HBM 读到 SRAM。这两个矩阵很大(每个 4d²,加起来 8d²),比单 token 的计算量大得多。

也就是说,decode 阶段 FFN 是带宽瓶颈,不是算力瓶颈

这个观察对推理优化非常关键:

具体优化策略 50 篇之后再展开。

10.3 prefill 阶段相反

prefill 阶段(处理 prompt),一次性给一长串 token(比如 1k 个)。这时 FFN 的计算量是 1k × 8d²,已经足够大,算力变成瓶颈而不是带宽——因为权重只读一次,被 1k 个 token 共享。

所以 prefill 和 decode 在 FFN 上的瓶颈完全不同:prefill 看 GPU 的 FLOPS,decode 看 HBM 带宽。一个推理服务的延迟分布往往两者都要分别建模。


十一、量化时为什么 FFN 难处理

前面提了几次,这一节展开说。

11.1 激活的厚尾问题

INT8 量化的本质是把一个浮点张量映射到 256 个量化级。如果张量分布是 N(0, 1) 这种钟形,256 个级别覆盖均匀、误差很小。

但 FFN 第一层的输出(即 4d 维的中间激活)有几个特点:

  1. ReLU 之后,所有负值变 0,正值保留——分布是「半正态」加一堆零;
  2. 训练后期,少数 k_i 的方向被反复激活,对应的 a_i 数值特别大——形成「重尾」(heavy-tail);
  3. 不同 batch、不同 token 之间,重尾的位置可能不同——很难找到一个全局缩放因子。

如果你用一个简单的 max scaling 把 [-127, 127] 映射回浮点:

scale = max(|x|) / 127
x_int8 = round(x / scale)

那个 max 可能是 4d 维里某一个极端激活,导致大多数其它值在量化时只占用了几个量化级——精度损失很大。

11.2 解决方案

SmoothQuant(Xiao et al., 2022):把激活的难度部分转移到权重上。如果激活的某个通道有重尾,让权重在这个通道上变小,激活变大,乘积不变——但激活的分布变得更平。

GPTQ(Frantar et al., 2022):对权重做逐列量化,保持低误差。

AWQ(Lin et al., 2023):识别「关键权重通道」(对应那些激活重尾的通道),保留它们高精度。

FP8 训练(H100 之后):用 FP8 而不是 INT8——表示范围更宽,重尾问题轻一些。

这些细节 48-50 篇会展开。这里只要记住:FFN 在量化里特别难,因为它激活有重尾、第一层 ReLU 后分布偏斜


十二、一些看起来无关、其实很关键的细节

12.1 偏置 b₂ 经常被去掉

很多现代实现把 FFN 第二层的偏置 b₂ 去掉了:

self.w1 = nn.Linear(d, 4*d, bias=True)
self.w2 = nn.Linear(4*d, d, bias=False)  # 注意 bias=False

理由是后面紧跟着残差连接和 LN,b₂ 的作用会被 LN 的 β 参数吸收。去掉省一点参数,几乎不影响表现。LLaMA 系列把 W₁、W₂、W_gate、W_up 全部都去掉了 bias。

12.2 d_ff 取整对齐

为了 GPU kernel 友好,d_ff 通常向上对齐到某个倍数(128、256、甚至 512)。例如 LLaMA-7B 的 d=4096,理论 d_ff=8d/3=10923,实际取 11008(=43 × 256)。这种对齐对训练速度有显著影响,对模型性能几乎无影响。

12.3 FFN 的「数据吞吐」相对低

Chinchilla 的 scaling law 论文里提到一个细节:增加深度(n_layers)和增加宽度(d、d_ff)对模型表现的贡献有微妙差异。FFN 主要由 d 决定,但「足够深」也很关键——3-4 层的 Transformer 即使每层 FFN 很宽也学不动复杂任务。

12.4 共享 FFN 的尝试

有几篇论文尝试让多层共享同一组 FFN 参数(ALBERT 那一类)。结果:参数量降下来了,但表现也降下来。说明 FFN 在不同层学的东西确实不同——浅层 FFN 处理表层模式,深层 FFN 处理高级概念,硬强制它们用同一组参数会丢信息。

12.5 FFN 是多语言能力的载体之一

有研究发现,多语言模型(mBERT、XLM-R)的「语言识别」能力主要分布在 FFN 中——不同 FFN 神经元对不同语言敏感。这与「FFN 是关联记忆」的视角一致:每种语言的常用模式被存到了不同的 k_i 里。


十三、PyTorch 里的最小实现走查

我们已经从原理、变体、参数、可解释性的角度把 FFN 讲了一遍。这一节落到代码——把 FFN 写成 PyTorch 模块,逐行说明每一步在做什么。这个练习的价值是:你能验证前面所有定性叙述在数值上确实成立。

13.1 原版 ReLU FFN

最直白的实现,对应原论文:

import torch
import torch.nn as nn

class FFN(nn.Module):
    def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1):
        super().__init__()
        self.w1 = nn.Linear(d_model, d_ff, bias=True)
        self.w2 = nn.Linear(d_ff, d_model, bias=True)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # x: [B, T, d_model]
        h = self.w1(x)              # [B, T, d_ff]
        h = torch.relu(h)           # [B, T, d_ff]
        h = self.dropout(h)         # 中间 dropout(部分实现没有这行)
        y = self.w2(h)              # [B, T, d_model]
        y = self.dropout(y)         # 子层输出 dropout
        return y

读这段代码时,要注意几个看起来微小、其实重要的点。

第一,nn.Linear 的输入张量可以有任意前缀维度——它只在最后一维做矩阵乘。所以你不需要把 [B, T, d] 显式 reshape 成 [B*T, d] 再做矩阵乘,PyTorch 自动处理。这从工程上印证了「FFN 是 position-wise」——所有前缀维度都被当作独立样本。

第二,torch.relu 是一个完全没有参数的算子,逐元素操作。它对反向传播的影响是「输入小于 0 的位置,梯度直接置零」——这就是 dead ReLU 的来源。

第三,dropout 在两处都加了。如果你训练大模型,通常会把第一处去掉,只保留子层输出 dropout;如果做 fine-tune 大模型,dropout 可能整体设为 0。

第四,bias=True 是 PyTorch 默认值。LLaMA 系列把它改成 bias=False——前面提过,因为后面 LN 的 β 已经吸收了偏置作用。

13.2 SwiGLU 实现

class SwiGLU(nn.Module):
    def __init__(self, d_model: int, d_ff: int):
        super().__init__()
        # d_ff 通常取 8*d_model/3 向上对齐到某个倍数
        self.w_gate = nn.Linear(d_model, d_ff, bias=False)
        self.w_up   = nn.Linear(d_model, d_ff, bias=False)
        self.w_down = nn.Linear(d_ff, d_model, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        gate = torch.nn.functional.silu(self.w_gate(x))   # Swish/SiLU
        up   = self.w_up(x)
        h    = gate * up                                  # 逐元素门控
        return self.w_down(h)

注意点:

13.3 用一个具体输入跑一遍

把 d_model = 4、d_ff = 16,做一次小尺度的前向,看看每一步的形状和数值:

torch.manual_seed(42)
ffn = FFN(d_model=4, d_ff=16, dropout=0.0)
x = torch.randn(1, 3, 4)        # batch=1, T=3, d=4
print("x:", x.shape, x)
print("w1:", ffn.w1.weight.shape)  # [16, 4],注意 PyTorch Linear 是 [out, in]
print("w2:", ffn.w2.weight.shape)  # [4, 16]

h_pre = ffn.w1(x)
print("h_pre (before ReLU):", h_pre.shape)  # [1, 3, 16]
h = torch.relu(h_pre)
print("h (after ReLU):", h.shape, "zeros:", (h == 0).float().mean().item())
y = ffn.w2(h)
print("y:", y.shape)              # [1, 3, 4]

如果把 (h == 0) 那个比例打出来,会发现大概一半的 h_pre 元素被 ReLU 砍掉——这是 ReLU 在 N(0, σ²) 输入下的预期(精确说是 50% 概率,因为对称分布的负半部分被丢)。

这个观察其实很关键:FFN 第一层有大约一半的「容量」在每个样本上是浪费的——被 ReLU 直接归零、不参与第二层的加权和。换句话说,FFN 的有效隐藏维度大约是 d_ff / 2 = 2d,这部分解释了为什么 SwiGLU 用 8d/3 的窄一点的 d_ff 也能匹配 4d 的 ReLU FFN——SwiGLU 的门控不会硬归零,「有效维度」更接近 d_ff 本身。

13.4 验证「逐位置」性质

写一个小实验:构造两个不同的 batch、不同的位置,但中间某一个位置的 x 值完全相同,看 FFN 输出是不是相同。

ffn.eval()
x_a = torch.randn(1, 3, 4)
x_b = x_a.clone()
x_b[0, 0, :] = torch.randn(4)   # 把第 0 个位置改了

with torch.no_grad():
    y_a = ffn(x_a)
    y_b = ffn(x_b)

# 第 1、2 个位置的输入相同 → 输出应该相同
diff_pos1 = (y_a[0, 1, :] - y_b[0, 1, :]).abs().max()
diff_pos2 = (y_a[0, 2, :] - y_b[0, 2, :]).abs().max()
diff_pos0 = (y_a[0, 0, :] - y_b[0, 0, :]).abs().max()

print("diff at pos 1:", diff_pos1.item())   # 应该是 0
print("diff at pos 2:", diff_pos2.item())   # 应该是 0
print("diff at pos 0:", diff_pos0.item())   # 应该 > 0

跑一遍会发现 pos 1、pos 2 的输出完全相同(只受自己位置的输入影响),pos 0 因为输入变了所以不同。这就是「FFN 是 position-wise」的可执行验证。

如果把它换成 attention,结论就不对——attention 子层的输出在每个位置都依赖所有位置,改任何一个位置的输入,所有位置的输出都会变。


十四、FFN 与 attention 的相互作用

把 FFN 单独讲完之后,回过头看它和 attention 的关系,能得到一些更细的洞察。

14.1 「先通信、再计算」的隐喻

最常见的隐喻是:attention 是「token 之间通信」,FFN 是「token 内部计算」。这个隐喻很好用,但需要小心一点——它给人一个错误印象,好像两者完全独立、可以拆开看。

实际上,attention 和 FFN 是紧密耦合的:

所以严格说,每一层 attention 和 FFN 形成一个「混合 → 加工 → 混合 → 加工 → …」的链条。多层下来,每个 token 的隐状态既反映上下文信息,也反映多次非线性变换的结果。

14.2 「事实回忆」需要两者配合

举一个具体例子。模型要预测 “The capital of France is _” 后面的词。

理想情况下,模型应该回忆起「France 的首都是 Paris」这条事实。这件事是怎么在网络里发生的?

按 Geva 等人的研究,大致是这样:

  1. 早期层:attention 把 “France” 这个 token 的信息搬到位置 5(也就是 “is” 后面这个待生成位置)周围。具体说,attention 头会让位置 5 的 Q 与「France」的 K 强匹配,从而把 France 的 V 加到位置 5 的隐状态里。

  2. 中后期层:位置 5 的隐状态现在已经携带了「country=France」「question_type=capital」这种语义。FFN 的 k_i(W₁ 的列向量)里,有一个或几个专门响应「the capital of 」这种模式——它的 k 与位置 5 的隐状态内积很高,对应的 v 在 vocab 投影后倾向 Paris、Lyon、Marseille 这些法国城市的方向。

  3. 最后一层:经过几次 attention + FFN 加工,位置 5 的隐状态在 unembedding 矩阵投影后,Paris 的 logit 最高。

也就是说,「事实回忆」是 attention 和 FFN 联合完成的——attention 负责把相关上下文搬到当前位置,FFN 负责在当前位置做 lookup。少了任何一个,事实都回忆不出来。

ROME(Meng et al., 2022)做模型编辑,就是利用这个机制:他们定位「the capital of France」对应的 FFN 神经元,然后修改它们的 v_i,让这个事实从 “Paris” 变成 “Beijing”——之后模型在回答这个问题时确实会输出错误答案。这反向验证了「FFN 存事实」的视角。

14.3 attention 和 FFN 谁更「重要」

这是一个常被问、其实没标准答案的问题。

从消融实验看,把 attention 换成简单的卷积或 MLP-Mixer,模型表现会下降但不会完全垮——说明 attention 不是不可替代的。把 FFN 换成更窄的版本(比如 d_ff=d 而不是 4d),表现也会下降但能用——说明 FFN 也不是不可替代的。

两者一起去掉,模型基本学不动。这说明 attention 和 FFN 是互补的,不是冗余的。

「重要性」这个问题更应该问:「在我的具体任务上,性能瓶颈在哪边?


十五、把 FFN 全部讲完之后再回头看一眼

我们花了非常长的篇幅讲一个表面上「两行代码就写完」的子层。这是值得的,因为 FFN 在 Transformer 里被严重低估了。

让我们用三句话做一个总结:

第一,FFN 是 Transformer 的「容量大头」——它占总参数的三分之二,模型能力的大部分都来自这里。剪枝、量化、MoE 的目标都是它。

第二,FFN 是模型存储「学到的内容」的地方——不是每个神经元一个事实,但通过叠加表示,它确实是知识的物理载体。attention 是调度器,FFN 是仓库。

第三,FFN 是 Transformer 唯一一处「位置无关」的子层——它逐位置独立处理,不混 token、不看上下文。这种性质让它工程友好、容易并行,但也让它在长上下文优化里显得格外突出(成本随 token 数线性增长,没法 cache)。

下一篇我们要回到原论文本身——讲清楚「Vaswani 他们到底是怎么把这个 Transformer 训出来的」:8 张 P100、12 小时、warmup 4000 步、label smoothing 0.1,所有这些细节当年是怎么调出来的。


十六、关键概念回顾(散文式)

回过头看这一篇,我们其实做了三件事。

第一件,是把 FFN 这个看似简单的子层「打开」:它是两层 MLP,先升维到 4d、过非线性、再压回 d。我们解释了为什么是 2 层、为什么是 4 倍、为什么逐位置——这三个设计选择都有原论文的实证依据,而且后来主流模型基本沿用没怎么改(除了把 4 倍改成更精巧的 SwiGLU 8/3 倍,那也只是因为多了一个门控矩阵,参数量其实一致)。

第二件,是引入「键值记忆」这个新视角:把 W₁ 的列看成 keys,W₂ 的行看成 values,FFN 就变成了「带固定 K/V 的 attention」——用输入向量与每个 key 算内积、ReLU 过一下作为权重、再加权求 values。这个视角的好处是把 FFN 和 attention 放到同一个数学框架下,让我们能用「检索接口 + 关联记忆」这套语言描述整个 Transformer。这也是 ROME、模型编辑、可解释性研究背后的理论基础。

第三件,是把 FFN 在工程上的影响讲透:它占参数 2/3、它在量化里最难、它在 decode 时是带宽瓶颈、它没法 cache、它是 MoE 的对象、它的激活有厚尾、它和 LN/dropout/初始化都密切相关。这些工程细节决定了你做 LLM 推理或微调时,绝大多数优化工作其实都在 FFN 这一块。

如果只让你记住一句话,那是:「Attention 让 token 之间通信,FFN 让 token 内部计算;attention 是接口,FFN 是仓库。


十七、常见误解

下面几条是我读论文、跟人讨论时反复见到的误解,逐一指出来。

误解一:FFN 只是辅助子层,attention 才是核心。

错。从参数量看 FFN 是 attention 的两倍;从「模型存了什么知识」看 FFN 是事实和模式的载体;从 decode 推理成本看 FFN 是大头。它不是辅助,它是核心。

误解二:4 倍扩张比是某个理论推出来的。

错。它是 2017 年原论文消融出来的经验值,2 倍不够、8 倍边际收益小,4 倍是甜点。后来所有 ReLU FFN 都沿用,但这是「实证习惯」而不是「数学定理」。

误解三:SwiGLU 的 d_ff = 8d/3 比原版 4d 窄、参数量更少。

错。SwiGLU 多了一个门控矩阵,三个矩阵合计参数量正好等于 ReLU FFN 的两个矩阵——8/3 这个数字就是为了让总参数对齐。SwiGLU 不是更省参数,是用同等参数换更好性能。

误解四:FFN 的每个神经元对应一个具体概念。

只对一半。早期可视化工作确实发现一些「单义」神经元,但 Anthropic 的 superposition 假设和后续 SAE 工作表明,多数神经元是「多义」的——一个神经元同时编码多个不相关概念,需要专门工具(稀疏自编码器)才能解出真正的单义特征。

误解五:MoE 是把 attention 切成多个专家。

错。MoE 几乎全部是把 FFN 切成多个专家,attention 不动。这是因为 FFN 占参数大头、逐位置易切分、键值记忆视角自然支持「多个子记忆库」。


十八、下一步

下一篇 27|训练原论文 Transformer 把视角从「结构」切换到「训练过程」。我们会讲:

再往后:


十九、参考文献

下面按相关度排序,列出本篇直接引用与延伸阅读,每条附一句话提示其在本篇中的角色。

  1. Vaswani, A. et al. “Attention Is All You Need.” NeurIPS 2017. FFN 原始定义、d_ff = 4d 的消融。
  2. Geva, M., Schuster, R., Berant, J., Levy, O. “Transformer Feed-Forward Layers Are Key-Value Memories.” EMNLP 2021. 「键值记忆」视角的开山论文。
  3. Geva, M. et al. “Dissecting Recall of Factual Associations in Auto-Regressive Language Models.” EMNLP 2023. 把事实回忆定位到具体 FFN 神经元。
  4. Meng, K. et al. “Locating and Editing Factual Associations in GPT (ROME).” NeurIPS 2022. 模型编辑工作,定位并改写 FFN 中的事实。
  5. Meng, K. et al. “Mass-Editing Memory in a Transformer (MEMIT).” ICLR 2023. ROME 的批量化扩展。
  6. Shazeer, N. “GLU Variants Improve Transformer.” arXiv:2002.05202, 2020. SwiGLU、GeGLU 等变体的对照实验。
  7. Dauphin, Y. et al. “Language Modeling with Gated Convolutional Networks.” ICML 2017. GLU 最早提出。
  8. Hendrycks, D., Gimpel, K. “Gaussian Error Linear Units (GELUs).” arXiv:1606.08415, 2016. GELU 激活的来源(BERT/GPT-2 使用)。
  9. Ramachandran, P., Zoph, B., Le, Q. V. “Searching for Activation Functions.” ICLR 2018 Workshop. Swish/SiLU 的来源。
  10. Elhage, N. et al. “Toy Models of Superposition.” Anthropic, 2022. 解释为什么神经元是多义的(叠加假设)。
  11. Bricken, T. et al. “Towards Monosemanticity: Decomposing Language Models With Dictionary Learning.” Anthropic, 2023. 用稀疏自编码器解出单义特征。
  12. Templeton, A. et al. “Scaling Monosemanticity: Extracting Interpretable Features from Claude 3 Sonnet.” Anthropic, 2024. SAE 在大模型上的应用。
  13. Shazeer, N. et al. “Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer.” ICLR 2017. MoE 的早期论文。
  14. Fedus, W., Zoph, B., Shazeer, N. “Switch Transformer: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity.” JMLR 2022. 1.6T 参数的 MoE。
  15. Jiang, A. Q. et al. “Mixtral of Experts.” arXiv:2401.04088, 2024. Mistral 的 MoE 工作。
  16. Touvron, H. et al. “LLaMA: Open and Efficient Foundation Language Models.” arXiv:2302.13971, 2023. SwiGLU 在大模型上的标准实现。
  17. Touvron, H. et al. “Llama 2: Open Foundation and Fine-Tuned Chat Models.” arXiv:2307.09288, 2023.
  18. Chowdhery, A. et al. “PaLM: Scaling Language Modeling with Pathways.” arXiv:2204.02311, 2022. 用 SwiGLU 的旗舰大模型之一。
  19. Brown, T. et al. “Language Models are Few-Shot Learners (GPT-3).” NeurIPS 2020. d=12288, d_ff=49152 的 4 倍 FFN 实例。
  20. Devlin, J. et al. “BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.” NAACL 2019. d=768, d_ff=3072 的 BERT-base FFN 实例。
  21. Xiao, G. et al. “SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models.” ICML 2023. 处理 FFN 激活重尾的量化方案。
  22. Frantar, E. et al. “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers.” ICLR 2023. 权重量化方案。
  23. Lin, J. et al. “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration.” MLSys 2024. 关键通道保留的量化方案。
  24. Lan, Z. et al. “ALBERT: A Lite BERT for Self-supervised Learning of Language Representations.” ICLR 2020. 跨层共享 FFN 参数的尝试。
  25. Karpathy, A. “The Unreasonable Effectiveness of Recurrent Neural Networks.” Blog, 2015. 神经元功能可视化的早期工作。

← 上一篇:25|Layer Normalization | 下一篇:27|训练原论文 Transformer

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .