三种 Transformer 形态
上一篇 primer 里的 block,可以装成三种不同的架构,每种都为不同的任务优化。 block 本身不变 —— 变的是 attention mask、以及一个地方的 token 能不能看另一个地方的 token。4 个短主题:用来理解的Encoder-only(BERT);用来生成的Decoder-only(GPT —— 所有现代 LLM 都是这个);用作序列到序列的Encoder-Decoder 混合(T5、BART、原始 Transformer); 以及把 BERT 变成 GPT 的那一行代码 —— 因果掩码(causal mask)。
Encoder-only —— BERT
一眼把整句话双向读全。为理解而生,不做生成。
encoder-only 模型就是一摞 Transformer 块,不带任何 attention 掩码。每个 token 都看其他所有 token —— 看左边也看右边。输出是每个输入 token 一个带上下文的向量,每一个都被整句话浸染过。BERT(2018)是典型代表, 也定下了一整个家族的模板:RoBERTa、ALBERT、DeBERTa,以及撑起语义搜索的句向量模型。
BERT 怎么训。双向模型没法像 GPT 那样训 —— 预测下一个 token 太简单了, 模型只要往右瞄一眼就能抄到答案。BERT 改用掩码语言建模:随机藏掉 15% 的 token, 换成一个特殊的 [MASK] 符号,让模型用两边的上下文把原词重建出来。 demo 演的正是这个 —— [MASK] 位置从左边的「the cat」和右边的「on the mat」 汲取信号,还原出「sat」。
它擅长什么。因为每个 token 都看到完整上下文,encoder 的输出是极好的表示。把一个序列映射到一个标签、或映射到其中的片段的任务 —— 情感分类、命名实体识别、抽取式问答、检索向量 —— 都是 BERT 的主场。 多年来,「fine-tune 一个 BERT」是任何 NLP 分类问题的默认第一步。
它做不了什么。生成。encoder 一次性产出所有输出、条件是完整输入 —— 没有「下一个 token」的概念,没有从左到右的顺序可利用,也没办法采样一个续写。 你要一个会写的模型,双向注意力恰恰是错的工具,你需要下一种形态。
Decoder-only —— GPT
只看左边、预测下一个 token、接上、再来。所有现代 LLM 都是这个形态。
decoder-only 模型是同样一摞块,但带因果掩码: 每个 token 只能看自己和它前面的 token,永远看不到未来。训练目标简单到极致 —— 在每个位置,同时预测下一个 token。这就是 GPT-2、GPT-3、GPT-4、Llama、Qwen、 DeepSeek、Mistral、Gemini 的架构 —— 基本上今天所有在产的大语言模型。
为啥这个形态赢了。三个原因叠加。第一,目标是自监督且无限的: 任何文本都是训练数据,因为每个位置的「标签」就是语料里已经存在的下一个 token。 不需要人工标注、不需要任务专属数据集 —— 就是在整个互联网上预测下一个词。 第二,一个模型干所有任务:都框成文本续写,翻译、摘要、问答、代码 全都变成同一个「下一个 token」问题。第三,它干净地scale —— 在 1 亿参数上管用的配方,到 1000 亿+ 还管用。
训练 / 推理的不对称。训练时模型一次看到整个序列、并行预测所有位置 —— 因果掩码就是让这件事安全的东西(位置 i 不能偷看 i+1 作弊)。 但生成时,它跑的是 demo 演的那个循环:预测一个 token、接上、 把更长的序列喂回去、再预测。这就是为什么生成 1000 个 token 的回复要 1000 次前向, 也是为什么 KV 缓存(把过去的 key/value 存下来,免得每步重算) 是推理的核心优化。
一个值得退休的说法。「decoder-only」是个略有误导的名字 —— 这些模型没有 encoder 可供「decode」,也没有 cross-attention。它们其实就是因果语言模型。 「decoder」这个标签是从原始 encoder-decoder Transformer 遗留下来的, 那个的 decoder 半边同时有因果掩码和 cross-attention。现代 GPT 留下了因果掩码、 丢掉了 cross-attention、留下了名字。
Encoder-Decoder —— T5、BART
先把输入读全,再生成输出 —— 中间用 cross-attention 搭桥。
encoder-decoder 形态就是 2017 年最初的 Transformer, 它把前两种合在一起。一个 encoder(双向,像 BERT)把整个输入读进一组上下文向量 —— 一份固定的「memory」。一个 decoder(因果,像 GPT)一次一个 token 地生成输出。 两者之间的桥是 cross-attention:在每个 decoder 层, 生成的 token 都回头看 encoder 的 memory。T5 和 BART 是最知名的例子。
cross-attention,讲精确点。一个 decoder 块其实有两个 attention 子层。 第一个是普通的因果自注意力,作用在目前已生成的 token 上(Q、K、V 全来自 decoder)。 第二个是 cross-attention:query 来自 decoder,但 key 和 value来自 encoder 的 memory。于是每个输出 token 都能问「源里哪些部分跟我现在要写的相关?」 翻译时,正在生成的词回头看它要翻的那些源词。
什么时候它是对的形态。当输入和输出是有清晰边界的两个不同序列、 而且输入一开始就完整可得时,encoder-decoder 最出彩。翻译(进法语、出英语)、 摘要(进长文、出短摘要)、语音转文字都是经典契合。独立的 encoder 给输入一个丰富的 双向表示,decoder 可以反复查阅。
但为啥 decoder-only 还是把它吃了。纯 decoder 也能做翻译: 只要喂它「French: … English: …」,让因果注意力处理两半。理论上更笨, 但实践上赢了 —— 一摞而不是两摞、一个目标,而且能 scale 到没有清晰输入/输出划分的 通用指令遵循。到了 GPT-3 时代,这个领域基本统一到 decoder-only 上了。 T5 式的模型在聚焦的序列到序列任务上仍然很强,但前沿是 decoder-only。
因果掩码
一个三角形的 −∞ 矩阵,就是 BERT 和 GPT 的全部区别。
上面三种形态,归根到底就一个问题:一个 token 能不能看未来?encoder 说能;decoder 说不能。强制「不能」的机制就是因果掩码 —— 而它简单到近乎好笑。回忆自注意力 primer 里的分数矩阵:一个 n × n 的格子, 第 (i, j) 项是查询 token i 对键 token j 的注意程度。 因果掩码在 softmax 之前,把所有 j > i(键相对查询在未来)的项 设成 −∞。
为啥用 −∞、为啥在 softmax 之前。softmax 把每个分数取指数:e^(−∞) = 0。所以被遮的格子归一化后贡献正好是零,而且 —— 关键是 —— 它根本不进分母。剩下的(过去 + 当前)格子在它们自己之间重新归一化、和为 1。 结果是一个严格下三角的 attention 矩阵:token i 把它全部的注意力 摊在 token 0 到 i 上,之后的一点没有。代码里通常就是scores.masked_fill(mask == 0, float('-inf')) —— 一行。
为啥这恰恰是生成需要的。当模型预测位置 i 的 token 时, 它只能用位置 0…i 的信息 —— 因为推理时,i+1 往后的位置还不存在。 因果掩码在训练时也保证这一点,于是训练时和测试时的信息集完全一致。 没有它,模型会学会靠偷看 token i 自己(或更后面)来预测 token i —— 在训练数据上轻松完美,生成时却没用。掩码让「预测下一个 token」成为一个真实的、 不作弊的学习问题。
统一的图景。Transformer 块是一个固定设计。掩码是 选架构的那个旋钮:无掩码 → encoder(双向,BERT);因果掩码 → decoder(自回归,GPT); 一摞 encoder 加一摞因果 decoder、用 cross-attention 连起来 → encoder-decoder(T5)。 同样的块、同样的注意力数学、同样的 FFN —— 一个三角矩阵决定了你造出来的是个会理解的模型、 还是个会写的模型。有了它,整个现代 LLM 就装好了:分词 → embedding + 位置 → N 个带掩码的块 → 最终 norm → 下一个 token 的 logits → 采样 → 循环。