本篇笔记基于 wdndev/llm_interview_note: 主要记录大语言大模型(LLMs) 算法(应用)工程师相关的知识及面试题 制作, 抽取了一些自认为面试时会被问到的内容, 用于补足个人薄弱的专业基础, 也许找个实习?

大语言模型基础

LLM 概念

当前主流的开源模型体系

  1. GPT: OpenAI 发布
  2. BERT: Google 发布
  3. XLNet: CMU 和 Google Brain 发布
  4. RoBERTa: Facebook 发布
  5. T5: Google 发布

Prefix LM 和 Causal LM

  • Prefix LM 前缀语言模型: 采用 Encoder-Decoder 的结构, Encoder 使用 Auto Encoding (AE, 自编码) 模式, Decoder 使用Auto Regressive (AR, 自回归) 模式. 待生成的 token 可以看到 Encoder 侧所有 token 和 Decoder 侧已生成的 token
  • Causal LM 因果语言模型: Decoder-Only 结构, 使用 Auto Regressive 模式, 根据历史 token 来预测下一个 token, 在 Attention Mask 这里操作

img

为什么现在的LLM都是Decoder only的架构? - 知乎

要明白 prefix LM, 要先知道 NLP 里的 Encoder-Decoder 结构:

  • Encoder 将输入转化为固定长度的向量, Decoder 将固定长度的向量转化为不定长度的文本输出
  • Encoder 的输出得到的是上下文向量, 这是解码器的初始输入, Decoder 的处理过程与 casual LM 是类似的, 只有 mask 不同
  • 所以严格来说, 上面两张图都是 Decoder 部分的 mask 表示, Prefix LM 左上角九个格子代表经过 Encoder, 在 Encoder 中互相看到过
  • 嗯, 现在才懂, 我是笨蛋

LLM 的训练目标

通常训练目标是最大似然估计 Maximum Likelihood Estimation (MLE), 即最大化模型生成训练数据中观察到的文本序列的概率

涌现能力

涌现能力: 模型在训练过程中能够生成出令人惊喜、创造性和新颖的内容或行为

涌现能力的产生原因:

  1. 任务的评价指标不够平滑. 当评价指标很严格时, 比如给四个 emoji 让猜电影名字, 在参数量很大时才能答对; 当评价指标比较宽松 (或者说离散的更 “连续”), 比如多选题, 任务效果就会有持续稳定的提升

  2. 复杂任务vs子任务: 子任务的平滑增长, 会带来复杂的整体任务的涌现

  3. Grokking (顿悟) 来解释涌现: 预训练数据是巨大的, 但是与相关问题的训练数据其实很少, 所以当训练数据/模型规模足够大时, 就能看到 Grokking 现象

    Grokking 现象: Grokking 现象是描述训练数据量较少的 ML 任务的,但是任务最小训练数据量必须达到一定的量,才会出现 Grokking 现象。这里有两点,一个是本身训练数据量少,另外是最小数据量要达到临界值。只有同时满足上面两个条件,才有可能出现Grokking现象。

为何现在的大模型大部分是Decoder only结构

为什么现在的LLM都是Decoder only的架构? - 知乎

为什么现在的LLM都是Decoder only的架构? - 知乎

  1. Encoder 的低秩问题: Attention 矩阵由一个 n×dn\times d 矩阵和一个 d×nd\times n 矩阵相乘后再加 softmax (n>dn> d) 得到, 这种形式的 Attention 矩阵因为低秩问题带来表达能力的下降. Decoder-Only 架构中, Attention 矩阵是一个下三角矩阵, 三角阵的行列式等于对角线元素之积, softmax使得对角线均为整数, 所以行列式为正, 即 Decoder-Only 架构的 Attention 矩阵满秩, 满秩由更强的表达能力
  2. 预训练任务难度问题: 每个位置能接触的信息比其他架构少, 要预测下一个 token 的难度更大, 当模型足够大, 数据足够多时, Decoder-Only 模型学习通用表征的上限更高
  3. Zero-Shot 性能问题: prompt 相当于隐式微调, 在Decoder-Only 中可以作用到 Decoder 的每一层, 这使得 Decoder-Only 在 in-context learning 上更有优势. 结果是, Decoder-Only 模型在没有数据作 tuning 时的 Zero-Shot 表现最好
  4. 效率问题/训练时间问题: Decoder-Only 支持复用 KV-Cache, 对多轮对话更友好
  5. causal attention 具有隐式的位置编码功能
  6. 依赖轨迹问题: 跟随 OpenAI 做出了很多工作

