系统篇

03|注意力的深入追问:位置、掩码、多头与反向传播

系统篇约 7 千字1 组分步图解
本章目录

本章预备知识

掩码规定哪些位置可见,位置编码帮助区分顺序,多头把表示分组处理;反向传播则追踪最终误差怎样沿原计算回传。它们解决不同问题,不能靠一张架构图一起猜。

本章各题列出所需基础。可先完成相应基础课,也可通过自检确认掌握程度。

进入深读前的小检查:先画3个历史位置后再加2个位置的可见表:新位置3能看0到3,新位置4能看0到4。随后再读非方阵mask与缓存偏移。

术语带虚线时可点击速查。公式中的字母先看定义;不要用猜缩写替代推导。

本章把看起来类似的维度和概念拆开:序列索引、逻辑位置、缓存槽位不是一回事;Q头数、KV头数也不是一回事。理解这些差别,才能解释“shape完全对,生成却错了”的故障。

沿着对象与状态理解本章

先在可见性表里选一个查询行,决定哪些Key位置允许参与;再看如何缩放和混合。多头改变表示的分组方式,位置旋转改变Q/K的比较,反向传播计算参数的影响,三者不是同一个步骤。

下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。

动态图解 · F12

Mask是可见性表,不是额外语义

怎样防止读到未来,或者把PAD读成内容?

把注意力的分数表看成一张“谁可以读谁”的座位表。行代表接收者,列代表提供者;禁止格子不能参与概率分母。因果限制、PAD限制可以同时存在,但一个负责时间顺序,一个负责有效内容,不能互相替代。

因果当前位置不读取将来位置。
加性mask允许处加0,禁止处用−∞等价地排除。
绝对位置加上历史前缀后的位置,而不是当前小批次中的行号。

输入与适用条件:前3帧用3个有效位置;第4帧是明确的屏蔽数值对照;第5帧另设past=3、new=2展示缓存偏移。图内始终标出当前条件。

1 / 5 · 处理绝对位置0

第1步,共5步

窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。

Mask是可见性表,不是额外语义:处理绝对位置0行:当前Query;列:可被读取的KeyK0K1K2Q0100Q1110Q2111现在读第0行:1=允许,0=禁止。

位置不是数字大小比赛。因果规则只允许当前查询读取自己和更早的Key;后面的位置即使已经在训练输入数组中,也不能读。

允许Key位置 ≤ 0

默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。

为什么需要

防止训练偷看目标,并排除只为组批存在的填充内容。

怎样实现

先按实际位置和有效长度构造关系,再核对所用API布尔mask语义。

可能的代价与误解

不同API的True可能表示允许或禁止;全行无可见Key还需明确策略,不能直接做普通softmax。

如何判断是否做对

用能暴露未来泄漏的数据,比较正确掩码与故意写错偏移的结果。

Q009|因果mask、padding mask、分块解码mask到底如何构造?

核心回答

因果mask禁止读取未来位置;padding mask排除没有实际内容的补齐位置;分块解码还要把新块的局部索引映射到已有前缀后的全局位置。判断某个键可否被读取,应看逻辑位置和有效性,而不是只画一个左上角三角形。[TR][CACHE]

先看完整输入,再看缓存场景

一个4-Token序列,行是查询位置,列是键位置,1表示允许读取:

       k0 k1 k2 k3
q0     1  0  0  0
q1     1  1  0  0
q2     1  1  1  0
q3     1  1  1  1

这里下三角是因为查询与键从相同的全局位置0开始。现在已有3个Token的缓存,新来2个Token。Q长度是2,K长度是5;新查询的全局位置是3与4,正确mask为:

       old0 old1 old2 new3 new4
new3     1    1    1    1    0
new4     1    1    1    1    1

通式是key_position ≤ past_length + local_query_index。如果直接创建tril(ones(2,5)),第一行只会看old0,第二行只会看old0与old1,历史的大部分被错误遮住。它仍是一个合法的2×5矩阵,所以不会靠shape检查暴露问题。

