找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

260

积分

0

好友

19

主题
发表于 昨天 17:12 | 查看: 4| 回复: 0
本帖最后由 云栈大前端 于 2025-12-11 17:18 编辑

旋转位置编码RoPE(Rotary Position Embedding)是Transformer模型中的一种创新位置编码策略,已广泛应用于LLaMA、ChatGLM等主流大模型。本文将从实现步骤、源码分析到数学原理进行全面解析,帮助读者深入理解这一关键技术。

位置编码基础概念

由于Transformer的Self Attention机制具有排列不变性,模型无法感知输入序列中单词的位置信息。为了解决这个问题,需要引入位置编码让模型理解词序关系。

位置编码主要分为两大类:

绝对位置编码:根据单个token的绝对位置定义编码,每个位置分配独立的位置向量。实现方式又分为:

  • 固定式:如原生Transformer的sin-cos三角函数编码,通过固定公式计算
  • 可学习式:如BERT和GPT,通过训练自适应学习位置向量

相对位置编码:对两个token之间的相对位置进行建模,将相对位置信息注入Self Attention结构中,代表模型包括Transformer-XL和DeBERTa。

绝对位置编码示意

RoPE的本质与计算流程

旋转位置编码RoPE是一种固定式的绝对位置编码策略,但其巧妙之处在于:通过绝对位置编码的实现方式,配合Attention内积机制,最终达到相对位置编码的效果。

RoPE的核心思想是对Query和Key向量进行变换,使变换后的向量自动携带位置信息,而无需修改Attention的内积结构。这种设计既保持了实现的简洁性,又兼具了相对位置编码的优势。

RoPE的计算公式如下:

RoPE位置编码公式

公式解读:

  • 第一项:位置为m的token的原始Query向量
  • 第二项:包含cos三角函数的向量,与Query逐位相乘
  • 第三项:由原始Query变换而来
  • 第四项:包含sin三角函数的向量

位置信息由参数m和θ共同表征,其中m为token在句子中的位置,θ与向量各元素位置相关:

θ计算公式

计算示例

以"我爱你"中的第二个词"爱"为例,假设词向量维度d=4,向量表征为[0.2, 0.1, -0.3, 0.7],RoPE变换的计算过程如下:

RoPE计算示意图

公式中的第三项由原始向量变换而来,将前后两个元素位置构成一对,交换位置并对偶数位取相反数。每个元素位的位置信息注入,可以看作该元素和其相邻元素分别经过sin、cos三角函数加权求和的结果。

RoPE如何表达相对位置信息

RoPE与传统sin-cos位置编码的对比:

实现途径不同

  • sin-cos编码:三角函数性质天然具备表达相对距离的能力
  • RoPE编码:本身不能表达相对距离,需结合Attention内积激发相对位置表达能力

融合方式不同

  • sin-cos编码:直接与原始输入相加
  • RoPE编码:采用类似哈达马积的相乘形式

RoPE的关键特性:带有RoPE位置编码的两个token,其Query向量和Key向量进入Self Attention后,内积结果可以恒等转化为一个函数,该函数仅与Query向量、Key向量以及两个token位置之差相关。这使得RoPE在不改变Attention结构的前提下,实现了相对位置信息的感知。

RoPE相对位置感知

LLaMA源码实现解析

在HuggingFace的LLaMA大模型实现中,RoPE的计算分为三个步骤:

步骤一:初始化cos向量和sin向量

根据上下文窗口大小m和每个头的向量维度d,生成cos和sin向量。在LLaMA2中,上下文窗口m=4096,每个头的向量维度d=128。

class LlamaRotaryEmbedding(torch.nn.Module):
    def __init__(self, dim, max_position_embeddings=2048, base=10000, device=None):
        super().__init__()
        # dim=128, max_position_embeddings=4096
        inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float().to(device) / dim))
        self.register_buffer("inv_freq", inv_freq)

        self.max_seq_len_cached = max_position_embeddings
        # 生成位置索引 [0, 1, 2, ..., 4095]
        t = torch.arange(self.max_seq_len_cached, device=self.inv_freq.device, dtype=self.inv_freq.dtype)
        # 笛卡尔积生成mθ组合矩阵
        freqs = torch.einsum("i,j->ij", t, self.inv_freq)
        # [4096, 64] => [4096, 128]
        emb = torch.cat((freqs, freqs), dim=-1)
        # [1, 1, 4096, 128]
        self.register_buffer("cos_cached", emb.cos()[None, None, :, :], persistent=False)
        self.register_buffer("sin_cached", emb.sin()[None, None, :, :], persistent=False)

通过m向量和θ向量的笛卡尔积构造mθ组合矩阵,最终形成[4096, 128]的二维矩阵,再转换为四维以匹配多头注意力中Query和Key的形状[batch_size, num_heads, seq_len, head_dim]。

