RoFormer: Enhanced Transformer with Rotary Position Embedding

RoPERotary Position Embedding)通过旋转 Q/K 特征,使 attention 内积包含相对位置信息。设旋转维度为偶数 ,频率底数为 ,位置为 ;第 个二维子空间的角频率与旋转角为

  • 对应 rope_theta,源码变量名为 base,是生成频率序列的底数

  • 时,频率以公比 递减:,后续频率逐渐降低。增大 base 会拉长 各分量的周期

  • inv_freq[i] 表示第 对旋转特征每单位位置的相位增量,实际旋转角为 position_id * inv_freq[i]

config.json · text_config JSON
{
  "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] 布局:Bbatch sizeT 为当前输入长度,Q/Khead 数分别为 24/4head_dim,旋转比例 ,故

张量维度 Text
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 个频率

compute_default_rope_parameters Python
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 传入 RoPEQ 向量 q(已完成投影和 Q norm)。设位置 q[0]=1q[32]=2;这对通道的频率 ,因此

  • 结果已经是旋转后 Q 的第 0、32 维。其余 31 对同样计算,再接回未旋转的后 192 维,得到完整的 的完整矩阵见下文。K 同理,随后用旋转后的 Q/K 计算 attention 内积。同一 token 的各 head 复用 cos/sin,分别旋转各自的 Q/K 数值

RoFormer 式(15)对全部 维旋转。令 ,以从 0 开始的索引写为

  • 每个 块作用于 Q 的相邻分量 K 同理;整体为 。式(14)定义 ;记投影结果为 ,由 得到相对位置内积

Qwen3.5partial RoPE 是分块正交变换:前 64 维参与旋转,后 192 维由单位映射保留

  • ,其中 ,则
  • 左上角的 旋转块记为 ,通过固定的通道置换与论文的块对角矩阵对应。旋转在 attention 内积之前分别作用于 QK,只在各自的成对通道内作线性组合,保持向量范数,不含平移项;旋转通道与保留通道之间没有交叉混合

  • 完整 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,就得到上式。这里代码中的 cossin 对应广播后的 K 的计算相同
rotate_half / apply_rotary_pos_emb Python
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