深入篇 19 / Transformer 逐步拆解 · 2026-11-08

RoPE:旋转的是向量,保留的是相对位置

手算一个二维旋转,并证明 Q/K 共同平移后点积为何不变。

核心阅读 12–18 分钟练习另需 10–20 分钟前置:16:点积;18:每头维度

本课只拿下这一件事

手算一个二维旋转,并证明 Q/K 共同平移后点积为何不变。

一个二维对子上的 RoPE。只展示一个频率对子;真实 head 有 d/2 个不同频率。
只展示一个频率对子;真实 head 有 d/2 个不同频率。

先从两个数开始

R(φ) = [[cosφ, −sinφ], [sinφ, cosφ]]
[x₁,x₂] → [x₁cosφ−x₂sinφ, x₂cosφ+x₁sinφ]

取 [1,0],旋转90°后是 [0,1],长度仍为1。RoPE 对不同二维对子使用不同频率 θᵢ=base^(−2i/d),位置 p 对应角度 pθᵢ。旋转发生在 Q/K 投影后,不是把位置整数直接加到 token ID 上。来源:RoFormer §3.2 与本地 rotary_embedding.py。

相对位置从哪来

(R(mθ)q)ᵀ(R(nθ)k)
= qᵀ R(mθ)ᵀ R(nθ) k
= qᵀ R((n−m)θ) k

把 m、n 同时加 Δ,只留下 n−m,所以同一对原始 q/k 的点积不变。这里是关于旋转算子的恒等式;不能直接推成“整模型在任何上下文下对平移完全不变”,因为 token 表示、上下文边界与训练分布也会改变。

实现细节:它不是相邻元素配对

本地 apply_rotary_emb 把最后一维切成两半:x1,x2=torch.chunk(x.float(),2,dim=-1),再拼回结果。d=4 时配对为(0,2)、(1,3),与某些教材的(0,1)、(2,3)写法布局不同;只要权重约定和算子一致就可以。不可照抄另一种布局而不做相应权重/维度变换。

Qwen3 的执行次序是 Q/K norm → RoPE → attention 写缓存;因此缓存中的 K 已经使用该 token 的绝对 positions。decode 时 positions=len(seq)−1,不能每步都从0开始,也不能把已旋转 K 再按新位置旋转一遍。cos/sin 表只是位置系数缓存,不是模型的 K/V cache。

把维度变化拆到每一步

  1. 本地RoPE把d切为两半再配对;d=4时是(0,2)、(1,3)。
  2. Q/K的数值改变,但token/head/每头维度都不变。

打开元素级图解:矩阵、轴、索引与可点击反例 →

nano-vllm 的实际执行顺序。本地实现对 Q/K 旋转,不对 V 做这一步旋转。
本地实现对 Q/K 旋转,不对 V 做这一步旋转。

改变变量,验证直觉

启用 JavaScript 后可交互;上面的图和下面的手算练习始终可读。

数值练习:先算,再核对

令 θ=π/2,q=k=[1,0],m=0、n=1,算出点积。再令 m=3、n=4,重新计算。运行 python labs/transformer_lab.py rope 查看半切布局和共同平移的数值误差。

闭卷检索

不要回看正文。答错后先解释原因,再重新打开这一课。

把结论落到源码

  1. 在 rotary_embedding.py 区分 cos_sin_cache 与 attention.k_cache。
  2. 在 Qwen3Attention.forward 标出 q_norm、rotary_emb、attn 的顺序,再追 prepare_decode 的 positions。

以下行号由本地 vendor 文件生成;上游链接固定完整 commit,离线摘录可直接打开。

优先阅读:RoFormer §3.2.1–3.2.2。

速查:形状与公式 · 三框架映射 · 术语表 · 复习与实战路线 · 4070 Ti本地实验

记录你此刻能做到什么

只在当前浏览器保存自评;不会自动写成已掌握的 learning record。

随时追问老师。把“第 19 课 + 哪个公式 / 哪个张量 / 哪一行 + 你的推导”发给我,我们可以继续拆到每一步。