步骤二:截取对应长度的向量

根据输入Query的实际长度截取cos和sin向量。例如上下文窗口为4096,但实际输入句子长度仅为10,则截取前10个位置的向量。

def forward(self, x, seq_len=None):
    return (
        self.cos_cached[:, :, :seq_len, ...].to(dtype=x.dtype),
        self.sin_cached[:, :, :seq_len, ...].to(dtype=x.dtype),
    )

步骤三:改造Query和Key

使用cos和sin向量,根据RoPE公式对原始Query和Key进行变换:

def apply_rotary_pos_emb(q, k, cos, sin, position_ids):
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed

def rotate_half(x):
    """旋转向量的一半维度"""
    # 前半部分
    x1 = x[..., : x.shape[-1] // 2]
    # 后半部分
    x2 = x[..., x.shape[-1] // 2 :]
    # 后半部分取负号,与前半部分拼接
    return torch.cat((-x2, x1), dim=-1)

值得注意的是,HuggingFace的实现与论文公式在元素配对方式上有所不同:

  • 论文公式:q0与q1配对
  • 代码实现:q0与q64配对(前半部分与后半部分配对)

这两种方式在数学上是等价的,因为RoPE本质是以元素对为单位进行旋转变换,只要配对策略不重复且维度为偶数,Attention内积都能感知相对位置信息。

在完成Query和Key的改造后,直接输入标准的Attention结构:

attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim)
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype)
attn_output = torch.matmul(attn_weights, value_states)

注意:Attention结构无需任何修改,且Value向量不参与RoPE变换。

数学原理推导

RoPE的数学推导涉及复数运算、欧拉公式和旋转矩阵等概念。

推导目标

设位置m的token1的Query为q,位置n的token2的Key为k,改造函数为f,目标是找到一个改造函数,使得:

注入位置信息的一般形式

并且Attention内积能够恒等转化为:

内积恒等变换

二维情况推导

从最简单的二维向量入手,将q和k用复数表示(第一维为实部,第二维为虚部)。两者的内积等于q和k的共轭复数相乘的实部:

内积转化为复数运算

将三个复数用三角形式表示:

复数的三角表示

其中R代表向量模长,根据欧拉公式:

欧拉公式

根据复数相乘的性质(模长相乘,幅角相加),可得:

模和幅角相等

取特例m=n=0,得到:

特例求解

幅角特例

可得θ是关于位置参数m的等差数列函数。将求解的R和θ代入,得到f的一般形式:

f的一般形式

根据欧拉公式展开,该变换对应向量的旋转操作,改写成矩阵形式:

旋转矩阵公式

旋转矩阵验证

以二维向量[1, 2]旋转60度为例:

import numpy as np

def rotary_matrix(xita):
    matrix = np.array([[np.cos(xita), -np.sin(xita)],
                       [np.sin(xita), np.cos(xita)]])
    return matrix

m = rotary_matrix(np.pi / 3)
one = np.array([[1], [2]])
two = np.matmul(m, one)
print(two)
# array([[-1.23205081],
#        [ 1.8660254 ]])

验证模长不变:

np.linalg.norm(one)  # 2.236
np.linalg.norm(two)  # 2.236

验证旋转角度为60度:

np.dot(one.T, two) / (np.linalg.norm(one) * np.linalg.norm(two))
# array([[0.5]])  # cos(60°) = 0.5

旋转矩阵示意图

推广到任意偶数维

由于内积满足线性叠加性,任意偶数维向量都可以表示为多个二维情形的拼接,因此RoPE的最终公式为:

任意偶数维RoPE公式

从旋转矩阵的角度理解:RoPE本质上是对各个位置的token向量根据自身位置m计算旋转角度,在Attention的内积操作中,内积能够感知旋转后两个向量之间的夹角,这个夹角就是相对位置信息。

总结

旋转位置编码RoPE通过巧妙的数学设计,实现了以下优势:

  1. 高效性:无需额外可学习参数,计算开销小
  2. 兼容性:无需修改Attention结构,易于集成
  3. 表达力:通过绝对位置编码实现相对位置感知
  4. 可扩展性:支持任意偶数维度的向量

这些特性使得RoPE成为当前大模型位置编码的主流选择,在LLaMA、ChatGLM等模型中得到广泛应用。理解RoPE的原理和实现,对于深入掌握Transformer架构和大模型技术具有重要意义。

转自 CSDN 作者 AI大模型datian




上一篇:Transformer架构深度解析:自注意力机制原理与模型演进
下一篇:Transformer词嵌入与位置编码详解:从原理到PyTorch实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-12 08:34 , Processed in 0.080237 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表