再加入padding。两条序列长度分别为3和5,组批时短序列补齐。被补齐的键位置不应获得注意力权重;padding查询位置可能仍被算子计算,但不应作为有效答案或训练目标使用。左padding时,数组下标和真实Token位置更容易分离,位置编号应服从模型与实现约定,不能只用数组下标。

运行示例 · Q009

把因果、padding与loss mask画在同一个序列上

输入与条件。批内第二句是 [你,爱,PAD]。三个位置有效性为 [1,1,0];采用右补齐,真实Token位置0、1。

步骤与对象内部动作中间结果 / 新状态
1查询位置0因果限制只许看Key0可见 [1,0,0]。
2查询位置1可看Key0、1可见 [1,1,0]。
3PAD查询位置2其输出不用于这句话的预测/训练实现可计算后丢弃,也可有显式跳过策略。
4训练标签假设只评价前两个有效预测位置loss mask是 [1,1,0],它控制计分,不自动控制attention。
5有缓存时3个历史+2个新Token新的注意力表是2×5,行位置为3、4,不是0、1。

把过程连起来。三个mask都可能长得像0/1表,但分别限制能读什么、哪些数据是真实输入、哪些位置参与评分。

边界与反例。0/1在这里是本例“可见/不可见”的说明符;具体库的布尔mask可能约定相反。

依据与适用范围:[SDPA] [CACHE]。示例输入和场景由本书明确设定。

运行示例 · X07

同一张“允许访问表”,不同接口的布尔值为什么相反

输入与条件。两个Token的因果可见关系是:第0行只能看第0列;第1行可看两列。先定义语义允许表[[真,假],[真,真]]。

步骤与对象内部动作中间结果 / 新状态
1先写语义真表示“本例希望允许访问”这个定义独立于任何API。
2传入scaled_dot_product_attention按该接口文档,布尔True表示参与注意力直接传[[True,False],[True,True]]。
3传入MultiheadAttention的attn_mask该接口布尔True表示禁止访问必须传反表[[False,True],[False,False]]。
4加性掩码表达允许处加0,禁止处加−∞本例为[[0,−∞],[0,0]];仍需核对接口接受的dtype、形状与设备。

把过程连起来。不要把另一篇代码里的True直接抄到新接口。先画语义表,再按文档转换,最后做未来Token扰动测试。

边界与反例。此处对照的是所列PyTorch 2.14接口文档。以后版本或其他库必须按其文档重新确认,不能把布尔约定当成通用标准。

依据与适用范围:[SDPA] [MHA]。示例输入和场景由本书明确设定。

追问:训练时一次输入整段答案,不就是偷看答案吗?

不是,只要每个位置的可见范围正确。位置i的隐藏状态只能读到x≤i,拿它预测x(i+1)。标签存在于batch里不等于前向可以读取它。多个位置的预测可同时计算,因为正确前缀已经由训练数据提供。若mask删掉,训练loss可能异常漂亮,却学习了不符合部署条件的任务。

追问:两份文档拼接训练,只放一个EOS就完全隔离了吗?

不一定。EOS是序列内容标记,不自动禁止注意力跨文档边界。若目标是严格隔离样本,需要分段attention mask,并同步处理位置和loss范围。若允许跨文档上下文,则可以有不同策略;关键是明确训练目标,不能把“出现分隔符”当成确定性隔离。

追问:如何写一个能抓住mask错误的测试?

固定权重、关闭dropout,先保存前缀输出;然后只改变未来Token,前缀对应logits不应变化。再比较整段、逐Token和多种分块推理的输出,三者应在合理浮点容差内一致。故意移除mask或重置位置,测试应失败;否则测试很可能没有覆盖目标错误。

Q010|RoPE如何让注意力知道位置?为什么缓存位置不能每轮从零开始?

核心回答