大模型架构介绍

  • 以BERT为代表的encoder-only
  • 以T5和BART为代表的encoder-decoder
  • 以GPT为代表的decoder-only
  • 以UNILM9为代表的PrefixLM(相比于GPT只改了attention mask,前缀部分是双向,后面要生成的部分是单向的causal mask%)

LLM 复读机问题

LLM 复读机问题 (LLMs Parroting Problem): 大模型生成文本时过度依赖输入文本的复制, 缺乏创造性和独特性. 回答问题时, 可能会简单地复制输入文本的一部分或全部内容

出现原因?

  1. 数据偏差: 一部分文本出现频率较高
  2. 训练目标的限制: 大模型训练通常基于自监督学习的方法训练, 这样的训练目标可能使模型更倾向于生成与输入相似的文本
  3. 缺乏多样性的训练数据
  4. 模型结构和参数设置: 比如生成策略, 温度系数

如何缓解?

  1. 多样性训练数据
  2. 引入噪声: 生成文本时, 引入一些随机性或噪声, 如采样不同的词或短语
  3. 温度系数调整: 较高的温度值会增加随机性
  4. Beam搜索调整: Beam搜索在生成过程中维护了一个候选序列的集合, 调整Beam大小和搜索宽度, 可以控制生成文本的多样性和创造性
  5. 后处理和过滤: 去除重复的句子或短语, 提高生成文本的质量和多样性, 可以使用文本相似度计算方法
  6. 人工干预和控制

如何让大模型处理更长的文本

  1. 分块处理: 长文本分割成短片段, 逐个输入处理
  2. 层次建模: 页分段, 段分句, 句分词
  3. 部分生成: 给 prompt, 让模型生成后续内容
  4. 注意力机制
  5. 模型结构优化

分词

中文分词难点

中文词语组合繁多, 分词容易产生歧义. 集中在分词标准, 切分歧义 (下雨天留客天留人不留) 和未登录词 (新词). 未登录词的影响最大

中文分词算法

主要分为基于词典的规则匹配方法和基于统计的机器学习方法

  1. 基于词典的分词算法: 本质是字符串匹配

  2. 基于统计的分词算法: 本质是序列标注问题. 将语句中的字, 按照他们在词中的位置进行标注. 标注有 B (词开始的一个字), E (词最后一个字), M (词中间的字, 可能多个), S (一个字表示的词)

    比如, HMM 隐马尔科夫模型, CRF 条件随机场, 深度学习

jieba 分词用法及原理

jieba 是一个准确度和速度都不错的开源项目

四种模式

  1. 精确模式: 将文本精确切割
  2. 全模式: 如果一句话有多重切割方式, 每一种都切割一遍. 切割后有冗余
  3. 搜索引擎模式: 精确模式基础上, 对长词语再次切分
  4. paddle 模式: 使用序列标注网络模型实现分词

懒了, 不看了

激活函数

激活函数作用

给模型引入非线性能力

  • Sigmoidtanh: 将输出限制在 (0, 1) 和 (-1, 1) 之间. 这二者适合做概率值的处理, 如 LSTM 中的各种门. 二者不适合做深层网络的训练, 否则会出现梯度的小事
  • ReLU 适合深层网络的训练, 无最大值限制

梯度爆炸和梯度消失

解决

  1. 截断梯度
  2. 残差链接
  3. normalization

Sigmoid

img

公式

σ(z)=11+ez\sigma(z) = \frac{1}{1+e^{-z}}

导数公式

σ(z)=σ(z)(1σ(z))\sigma'(z) = \sigma(z)(1-\sigma(z))

优缺点:

  • 优点:

    1. 平滑, 易于求导
    2. 取值范围是 (0, 1), 可用于求概率或分类问题
  • 缺点:

    1. 计算量大, 包含幂运算
    2. sigmoid 导数的取值范围是 [0, 0.25], 多次相乘后容易梯度消失
    3. sigmoid 的输出均值不是 0, 随着网络加深会改变数据的原始分布

