Appearance
第7章 · Attention 与 Transformer Block
因果性和 shape 测试不自动推出学习者掌握。
先修:第6章的 shape、normalization 和梯度对拍。
本章目标
- 从 Q/K/V 推导 scaled dot-product attention、causal mask 和多头拆分。
- 手写 pre-norm 残差、FFN、位置表示和最小 decoder-only Transformer Block。
- 用因果性、head reshape、消融和参考实现对拍证明每个边界,而不是只看输出形状。
公式与 shape
单层 attention:
其中未来位置的 M 为 -∞(或等价的布尔屏蔽)。典型 batch/head 形状如下:
| 对象 | shape | 说明 |
|---|---|---|
hidden X | (B, T, D) | D = H × d_head |
| Q/K/V | (B, H, T, d_head) | head 拆分后保持 token 顺序 |
| score | (B, H, T, T) | 第三个轴 query,第四个轴 key |
| mask | (T, T) 或可广播形状 | 位置 i 只能看到 j ≤ i |
| attention output | (B, H, T, d_head) → (B, T, D) | concat 后过 output projection |
| block output | (B, T, D) | residual 不改变 shape |
数学桥接:第2章 → 第7章
第2章 §4 的 dot_product(a, b) = Σ a[i]·b[i] 就是 attention score 的核心:
除以
第2章 §8 的 `relu(x) = \max(0, x)$ 是 Transformer FFN 的基础激活函数(虽然 LLaMA 用 SwiGLU,但 ReLU 是最小理解单元)。
前端类比:attention = Array.map(query => keys.map(key => dot(query, key) / sqrt(d_k)))——每个 query 和所有 keys 做点积(CSS 颜色相似度),然后 softmax 归一化为权重,最后加权求和 V。
位置从哪里来:sinusoidal 与学习式位置编码
到目前为止的 attention 只依赖 token 内容:把输入序列打乱,
1. Sinusoidal(正弦位置编码,Attention Is All You Need):零新增参数,把位置
低频通道随位置缓慢变化、高频通道快速振荡,合起来像位置的“多进制计数”。它有一条可直接验证的代数性质:同一间距的内积与绝对位置无关,
即位置内积天然携带“相距多远”的信息。仓库已有实现并被 python/tests/test_blocks_opt.py 覆盖,可直接对拍:
python
from llm_core.blocks import sinusoidal_positional_encoding
pe = sinusoidal_positional_encoding(seq_len=16, d_model=32) # (16, 32)
d1 = [pe[p] @ pe[p + 1] for p in (0, 5, 10)] # 间距 Δ=1,三个不同起点
d5 = [pe[p] @ pe[p + 5] for p in (0, 5, 10)] # 间距 Δ=5
# 实测:d1 三个值均为 15.313649(spread < 2e-15),d5 均为 11.777383(spread 0)
# → 同距内积与起点无关;不同 Δ 给出不同值2. 学习式位置编码(learned PE,GPT-2 路线):直接学一个位置表 blocks.py 的 apply_token_and_position 就是这个加法)。好处是位置模式可以按任务学习;代价是上下文上限被
为什么第7章的 block 暂时不含它:本章要把 QKV、causal mask、residual 的契约隔离出来单独对拍;fixture 用互异 token,加入位置向量只会稀释 mask 写反、head 轴混淆这类故障的信号。但要明确记账:没有位置编码的模型对顺序不敏感(mask 除外)——这是刻意留白,不是遗漏。
衔接第9章:sinusoidal 的“内积只依赖间距”暗示了更彻底的做法——不把位置加进 embedding,而是直接旋转 Q/K,让相对位置成为 attention 的内建性质。那就是第9章的 RoPE。
交互观察
交互:因果 Attention 权重
用玩具向量演示「当前 token 该看谁」。真实模型里 Q/K/V 是学出来的。
完整权重矩阵(行=query,列=key)
the
cat
sat
the
1.0
cat
0.5
0.5
sat
0.3
0.3
0.3
把 query 位置移到句子中间,观察未来列始终为零权重;再用 SoftmaxDemo 解释缩放和温度如何改变权重尖锐度。
交互:Softmax + Temperature
把 logits 看成「未归一化分数」。Temperature 越高分布越平,越低越尖。
从零实践
先实现单头 attention,输出 score、mask 后 score、softmax 权重和加权和,保存中间张量。
将
D拆成H × d_head,用小随机矩阵对拍 head reshape 前后元素顺序。组装 pre-norm
x + Attention(Norm(x))和x + FFN(Norm(x));明确激活和 projection shape。运行当前入口:
bashPYTHONPATH=python python -m pytest python/tests/test_attention_gpt.py -q PYTHONPATH=python python -m pytest python/tests/test_transformer_contract.py -q PYTHONPATH=python python -m pytest python/tests/test_attention_ablation.py -q
当前 attention.py、blocks.py、gpt.py 和 transformer.py 已存在,attention_ablation.py 与 test_attention_ablation.py 补齐了 scale、mask、residual 消融和 future-token 扰动。固定合成 fixture 的实测值为:causal prefix future perturbation 0.0、去 mask 后 10.799219943491902,去 scale 的最大权重差 0.2811741490082953,去 residual 的最大输出差 1.7321348424395848。这些是本地 NumPy 契约证据;learner 的独立 clean-room 对拍仍是本章 gate。
L3 clean-room handoff
本章的目标不是在现有模块上补几行代码。关闭参考实现后,从 clean_room/transformer_block.py 的冻结接口重写 QKV projection、causal attention、head 合并、pre-norm residual 和 FFN;不得导入课程参考实现。第19章的行为 runner 会检查 token → logits → loss/update → generate 的完整主链,因此这里必须保留 shape 草图、一次 future-leak 或 head-axis 故障的失败前/修复后记录,以及与逐头参考的对拍。接口合同可先检查:
bash
pnpm clean-room:contract故障注入与预期信号
本表是本章唯一的故障注入权威清单;「动手实验」一节不再另列第二份。
| 注入 | 预期失败信号 | 修复后证据 |
|---|---|---|
| residual 分支 shape 或顺序错误 | block 输出维度错或训练信号断裂 | residual/Norm contract 和消融报告 |
| 忘记除以 | softmax 输出趋近 one-hot、梯度消失 | 点积后立刻缩放,scale 消融对拍通过 |
| mask 加 0 而不是负无穷 | 未来位置概率非零、未来信息泄漏 | 遮罩位置填极小负值再相加,future-token 扰动差回到 0 |
| mask 上下三角写反 | 当前 token 被未来位置主导 | 因果遮罩取上三角为不可见区域,因果性测试通过 |
| 多头 reshape 时 head 与序列维混淆 | 拼回 shape 报错或注意力加权错位 | head 维先移到 batch 维再做 matmul,reshape 对拍通过 |
论文与延伸
- Attention Is All You Need(Vaswani 等,2017)
- 选读:RMSNorm 的 Root Mean Square Layer Normalization。
前端/Agent 迁移
attention 是受上下文窗口约束的动态路由:每个 query 选择哪些 key/value,类似组件依赖图中的加权读取,但它本身不保证可解释。Agent 的上下文组装应明确“可见消息集合”和“被禁止读取的未来/未授权状态”。
口述与自测(不看资料,5–10 分钟)
- 以
(B, T, D)为起点,逐步口述 Q/K/V、score、mask、softmax、head concat 和 output projection 的 shape;说明1/√d_k的作用。 - 结合 Attention Is All You Need,解释 causal mask、residual、normalization 和 FFN 分别解决什么;为什么”未来扰动不改变 prefix”是必要但不充分的实现证据。
- 用第2章的
dot_product([1,2,3], [4,5,6]) = 32解释 attention score 的几何含义:两个向量方向相同时点积最大,正交时为 0。 - 说明为什么
1/√d_k是必需的:如果,两个随机向量的点积期望约为多少?不缩放时 softmax 的梯度会怎样?
实验与参考
动手实验
用 pytest 把缩放点积注意力的三个核心性质钉死:每行 softmax 权重之和为 1;改动未来位置 token 不影响当前位置输出;多头拆分再拼回 shape 一致、语义不变。
环境准备
bash
cd <仓库根>
export PYTHONPATH="$PWD/python"命令与预期输出
以下三条命令已在「从零实践」跑过(test_attention_gpt.py、test_transformer_contract.py、test_attention_ablation.py),预期输出的判定信号补充如下:
text
attention 行权重之和 ≈ 1.0(atol=1e-6)
替换未来位置 token,当前位 logits 差值 < 1e-6
多头 reshape/permute/contiguous 后拼回 shape == (B, T, n_embd)概念图
图:第7章 Scaled Dot-Product Attention 全链路 — 从第2章的点积和 softmax,经第5章 embedding 和第6章线性投影,到本章的 Q/K/V → 缩放点积 → causal mask → softmax → 加权求和 → 多头 → 输出投影。后续第8章把 attention 封装为 Transformer Block 的子层,第9章用 KV Cache 避免重复计算,第11章把 attention 机制用于检索文档的语义匹配。
故障注入清单
故障注入以正文「故障注入与预期信号」一节为唯一权威清单(5 项,覆盖 residual、scale、两种 mask 写法和 head 轴混淆),此处不再另列第二份;做故障题时逐项对照该表的预期信号与修复后证据。
资源 / 成本 / 隐私
小 batch 的 NumPy attention/Transformer 在本地 CPU 即可,预计 gross cost 为 0;浏览器交互不发第三方请求。只使用随机或合成 token,禁止把真实对话放入 fixture。
Evidence
仓库当前机器证据(只读快照)
evidence/module-manifest-v1.json 中 07.evidence 指向当前文件:evidence/07-runtime-v1.json。这是当前 checkout 的脱敏机器运行记录,只覆盖该 JSON 记录的命令、指标、产物和已知失败;它不是学习者提交,也不能推出学习者已完成本章。
补充的本地消融实现也记录在 python/llm_core/attention_ablation.py 和对应测试中;这些补充文件不改变 manifest 的 canonical evidence 指向。
学习者提交模板(待填写,不是当前机器证据)
复制下面模板并填写自己的真实运行结果。所有 <...> 都是未填写状态;actual 和 artifacts 尤其不能被当作已运行或已通过。artifacts 必须替换为本次提交中真实存在的仓库相对路径。
yaml
schema: learn-llm.evidence.v1
module: 07-attention-transformer
commit: <learner-commit-sha>
verified_at: <iso-date>
environment: <sanitized-python-device>
seed: 5
commands:
- PYTHONPATH=python python -m pytest python/tests/test_attention_gpt.py -q
- PYTHONPATH=python python -m pytest python/tests/test_transformer_contract.py -q
- PYTHONPATH=python python -m pytest python/tests/test_attention_ablation.py -q
metrics:
- name: future_token_invariance
expected: <versioned-tolerance>
actual: <recorded-value>
- name: reference_output_max_error
expected: <versioned-tolerance>
actual: <recorded-value>
- name: no_mask_future_prefix_influence
expected: >1e-5
actual: <recorded-value>
- name: scale_ablation_max_weight_delta
expected: >0.01
actual: <recorded-value>
- name: residual_ablation_max_output_delta
expected: >1e-5
actual: <recorded-value>
artifacts:
- <learner-repo-relative-artifact-path>
cost:
gross_usd: 0
credit_usd: 0
licenses:
- source: <source>
version: <version>
license: <license>
attribution: <attribution>
redistribution: <redistribution>
known_failures:
- <sanitized-failure-or-none>只有输出中间张量、因果扰动、scale/mask/residual 消融和独立 clean-room 对拍都可追溯时,第7章才能离开 gate。