RoPE把Q/K每一对分量按位置相关角度旋转,使两位置旋转后点积包含相对位置差。它不是简单把位置整数加到Token ID上。缓存推理必须保持新旧Token的逻辑位置连续或符合指定位置策略,否则相对关系被改变。[ROPE]

从二维旋转推导相对位置

对二维向量x=[a,b],位置p与频率θ对应:

R(pθ) x = [a cos(pθ) − b sin(pθ), a sin(pθ) + b cos(pθ)]

Q在位置p、K在位置t时,点积为(R(pθ)q)ᵀ(R(tθ)k)。利用R(pθ)ᵀ=R(-pθ)与旋转的组合性质,得到qᵀR((t-p)θ)k。因此位置信息通过差t-p进入相关性。高维向量按分量对应用不同频率,提供多尺度位置变化。

这并不意味着注意力只依赖位置差:q与k本身仍由文本内容和前层表示决定。RoPE提供相对位置结构,内容匹配仍由学习得到的投影负责。也不能说它让模型对任意长度天然泛化;超过训练分布的相位组合仍可能带来退化。

假设前缀长度P=100,新Token本应在位置100。如果错误地把它的Q/K按位置0旋转,而旧K仍按0到99存储,那么模型读到的“距离”已不是真实距离。张量长度、dtype、缓存拼接轴都可能正确,但logits已经偏离整段前向。

运行示例 · Q010

相对位置保持与缓存错位:同一组旋转算两遍

输入与条件。用二维旋转R(θ),q=k=[1,0]。设位置m的角度是m×90°,只用于展示相对位置性质。

步骤与对象内部动作中间结果 / 新状态
1比较位置0与1q0=[1,0],k1=[0,1]点积0。
2两者整体后移1位q1=[0,1],k2=[−1,0]点积仍为0。
3比较同位置q1与k1点积1,说明相对差0与差1不同。
4缓存正确处理新Token继续采用绝对位置2位置不因为进入新调用而重置。
5重置反例把位置2误当0k2误成[1,0],与位置0的比较从−1变1。

把过程连起来。固定频率旋转中共同平移不改变相对角度;错误重置位置则改变模型实际计算。

边界与反例。真实RoPE多坐标对、频率与扩展策略需按模型实现;示例不代表所有模型采用90°步长。

依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。

追问:可以把全部位置同时平移吗?

在理想纯RoPE点积中,同时给Q/K位置加同一偏移,会保留相对差。但完整模型可能还有其他位置相关机制、滑窗规则、特殊位置或缩放策略;已旋转的缓存也不能只改一个position counter就完成一致变换。数学上的整体平移性质不能直接当任意缓存截断实现的安全证明。

追问:长上下文扩展是怎么做的?

常见思路包括位置插值、频率缩放、特定频段调整和长序列继续训练。它们试图让超出原训练长度的位置模式更接近可学习范围,同时保留局部区分。局部精度与长距离覆盖可能发生取舍,因此需测检索、跨段组合、代码定位等真实任务,不只测“接口是否接受更多Token”。

追问:为什么通常旋转Q/K而不是V?

这种设计把相对位置信号主要放到读取权重的计算里,Value仍提供被混合的内容。不同架构可有其他方案;不能把这个实现选择扩展成数学上所有模型都必须遵守的定理。阅读源码时应检查缓存里保存的是旋转前还是旋转后的K,以及重用时是否重复旋转。

Q011|MHA、MQA、GQA到底少了什么?是不是把多头删掉了?

核心回答

MHA通常每个Q头有对应K/V头;MQA让多个Q头共享一组K/V;GQA在两者之间,让一组Q头共享一个K/V头。主要减少K/V投影输出和缓存量,不等于所有查询头变成同一注意力分布。[GQA][CACHE]

逐形状比较

设隐藏维度4096,查询头H=32,每头Dh=128。MHA有32个KV头;GQA假设只有8个KV头;MQA只有1个。忽略bias时:

对象 MHA GQA(8 KV头) MQA
Wq输出宽度 4096 4096 4096
Wk/Wv各自输出宽度 4096 1024 128
单层K缓存 [B,32,T,128] [B,8,T,128] [B,1,T,128]
每个Q头的注意力权重 各自计算 各自计算 各自计算

GQA中可以把Q头0—3分给KV头0、Q头4—7分给KV头1,以此类推。共享K不意味着Q相同;不同Q与同一K点积,仍可产生不同权重。共享V也不意味着输出相同,因为加权比例不同。

缓存空间从32组降为8组,理论上这部分减少为四分之一。但总显存还有权重、激活、工作空间和调度元数据,不会因此整机显存必然减少四倍。QKᵀ和AV仍需服务32个Q头;实际计算效率还依赖kernel如何避免重复加载共享KV。

运行示例 · Q011

保留4个查询头,但只缓存2组Key/Value

输入与条件。设B=1、T=3、查询头Hq=4、每头Dh=2。MHA有4个KV头,GQA设2个,MQA设1个。

步骤与对象内部动作中间结果 / 新状态
1MHAK和V各有 1×4×3×2=24 个数两份共48数。
2GQAK和V各有 1×2×3×2=12 个数共24数;查询头仍4个。
3设分组Q头0、1读KV组0;Q头2、3读KV组1每个Q有自己的查询向量,不是删掉两个Q头。
4MQA所有4个Q共用1组KV共12数,但每个Q算出的读取份额仍可不同。
5投影宽度模型宽度8时,GQA的K投影可输出4维Q投影仍8维;最终残差宽度不随KV组数自动减半。

把过程连起来。GQA主要减少K/V投影与状态规模,不等于把注意力的查询头数一起删除。

边界与反例。这是常见均匀分组配置;具体模型与内核布局可不同,已有MHA权重不能不经适配直接当GQA使用。

依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。

运行示例 · X06

4个Q头共享2个KV头,究竟怎样配对

输入与条件。设Q头数4、KV头数2,每头宽度2。采用连续分组:Q0/Q1用KV0,Q2/Q3用KV1。

步骤与对象内部动作中间结果 / 新状态
1写清映射Q0→KV0,Q1→KV0,Q2→KV1,Q3→KV1共享的是对应的K/V,而不是把四个Query平均。
2保持Query独立Q0=[1,0]、Q1=[0,1],同一键K0=[2,3]点积分别为2和3,即使共享键也可能得到不同注意力权重。
3计算本体每Token的K和V各有2头×2数共2×2×2=8个数;若4个KV头则16个数。
4还原输出每个Q头各输出2个数,再按头拼接得到8个数,之后由输出投影映射回模型的隐藏宽度。

把过程连起来。GQA减少KV存储,但并不把输出头数降成KV头数。要分别跟踪Q头、KV头和输出宽度。

边界与反例。组映射、头维度和输出投影依架构决定;本例明确采用连续分组,不讨论某一闭源模型的具体实现。

依据与适用范围:[SDPA]。示例输入和场景由本书明确设定。

追问:能否把一个训练好的MHA模型直接改配置成GQA?

不能只改头数。参数形状、权重组合和学习到的功能会变化。GQA论文研究了从多头检查点转换并继续训练的方式;这不是修改一个配置就获得等价模型。模型部署必须匹配权重与架构。

追问:为什么有的GQA代码还把KV重复成H个头?

这可能是为了兼容普通attention接口而做概念展开。若物理复制长缓存,会抵消内存收益;高效实现应通过映射、视图或专用kernel共享数据。阅读代码时要看实际内存分配与stride,而不是只看注释写了“共享”。

追问:MQA缓存最小,为什么不全部用MQA?

共享程度提高会改变表示容量与模型质量,硬件收益也受工作负载影响。GQA是在质量、训练成本与推理资源之间的一种折中。不存在不看模型与任务就永远最优的KV头数。

Q012|Attention如何反向传播?为什么自动求导正常不等于模型实现正确?

核心回答

