Attention - 面经八股

Attention

本文整理大模型架构面试高频知识,重点覆盖 Attention 的计算逻辑、Transformer 的核心模块、BERT 的预训练与输入表示、MHA / MQA / GQA 的推理效率差异,以及 FlashAttention 的显存与 IO 优化思想。

Attention

谈谈你对 Attention 的理解?

​ Attention 的核心思想是:在处理一个 token 或生成一个输出时,不是平均看所有输入,而是根据当前需要,动态地关注更重要的信息。

​ 在 NLP 任务中,一个句子里的不同词对当前预测的重要性是不一样的。比如机器翻译时,生成某个目标词时,模型应该重点关注源句中和它语义对应的词;做问答时,模型应该重点关注和问题相关的上下文片段。Attention 就是让模型自动学习“该看哪里、看多少”。

​ 从直观上理解,Attention 会为输入序列中每个位置分配一个权重。权重越大,说明当前位置对当前输出越重要。然后模型会对这些位置的表示做加权求和,得到一个融合后的上下文表示。

在 Transformer 里,Attention 通常通过 Q、K、V 来实现:

  • Query 表示当前 token 想要查询什么信息
  • Key 表示每个 token 能提供什么索引或匹配特征
  • Value 表示每个 token 真正携带的信息内容

模型会用 Query 和 Key 计算相关性,得到 attention score,再经过 softmax 转成权重,最后对 Value 做加权求和。

这样做的好处是:

​ 第一,可以建模长距离依赖。任意两个 token 之间都可以直接建立联系,不需要像 RNN 那样一步步传递信息。

​ 第二,可以动态选择重要信息。不同任务、不同位置、不同上下文下,模型关注的 token 可以不同。

​ 第三,并行计算能力强。相比 RNN 按时间步顺序计算,Self-Attention 可以同时处理整个序列,更适合大规模训练。

​ 第四,可解释性相对更好一些。Attention 权重可以在一定程度上反映模型关注了哪些位置,虽然不能完全等同于解释。

总结: Attention 是一种动态加权的信息选择机制。它通过计算当前位置和其他位置之间的相关性,为不同 token 分配不同权重,再对信息进行加权汇聚,使模型能够关注关键内容、建模长距离依赖,并提高序列建模能力。Transformer 正是因为使用 Self-Attention,才成为当前 NLP 和大模型的核心架构。

Attention 的计算步骤是什么?

Attention 的计算过程可以概括为四步:生成 QKV、计算相关性、归一化权重、加权求和

第一步,由输入生成 Query、Key、Value。对于输入序列中的每个 token,模型会通过三个不同的线性变换得到三个向量:

  • Q,Query,表示当前 token 想要查询什么信息
  • K,Key,表示每个 token 能被匹配的特征
  • V,Value,表示每个 token 实际携带的信息内容

第二步,计算 Query 和 Key 的相似度。模型会用 Q 和所有位置的 K 做点积,得到 attention score。这个分数表示当前 token 和其他 token 的相关程度。分数越高,说明当前 token 越应该关注那个位置。

第三步,缩放并做 softmax。在 Transformer 中,会把点积结果除以dk\sqrt{d_k},这是为了避免向量维度较大时点积值过大,导致 softmax 进入饱和区,梯度变得不稳定。然后对分数做 softmax,把它们转换成一组和为 1 的注意力权重。

第四步,对 Value 加权求和。用得到的注意力权重去加权所有位置的 V,得到当前 token 的输出表示。也就是说,模型根据相关性强弱,从整个序列中聚合信息。

对应到公式就是:Attention(Q,K,V)=softmax(QKTdk)VAttention(Q, K, V)=softmax(\frac{QK^T}{\sqrt{d_k}})V

如果是 Self-Attention,那么 Q、K、V 都来自同一个输入序列。如果是 Cross-Attention,通常 Q 来自 Decoder,K 和 V 来自 Encoder 输出。

总结: Attention 先把输入映射成 Q、K、V,用 Q 和 K 计算相关性,经过缩放和 softmax 得到注意力权重,再用这些权重对 V 加权求和,最终得到融合上下文信息的表示。Transformer 中使用缩放点积注意力,是为了在建模全局依赖的同时保持训练稳定。

Attention机制和传统的Seq2Seq模型有什么区别?

​ 传统 Seq2Seq 和带 Attention 的 Seq2Seq,核心区别在于 Decoder 获取 Encoder 信息的方式不同

​ 传统 Seq2Seq 通常由 Encoder 和 Decoder 组成。Encoder 先把整个输入序列编码成一个固定长度的上下文向量,通常是最后一个 hidden state;Decoder 再基于这个向量一步步生成输出。

​ 这种方式有一个明显问题:所有输入信息都被压缩到一个固定长度向量里。当输入句子比较长时,前面的信息容易丢失,长距离依赖建模效果不好,Decoder 生成每个词时也无法灵活选择输入中的关键信息。

​ Attention 机制改进了这一点。加入 Attention 后,Encoder 不再只把最后一个状态传给 Decoder,而是保留每个输入位置的 hidden state。Decoder 在生成每个目标 token 时,会根据当前解码状态,动态计算它和输入序列各位置的相关性,然后对 Encoder 的所有 hidden state 做加权求和,得到当前时间步需要的上下文表示。

​ 也就是说:传统 Seq2Seq 是把整句话压缩成一个固定向量;Attention Seq2Seq 是每生成一个词,就动态地回看输入序列中最相关的部分。

这样带来的好处是:

​ 第一,缓解长序列信息压缩问题。输入的每个位置都可以被 Decoder 直接访问,不再完全依赖最后一个 hidden state。

​ 第二,更好地建模对齐关系。比如机器翻译中,生成某个目标词时,模型可以关注源句中对应的词或短语。