Tanh

img

公式

tanh(z)=ezezez+ez=21+e2z1\tanh(z) = \frac{e^z-e^{-z}}{e^z+e^{-z}} = \frac{2}{1+e^{-2z}} - 1

导数公式

tanh(x)=1(tanh(x))2\tanh(x)' = 1-(\tanh(x))^2

tanh 函数可以由 sigmoid 函数经过平移和拉伸得到, 可以认为是基于 sigmoid 函数的一种改进

  • tanh 的输出范围是 (-1, 1), 解决了输出均值非 0 的问题
  • tanh 的导数取值范围是 (0, 1), 存在梯度消失问题, 但问题会比 sigmoid 好一些
  • 幂运算依然存在, 计算量比较大

ReLU 系列

ReLU 函数

img

优缺点

  • 优点:
    1. z>0 时, ReLU 激活函数的导数恒为常数 1, 避免了 sigmoid 和 tanh 的梯度消失问题
    2. 计算复杂度低, 没有幂运算
    3. 实验验证发现, 模型收敛的速度更快
    4. z<0 时, ReLU 激活函数的导数恒为常数 0, 保留了数据的关键信息, 将 z<0 的部分置为 0, 产生稀疏矩阵, 使模型具有鲁棒性
  • 缺点:
    1. z<0 时, ReLU 激活函数的导数恒为常数 0, 可能导致部分神经元不对输入数据做响应, 一部分参数不被更新
    2. 可能导致梯度爆炸, 解决方法是梯度截断
    3. ReLU 的输出不是 0 均值

Leaky ReLU

img

z<0 部分, 采用一个很小的斜率 0.01. 效果不稳定, 用得不多

GeLU

这个比较好玩, 我们想一下 ReLU 的设计思路, 输入值大于 0 时是恒等映射, 输入值小于 0 时是置零映射.

参考 ReLU 激活函数, 设计另一个包含恒等映射和置零映射的激活函数, 新的函数应当满足: 输入为一个较大的正值时, 希望为恒等映射; 输入为一个较小的负值时, 希望为置零映射. 只有这两种映射

ϕ(x)\phi(x) 为输入 xx 后输出 f(x)f(x) 的可能性, 函数应当满足:

f(x)=xϕ(x)+0(1ϕ(x))=xϕ(x)f(x) = x\cdot \phi(x) + 0\cdot (1-\phi (x)) = x\cdot \phi(x)

也即:

f(x)=xp(X<x)+0(1p(X<x))=xp(X<x)\begin{aligned} f(x) & =x \cdot p(X<x)+0 \cdot(1-p(X<x)) \\& =x \cdot p(X<x) \end{aligned}

img

优点

  1. 负值区域不为 0, 解决了 Dead ReLU 问题
  2. GeLU 函数处处连续, 光滑可导

Swish

img

公式 (这里 σ\sigma 为 Sigmoid 函数)

f(x)=xσ(x)f(x) = x\cdot \sigma(x)

导数公式

f(x)=f(x)+σ(x)(1f(x))f'(x) = f(x) + \sigma(x)\cdot (1-f(x))

特点:

  1. 没有上边界, 不会出现梯度消失问题, 和 ReLU 一样
  2. 有下边界, 可以产生更强的正则化效果
  3. 非单调
  4. 处处连续且可导, 容易训练

GLU

PaLM 和 LLaMA 中使用 SwiGLU 替代了 FFN

GLU(x)=xσ(g(x))\operatorname{GLU}(x)=x \otimes \sigma(g(x))

这里 g(x)g(x) 指的是向量 xx 经过一层 MLP 或卷积, \otimes 表示两个向量逐元素相乘, σ\sigma 表示 Sigmoid 函数

简单讲就是通过 Sigmoid 在趋近于 0 时截断函数, 接近于 1 时允许通过, 故称门控激活函数

语言模型

语言模型的经典定义是对 token 序列的概率分布

自回归语言模型

