Encoder 详解:6 层堆叠到底在做什么

很多人第一次看 Transformer 图时,会把 encoder 理解成"左半边那 6 层",然后继续把主要注意力放到 decoder、mask、生成、采样上。这样理解不算错,但会漏掉一件很重要的事:encoder 是后来整条"理解类模型"路线的起点。BERT、RoBERTa、DeBERTa、ViT 的 backbone,本质上都在回答同一个问题:如果我不需要一边读一边写,只想把输入序列编码成尽可能好的上下文表示,该怎么堆 Transformer?

这一篇就只做这件事:把 encoder 单独拿出来讲透。重点不是重复第 20 篇那张总图,而是回答四个更具体的问题:

  1. 一个 encoder layer 到底由哪些步骤组成?
  2. 既然每层 self-attention 一次就能"看全局",为什么还要堆 6 层甚至 24 层?
  3. encoder 的输出是什么性质的表示,为什么它特别适合理解任务?
  4. 为什么 2018 年之后,BERT 这条 encoder-only 路线会在 NLP 里独立长成一整棵树?

原文链接

一、先把 encoder 的骨架钉死

Transformer 原论文里的 encoder 很简单:同一个 block 重复 6 次。每一层都有两块子层:

  1. Multi-Head Self-Attention
  2. Position-wise Feed-Forward Network

两块子层外面各包一层残差连接和 LayerNorm。原论文是 post-LN 写法,现代实现多半改成 pre-LN,但主骨架没变。