​ 第三,提升长文本和复杂序列任务效果。尤其在翻译、摘要、问答等任务中,Attention 通常能显著提升性能。

总结: 传统 Seq2Seq 依赖 Encoder 的最后状态作为固定语义向量,容易造成长句信息丢失;Attention 机制让 Decoder 在每一步生成时都能动态关注 Encoder 的不同位置,从而缓解信息瓶颈,增强长距离依赖和词语对齐能力。

self-attention 和 target-attention的区别?

​ Self-Attention 和 Target-Attention 的核心区别在于:Q、K、V 来自哪里不同

Self-Attention 是序列内部自己和自己做 attention。也就是说,Q、K、V 都来自同一个序列。模型会让序列中的每个 token 去关注同一序列里的其他 token,从而建模序列内部的依赖关系。

​ 比如在 BERT 里,输入是一个句子:我 喜欢 自然语言处理,每个词都会和句子里的其他词计算相关性,从而得到融合上下文的表示。这就是 self-attention。所以 self-attention 主要解决的是:一个序列内部,各个 token 之间应该如何相互关联。

Target-Attention 这个说法有时也叫 Encoder-Decoder AttentionCross-Attention。它不是同一个序列内部做 attention,而是让目标序列去关注另一个序列。通常是:Q 来自目标端,也就是 Decoder 当前状态;K 和 V 来自源端,也就是 Encoder 输出。比如机器翻译中,Decoder 正在生成英文单词时,会用当前目标端状态作为 Query,去关注中文源句中最相关的词。这就是 target-attention / cross-attention。所以 target-attention 主要解决的是:目标序列在生成时,应该关注源序列中的哪些信息。

二者可以这样对比:

类型Q 来自K/V 来自作用
Self-Attention当前序列当前序列建模序列内部关系
Target-Attention / Cross-Attention目标序列 / Decoder源序列 / Encoder建模目标端与源端的对齐关系

总结: Self-Attention 是同一个序列内部 token 之间相互关注,Q、K、V 都来自同一输入;Target-Attention 通常指 Decoder 对 Encoder 输出做 attention,Q 来自目标端,K 和 V 来自源端,用来建立源序列和目标序列之间的对齐关系。前者建模序列内部依赖,后者建模跨序列依赖。

目前主流的 attention 方法有哪些?

​ 目前主流的 Attention 方法,我会按 基础形式、增强形式和长序列优化 来理解。

​ 第一类是 Scaled Dot-Product Attention。这是 Transformer 里最基础、最常用的 Attention。它通过 Q 和 K 做点积计算相关性,再除以dk\sqrt{d_k} 进行缩放,经过 softmax 得到注意力权重,最后对 V 加权求和。缩放的作用是防止维度较大时点积值过大,导致 softmax 饱和、梯度不稳定。

​ 第二类是 Multi-Head Attention。它不是只做一次 attention,而是把 Q、K、V 投影到多个子空间中,并行做多组 attention。每个 head 可以关注不同的信息,比如有的关注局部关系,有的关注长距离依赖,有的关注语法或语义关系。最后把多个 head 的结果拼接起来,再经过线性变换输出。现在 Transformer、大模型基本都使用 Multi-Head Attention 或它的变体。

​ 第三类是 Self-Attention 和 Cross-Attention。Self-Attention 是同一个序列内部做注意力,Q、K、V 都来自同一序列,比如 BERT、GPT 的主体结构。Cross-Attention 是一个序列去关注另一个序列,常见于 Encoder-Decoder 结构,比如机器翻译中 Decoder 用 Q 去关注 Encoder 的 K 和 V。

​ 第四类是 带位置建模的 Attention。原始 attention 本身不包含顺序信息,所以需要位置编码。常见方法包括绝对位置编码、相对位置编码、RoPE、ALiBi 等。相对位置编码关注 token 之间的相对距离,RoPE 则通过旋转位置编码把位置信息融入 QK 计算,现在很多大模型会使用 RoPE。

​ 第五类是 长序列 Attention 优化方法。标准 Self-Attention 的复杂度是平方级,长文本成本很高,所以出现了很多优化方法,比如:

  • Sparse Attention:只关注部分位置,减少计算量
  • Sliding Window Attention:每个 token 只关注附近窗口,常用于长文本模型
  • Linear Attention:把 attention 复杂度近似降到线性
  • FlashAttention:不改变数学结果,通过 IO 优化提升 attention 计算效率
  • Transformer-XL:引入 segment-level recurrence,让模型利用前一段的 hidden states,扩展上下文建模能力

总结: 主流 Attention 包括 Scaled Dot-Product Attention、Multi-Head Attention、Self-Attention、Cross-Attention,以及相对位置编码、RoPE、ALiBi 等位置增强方法。对于长文本场景,还有 Sparse Attention、Sliding Window Attention、Linear Attention、FlashAttention 和 Transformer-XL 等优化。面试中重点讲清楚 Scaled Dot-Product Attention 和 Multi-Head Attention,再补充长序列优化即可。

self-attention 在计算过程中,如何对 padding 位做 mask?

​ 在 self-attention 中,padding 位置不应该参与注意力计算,否则模型会把无意义的 [PAD] token 当成正常 token 来关注,影响表示学习。具体做法是在QKTQK^T 得到 attention score 之后、softmax 之前加入 padding mask

  1. 先计算 attention score:score=QKT/dkscore = QK^T / \sqrt{d_k} ,根据输入中的 padding 位置构造 mask。对真实 token 的位置保留,对 padding 位置进行屏蔽。

  2. 在 softmax 之前,把 padding 对应位置的 score 加上一个非常大的负数,比如 -1e9,理论上等价于负无穷。再做 softmax,因为 exp(-∞) 接近 0,所以 padding 位置的 attention weight 就会变成 0。这样模型在对 V 加权求和时,就不会关注 padding 位置。