序列 x1:Lx_{1:L} 的联合分布 p(x1:L)p(x_{1:L}) 的常见写法是概率的链式法则

p(x1:L)=p(x1)p(x2x1)p(x3x1,x2)p(xLx1:L1)=i=1Lp(xix1:i1)p(x_{1:L}) = p(x_1)p(x_2|x_1)p(x_3|x_1, x_2)\cdots p(x_L|x_{1:L-1}) = \prod_{i=1}^L p(x_i|x_{1:i-1})

token 的分布有 xip(xix1:i1)1Tx_i\sim p(x_i|x_{1:i-1})^{\frac{1}{T}}, 这里 T0T\geq 0 是温度系数, 用于控制能从语言模型中得到多少随机性

"退火"这个术语来源于冶金学,其中热的金属会逐渐冷却以改变其物理性质。在这里,它类比的是对概率分布进行调整的过程。"退火"分布是通过将原始概率分布的每个元素都取幂 1/T ,然后重新标准化得到的新分布。当 T≠1 时,这个过程会改变原始概率分布,因此从"退火"分布中采样得到的结果可能与对每一步的条件分布应用 T 并进行迭代采样的结果不同。

大模型相关历史

信息理论, 英语的熵, n-gram 模型

信息论, 香农 TAT

熵与交叉熵

熵的信息意义: 将样本 pp 编码乘比特串所需要的预期比特数的度量. 熵越小, 表明序列的结构性越强, 编码的长度越短

交叉熵的信息意义: 使用由模型 qq 给出的压缩方案, 需要多少比特来编码样本 pp. 交叉熵的上界是熵

N-gram 模型

一个 N-gram 模型中, 关于 xix_i 的预测只依赖最后 n1n-1 个字符. 但是存在问题, 如果 nn 太小, 无法捕获长距离依赖, 如果 nn 太大, 统计上无法得到概率的有效估计. 这导致语言模型只能限制在语音识别和机器翻译等任务中

N-gram 模型在计算上极其高效, 但是统计上效率低下, 在短上下文长度中与另一个模型联合使用是很有用的

神经语言模型

LSTM, RNN, Transformer, nn 一直在变大

词性标注

句法分析

词向量 (Embedding)

大语言模型架构

Attention

对 Attention 机制的理解?

核心思想是显式的对重要部分动态加权, 关注序列中的重要部分, 忽略不重要的部分.

Self Attention 的计算步骤

  1. 对输入序列的每个位置, 计算其与其他位置间的相似度得分
  2. 对得分进行缩放处理, 防止梯度爆炸
  3. 将得分用 softmax 函数转换为注意力权重, 计算每个位置的加权和
  4. 用注意力权重对输入序列的所有位置进行加权求和, 得到每个位置的自注意输出

Attention(Q,K,V)=softmax=(QKTdk)V\text{Attention} (Q,K, V) = \text{softmax} = \left(\frac{QK^T}{\sqrt{d_k}} \right)V

Self Attention 计算中, 如何对 padding 位做 mask

QK在点积后, 先经过 mask 再进行 softmax, 因此要屏蔽的部分要在 mask 后输出为负无穷

Self Attention 和全连接层的区别

单一的全连接层其实就是 V, Q 和 K 共同产生 Attention Score, 这个分数其实就是给 V 一个权重

来一个比较形象的比喻吧。如果一个神经网络的任务是从一堆白色小球中找到一个略微发灰的,那么全连接就是在里面随便乱抓然后凭记忆和感觉找,而attention则是左手拿一个白色小球,右手从袋子里一个一个抓出来,两两对比颜色,你左手抓的那个白色小球就是Query。

Transformer

多头注意力机制中每个头为什么要降维

对每个头降维是为了增加模型的表达能力和效率

每个 head 是独立的注意力机制, 可以学习不同类型的特征和关系. 多个 head 可以并行地学习多种不同的特征表示, 从而增强了模型的表示能力

使用多个注意力头时, 注意力机制的计算复杂度会增加, 这使得 Transformer 在处理大规模输入时很耗时

为了缓解计算复杂度问题, 每个 head 上要降维, 输入向量通过线性变换被映射到一个较低的维度空间. (也就是生成QKV的三个映射)