反向传播把输出误差沿Value内容路径和Q/K权重路径传回。自动求导能忠实计算当前计算图的梯度,却不能知道你的mask、标签或维度是否表达了正确任务。必须把数学梯度检查和语义不变性测试分开。[TR]

沿计算图逐步求导

定义S=QKᵀ/√d、A=softmax(S)、O=AV,设上游梯度G=∂L/∂O。先对最后一步矩阵乘法:

dV = AᵀG
dA = GVᵀ

第一个式子表示某历史Value从所有读取它的查询位置累计误差;第二个式子衡量某个读取权重改变时损失如何变化。接着对softmax,某行概率a与上游g对应:

ds = a ⊙ (g − sum(a ⊙ g))

这里减的是整行加权和,不能省略。其结果各分量之和为0,与“给整行分数加常数不改变softmax”的性质一致。最后:

dQ = dS K / √d
dK = dSᵀ Q / √d

若Q/K/V由同一X投影而来,X梯度是三条支路分别乘投影转置后的和,再加上残差直接回传的梯度。多头的换轴、拼接在反向中要走对应的逆变换。

一个有用的诊断:只让第一位置输出参与损失,在因果mask下,未来K/V不应通过这一损失得到梯度。如果未来位置有非零依赖,可能mask或索引错误。这个测试关注任务语义,不只是数值求导。

运行示例 · Q012

对一个查询手算Attention的梯度,再用扰动检查

输入与条件。Q=[[0.4,−0.2]];K=[[1,0.5],[−0.5,1]];V=[[2,−1],[0.5,3]];两个Key都可见。上游梯度G=[[0.7,−0.3]],定义标量损失L=sum(O⊙G)。

步骤与对象内部动作中间结果 / 新状态
1前向算缩放点积、softmax与混合P约 [0.621278,0.378722],O约 [1.431916,0.514889]。
2内容路径dV=PᵀG两行约 [0.434894,−0.186383]、[0.265106,−0.113617]。
3份额路径dP=GVᵀ得到 [1.7,−0.55]。
4传过softmaxdS=P⊙(dP−sum(P⊙dP))约 [0.529406,−0.529406],和为0。
5传到Q/KdQ=dS K/√2,dK=dSᵀ Q/√2dQ约 [0.561520,−0.187173]。
6数值检查对Q/K/V的10个坐标逐个±1e−6最大绝对差约1.90e−10,核验当前公式导数一致。

把过程连起来。梯度分成“改读取份额”和“改被读取内容”两条路径。只验证dV不能证明Q/K学习正确。

边界与反例。有限差分能核对本例前向的导数,不能单独证明标签或mask符合任务;因此还需独立的因果反例测试。

依据与适用范围:[SDPA]。示例输入和场景由本书明确设定。

追问:有限差分怎样检查梯度?

对某输入维度j,把它分别改为xj+ε和xj-ε,用[L(x+εej)-L(x-εej)]/(2ε)近似导数。小矩阵用float64更容易定位错误。ε太大会有截断误差,太小则舍入误差占主导;不能仅通过不断放宽容差让测试通过。

追问:有限差分通过,是否证明模型对了?

它证明解析或自动梯度与当前前向函数一致;如果前向错误地允许看未来,两者仍可能一致。还需要标签移动、因果性、整段与缓存一致性等测试。因此“梯度正确”和“目标正确”是不同证据。

追问:推理时为什么不需要反向传播?

普通部署生成使用固定参数,只计算条件分布并选择Token,不需要计算损失和更新权重。长推理或多次工具调用增加的是前向计算与输入信息,不自动变成梯度训练。特定在线学习系统可以额外更新参数,但必须显式设计,不能从“模型越来越了解当前任务”推断发生了训练。

记录保存在本机浏览器。

输入关键词即可检索。

↑ ↓ 选择 Enter 打开Esc 关闭 · 仅本地搜索

阅读设置

阅读设置仅保存在本机浏览器,不上传任何数据。