​ 需要注意的是,padding mask 通常是作用在 Key 的位置 上。也就是说,对于每个 Query,都不能参与到 padding 的 Key/Value。

如果是在 Decoder 里,还会同时用到两种 mask:

  • padding mask:屏蔽 [PAD]
  • causal mask:屏蔽未来 token,防止信息泄露

总结: Self-attention 中对 padding 做 mask,一般是在 attention score 计算完成后、softmax 之前,把 padding 位置对应的 score 置为负无穷。这样 softmax 后 padding 位置的权重就是 0,模型就不会关注无意义的 padding token。

深度学习中 attention 与全连接层的区别何在?

​ Attention 和全连接层的核心区别在于:全连接层使用固定参数做映射,Attention 使用输入之间动态计算出来的权重做信息聚合

​ 全连接层,也就是 Linear/Dense Layer,本质是一个固定的线性变换。输入经过参数矩阵 W 和偏置 b,得到输出。这个权重矩阵是训练好的参数,推理时对所有样本都是固定的。它关注的是“如何把一个输入向量映射到另一个空间”。

​ 而 Attention 的权重不是固定写死的参数,而是根据当前输入动态算出来的。它会用 Query 和 Key 计算相关性,得到 attention score,再用这个 score 对 Value 加权求和。也就是说,Attention 的重点是“当前这个 token 应该从其他 token 中取多少信息”。

可以从这几个角度对比。

​ 第一,权重来源不同。全连接层的权重来自模型参数,训练完后固定。Attention 的注意力权重来自 Q 和 K 的相似度,是随输入动态变化的。

​ 第二,建模对象不同。全连接层主要建模特征维度之间的变换。Attention 主要建模 token 与 token、序列位置与序列位置之间的关系。

​ 第三,是否有查询机制不同。Attention 有明显的 Q-K-V 机制:Query 表示当前要找什么,Key 表示每个位置有什么匹配特征,Value 表示真正被聚合的信息。全连接层没有 Query 和 Key 的匹配过程。

​ 第四,参数和序列长度关系不同。全连接层如果直接作用在整个序列上,往往会和固定输入长度绑定。Self-Attention 的参数主要是 Wq、Wk、Wv,可以处理不同长度的序列,并根据输入动态建立位置之间的联系。

​ 第五,信息交互方式不同。全连接层是把输入做一次固定映射。Attention 是让每个位置根据相关性,从其他位置选择性地聚合信息,因此更适合长距离依赖和上下文建模。

总结: 全连接层是静态参数映射,学习的是固定的特征变换;Attention 是动态权重聚合,学习的是输入之间的相关关系。Attention 通过 Q 和 K 计算注意力分数,再对 V 加权求和,因此它可以根据不同上下文自适应地关注不同 token,而全连接层没有这种动态查询和对齐机制。

Transformer

Multi-Head Attention 中每个 head 为什么要降维?

​ 多头注意力中,每个 head 通常不会使用完整的 d_model 维度,而是把它拆成 h 个较小的子空间,每个 head 的维度是:dk=dmodel/hd_k = d_{model} / h

这样设计主要有三个原因。

​ 第一,控制计算量和参数量。如果每个 head 都使用完整的 d_model,那么做 h 个 head 的 attention,计算量会接近单头的 h 倍,成本太高。但如果把每个 head 的维度降到 d_model / h,那么每个 head 的 attention 计算更小,h 个 head 合起来的总计算量大致和单头 attention 同一个量级。这样就可以在不显著增加计算成本的情况下使用多头机制。

​ 第二,让不同 head 学习不同子空间的关系。每个 head 都有独立的 Wq、Wk、Wv 投影矩阵,它们会把输入映射到不同的表示子空间中。不同 head 可以关注不同模式,比如局部依赖、长距离依赖、语法关系、实体关系等。最后再把多个 head 的结果拼接起来,得到更丰富的上下文表示。

​ 第三,保持输入输出维度一致,方便残差连接和堆叠。多个 head 的输出会拼接起来。如果每个 head 是 d_model / h 维,那么拼接后刚好回到 d_model 维,再经过输出投影 W_o,就可以和原输入维度对齐。这样后续的残差连接、LayerNorm 和堆叠多层 Transformer 都会更方便。

总结: Multi-Head Attention 拆分 d_model,是为了让多个 head 在不同低维子空间中并行建模不同的注意力关系,同时保持总计算量接近单头 attention。如果不降维,多个 head 会让计算量和参数量成倍增加;降维后,拼接输出仍然是 d_model,既保证效率,也方便残差连接和网络堆叠。

Transformer的点积模型做缩放的原因是什么?

​ Transformer 中点积 Attention 要做缩放,主要是为了 控制 Q 和 K 点积结果的数值尺度,避免 softmax 过早饱和,从而让训练更稳定

​ 在 self-attention 中,注意力分数是通过 Q 和 K 做点积得到的。如果 Q 和 K 的维度是dkd_k,那么维度越大,点积结果的方差就越大,数值范围也越容易变大。如果这个分数直接送进 softmax,softmax 的输出就可能变得非常尖锐,接近 one-hot。这样会导致模型在训练初期就过度关注某几个位置,其他位置的权重接近 0,梯度也会变得很小,训练不稳定。

​ 所以 Transformer 会在点积之后除以dk\sqrt{d_k}Attention(Q,K,V)=softmax(QKT/dk)VAttention(Q, K, V) = softmax(QK^T / \sqrt{d_k}) V。 这个缩放操作可以把 attention score 的尺度控制在比较合理的范围内,使 softmax 不容易进入饱和区,注意力分布不会过早变得极端,梯度传播也更稳定。