Transformer 在哪里权重共享? 为什么可以共享?

Transformer 的权重共享指在堆叠的不同 Transformer 层中, 相同结构用相同的参数

  1. 嵌入层和输出层, 即 word2vec 和 vec2word 的过程
  2. 位置编码的权重, 不同位置的输入在不同层间有相同表示
  3. 编码器和解码器的词嵌入权重共享
  4. 解码器自注意力中的权重共享 (QKV 权重共享)

Transformer 的点积模型做缩放的原因

控制注意力权重的尺度, 避免在计算过程中出现梯度爆炸的问题. 在 softmax 后, attention 的分布接近一个 one-hot 分布, 这带来梯度消失问题, 导致训练效果差

解决方案:

  1. Q 和 K 内积后除以 d\sqrt{d}, 使 QK 的方差变为 1. 常规的 Transformer 如 BERT 里都是这样做的
  2. 不除以 d\sqrt{d}, 在初始化 Q 和 K 的全连接层时, 其初始化方差要多除以一个 dd. T5 采用了这样的方法

BERT

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

字粒度和词粒度都是用来文本表示的方法

  • 字粒度
    • 优点: 处理未登录词更方便. 字粒度可以处理任意字符串, 不需要像词粒度那样遇到未登录词就忽略或特殊标记. 字粒度对少见词和低频词可以学习更丰富的字符级别表示, 能捕捉细粒度信息
    • 缺点: 计算复杂度高, 需要更多的训练数据. 输入序列变长, 计算复杂度和内存消耗更多; 对少见词和低频词需要更多训练数据来学习有效的字符级别表示, 否则会过拟合
  • 词粒度
    • 优点: 计算效率高, 学习更稳定的词级别表示. 减少输入序列长度, 降低模型计算复杂度和内存消耗, 对高频词和常见词有更好的表示能力
    • 缺点: 处理未登录词较难. 只能忽略或采用特殊处理. 多音字等形态复杂的词汇难以捕捉细粒度信息

BERT 使用的是 Transformer 里的 Encoder 还是 Decoder

BERT 仅使用了 Transformer 里的 Encoder 部分, 对其进行了一些修改和自定义的预训练任务

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

Encoder 使用自注意力掩码和填充掩码 (padding mask, 处理句子变长问题), 此外 Decoder 还要使用 Encoder-Decoder 注意力掩码来避免未来位置信息的泄露,

查资料时看到一个讲 mask 挺好的博客
Transformer 的掩码主要分为三种

  1. 序列掩码 Sequence Mask. BERT 使用 MLM 双向掩码训练, 此时要隐藏输入序列的某些部分
  2. 前瞻掩码 Look-ahead Mask, 也称因果掩码或未来掩码. 自回归模型防止前面的 token 看到后面的 token, 即上三角掩码
  3. 填充掩码, Padding Mask. 处理句子变长问题

BERT 为什么要 mask 15% 比例的词?

经验尝试

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

BERT 最后一层 [CLS] 对应向量可以作为整句话的语义表示, 从而用于下游的分类任务. 这个无语义信息的符号能更公平地融合文本中各个词的语义信息

BERT 非线性的来源在哪里?

FeedForward Network 里的 GeLU 激活函数 和 Self-Attention 里的 softmax

BERT 训练时使用的学习率 warm-up 策略是怎样的? 为何要这样做?

warm-up 策略: 学习率在训练初期从小增大, 提高训练的稳定性, 加快模型收敛. 方法来自 ResNet, 设置如 StepLP, MultiStepLR, ExponentialLR 等函数

具体做法是, 在训练开始的若干个 step(也即一小部分训练数据的迭代次数) 内, 将学习率从一个较小的初始值增加到预定的最大学习率. 学习率线性变化.

学习率 warm-up 解决了 BERT 训练初期的两个问题

  1. 训练不稳定: 训练初期, 由于模型参数随机初始化和模型的复杂性, 模型处于不稳定状态, 难以收敛, 学习率较小模型训练较为稳定
  2. 避免过拟合: BERT 模型需要较长的训练时间来获得高质量的表示, 早期使用较大的学习率可能导致模型在训练初期过度你和训练数据, 降低模型泛化性能

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

  1. 截断与填充: BERT 输入为固定长度
  2. Sliding Window: 长文本分成多个短文本
  3. Hierarchical Model: 分层模型来处理长文本, 底层模型用于处理短文本, 不同片段的表示进行汇总或融合得到整个长文本表示
  4. Longformer, BigBird 等模型: 采用不同注意力机制以处理超长序列
  5. Document-Level Model: 更大的模型

