深蓝黑背景封面。左侧纵向排列三张注意力掩码小矩阵:第一张标注「causal Decoder」,格子呈下三角点亮,说明每个词只能看见自己和左边的词;第二张标注「prefix Decoder」,左半段整片点亮、右半段下三角点亮,说明前缀部分双向可见、续写部分单向可见;第三张标注「Encoder-Decoder」,编码器方阵全部点亮、另有一条指向解码器的交叉箭头,说明输入全文双向可见、输出逐词生成。矩阵下方一行小字注「掩码决定每个位置能『看见』谁,也就决定了模型适合什么任务」。右侧大标题两行「prefix、causal、Encoder-Decoder」「三种架构有啥区别」,副标题「差别的本质:注意力掩码允许信息往哪个方向流动」,小注「看懂掩码矩阵,就看清了生成、理解与混合任务的分工」。左上标注「面试经典 · 模型架构」,右上圆角胶囊标注「Day 10 · 12-07」。 图 1:本文封面,三张掩码矩阵看懂三种架构的信息流方向。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料整理,与任何公司的当前招聘无关。「prefix Decoder、causal Decoder、Encoder-Decoder 有什么区别」是大模型面试里最经典的架构题,考的不是背模型名单,而是你能不能把三种架构的差异收拢到一件事上——注意力掩码。三种架构用的是同一套 Transformer 积木,差别只在「每个位置允许看见谁」。

一句话结论:三种架构的本质差异就是注意力掩码的取值模式——causal Decoder 是下三角单向掩码,prefix Decoder 是「前缀双向 + 续写单向」的混合掩码,Encoder-Decoder 是编码器全双向、解码器单向、再加一条全通的交叉注意力;掩码决定信息流方向,信息流方向决定训练目标,训练目标决定适用任务。

先统一度量衡:掩码是怎么实现的。

自注意力的核心是 softmax(QKᵀ/√d)。掩码不靠删掉连接,而是在做 softmax 之前给注意力分数矩阵加上一个掩码矩阵 M:允许看见的位置加 0,不允许看见的位置加负无穷。负无穷经过 softmax 之后权重恒等于 0,被遮住的位置对输出没有任何贡献——信息就被精确地掐断了。所以「架构之争」翻译过来就是「M 矩阵的 0 和负无穷怎么摆」。

第一种:causal Decoder,下三角掩码,一路向左看。

第 i 个位置只能看见第 1 到第 i 个位置,右边一律屏蔽。训练目标是自回归的「预测下一个词」:每个位置的输出都以它左边的全部内容为条件,损失在所有位置上都计算。这套因果约束保证了训练与推理完全一致——模型生成时本来就是一个词一个词往外吐,永远看不到未来。代表模型是 GPT 系、LLaMA 系。它天生适合开放式生成与对话:续写、聊天、代码补全,本质都是「给定前文生成后文」。

第二种:prefix Decoder,前缀双向、续写单向。

把输入切成「前缀」和「续写」两段:前缀内部的词互相可见(双向注意力,类似 BERT 的理解方式),续写部分仍遵守单向因果,只能看前缀和已生成的部分。损失只计算在续写段上。直觉很直白:读题的时候要读透(双向),答题的时候不能作弊看后面(单向)。代表是 GLM 系,2021 年的 GLM 论文用「自回归空白填充」把这套掩码统一成一个预训练框架,同一模型既能做理解任务也能做条件生成。可以把它看成 causal Decoder 和 Encoder-Decoder 的折中:比纯单向更充分地消化输入,又不必维护两个组件。

第三种:Encoder-Decoder,两个组件、三种掩码。

编码器对输入做全双向自注意力,把整段输入消化成一组上下文表示;解码器自注意力是单向的,逐词生成输出;两者之间靠交叉注意力连接——解码器的每个位置拿着自己的查询去「查阅」编码器的全部输出,这条通路全通。训练目标是序列到序列的「整句映射整句」,比如 T5 把一切任务统一成 text-to-text 格式,预训练采用去噪式的 span 还原。代表是 T5、BART。输入理解充分、输出又受因果约束,所以翻译、摘要这类「先吃透全文、再定向改写」的任务是它的主场。

用代码把三张掩码摆出来(Node.js 22.15.0,node masks.js):分别构造三种掩码矩阵,并验证「加负无穷再过 softmax」确实把屏蔽位置压成 0:

const pl = 2; // prefix 长度
const causal = Array.from({length: n}, (_, i) =>
  Array.from({length: n}, (_, j) => (j <= i ? 1 : 0)));
const prefix = Array.from({length: n}, (_, i) =>
  Array.from({length: n}, (_, j) => (j <= Math.max(i, pl - 1) ? 1 : 0)));
// 掩码实现:注意力分数加 -∞ 后 softmax 恒为 0
const masked = scores.map((s, i) => (mask[i] ? s : -Infinity));

实测输出(■ 可见、· 屏蔽):

causal Decoder(5 词,下三角)
  ■ · · · ·
  ■ ■ · · ·
  ■ ■ ■ · ·
  ■ ■ ■ ■ ·
  ■ ■ ■ ■ ■
prefix Decoder(前 2 词为前缀,双向)
  ■ ■ · · ·
  ■ ■ · · ·
  ■ ■ ■ · ·
  ■ ■ ■ ■ ·
  ■ ■ ■ ■ ■
未掩码 softmax: 0.2934, 0.0536, 0.6530
掩码后 softmax: 0.8455, 0.1545, 0.0000

最后两行是关键:未掩码时第三个位置分到 0.6530 的权重,掩码后精确变成 0.0000,注意力重新归一到可见位置。Encoder-Decoder 的三块矩阵(编码器全亮方阵、解码器下三角、交叉注意力全通)在概念图里画得比文字更直观。

深蓝黑背景概念图,顶部小字「概念图 · 注意力掩码与模型拓扑」,主标题「一张掩码表,三种信息流」。下方三个竖向分区并排:左列「causal Decoder」画一个 5×5 下三角掩码矩阵,标注注意力公式 softmax(QKᵀ/√d + M),M 上三角取 −∞,训练目标是「预测下一个词」,代表任务是开放生成与对话;中列「prefix Decoder」画左半块全亮、右半下三角的掩码矩阵,标注「前缀内互相可见,答案只能看前缀与已生成部分」,训练目标是「以前缀为条件续写」,代表任务是对话与条件生成;右列「Encoder-Decoder」画上下两块:上方全亮方阵标注「编码器自注意力全双向」,下方下三角标注「解码器自注意力单向」,中间一条「交叉注意力」竖箭头连接,训练目标是「整句映射整句」,代表任务是翻译与摘要。底部总结条:三者的差别只在掩码 M 的取值模式;同一份 Transformer 积木,换个掩码就换了性格。最底行小字补充:掩码通过对注意力分数加 −∞ 实现,被遮住的位置 softmax 后权重恒为 0。 图 2:三种架构的掩码矩阵、训练目标与适用任务对照。

为什么今天的生成式主流收敛到了 causal Decoder?

这不是因果掩码「更强」,而是它更省、更通用。第一,预训练与下游用法完全同构:无标注语料拿来就能做「预测下一个词」,训完直接零样本生成,不需要像 Encoder-Decoder 那样靠多任务微调激发性能;第二,训练效率高,每个位置都算损失,而且单一组件的工程实现、KV cache 复用都最简单;第三,纯生成的任务里双向注意力本来就没有实质收益——「输入理解」完全可以用「把输入塞进前缀」实现。Encoder-Decoder 的双组件结构还意味着更多参数与更复杂的推理流水线。于是在「生成是主战场」的大模型时代,causal Decoder 成了默认答案,prefix Decoder 作为折中在 GLM 系里保留下来,Encoder-Decoder 则退守翻译、摘要等定向改写场景。

面试现场的答题结构。

先给结论「差别全在注意力掩码」,顺手讲掩码的实现原理(加负无穷再过 softmax)。然后按掩码模式逐个过:causal 下三角、prefix 混合、Encoder-Decoder 三块掩码(编码器双向、解码器单向、交叉全通),每个都带上训练目标与代表任务——自回归续写对开放生成、空白填充对理解与生成混合、序列到序列对翻译摘要。追问「为什么现在主流是 causal Decoder」时答三点:训推一致零样本强、单组件效率高、生成任务里双向注意力收益有限。有余力再补一句 GLM 的空白填充如何统一三类任务,展示你读过代表性论文。你在实际项目里用过 Encoder-Decoder 架构做生成任务吗?效果跟 Decoder-only 比怎么样?欢迎聊聊。