​ 需要注意的是,这里更准确地说是 防止 softmax 饱和导致梯度变小或训练不稳定,不主要是防止梯度爆炸。点积值过大时,softmax 输出接近 one-hot,反而更容易出现梯度不稳定或梯度变小的问题。

总结: 点积 attention 除以dk\sqrt{d_k},是因为QKQK 点积的方差会随维度增大而增大。如果不缩放,softmax 容易饱和,attention 分布会接近 one-hot,导致梯度变小、训练不稳定。缩放后可以让分数尺度更平稳,使模型更容易训练。

Transformer 的整体结构是什么?

Transformer 最初是一个 Encoder-Decoder 架构,Encoder 负责理解输入序列,Decoder 负责根据 Encoder 表示自回归生成目标序列。后来大模型根据任务目标演化出了 Encoder-only、Decoder-only 和 Encoder-Decoder 三类主流结构。

一个标准 Transformer block 通常包含几个核心模块:

  1. Multi-Head Attention:让每个 token 与其他 token 建立联系,建模上下文依赖。
  2. Feed Forward Network,FFN:对每个位置的表示做非线性变换,提升特征表达能力。
  3. Residual Connection:把子层输入直接加到输出上,缓解深层网络训练退化和梯度传播问题。
  4. LayerNorm:稳定每层输入分布,使训练更平稳。
  5. Position Encoding / Position Embedding:给模型注入位置信息,因为 attention 本身不包含顺序概念。

从模型类型看:

  • Encoder-only:代表是 BERT,使用双向 self-attention,适合理解、分类、匹配、抽取等任务。
  • Decoder-only:代表是 GPT、LLaMA、Qwen、DeepSeek 等,使用 causal self-attention,适合开放式生成、对话、代码和 Agent 场景。
  • Encoder-Decoder:代表是原始 Transformer、T5、BART,Encoder 双向理解输入,Decoder 通过 cross-attention 读取 Encoder 输出并自回归生成,适合翻译、摘要等输入到输出转换任务。

总结: Transformer 的核心是 attention + FFN + 残差连接 + LayerNorm + 位置编码。Encoder 侧偏理解,Decoder 侧偏生成。现代大模型多数采用 Decoder-only,是因为它的 next token prediction 目标统一、生成能力强、KV Cache 推理友好,工程生态也更成熟。

BERT

介绍一下 BERT

BERT 是 Google 提出的经典预训练语言模型,全称是 Bidirectional Encoder Representations from Transformers。它的核心特点是使用 Transformer Encoder 结构,通过双向注意力同时建模左右上下文,因此非常适合文本理解类任务,比如分类、匹配、阅读理解、检索排序等。

​ 需要注意一点:BERT 不是用来做自回归文本生成的模型,它主要是 Encoder-only 的语言理解模型。和 GPT 这种从左到右预测下一个词的模型不同,BERT 可以在编码每个 token 时同时看到左边和右边的上下文,所以它的语义表征能力很强。

BERT 的预训练主要包含两个任务。

​ 第一个是 Masked Language Model,MLM。训练时会随机选择输入中 15% 的 token 作为预测目标。对于这些 token:80% 替换成 [MASK], 10% 替换成随机 token,10% 保持原样。模型需要根据上下文预测这些位置原本的 token。这样做的好处是,模型可以学习双向上下文信息,也能获得更好的上下文相关词表示,比如解决一词多义问题。

​ 第二个是 Next Sentence Prediction,NSP。模型输入两个句子 A 和 B,判断 B 是否是 A 在原文中的下一句。训练时,50% 是真实连续句子,50% 是随机拼接的句子。这个任务主要是为了增强句子关系建模能力,比如问答、自然语言推理等任务。不过后续 RoBERTa 等工作发现 NSP 不一定必要,并对 BERT 的训练方式做了改进。

BERT 的输入由三部分 embedding 相加得到:

​ 第一是 Token Embedding,也就是 WordPiece 子词嵌入。BERT 不直接以完整单词为单位,而是使用 WordPiece,把词拆成子词,缓解未登录词问题。

​ 第二是 Position Embedding,用于表示 token 在句子中的位置。因为 Transformer 本身没有序列顺序感,所以需要位置编码。

​ 第三是 Segment Embedding,用于区分句子 A 和句子 B。比如句子对任务中,第一个句子的 segment id 是 0,第二个句子的 segment id 是 1。

​ 此外,BERT 还会使用两个特殊符号:[CLS] 放在序列开头,它的最终表示通常用于分类任务;[SEP] 用来分隔两个句子,也表示句子结束。

总结: BERT 是一个基于 Transformer Encoder 的双向预训练语言模型,通过 MLM 学习上下文相关的 token 表示,通过 NSP 学习句子间关系。它的输入由 Token Embedding、Position Embedding 和 Segment Embedding 组成。由于 BERT 能同时利用左右上下文,所以非常适合文本分类、语义匹配、命名实体识别、阅读理解等自然语言理解任务,但不适合直接做开放式文本生成。

BERT使用字粒度和词粒度的优缺点有哪些?

​ BERT 使用字粒度还是词粒度,本质是在 OOV、语义完整性、序列长度和计算成本 之间做权衡。中文场景里尤其常见这个问题。

字粒度的优点 是 OOV 问题比较小。中文常用字数量有限,用字作为基本单位时,基本可以覆盖绝大部分输入,不容易出现未登录词。比如新词、网络词、人名、地名,即使整个词没见过,也可以拆成单字处理。字粒度还有一个好处是粒度更细,对中文比较自然。中文本身不像英文那样天然有空格分词,用字粒度可以避免分词错误带来的影响。