MHA & MQA & MGA

MHA

多个头本质是多个线性变换层, MHA 允许模型共同关注来自不同位置的不同表示子空间的信息, 如果只有一个头, 平均值会削弱这个信息

以代码实现为例, 头数表示为 HH, 输入通道数为 CC, 每个头的通道数为 CC', batch 数为 BB, LL 代表序列长度. 下面的过程忽略了维度转置过程

Q×KT=(B,L,C)×(B,C,L)=(B,H,L,C)×(B,H,C,L)=(B,H,L,L)\begin{aligned} Q\times K^T &= (B, L, C) \times (B, C, L)\\ &= (B, H, L, C') \times (B, H, C', L)\\ &= (B, H, L, L) \end{aligned}

上面是多头注意力机制得到的 QKTQK^T, 这里 HH 代表不同的子空间, 所以每个头可以关注输入的不同部分, 从而提升模型的表达能力

MQA

MQA (Multi-Query Attention) 让所有头共享同一份 K 和 V, 只有每个头的 Q 不同, 从而减少参数量

上面的方法提升了推理速度, 但损失了精度. 后续有论文将多个 MQA 的 checkpoint 融合, 具体实现是对 key 和 value 的头进行平均池化, 再用少量数据 finetuning

GQA

GQA (Grouped-Query Attention) 是 MQA 的改进, 将 Q 分组, 每组共享一对 K 和 V. 具体实现与 MQA 相似

Flash Attention

Flash Attention 主要用于加速和节省内存

  1. 计算 softmax 时不需要全量数据, 可以分段计算
  2. 反向传播时不存储 attention matrix, 只存储 softmax 归一化系数

Flash Attention 的动机

知乎文章

过去很多 Efficient Transformer 工作关注减少 FLOPS, 但是计算速度并没有响应地减少, 归根结底是 MAC(Memory Access Cost 存储访问开销) 的问题. 不同硬件模块间的带宽和存储空间有明显差异. 以 A100 为例, SRAM 约 20MB, 但是吞吐量高达 19TB/s; 而 HBM(也即显存) 大小在 40~80GB, 但带宽只有 1.5TB/s. 我们平时常用 HBM, HBM 读写操作很频繁, 但 SRAM 使用率不高.

Flash Attention 希望将计算模块分解, 拆成很多小的计算任务, 从而利用 SRAM

Softmax Tiling

回顾下 softmax 的计算公式, 对于维度为 BB 的向量 xRBx\in \mathbb{R}^B

softmax(xi)=exij=1Bexj\mathrm{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^B e^{x_j}}

softmax 的指数容易很大, 在计算中容易溢出, 既有深度学习框架中大多使用 softmax 的稳定版本如下, 该版本与原始 softmax 等价

m(x)=max([x1,x2,,xB])f(x)=[ex1m(x),,exBm(x)]l(x)=if(x)isoftmax(x)=f(x)l(x)\begin{aligned} m(x) &= \max ([x_1, x_2, \dots, x_B])\\ f(x) &= [e^{x_1-m(x)}, \dots, e^{x_B-m(x)}]\\ l(x) &= \sum_i f(x)_i\\ \mathrm{softmax}(x) &= \frac{f(x)}{l(x)} \end{aligned}

直觉上看, softmax 难以分块计算是因为分母指数求和项依赖于输入的每一个值

