RoFormer: Enhanced Transformer with Rotary Position Embedding
RoPE(Rotary Position Embedding)通过旋转 Q/K 特征,使 attention 内积包含相对位置信息。设旋转维度为偶数
-
对应 rope_theta,源码变量名为base,是生成频率序列的底数 -
当
时,频率以公比 递减: ,后续频率逐渐降低。增大 base会拉长各分量的周期 -
inv_freq[i]表示第对旋转特征每单位位置的相位增量,实际旋转角为 position_id * inv_freq[i]
{
"text_config": {
"hidden_size": 5120,
"num_attention_heads": 24,
"num_key_value_heads": 4,
"head_dim": 256,
"max_position_embeddings": 262144,
"rope_parameters": {
"rope_type": "default",
"rope_theta": 10000000,
"partial_rotary_factor": 0.25,
"mrope_interleaved": true,
"mrope_section": [11, 11, 10]
}
}
}
源码采用 [B,H,T,d_h] 布局:B 为 batch size,T 为当前输入长度,Q/K 的 head 数分别为 24/4;head_dim 为
Q / K [B, 24, T, 256] / [B, 4, T, 256]
position_ids [B, T]
inv_freq [32]
相位 Φ [B, T, 32]
cos / sin [B, T, 64] → unsqueeze(1) → [B, 1, T, 64]
先由 Qwen3_5TextRotaryEmbedding.compute_default_rope_parameters() 计算 32 个频率
base = config.rope_parameters["rope_theta"]
partial_rotary_factor = config.rope_parameters.get("partial_rotary_factor", 1.0)
head_dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
dim = int(head_dim * partial_rotary_factor)
attention_factor = 1.0
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float) / dim))
return inv_freq.to(device), attention_factor
arange(0,64,2)/64生成指数,得到 inv_freq为- 频率按旋转维度
64直接计算,而非从全维RoPE中截取
RoFormer §3.2.1 式(13)先给出二维形式。只写 Q 分支,以 Q、Q
-
从右向左计算:输入
先经 投影得到 ,再乘旋转矩阵得到 。角频率统一记为 ,对应前文某一通道对的 ; 是位置乘频率 -
对应到
Qwen3.5,取一个token、一个head传入RoPE的Q向量q(已完成投影和Q norm)。设位置, q[0]=1、q[32]=2;这对通道的频率,因此
- 结果已经是旋转后
Q的第 0、32 维。其余 31 对同样计算,再接回未旋转的后 192 维,得到完整的; 的完整矩阵见下文。 K同理,随后用旋转后的Q/K计算attention内积。同一token的各head复用cos/sin,分别旋转各自的Q/K数值
RoFormer 式(15)对全部
- 每个
块作用于Q的相邻分量 ,K同理;整体为 。式(14)定义 、 ;记投影结果为 ,由 得到相对位置内积
Qwen3.5 的 partial RoPE 是分块正交变换:前 64 维参与旋转,后 192 维由单位映射保留
- 令
、 ,其中 ,则
-
左上角的
旋转块记为 ,通过固定的通道置换与论文的块对角矩阵对应。旋转在attention内积之前分别作用于Q和K,只在各自的成对通道内作线性组合,保持向量范数,不含平移项;旋转通道与保留通道之间没有交叉混合 -
完整
head在添加mask前的attention score因而分解为
- 第一项通过
引入相对位置,第二项是保留通道的普通内积;两项共同构成attention score,缩放分母仍为 。完整的 256 维K均写入cache
对普通 RoPE,位置张量为 token 的位置编号。每个位置与 32 个频率分别相乘,得到该 token 的 32 个旋转角
-
保存旋转角,本例形状为[B,T,32]; 、 保存对应的cos/sin系数。方括号表示沿最后一维拼接,使 的形状成为[B,T,64]:第 、 个通道属于同一旋转对,因而需要相同的系数。unsqueeze(1)再将系数变为[B,1,T,64],供同一token的所有Q/K head复用 -
固定一个
token和一个head,记 。源码将二维旋转矩阵的乘法展开为逐元素乘加,第 、 个通道的输出为
rotate_half(q_rot)在这两个位置分别提供 和 ;乘以sin后,再加上q_rot * cos,就得到上式。这里代码中的cos、sin对应广播后的 、 ,K的计算相同
def rotate_half(x):
x1 = x[..., : x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2 :]
return torch.cat((-x2, x1), dim=-1)
def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
cos = cos.unsqueeze(unsqueeze_dim)
sin = sin.unsqueeze(unsqueeze_dim)
# Keep half or full tensor for later concatenation
rotary_dim = cos.shape[-1]
q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
# Apply rotary embeddings on the first half or full tensor
q_embed = (q_rot * cos) + (rotate_half(q_rot) * sin)
k_embed = (k_rot * cos) + (rotate_half(k_rot) * sin)
# Concatenate back to full shape
q_embed = torch.cat([q_embed, q_pass], dim=-1)
k_embed = torch.cat([k_embed, k_pass], dim=-1)
return q_embed, k_embed