​ 但字粒度的缺点也明显。单个字的语义通常不如词完整,比如“苹果”作为一个词含义很明确,但拆成“苹”和“果”后,单字语义弱很多。模型需要通过上下文重新组合词义,对于少见词和低频词需要更多的训练数据来学习有效的字符级别表示,否则可能会导致过拟合。另外,字粒度会让序列长度变长,尤其是英文或混合文本中,字符级输入会显著增加计算量。

词粒度的优点 是语义更完整。词通常是更自然的语义单位,比如“人工智能”“自然语言处理”作为词或短语整体表示,信息更集中,序列也更短,计算效率更高。

​ 但词粒度的问题是依赖分词质量。中文分词如果切错,会直接影响后续表示。例如“南京市长江大桥”如果切分不合理,语义就会偏。同时词粒度还容易遇到 OOV 问题,新词、低频词、专有名词如果不在词表里,就只能用 [UNK] 或其他方式处理,信息损失较大。

所以实际 BERT 中更常见的是 WordPiece / BPE / SentencePiece 这类子词粒度,它是在字和词之间做折中:

  • 比纯词粒度更能缓解 OOV
  • 比纯字符粒度更保留语义片段
  • 词表大小和序列长度也更可控

总结: 字粒度的优势是 OOV 少、不依赖分词,适合中文;缺点是语义粒度细、序列更长。词粒度的优势是语义完整、序列短、计算效率高;缺点是依赖分词,容易遇到 OOV。实际 BERT 通常采用 WordPiece 等子词粒度,在覆盖率、语义表达和计算效率之间取得平衡。

BERT 的 Encoder 与 Decoder 掩码有什么区别?

严格来说,BERT 只有 Transformer Encoder,没有 Decoder。面试中问 Encoder 和 Decoder 的 mask 区别,通常是在比较 Transformer Encoder、Transformer Decoder 或 BERT/GPT 这类模型的注意力可见性。

Encoder 侧 通常使用的是 padding mask。Encoder 的 self-attention 是双向的,每个真实 token 可以看到同一句子里的其他真实 token,包括左边和右边的上下文;但不能关注 [PAD] 位置。因此 padding mask 的作用是把无意义的 padding token 屏蔽掉,避免它参与注意力分布和表示计算。

Decoder 侧 除了 padding mask,还必须使用 causal mask / subsequent mask。Decoder 在自回归生成时,第tt 个位置只能看到自己和之前的位置,不能看到未来 token,否则训练时会发生信息泄露。也就是说,Decoder self-attention 的 attention matrix 通常是下三角可见的。

如果是标准 Encoder-Decoder 结构,比如原始 Transformer 或 T5,Decoder 还会有一层 cross-attention。在 cross-attention 中,Query 来自 Decoder 当前状态,Key 和 Value 来自 Encoder 输出。这一层通常不需要 causal mask,因为 Encoder 输出本身已经是完整输入的编码结果;但仍然需要对 Encoder 输入中的 padding 位置做 padding mask。

可以简单对比如下:

位置是否双向可见主要 mask作用
Encoder self-attentionpadding mask屏蔽输入中的 [PAD]
Decoder self-attentioncausal mask + padding mask屏蔽未来 token 和 [PAD]
Decoder cross-attention对 Encoder 输出可见encoder padding mask避免关注 Encoder 侧 [PAD]

总结: Encoder 的 self-attention 通常是双向的,主要用 padding mask 屏蔽 [PAD];Decoder 的 self-attention 是自回归的,需要 causal mask 防止看到未来 token,同时也要处理 padding。BERT 属于 Encoder-only,因此只有双向 self-attention 和 padding mask;GPT 属于 Decoder-only,因此核心是 causal mask。

为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?

​ BERT 中选择 mask 掉 15% 的 token,主要是一个 经验性的超参数选择,不是严格理论推导出来的。这个比例可以换成其他值,但 15% 是在训练效果和训练难度之间的一个折中。

​ 如果 mask 比例太低,比如只 mask 5%,那么每个 batch 中真正参与 MLM 预测的 token 太少,训练信号不足,模型学习效率会比较低。

​ 如果 mask 比例太高,比如 30% 或 40%,输入句子会被破坏得比较严重,模型可利用的上下文信息变少,预训练任务会变得过难,甚至影响模型学习正常的语言表示。

​ 所以 15% 的作用是:既保留大部分原始上下文,让模型有足够信息推断被 mask 的词;又提供足够数量的预测位置,让模型获得有效的训练信号。

此外,BERT 对这 15% token 还有进一步处理:80% 替换成 [MASK];10% 替换成随机 token;10% 保持原 token 不变。这样做是为了缓解预训练和微调之间的不一致,因为 [MASK] 这个符号在下游任务中通常不会出现。

总结: BERT mask 15% 是经验选择,可以调整。比例太低会导致训练信号不足,比例太高会破坏上下文,使任务过难。15% 在上下文保留和预测信号之间取得了比较好的平衡,因此被原始 BERT 采用并沿用下来。

为什么 BERT 在第一句前会加一个 [CLS] 标志?

​ BERT 在输入序列最前面加入 [CLS],主要是为了得到一个 整句或句子对的聚合表示,方便用于分类任务。

​ 在 BERT 中,每个 token 经过多层 Transformer Encoder 后,都会融合上下文信息。[CLS] 是一个特殊 token,本身没有具体词义,放在序列开头后,它可以通过 self-attention 和句子中的所有 token 交互。经过多层编码后,[CLS] 对应的最后一层 hidden state 就可以作为整个输入序列的表示。

​ 这个表示常用于下游任务,比如:文本分类、情感分析、自然语言推理、句子对匹配、是否下一句 NSP 任务。