下面介绍 softmax 的分块计算方法:

  1. 考虑一个大小为 2B 的向量 xR2Bx\in \mathbb{R}^{2B}, 将其分为两部分 x1x_1x2x_2
  2. 按照上面稳定版 softmax 的计算方法, 得到 x1x_1 的softmax 结果
  3. 保存 m(x1)m(x_1)l(x1)l(x_1), 再额外保存两个全局标量 mmaxm_{max}lalll_{all}, 此时全局标量与局部标量的值是相同的
  4. 计算 x2x_2 的 softmax 结果, 然后更新 mmaxm_{max}lalll_{all} 的值, 新值记为 mmaxnewm_{max}^{new}lallnewl_{all}^{new}
  5. mmaxnewm_{max}^{new} 显然易得, 经计算另一个新值为 lallnew=emmaxmmaxnewlall+emx2mmaxnewl(x2)l_{all}^{new} = e^{m_{max}-m_{max}^{new}}l_{all} + e^{m_{x_2}-m_{max}^{new}}l(x_2)

    下面是 lallnewl_{all}^{new} 的推导过程
    我们计算得到 l(x2)l(x_2), 这是局部的值, 从此处更新到全局需要用到 mmaxnewm_{max}^{new}
    能计算得到 l(x2)=ie(x2)im(x2)l(x_2) = \sum_i e^{(x_2)_i} - m(x_2), 导致该值为局部值而非全局值的原因是指数项被减数为局部值, 所以要将该 max 值替换为全局值. 所以有以下变换

    lnew(x2)=l(x2)emmaxmmaxnew=ie(x2)im(x2)em(x2)mmaxnew=ie(x2)immaxnew \begin{aligned} l^{new}(x_2) &= l(x_2)\cdot e^{m_{max}-m_{max}^{new}}\\ &= \sum_i e^{(x_2)_i} - m(x_2) \cdot e^{m(x_2)-m_{max}^{new}}\\ &= \sum_i e^{(x_2)_i - m_{max}^{new}} \end{aligned}

    此时 l(x2)l(x_2) 更新为全局值. 简言之, 要把某个 ll 更新为全局值时, 只要将其乘以一项 emmmaxnewe^{m-m_{max}^{new}}, 这里 mm 为当前 ll 对应的最大值, mmaxnewm_{max}^{new} 为当前最大值

  6. 基于以上方法, 也可以直接更新 softmax 值, softmax(new)(x2)=softmax(x2)l(x2)em(x2)mmaxnewlallnew\mathrm{softmax}^{(new)}(x_2) = \frac{\mathrm{softmax}(x_2)\cdot l(x_2)\cdot e^{m(x_2)-m_{max}^{new}}}{l_{all}^{new}}

    类似过程, 不赘述 (其实是看累了\doge)

上述是一个增量计算的过程. 先计算一个分块的局部 softmax 值, 存储起来, 处理完下一个分块, 然后根据此时新的全局最大值和全局指数求和项来更新旧的 softmax 值. 再处理下一个分块, 然后更新

m(x):=maxi xi f(x):=[ex1m(x)exBm(x)] (x):=if(x)i softmax(x):=f(x)(x)m(x):=\max {i} ~ x{i} \ f(x):=\left[\begin{array}{llll}e^{x_{1}-m(x)} & \ldots & e^{x_{B}-m(x)}\end{array}\right] \ \ell(x):=\sum_{i} f(x)_{i} \ \operatorname{softmax}(x):=\frac{f(x)}{\ell(x)}

Flash Attention 的算法流程

需要做到

  1. 在不访问整个输入的情况下计算 softmax; 将输入分块, 在输入快上多次传递, 以增量方式执行 softmax 缩减
  2. 后向传播中不能存储中间注意力矩阵, 通过存储归一化因子来减少 HBM 内存的消耗

Flash Attention 算法流程图

Transformer

Transformer 与 RNN

其实是 Attention + Point-Wise MLP 与 RNN 的区别. Attention 对输入做一个加权和, 加权和进入 point-wise MLP, 得到每个输入点的输出, 这里 Attention 相当于聚合了整个序列的信息; RNN 是把上一时刻的信息输出传入下一个做输入. 二者都实现了语义空间的转换, 都在关注如何有效使用序列的信息

为何使用多头注意力机制

多头保证 Transformer 注意不同子空间的信息, 可以捕捉更丰富的特征信息, 类比 CNN 中同时使用多个滤波器的作用.