把一层写成公式,原论文的 post-LN 版本是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> h ′ = LayerNorm ⁡ ( x + MHA ⁡ ( x ) ) y = LayerNorm ⁡ ( h ′ + FFN ⁡ ( h ′ ) ) \begin{aligned} h' &= \operatorname{LayerNorm}(x + \operatorname{MHA}(x)) \\ y &= \operatorname{LayerNorm}(h' + \operatorname{FFN}(h')) \end{aligned} </math>h′y=LayerNorm(x+MHA(x))=LayerNorm(h′+FFN(h′))

现代更常见的 pre-LN 写法是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> h ′ = x + MHA ⁡ ( LayerNorm ⁡ ( x ) ) y = h ′ + FFN ⁡ ( LayerNorm ⁡ ( h ′ ) ) \begin{aligned} h' &= x + \operatorname{MHA}(\operatorname{LayerNorm}(x)) \\ y &= h' + \operatorname{FFN}(\operatorname{LayerNorm}(h')) \end{aligned} </math>h′y=x+MHA(LayerNorm(x))=h′+FFN(LayerNorm(h′))

如果你把公式里的细节暂时拿掉,encoder 每层只是在做一件很朴素的事:先让每个 token 看完整个序列,再对这个已经混过上下文的表示做一次逐位置的非线性加工。前者负责"沟通",后者负责"计算"。

1.1 输入和输出的形状没有变

这是理解 encoder 的第一条基本事实。

设输入序列长度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n,隐藏维度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> d model d_{\text{model}} </math>dmodel,那么一层 encoder 的输入、输出形状始终都是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> X ∈ R n × d model X \in \mathbb{R}^{n \times d_{\text{model}}} </math>X∈Rn×dmodel

不管中间 attention 拆成多少头、FFN 扩张到 <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 d 4d </math>4d 还是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 8 d / 3 8d/3 </math>8d/3,最后都会回到同一个形状。也就是说,encoder layer 不是在改变张量外形,而是在反复重写同一组 token 表示

这件事很关键。RNN 的"深"有两种:时间方向的深、层数方向的深。CNN 的"深"对应感受野扩大。Transformer encoder 的深度更像"迭代 refinement":同一批 token 表示,每过一层就被重写得更上下文化一点。

1.2 没有 causal mask,只有双向上下文

encoder 的 self-attention 和 decoder 最大的区别,不在公式本身,而在约束条件。

encoder 里没有 causal mask,所以位置 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i 的 query 可以看见所有位置的 key,包括左边和右边。对一个句子里的 token 来说,它从第一层开始就是双向的

这意味着:

  • "bank" 这个词在 "river bank" 和 "investment bank" 里,会从左右文同时拿信息;
  • 一个 pronoun 要解析指代时,不必等到后面几层才"看到未来",第一层就能看全句;
  • 序列里的每个位置,从结构上都不是局部状态,而是"面向全句的候选表示"。

这正是 encoder 特别适合理解任务的第一层原因:它不是为了预测下一个 token,而是为了把当前这个 token 在整个句子里的含义尽快编码出来。


二、一层里到底发生了什么

把 encoder 的一层拆开,最好的办法不是背公式,而是跟一个 token 走一遍。

假设输入句子是:

text 复制代码
The animal didn't cross the street because it was too tired.

我们关心其中的 it。在词嵌入阶段,it 只是一个通用代词向量;加上位置编码后,它知道自己在句子里第几个位置;但它还不知道自己指的是 animal 还是 street

进入第一层 self-attention 之后,it 的 query 会去和全句每个位置的 key 做匹配。模型可能学到:

  • itanimal 的相似度高;
  • crosstired 这些谓词周围的 token 也有帮助;
  • street 不是这个语义角色里最相关的对象。

经过 softmax 加权求和之后,it 对应的输出向量已经不再只是"代词 it",而是"在这句话里大概率指向 animal 的那个代词"。这就是上下文化表示

2.1 Self-attention 负责跨位置信息流动

这部分我们前面几篇已经推过公式,这里只强调 encoder 语境下最重要的直觉:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> Attention ⁡ ( Q , K , V ) = softmax ⁡ ( Q K ⊤ d k ) V \operatorname{Attention}(Q, K, V) = \operatorname{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V </math>Attention(Q,K,V)=softmax(dk QK⊤)V

在 encoder 里, <math xmlns="http://www.w3.org/1998/Math/MathML"> Q Q </math>Q、 <math xmlns="http://www.w3.org/1998/Math/MathML"> K K </math>K、 <math xmlns="http://www.w3.org/1998/Math/MathML"> V V </math>V 都来自同一个输入 <math xmlns="http://www.w3.org/1998/Math/MathML"> X X </math>X,所以它做的是"同一句子内部的全连接交互"。每个位置都可以:

  • 汇总和自己语义相关的位置;
  • 抑制无关位置;
  • 让自己的表示携带整句信息。

如果只看这一块,encoder 像一个高带宽通信层。

2.2 FFN 负责逐位置重写

self-attention 做完后,token 表示已经带上了上下文;但它还只是"线性混合后的结果"。接下来的 FFN 会对每个位置独立做一次非线性变换:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> FFN ⁡ ( x ) = W 2 σ ( W 1 x + b 1 ) + b 2 \operatorname{FFN}(x) = W_2 \sigma(W_1 x + b_1) + b_2 </math>FFN(x)=W2σ(W1x+b1)+b2

它不再看别的位置,但会把当前 token 的隐藏状态投到更高维空间,再压回来。你可以把它理解成一句话:

attention 决定"该从哪里拿信息",FFN 决定"拿回来之后怎么改写当前表示"。

如果 attention 像在群聊里收集消息,FFN 就像每个人回到自己脑子里做一次整理。

2.3 残差和 LayerNorm 让"重写"不是硬覆盖

这一层还有两个常被忽略的保护机制:

  1. 残差:新的变换是加到旧表示上的,不是直接替换。
  2. LayerNorm:每一层输出都被拉回比较稳定的数值范围。

这意味着 encoder 不是每过一层就把旧语义推倒重来,而是在旧表示上不断做小修正。后面我们在残差和 LayerNorm 两篇会把这件事展开。


三、既然一层就能看全局,为什么还要堆 6 层

这是理解 encoder 的核心问题。

很多人第一次学 self-attention 时会产生一个自然疑问:既然第一层里每个 token 已经能和全句所有 token 交互,第二层、第三层到底在增加什么?

答案不是"增加感受野"。Transformer 从第一层起感受野就是全局的,后面增加的是表示的抽象层级与迭代修正能力。

3.1 第一层看到的是"原始词之间的关系"

在第一层里,Q、K、V 来自词嵌入加位置编码。它能学到的,多半是比较浅的模式:

  • 词性关系;
  • 近距离依存;
  • 常见搭配;
  • 指代、主谓、修饰等初级结构。

这已经很有用,但表达还是偏表层。因为输入本身还没有经过多轮上下文化,很多高阶模式还没浮出来。

3.2 更深的层在重写"已经上下文化过的表示"

第二层拿到的输入,不再是裸 token embedding,而是第一层改写后的表示。于是第二层的 attention 看到的是:

  • 哪些位置已经被第一层标成主语、宾语、实体、否定词;
  • 哪些位置已经携带了跨短语的信息;
  • 哪些维度已经隐含了"这是一句问句""这里有对比关系""这是一个条件句"。

这会让第二层学到比第一层更抽象的关系。第三层、第四层再往上,就像反复做"读一遍全文后更新你的理解"。

3.3 深度的作用更像迭代推理

一个很有用的类比是:你第一次读一句复杂句子时,只能大概知道各词的字面意思;第二遍会把主干抓清楚;第三遍才意识到让步、转折、因果这些关系。

encoder 的多层堆叠就像这种"反复读、反复重写"的过程。

这也是为什么很多分析工作会发现:

  • 底层头更偏局部语法和位置模式;
  • 中层更偏句法依赖和实体关系;
  • 高层更偏任务相关语义。

它不是严格分层定律,但作为经验观察相当稳定。

3.4 6 层只是 2017 年的工程平衡点

原论文用 6 层,不代表 6 是什么神奇数字。那只是当时在 WMT 机器翻译任务、P100 算力、base/big 两个尺寸下的一个合理折中。

后来:

  • BERT-base 用 12 层;
  • BERT-large 用 24 层;
  • ViT、DeBERTa、现代 encoder backbone 动不动 24 层起步。

深度能继续带来收益,前提是你有足够数据、合适优化和稳定结构。真正不变的不是"6 层",而是"重复同一种 encoder block,通过深度反复 refinement"这条设计。


四、encoder 输出到底是什么

如果说 decoder 的输出是"下一步生成哪个 token 的分布",那 encoder 的输出更像什么?

答案是:一组上下文化的 token 表示

设最后一层输出是:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> H ( L ) = [ h 1 , h 2 , ... , h n ] H^{(L)} = [h_1, h_2, \dots, h_n] </math>H(L)=[h1,h2,...,hn]

其中每个 <math xmlns="http://www.w3.org/1998/Math/MathML"> h i ∈ R d model h_i \in \mathbb{R}^{d_{\text{model}}} </math>hi∈Rdmodel。这些向量不是概率,不是标签,也不是某个离散结构,而是把"这个 token 在整句里的意义"压缩进来的连续表示。

4.1 它适合做"读完再判断"的任务

因为 encoder 给每个位置都构造了双向上下文表示,所以它天然适合下面这类任务:

  • 句子分类:用 [CLS] 或池化后的整句表示做分类;
  • Token 分类:用每个位置的表示做 NER、POS tagging;
  • 抽取式问答:用起止位置的表示预测 span;
  • 检索/匹配:把句子或文档编码成向量,再做相似度比较;
  • 视觉与多模态编码:把 patch 或其它模态元素编码成统一表示。

这些任务共享一个前提:先把输入整体看完,再输出判断。

4.2 它不擅长直接生成

反过来说,encoder-only 不适合直接做自回归生成。原因很简单:

  1. 它没有 causal mask;
  2. 训练目标通常不是"预测下一个 token";
  3. 输出是表示,不是逐步展开的生成状态。

所以 encoder 特别像一个"读者",而不是"说话者"。

4.3 这也是三大 Transformer 家族分化的根源

下面这张图可以帮助把三条路线一次看清:

架构 看到的信息 更擅长的任务 代表模型
Encoder-only 双向上下文 理解、分类、抽取、检索 BERT、RoBERTa、DeBERTa
Decoder-only 只看左侧历史 生成、补全、对话 GPT、LLaMA、Qwen
Encoder-Decoder 源序列双向 + 目标序列自回归 翻译、摘要、条件生成 Transformer、T5、BART

理解这张表之后,BERT 和 GPT 的分家就很自然了:它们不是"谁比谁更先进",而是把原论文的完整架构沿不同任务目标裁成了两半。


五、为什么 BERT 只保留了 encoder

2018 年 BERT 把 encoder-only 路线推成主流,不是偶然,而是因为它刚好抓住了 encoder 最强的一点:双向表示学习

5.1 机器翻译里的 encoder 只是"中间件"

在原论文里,encoder 的任务是把源句编码成 memory,供 decoder cross-attention 读取。它本身不是最终输出,只是生成链条中的一环。

但如果你的目标不是翻译,而是"读一句话后判断情感""从段落里找答案""识别实体边界",那你并不需要 decoder。你只需要一个足够强的编码器,把输入读透即可。

5.2 双向语境对理解任务太重要了

以情感分类为例,句子:

text 复制代码
The movie is not good.

如果只从左到右读,到 good 这个位置时,模型要额外记住前面的 not 才能得出正确语义;而双向 encoder 会让 good 从一开始就看见 not。类似地,在问答、指代、实体边界这些任务里,右侧上下文往往直接决定当前 token 的角色。

这就是 BERT 用 masked language modeling 而不是纯 left-to-right LM 的深层动机:它要把 encoder 的双向特性保留下来。

5.3 现代多模态 backbone 也延续了这条思想

ViT 把图像 patch 当 token 丢进 encoder;CLIP 的图像编码器和文本编码器本质也是 encoder backbone;很多语音识别和时间序列建模工作,也会先用 encoder 做统一表征,再接任务头。

所以 encoder 不是"Transformer 里被 GPT 时代淘汰的左半边"。更准确地说,它是"不负责说话,但极其擅长读懂输入"的那半边。


六、Post-LN 与 Pre-LN:结构一样,训练手感完全不同

这件事在 24、25 两篇会分别展开,这里只把和 encoder 最直接相关的结论先说清。

原论文的 encoder 用 post-LN。好处是形式整洁,和 ResNet 那种"变换后再归一化"的写法比较接近;缺点是层数一深,梯度更难稳定传下去,所以 warmup、初始化、学习率配方都更敏感。

现代大模型更偏向 pre-LN,因为它让残差主路径更像一条真正的直通高速路。直观地说:

  • post-LN:每走一层都要先经过一次归一化"闸门";
  • pre-LN:主路径基本直通,归一化只作用在旁路变换的输入上。

这也是为什么今天你看到的大多数 encoder backbone,虽然骨架和 2017 年几乎一样,但训练稳定性已经比原版好得多。


七、几个常见误解

7.1 "encoder 一层 already 看全局,所以多层是浪费"

错。全局可见不等于全局理解。第一层看到的是原始 embedding 的全局关系;更深层看到的是多轮改写后的高阶表示。深度带来的不是更大感受野,而是更强的表示变换与迭代推理。

7.2 "encoder 只是 decoder 的配角"

只在机器翻译这张图里它像配角。到 BERT、ViT、CLIP 这类任务里,encoder 就是主角,而且是整条表示学习路线的核心骨架。

7.3 "encoder 的输出就是一句话的向量"

不对。严格说,encoder 默认输出的是一整串 token 表示。句向量通常是额外构造出来的,比如 [CLS]、mean pooling、attention pooling。不要把"最后一层输出"直接等同于"整句 embedding"。

7.4 "encoder 不会建模顺序"

如果没有位置编码,这句话成立;一旦加上位置编码,encoder 就能利用顺序,只是它不是通过递归顺序来建模,而是通过 attention + positional signal 来建模。

7.5 "encoder-only 一定比 decoder-only 更懂语义"

也不能这么绝对。它在理解任务上有结构优势,但现代 decoder-only LLM 通过大规模自回归训练也能学到非常强的语义表示。真正的区别是训练目标和推理方式,不是"一个懂语义、一个不懂"。


八、结语

把 encoder 单独拿出来看,会发现它远不只是 Transformer 图上的左半边。它的本质是:用双向 self-attention 让每个 token 尽快获得全局上下文,再通过 FFN、残差和归一化反复重写这组表示。6 层、12 层、24 层都只是这个思想在不同尺度下的展开。

如果你理解了这一点,后面的很多分叉就都顺了:BERT 为什么只要 encoder,ViT 为什么也能直接套这套骨架,decoder 为什么非要加 mask,encoder-decoder 又为什么适合翻译和摘要。下一篇我们就顺着这个分叉,去看另一半------decoder 为什么必须一边读历史、一边生成未来。


九、参考文献

  1. Vaswani, A. et al. "Attention Is All You Need." NeurIPS 2017. 原始 encoder-decoder 结构定义与 6 层 encoder 设计。
  2. Devlin, J. et al. "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding." NAACL 2019. encoder-only 路线成为 NLP 主流的关键论文。
  3. Liu, Y. et al. "RoBERTa: A Robustly Optimized BERT Pretraining Approach." arXiv:1907.11692, 2019. 说明 encoder-only 在预训练配方优化后仍有巨大空间。
  4. He, P. et al. "DeBERTa: Decoding-enhanced BERT with Disentangled Attention." ICLR 2021. encoder backbone 的进一步演化代表。
  5. Dosovitskiy, A. et al. "An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale." ICLR 2021. encoder 作为视觉 backbone 的代表。
  6. Xiong, R. et al. "On Layer Normalization in the Transformer Architecture." ICML 2020. 解释 pre-LN 相比 post-LN 更稳定的原因。

← 上一篇:21|位置编码 | 下一篇:23|Decoder 详解

相关推荐
程序员cxuan1 小时前
微信读书官方发了 skills,把我给秀麻了。
人工智能·后端·程序员
未若君雅裁2 小时前
Spring AOP、日志切面与声明式事务原理
java·后端·spring
zhangxingchao2 小时前
AI应用开发六:企业知识库
前端·人工智能·后端
红尘散仙3 小时前
一个 `#[uniffi::export]`,把 Rust 接进 React Native
前端·后端·rust
红尘散仙3 小时前
一行 `#[specta::specta]`,让 Tauri IPC 有类型
前端·后端·rust
XinZong5 小时前
OpenClaw 中最经典的 6 款skill,真正能进工作流的 skills
javascript·后端
zhangxingchao6 小时前
AI Agent 基础问题系统整理:从 LangChain、LangGraph、MCP 到 Agent 架构、记忆、工具调用与评估体系
前端·人工智能·后端
Moment6 小时前
AI 为什么总喜欢写防御性代码?
前端·后端·面试
IT_陈寒6 小时前
SpringBoot自动配置的坑,差点让我加班到天亮
前端·人工智能·后端