​ 至于为什么不用普通词的向量,而用 [CLS]?因为普通词本身有词义,它的最终表示虽然也融合了上下文,但仍然更偏向表示自己这个词;而 [CLS] 没有具体语义,它的作用就是作为一个专门的聚合位置,在训练过程中学习如何收集整句信息,所以更适合作为句子级表示。

​ 不过也要注意:[CLS] 并不天然一定就是最好的句向量。它之所以能用于分类,是因为预训练和下游微调都会让它承担分类表示的功能。在一些语义相似度或向量检索任务中,直接用 [CLS] 未必最优,有时 mean pooling 或专门训练的 sentence-BERT 效果更好。

总结: BERT 加 [CLS] 是为了提供一个专门的序列级表示位置。它本身没有具体词义,可以通过 self-attention 聚合整段文本的信息,最后一层 [CLS] 向量常用于分类、匹配、NSP 等句子级任务。普通 token 更偏向表示自身语义,而 [CLS] 更适合作为整体输入的表示。

BERT 非线性的来源在哪里?

​ BERT 的非线性主要来自两个地方:Self-Attention 里的 softmax前馈网络 FFN 里的 GELU 激活函数

​ 首先,BERT 的每一层 Transformer Encoder 都包含一个 Self-Attention 模块。在 Self-Attention 中,模型会先用 Q 和 K 计算注意力分数,然后经过 softmax 得到注意力权重。softmax 本身是非线性函数,所以 attention 权重是输入相关、动态变化的,这给模型带来了非线性建模能力。

​ 其次,每个 Transformer Encoder 里还有一个 Feed Forward Network,也就是前馈神经网络。它通常由两层线性变换组成,中间接一个非线性激活函数。BERT 里用的是 GELU。如果没有 GELU,那么多层线性层叠加本质上仍然可以合并成一个线性变换,表达能力会受限。GELU 的作用就是引入非线性,让模型能够学习更复杂的特征组合。

可以简单理解为:

  • Self-Attention 负责 token 之间的动态交互
  • Softmax 让注意力分布变成非线性的动态权重
  • FFN + GELU 负责对每个 token 的表示做非线性变换

总结: BERT 的非线性主要来自 Self-Attention 中的 softmax,以及 FFN 中的 GELU 激活函数。Self-Attention 通过 softmax 产生输入相关的动态注意力权重,FFN 通过 GELU 引入非线性特征变换,两者共同增强了 BERT 的表达能力。

BERT 训练时使用的学习率 warm-up 测试是什么?为什么这么做?

​ BERT 训练时常用 learning rate warm-up 策略,也就是训练刚开始时不直接使用最大学习率,而是先从一个很小的学习率开始,在前面一小段 step 内逐渐线性升高到设定的峰值学习率,然后再按照学习率衰减策略逐渐降低。

​ BERT 原始训练中常见做法是 线性 warm-up + 线性 decay。这样做的好处有:

​ 第一,提高训练初期的稳定性。BERT 参数量大,训练刚开始时参数还比较随机,Adam 的一阶、二阶动量估计也还不稳定。如果一开始就使用较大的学习率,参数更新可能过大,导致 loss 震荡甚至训练发散。warm-up 可以让模型先用较小步子进入稳定训练状态。

​ 第二,适配 Transformer 和 Adam 优化器。Transformer 训练对学习率比较敏感,尤其是大 batch、大模型训练时。warm-up 能让优化器的统计量逐渐稳定,避免训练早期梯度更新过猛。

​ 第三,帮助后续更快收敛。warm-up 不是一直小学习率,而是先稳住训练,再逐渐升到较大学习率,让模型在稳定后能进行更有效的参数更新。之后再 decay,帮助模型逐步收敛到更好的解。

​ 需要注意的是,warm-up 的主要目的不是防止过拟合,而是 防止训练初期不稳定或发散。过拟合更多依赖数据规模、正则化、dropout、weight decay、early stopping 等方法来控制。

总结: BERT 的 warm-up 策略是在训练前若干 step 内,把学习率从很小的值线性升高到峰值,然后再逐步衰减。这样做主要是为了避免大模型在训练初期因参数随机、Adam 状态不稳定和学习率过大而训练震荡或发散,从而提升训练稳定性,并帮助后续更好收敛。

在BERT应用中,如何解决长文本问题?

​ BERT 处理长文本的主要问题是:原始 BERT 的最大输入长度通常是 512 个 token,超过这个长度就不能直接完整输入。所以实际应用中要根据任务选择不同策略。

​ 最简单的方法是 截断。比如文本分类任务中,如果关键信息通常出现在开头,可以直接保留前 512 个 token;如果开头和结尾都重要,也可以采用 head + tail 截断。这个方法简单高效,但缺点是可能丢失中间重要信息。

​ 第二种是 滑动窗口切分。把长文本按窗口切成多个片段,每个片段长度不超过 512,并设置一定 overlap,避免边界信息丢失。然后分别送入 BERT,得到每个 chunk 的表示或预测结果,最后再做 pooling、投票、最大值选择、加权融合等。阅读理解任务中常用这种方式。

​ 第三种是 分层建模。先用 BERT 编码每个句子或段落,得到 chunk-level 表示;再用一个上层模型,比如 Transformer、LSTM、Attention Pooling 或 MLP,把多个片段的表示融合成文档级表示。这种方法适合长文档分类、长报告理解等任务。

​ 第四种是 检索或筛选关键片段。如果任务是问答或信息抽取,可以先用 BM25、向量检索或规则方法从长文档中找到和问题最相关的片段,再把相关片段送入 BERT。这样能减少无效上下文,也更符合 RAG 思路。

​ 第五种是 使用长文本 Transformer 模型。比如 Longformer、BigBird、RoBERTa-Long 等,它们通过稀疏注意力、滑动窗口注意力、全局 token 等方式降低长序列计算成本,可以处理比 BERT 更长的输入。