为什么 Q 和 K 使用不同的权重矩阵生成, 为何不能用同一个值自身点乘

  1. QKV 不同可以保证在不同空间进行投影, 增强表达能力, 提高泛化能力
  2. softmax 函数的性质决定, Attention 做的是一个 soft 版本的 argmax 操作, 得到的向量接近一个 one-hot 向量; 如果 QK 相同, 那么模型大概率会得到一个类似单位矩阵的 attention 矩阵, 这样自注意力就退化成一个 point-wise 线性映射

Attention 为何选择点乘而不是加法? 计算复杂度和效果上有什么区别

  1. 点乘操作可以通过矩阵乘法的方式优化, 可以并行化加快计算过程; 加法操作还需要激活函数等非线性计算, 相同计算复杂度下开销较高. 复杂度对比: 点乘和加法的注意力复杂度都是 O(d)O(d), dd 为向量维度
  2. 数学上, 点乘是衡量两个向量相似度的自然方法, 这种相似性直接影响了注意力权重的大小, 直接且高效

为什么 softmax 前要对 attention 进行 scaled (为什么除以 dk\sqrt{d_k})

  1. softmax 内计算量级较大时, 输出近似 one-hot 编码形式, 导致梯度消失
  2. 为什么是 dk\sqrt{d_k}? Q K 各分量独立同分布, 均值为 0, 方差为1, 则 QK 点积均值为 0, 方差为 dkd_k. 统计上, 要让 QK 点击方差控制在 1, 需要将其除以 dk\sqrt{d_k}, 让 softmax 更平滑

考虑两个随机变量 uuvv, 长度均为 dd, 它们的元素都来自均值为 0, 方差为 1 的独立分布. 对于向量的元素 uiu_iviv_i 来说, 点积为 uiviu_iv_i. 我们来计算这一项的期望值和方差

期望值有 E[uivi]=E[ui]E[vi]=0E[u_iv_i] = E[u_i]E[v_i] = 0

方差有

Var(uivi)=E[(uivi)2](E[uivi])2E[ui2]=E[vi2]=1Var(uivi)=1Var(u_iv_i) = E[(u_iv_i)^2] - (E[u_iv_i])^2\\ E[u_i^2] = E[v_i^2] = 1 \\ Var(u_iv_i) = 1

因此向量 uuvv 的点积是这些 uiviu_iv_i 项的总和 uv=i=1duiviu\cdot v = \sum^d_{i=1}u_iv_i. 均值期望为 E[uv]=d0=0E[u\cdot v] = d\cdot 0 = 0; 方差期望是 Var(uv)=d1=dVar(u\cdot v) = d\cdot 1 = d., 因此, Q 和 K 点积的方差与 dkd_k 成正比

再回顾下基础知识, 一个方差为 σ2\sigma^2 的随机变量 XX 乘以一个常数 aa 后, 其方差变为 a2σ2a^2\sigma^2. 因此, 为了让 Q 和 K 点积的方差控制在 1, 需要将其除以 dk\sqrt{d_k}

计算 attention score 时如何对 padding 作 mask 操作

padding 位置的 attention score 为负无穷 (一般为 -1000), 使得 softmax 后的值接近 0, 从而不参与加权和计算

为何多头注意力要对每个 head 降维

对每个头降维是为了增加模型的表达能力和效率

每个 head 是独立的注意力机制, 可以学习不同类型的特征和关系. 多个 head 可以并行地学习多种不同的特征表示, 从而增强了模型的表示能力

使用多个注意力头时, 注意力机制的计算复杂度会增加, 这使得 Transformer 在处理大规模输入时很耗时

为了缓解计算复杂度问题, 每个 head 上要降维, 输入向量通过线性变换被映射到一个较低的维度空间. (也就是生成QKV的三个映射)

Transformer 位置编码的优点和缺点

self attention 是位置无关的, 无论句子顺序如何, 计算出的 token 的 hidding embedding 是一样的. transformer 用固定的 positional encoding 来表示 token 在句子中的绝对位置信息

其他位置编码技术, 优缺点?

相对位置编码 (RPE)

  1. 计算 attention score 和 weighted value 时各加入一个可训练的表示相对位置的参数
  2. 生成多头注意力时, 将相对位置编码加入到 Q 和 K 的点积中, 使得绝对位置转换为相对