位置编码

RoPE 直观理解与示例

直观讲解视频

1. RoPE 和传统位置编码的区别

传统 Transformer 常见的位置编码方式是把位置向量直接加到 token embedding 上:

token_embedding + position_embedding

RoPE 的做法不同。

token 先得到 embedding,再经过线性变换生成 Q、K、V。RoPE 主要作用在 Q 和 K 上,把每两个维度看成一个二维向量,根据 token 的位置进行旋转。

embedding
生成 Q、K、V
对 Q、K 做 RoPE 旋转
计算 Attention

所以 RoPE 不是给 embedding 乘一个系数,也不是直接给 embedding 加一个位置向量。


2. 一个具体例子

假设 token 序列为:

hello world GPT ASTRA

假设为了演示,每个 token 的 embedding 只有 4 维,并暂时令 Wq 等于单位矩阵,也就是 Q 等于 embedding。

hello = [1, 0, 1, 0]
world = [0, 1, 1, 0]
GPT   = [1, 1, 0, 1]
ASTRA = [1, 0, 0, 1]

它们的位置为:

hello  m = 0
world  m = 1
GPT    m = 2
ASTRA  m = 3

4 维向量会拆成两组:

维度 (0,1)
维度 (2,3)

RoPE 的基础角频率:

$$ \theta_i = 10000^{-2i/d} $$

这里 d = 4,因此第 0 组对应 i = 0,第 1 组对应 i = 1:

$$ \begin{aligned} \theta_0 &= 10000^0 = 1,\\ \theta_1 &= 10000^{-2/4} = 0.01 \end{aligned} $$

实际旋转角为:

$$ m\theta_i $$

3. world 的计算

world 的 Q 为:

Q_world = [0, 1, 1, 0]

位置:

m = 1

第一组维度 (0,1)

原向量:

[0, 1]

旋转角:

$$ 1\times\theta_0 = 1\,\mathrm{rad} $$

旋转后:

$$ \begin{aligned} x’ &= x\cos(1)-y\sin(1),\\ y’ &= x\sin(1)+y\cos(1) \end{aligned} $$

代入:

$$ R(1)(0,1)=(-\sin(1),\cos(1))\approx(-0.8415,\,0.5403) $$

第二组维度 (2,3)

原向量:

[1, 0]

旋转角:

$$ 1\times\theta_1 = 0.01\,\mathrm{rad} $$

旋转后:

$$ R(0.01)(1,0)=(\cos(0.01),\sin(0.01))\approx(0.99995,\,0.01000) $$

最终:

$$ Q_{\mathrm{world},\mathrm{RoPE}}\approx(-0.8415,\,0.5403,\,0.99995,\,0.01000) $$

4. GPT 的计算

GPT 的 Q:

Q_GPT = [1, 1, 0, 1]

位置:

m = 2

第一组的旋转角:

$$ 2\times 1 = 2\,\mathrm{rad} $$

所以:

$$ R(2)(1,1)=(\cos(2)-\sin(2),\,\sin(2)+\cos(2))\approx(-1.3254,\,0.4932) $$

第二组的旋转角:

$$ 2\times 0.01 = 0.02\,\mathrm{rad} $$

所以:

$$ R(0.02)(0,1)=(-\sin(0.02),\cos(0.02))\approx(-0.0200,\,0.9998) $$

最终:

$$ Q_{\mathrm{GPT},\mathrm{RoPE}}\approx(-1.3254,\,0.4932,\,-0.0200,\,0.9998) $$

5. 为什么几千维时仍然是每两个维度一组

标准 RoPE 中,Q 和 K 的维度仍然是每两个一组。

例如一个 attention head 的维度是 128:

(0,1)
(2,3)
(4,5)
...
(126,127)

一共 64 组。

每组都有不同的 θ_i,因此不同组随位置变化的速度不同。


6. “旋转快慢”是什么意思

每个 token 最终只根据自己的位置计算一次旋转结果。

所谓旋转快慢,是指:

token 位置 m 每增加 1,
这一组二维向量的旋转角增加多少。

例如:

θ = 1

m = 0: 0 rad
m = 1: 1 rad
m = 2: 2 rad
m = 3: 3 rad

而:

θ = 0.01

m = 0: 0 rad
m = 1: 0.01 rad
m = 2: 0.02 rad
m = 3: 0.03 rad

因此:

θ 大:
位置稍微变化,旋转角就明显变化。

θ 小:
位置要变化很多,旋转角才明显变化。

7. 为什么低索引维度和高索引维度使用不同的 θ

低索引维度的 θ 较大,位置每增加一点,角度就变化较多。

高索引维度的 θ 较小,位置增加很多,角度才逐渐产生明显差异。

可以类比钟表:

低索引维度:秒针
旋转快,对短距离变化敏感。