总结: BERT 原生最大长度通常是 512 token,处理长文本时可以用截断、滑动窗口、分层建模、关键片段检索,或者换用 Longformer、BigBird 等长文本模型。实际选择取决于任务:分类可以用截断或分层,阅读理解常用滑动窗口,问答和知识库场景更适合先检索再编码。

MHA & MQA & GQA

MHA

Multi-Head Attention,也就是多头注意力,是 Transformer 里的核心模块。它的思想是:不要只用一个注意力头去建模 token 之间的关系,而是并行使用多个 attention head,让不同 head 在不同表示子空间里学习不同类型的依赖关系。

它的计算可以概括为:

MultiHead(Q,K,V)=Concat(head1,...,headh)WOheadi=Attention(QWiQ,KWiK,VWiV)\begin{aligned} MultiHead(Q,K,V)&=Concat(head_1,...,head_h)W^O \\ head_i&=Attention(QW_i^Q, KW_i^K, VW_i^V) \end{aligned}

其中,每个 head 都有自己独立的投影矩阵:WiQRdmodel×dkW^Q_i \in \mathbb{R}^{d_{model} \times d_k}WiKRdmodel×dkW^K_i \in \mathbb{R}^{d_{model} \times d_k}WiVRdmodel×dvW^V_i \in \mathbb{R}^{d_{model} \times d_v},以及WORhdv×dmodelW^O \in \mathbb{R}^{hd_v \times d_{model}}

它们会把输入的 Q、K、V 投影到不同的子空间中,然后分别做 scaled dot-product attention。最后把多个 head 的输出拼接起来,再经过一个输出矩阵 W^O 做融合。

这里有一个容易误解的地方:从工程实现上看,代码里经常是用一个大的线性层一次性算出所有 head 的 Q、K、V,然后再 reshape 成多个 head。比如输入维度是 d_model,输出仍然是 d_model,再切成 h 个 head,每个 head 的维度是 d_model / h

但从数学等价角度看,这其实等价于每个 head 都有自己的一组 W_i^Q、W_i^K、W_i^V。所以不能简单说“多头只是切分向量”,更准确地说是:先通过可学习的线性投影得到不同子空间的 Q、K、V,再把这些表示按 head 维度拆开,并行计算多组 attention。

为什么要做多头注意力?

第一,单头 attention 表达能力有限。Scaled Dot-Product Attention 本身主要是通过 Q 和 K 的点积计算相似度。如果只有一个 head,所有关系都要压到同一个注意力分布里,可能会把不同类型的信息混在一起。

第二,多头可以学习不同的关注模式。不同 head 有不同的投影矩阵,因此可以在不同语义子空间里计算相关性。有的 head 可能关注局部词语搭配,有的关注长距离依赖,有的关注主谓关系,有的关注指代关系。这样模型能从多个角度理解一句话。

第三,可以避免单一注意力分布的信息平均化问题。如果只有一个 attention head,它可能对多个位置分配平均权重,导致关键信息被稀释。多个 head 可以分别聚焦不同信息,最后再融合,表达会更丰富。

第四,计算量仍然可控。每个 head 的维度通常不是完整的 d_model,而是:dk=dmodel/hd_k = d_{model} / h 。 所以虽然有 h 个 head,但每个 head 的维度变小了,多个 head 合起来的总计算量和单头使用完整 d_model 的计算量大致相当。

总结: Multi-Head Attention 的本质是多组可学习的 Q/K/V 投影加多组 attention 并行计算。它让模型在多个子空间中学习不同的 token 关系,再把这些信息拼接融合,从而增强模型对语法、语义、位置和长距离依赖的建模能力。工程上可能用一个大线性层实现,但逻辑上等价于每个 head 有独立的 Q、K、V 投影矩阵。

MQA

MQA 全称是 Multi-Query Attention,最早由 Google 在 2019 年的论文《Fast Transformer Decoding: One Write-Head is All You Need》中提出,主要目的是提升大模型自回归解码时的推理效率。

在传统的 MHA,也就是 Multi-Head Attention 中,每个注意力头都有自己独立的 Query、Key 和 Value:

1
MHA:Q heads = h;K heads = h;V heads = h

而 MQA 会保留多个独立的 Query 头,但让所有 Query 头共享同一组 Key 和 Value:

1
MQA:Q heads = h;K heads = 1;V heads = 1

因此,MQA 的不同 Query 头仍然可以从不同角度计算注意力,但它们使用的是同一份 Key 和 Value。

为什么 MQA 能提升推理速度?

在自回归生成过程中,模型每生成一个 token,都需要保存历史 token 的 Key 和 Value,也就是 KV Cache。

MHA 中每个注意力头都有独立的 K 和 V,所以 KV Cache 的显存占用与 head 数量成正比。MQA 只保留一个 KV head,因此可以显著减少:

  • KV Cache 显存占用
  • 显存读取带宽
  • 解码阶段的数据传输量
  • 长上下文和大 batch 推理成本

需要注意,MQA 的主要优势不只是减少模型参数,而是 大幅压缩 KV Cache、降低内存带宽压力。在大模型逐 token 解码阶段,这一点尤其重要。

MQA 有什么缺点?

由于所有 Query 头共享同一组 K 和 V,模型在 Key/Value 表示上的多样性会下降。不同 head 无法独立学习完全不同的 K/V 子空间,因此相比 MHA,MQA 可能带来一定的模型质量损失。

可以简单理解为: MHA 表达能力强,但 KV Cache 大;MQA 推理效率高,但 K/V 表达能力受到一定限制。

什么是 Uptraining?

Uptraining 通常是把一个已经训练好的 MHA checkpoint 转换成 MQA 或 GQA 模型,然后使用少量数据继续训练。

