02|进入Decoder:张量形状、RoPE、GQA与KV缓存的正确性
目标:不用“注意力会关注重要内容”这种比喻,直接解释一次前向计算;能实现和检验整段、逐Token、分块三种推理的一致性。实验:
numerics.Decoder。
本章目录
沿着对象与状态理解本章
先画每一轴和可见性,再推多头位置变换;缓存正确性首先是前缀和位置条件。
下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。
Mask是可见性表,不是额外语义
怎样防止读到未来,或者把PAD读成内容?
把注意力的分数表看成一张“谁可以读谁”的座位表。行代表接收者,列代表提供者;禁止格子不能参与概率分母。因果限制、PAD限制可以同时存在,但一个负责时间顺序,一个负责有效内容,不能互相替代。
输入与适用条件:前3帧用3个有效位置;第4帧是明确的屏蔽数值对照;第5帧另设past=3、new=2展示缓存偏移。图内始终标出当前条件。
1 / 5 · 处理绝对位置0
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
位置不是数字大小比赛。因果规则只允许当前查询读取自己和更早的Key;后面的位置即使已经在训练输入数组中,也不能读。
允许Key位置 ≤ 0默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
防止训练偷看目标,并排除只为组批存在的填充内容。
怎样实现
先按实际位置和有效长度构造关系,再核对所用API布尔mask语义。
可能的代价与误解
不同API的True可能表示允许或禁止;全行无可见Key还需明确策略,不能直接做普通softmax。
如何判断是否做对
用能暴露未来泄漏的数据,比较正确掩码与故意写错偏移的结果。
1. 本书实现的架构与原论文不是一回事
2017年Transformer论文提出编码器—解码器架构;本书数值实验是现代风格的仅解码器教学模型,采用前置RMSNorm、RoPE、GQA与SwiGLU。组合这些机制是为了暴露常见实现边界,不表示任何某款闭源模型正好使用相同配置。[N07][N08][N09]
设批大小B,序列长度T,隐藏宽度d,查询头数h,KV头数g,每头宽度dh=d/h,且h可被g整除。随机初始化实验取d=16,h=4,g=2,共两层。小数字允许逐个打印形状,不能据此评估大模型能力。
Token IDs [B,T]
Embedding [B,T,d]
Q = norm(X) Wq [B,T,h×dh] → [B,h,T,dh]
K,V [B,g,T,dh]
按组供Q头读取的K,V [B,h,T,dh](概念视图,不必物理复制)
QKᵀ / √dh [B,h,T,T]
softmax(scores+mask) V [B,h,T,dh]
合头与输出投影 [B,T,d]
残差 + 前馈 + 残差 [B,T,d]
词表投影 [B,T,V]
逐行核对形状比背诵组件名称重要。例如错误地在头维求softmax,形状仍合法,却把“从哪些历史位置取信息”变成了“不同头之间竞争”。这样的错误可能不会触发异常,只会悄悄改变模型。
2. 一层内部到底怎样流动
RMSNorm对最后一维计算均方根:norm(x)=x/sqrt(mean(x²)+ε) ⊙ γ。它与减均值的LayerNorm不同。前置归一化的残差结构可写成u=x+Attn(norm(x)),y=u+FFN(norm(u))。残差为信息与梯度提供直接路径,但不能简单声称保证任意深度训练稳定。
SwiGLU前馈结构在本实验写为(silu(uWgate) ⊙ uWup) Wdown,其中silu(a)=a×sigmoid(a)。它对每个位置独立进行非线性变换;注意力负责位置间混合。把前馈层想象成只有检索、没有计算的静态事实库是不准确的,模型行为来自全部层共同作用。
注意力分数除以√dh的直觉来自点积方差:若各分量独立、均值零、方差一,点积方差随dh增长;缩放使初始化附近的分数尺度更温和。真实训练后这些假设并不严格成立,但公式不是随便选的装饰项。
3. 因果掩码的语义与负对照
完整前向时,第i个查询只能看到键位置j≤i。非法位置加负无穷,softmax后权重为零。测试应当改变位置k之后的输入,断言位置k之前输出不变。若把因果掩码删掉,相同测试应该失败;这称为负对照,证明测试有能力抓住目标错误。
全被遮住的一行是特殊情况。对一行全负无穷直接做“减最大值”会出现−∞−(−∞)并产生NaN。本书实现显式拒绝这种输入;生产算子可能定义零输出或采取其他约定,但调用方必须知道该契约。不能仅因普通样本可运行,就认为padding和空样本已经处理正确。
4. RoPE为什么使位置进入相关性
对每对分量(a,b),位置p对应旋转角pθ:
R(pθ)[a,b] = [a cos(pθ) − b sin(pθ), a sin(pθ) + b cos(pθ)]
(R(p)q)ᵀ(R(t)k) = qᵀR(t−p)k
第二式来自旋转矩阵的正交性与角度相加性质,说明旋转后的点积能含有相对位置差。多个分量对使用不同频率,使位置信号分布在不同尺度。实验检查旋转不改变每一对向量的范数;但范数保持只能证明旋转实现的一部分性质,不证明长上下文外推能力。[N09]
缓存推理时,已有前缀长度为P,新块长度为C,新块位置必须是P…P+C−1。把每次调用位置重置成零,会改变新查询与旧键的相对位置,因此虽然cache长度和张量shape正确,输出仍错误。wrong_offset=True故意制造这个错误,数值测试应该检测到。
RoPE扩展并不意味着“把最大位置参数改大就获得可靠长上下文”。位置插值、频率修改、训练长度和数据都会影响行为。对具体模型应使用其受支持的配置,再做长范围信息组合任务,而不只是检查能否接受长输入。
5. KV缓存究竟缓存了什么
在因果模型中,追加新Token不会改变旧位置在各层的表示:旧位置不能看到未来。因此旧位置投影后的K、V可以保存,新位置只计算自己的Q、K、V,并读取历史缓存。它缓存的是该层、该前缀、该模型参数下的中间张量,不是一个与输入无关的知识库。
每层新块处理的掩码不是简单C×C三角矩阵,而是C×(P+C):新块第i行允许键j≤P+i。如果套用未对齐的“左上角三角”,就会让新Token看不到本应可见的旧前缀。完整推理、逐Token推理、任意分块推理都应得到容差范围内相同logits。
实验中缓存存储旋转后的K和原投影V。另一些架构可能有不同缓存表示,不应套用这个假设。模型权重、LoRA适配器、模板前缀、位置策略变化,都可能使已有cache不能复用;缓存命中条件不是字符串包含相同词语,而是相关计算状态匹配。
6. GQA减少什么,不减少什么
MHA中每个Q头通常拥有对应K、V头;GQA让一组Q头共享一个K、V头,MQA可看作只有一个KV头的极端。h不变而g减少,缓存与K/V投影输出规模下降,Q头仍可通过不同Q表示产生不同注意分布。[N08]
KV内存近似为2×层数×批大小×序列长×KV头数×每头维度×每元素字节。教学假设32层、1条32768长序列、8个KV头、每头128维、2字节,结果是4GiB;若KV头变成32,则16GiB。这里没有计分页元数据、对齐、碎片、临时buffer或量化尺度,也不是某款服务的实际用量。
代码为了可读性用repeat扩展共享KV;高性能实现应避免无意义复制。算法概念减少的内存,可能被天真的张量操作抵消,这正是“懂论文”与“写出有效工程实现”的差距。
批内真实长度不同,缓存位置不能只看补齐长度
输入与条件。样本A真实长3,B真实长2,右补齐到3。要继续生成时,B的有效历史只有2个Token。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1记录长度 | A=3,B=2 | PAD不是有效的历史词。 |
| 2选预测位置 | A取第2位置,B取第1位置 | 错误地都取最后一格会读B的PAD表示。 |
| 3位置编号 | B的新真实Token应延续其有效位置 | 不能因为整批T=3就任意跳过其序列位置。 |
| 4缓存布局 | 某实现可保留PAD槽位,但必须正确mask与跟踪位置 | 物理槽数和语义位置可以不同。 |
| 5验证 | 逐条无padding运行与批处理比较 | 应在声明的推理设定下得到一致有效结果。 |
把过程连起来。批处理增加的是实现复杂度,不应改变每条输入独立计算的语义。
边界与反例。服务可能用左padding、打包或分页处理;本例不提供所有框架统一的position_ids公式。
依据与适用范围:[CACHE]。示例输入和场景由本书明确设定。
7. 实现验收
阅读Decoder.forward时应定位:归一化、投影、RoPE的位置、缓存拼接轴、非方形mask、softmax轴、合头顺序、前馈残差。每项都应对应至少一个不变性或负对照。
验收题:若整段和缓存输出不同,先查什么?先检查dropout是否关闭、参数是否一致、位置偏移与mask对齐,再检查dtype和数值容差。不能仅通过放宽容差掩盖逻辑错误。若误差因长度突然增大,还应检查位置上限、cache截断、padding与滑窗规则。高精度教学结果只是基线,低精度核需另外定义可接受误差及最终任务退化范围。