高索引维度:时针
旋转慢,适合表示更大的位置范围。

因此一个 Q 或 K 向量中,同时存在多种不同的位置尺度。

可以概括为:

低维度相当于秒针,速度快敏感捕捉局部信息,高维度相当于时针,旋转慢,只有长距离才能体现出差别,捕捉远距离信息。同时注意力内积之后还能体现出相对位置。


8. 注意力计算

位置首先独立作用于每个 token 自己的 Q 和 K。

例如:

token A 位于位置 100,token B 位于位置 101。它们的 Q 和 K 分别旋转为:

$$ Q_A’=R(100\theta)Q_A,\qquad K_B’=R(101\theta)K_B $$

在计算 Attention 时,A 的 Q 会和 B 的 K 做内积:

$$ Q_A’\cdot K_B’=\bigl(R(100\theta)Q_A\bigr)\cdot\bigl(R(101\theta)K_B\bigr) $$

旋转矩阵有一个性质,使得最终的位置部分可以转化为两者的角度差。

也就是:

$$ 101\theta-100\theta=\theta $$

如果位置分别是 100 和 110:

$$ 110\theta-100\theta=10\theta $$

因此 RoPE 虽然先作用在单个 token 的 Q/K 上,但两个 token 做注意力计算时,最终体现出来的是它们之间的相对位置差。


9. 为什么注意力内积能体现相对位置

设两个位置分别为 m 和 n:

$$ Q_m’=R(m\theta)Q_m,\qquad K_n’=R(n\theta)K_n $$

做内积:

$$ Q_m’\cdot K_n’ $$

可以整理为:

$$ (Q_m’)^\mathsf{T}K_n’=Q_m^\mathsf{T}R((n-m)\theta)K_n $$

因此位置相关部分只依赖:

n - m

而不直接依赖 m 和 n 的绝对值。

这就是 RoPE 能自然把相对位置信息放进 Attention Score 的原因。


10. RoPE 与 KV Cache 的复用

在常见的 RoPE 模型中,KV Cache 通常存的是已旋转的 K和未经过 RoPE 旋转的 V。Q 也会旋转,但不存进 KV Cache。vLLM 的 Llama 实现先对 Q、K 施加 RoPE,再把 Q、K、V 交给 Attention,由后端写入 K、V 缓存。不同架构或特制缓存实现可能采用别的格式。

vLLM 的普通前缀缓存使用父块哈希、当前块的 token IDs 和 extra_keys 计算缓存键,不以 embedding、KV 向量或 RoPE 后的 K 为哈希输入。假设每块 4 个 token,extra_keys 保持一致,原请求的前两个块是:

H0 = hash(NONE_HASH, (101,102,103,104), extra_keys)
H1 = hash(H0, (105,106,107,108), extra_keys)

如果截断前 4 个 token,原来位于第二块的 (105,106,107,108) 变成第一块,其哈希改为:

H0_new = hash(NONE_HASH, (105,106,107,108), extra_keys)

这段 token 没变,父块哈希却从 H0 变成了 NONE_HASH,因此无法命中旧的 H1;即使模型不使用 RoPE,结果也一样。vLLM 前缀缓存设计解释了这条哈希链。没有改变的开头前缀仍可复用,其 token 位置也没有变化。RoPE 不参与普通 APC 的哈希计算或命中判断,它影响的是存入缓存的 K 的数值。

跨位置复用是另一个问题。旧 K 带着旧位置的旋转角,直接放在新位置参与 Attention 会算错相对位置。方案 B 是缓存旋转前的 K,读取时按新位置施加 RoPE;方案 C 是缓存已旋转的 K,读取时校正新旧位置的旋转差。在旋转前的 K 不变的前提下,方案 C 可写为:

$$ K_{\mathrm{new}}=R\bigl((m_{\mathrm{new}}-m_{\mathrm{old}})\theta\bigr)K_{\mathrm{old}} $$

当前 LMCache 的 MP CacheBlend 采用方案 C 的缓存方式:保存已旋转的 K,载入后在位置校正内核中先按旧位置逆旋转,再按新位置旋转,结果等价于补上旋转差。方案 B 指缓存时就保存未旋转的 K;这里没有这样存。位置校正只修正旋转;当前文改变使深层 K/V 的内容也发生变化时,CacheBlend 还会选择性重算部分 token。


11. 总结

RoPE 可以理解为给 Q 和 K 中的每两个维度安装一个不同转速的“指针”。

低索引维度:
θ 大
位置变化一点,角度就变化很多
偏向表示短距离差异

高索引维度:
θ 小
位置变化很多,角度才明显变化
偏向表示长距离差异

每个 token 根据自己的绝对位置完成旋转。

随后两个 token 做 QK 内积时,两个绝对旋转角相减,最终 Attention 中体现的是它们之间的相对位置。