典型过程是:

  1. 保留原模型的所有 Query 头。
  2. 对原来多个 K/V head 的参数做 mean pooling,将它们合并成一个或若干个 K/V head。
  3. 使用一小部分预训练数据继续训练,让模型适应新的注意力结构。

这个方法可以复用已有的 MHA 模型,不需要从头训练 MQA,从而降低训练成本,并恢复一部分结构转换造成的性能损失。

总结: MQA 保留多个 Query 头,但让所有头共享一组 Key 和 Value。它通过减少 KV head 数量显著压缩 KV Cache,提升自回归解码速度,但可能损失部分表达能力。工程上可以通过将 MHA 的 K/V 参数做平均池化,再进行少量继续训练的 Uptraining 方法,把已有 MHA 模型高效转换成 MQA 模型。

GQA

GQA 全称是 Grouped-Query Attention,分组查询注意力,由 Google 在 2023 年的论文《GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints》中系统提出。

GQA 可以理解为 MHA 和 MQA 之间的折中方案,主要目标是在尽量保持模型效果的同时,减少 KV Cache,提升推理效率。

三种注意力的区别主要在于 Query head 如何共享 Key 和 Value。

GQA(Grouped-Query Attention)

GQA 把多个 Query head 分成若干组,同一组内的 Query head 共享一组 Key 和 Value:

1
2
3
4
5
Query heads = H
Key heads = G
Value heads = G

通常满足:1 < G < H

例如模型有 32 个 Query head、8 个 KV head,那么每 4 个 Query head 共享一组 K/V。

因此:

  • GQA-1 只有一个 KV 组,等价于 MQA
  • GQA-H 有 H 个 KV 组,等价于 MHA
  • 中间的组数就是标准 GQA

GQA 的主要优势是: 它的模型质量通常比 MQA 更接近 MHA,同时 KV Cache 和推理开销又明显小于 MHA,因此在效果与效率之间取得了较好的平衡。

MHA 模型如何转换为 GQA?

已有的 MHA checkpoint 可以通过 uptraining 转换成 GQA,不需要完全从头训练。

具体做法是:

  1. 保留原有的 Query head。
  2. 将原来的 Key/Value head 按照 GQA 分组。
  3. 对每组 K/V head 的参数进行 mean pooling,合并成一个 KV head。
  4. 使用少量预训练数据继续训练,让模型适应新的注意力结构。

需要注意的是,mean pooling 是对 同一模型中每组 K/V head 的参数进行合并,不是融合多个模型 checkpoint。

总结: MHA 是每个 Query head 独享一组 K/V,效果好但 KV Cache 大;MQA 是所有 Query head 共享一组 K/V,推理快但可能损失精度;GQA 则让一组 Query head 共享一组 K/V,在 MHA 的效果和 MQA 的效率之间取得折中,因此成为当前大模型常用的注意力结构。

Flash Attention

FlashAttention 是一种 IO-aware 的精确 Attention 实现算法。它不改变标准 Attention 的数学定义,也不是稀疏注意力或近似注意力,计算结果与普通 scaled dot-product attention 基本一致。

它主要解决标准 Attention 中两个问题:

  1. N × N 注意力矩阵占用大量显存。
  2. 中间结果在 GPU 的 HBM 和片上 SRAM 之间频繁读写,造成较大的 IO 开销。

标准 Attention 通常依次计算:

1
2
3
S = QK^T
P = softmax(S)
O = PV

这里的 SP 都是 N × N 矩阵。传统实现会将它们写入显存 HBM,后续再读出来。序列越长,显存占用和数据搬运成本越高。

GPU 的 HBM 容量较大,但访问速度相对较慢;片上 SRAM 容量很小,但访问速度非常快。FlashAttention 的核心思想就是: 将 Q、K、V 切分成能够放入 SRAM 的小块,在 SRAM 中完成矩阵乘法、softmax 和加权求和,减少大型中间矩阵在 HBM 中的读写。

FlashAttention 如何分块计算 Softmax?

Softmax 通常需要知道整行的最大值和指数和,看起来必须一次读完整行。FlashAttention 使用 Online Softmax,逐块维护:

  • 当前已经处理数据的最大值
  • 当前指数和,也就是归一化因子
  • 当前累计的 Attention 输出

当读取下一个 block 时,再根据新的最大值修正之前的累计结果。因此,不需要一次性保存或读取完整的注意力分数矩阵,也能得到数学上等价的 softmax 结果。

反向传播如何节省显存?

普通 Attention 为了反向传播,通常会保存完整的 attention matrix。

FlashAttention 不保存这个 N × N 矩阵,只保存每一行的 softmax 归一化统计量以及最终输出。反向传播时,再根据 Q、K、V 和这些统计量重新计算所需的 attention block。

它采用的是: 用少量额外计算换取大量显存节省和更少的 HBM 访问。虽然存在重计算,但因为减少了慢速显存的数据搬运,整体速度通常反而更快。

复杂度有没有改变?

FlashAttention 的理论计算复杂度仍然是:O(N2d)O(N²d) , 所以它没有消除标准 Attention 的平方计算量。它主要优化的是:

  • 不显式存储完整的 N × N Attention 矩阵
  • 降低显存占用
  • 减少 HBM 与 SRAM 之间的数据搬运
  • 融合多个计算操作,减少 kernel 启动和中间结果读写

总结: FlashAttention 是一种精确且 IO 感知的 Attention 实现。它通过 tiling 把 Q、K、V 分块加载到高速 SRAM,利用 online softmax 增量计算注意力,并避免把完整的 注意力矩阵写入 HBM。反向传播时通过保存少量归一化统计量并重计算 attention block,进一步降低显存占用。它没有改变 Attention 的平方计算复杂度,但显著减少了显存读写,因此能同时提升速度并节省显存。