【AI】Position Encoding -- 位置编码
词嵌入矩阵 :形状为 [6, 768] ,包含了每个 Token 的 语义。位置编码矩阵 :形状为 [6, 768] ,我们通过上述公式为 pos=0 到 pos=5 计算出了 6 个 768 维的 位置向量 ,并将它们堆叠起来。操作 :将这两个矩阵进行 逐元素相加 (Element-wise Addition)。最终产物 :我们得到了一个最终的输入矩阵,形状仍然是 [6, 768]。它是什么 (
在上一部分,我们已经成功地将输入 “请帮我生成一首诗” 转换成了一个 [6, 768] 的 语义矩阵 。现在,这个矩阵里的每一行向量都包含了对应 Token 的丰富含义,但模型还不知道它们的顺序。
这就是位置编码要解决的核心问题。
为什么需要位置编码?—— Transformer 的“无序性”
这是理解位置编码存在意义的关键。Transformer 的核心是自注意力机制(Self-Attention),这个机制在处理输入时,天生是**“无序的”**。
可以把它想象成一个投票系统。在计算“生成”这个词的新表示时,模型会让“请”、“帮”、“我”、“一首”、“诗”等所有词同时“投票”,决定谁对“生成”的意义影响最大。这个过程是并行的,没有先后顺序。
如果不加干预,对于模型来说, [“我”, “爱”, “你”] 和 [“你”, “爱”, “我”] 这两句话的初始输入是完全一样的,因为它只是把所有词看作一个无序的集合(a bag of words)。这显然是灾难性的。
- RNN/LSTM 的做法 :像我们说话一样,一个词一个词地处理,天然就包含了顺序信息。
- Transformer 的做法 :像看一张全家福,它 同时 看到了句子里的所有词。如果不加额外信息, “我 爱 你” 和 “你 爱 我” 在它眼里是一样的,它只知道这三个词出现了,但不知道谁先谁后。
所以,我们必须人为地给每个词贴上一个“位置标签”,这就是**位置编码(Positional Encoding)**的由来。
如何编码位置?—— 优雅的正弦与余弦
我们有很多方法可以编码位置,但原始 Transformer 论文《Attention Is All You Need》提出了一种极其巧妙、优雅且高效的方法,至今仍被广泛使用。
核心思想 :我们为句子中的每一个绝对位置(第0位、第1位、第2位…)都生成一个 独特的、固定不变的 向量。然后,将这个“位置向量”与该位置的“词嵌入向量” 相加 。
这个位置向量必须满足几个苛刻的条件:
- 它必须为每个位置输出一个独一无二的编码。
- 它不能随着句子变长而无限增大,值应该在一定范围内。
- 模型必须能从这些编码中,轻松地推断出词与词之间的 相对位置 关系。
- 它必须能推广到比训练时见过的所有句子都更长的句子。
解决方案:不同频率的正弦和余弦波
研究人员设计了下面这两个公式来生成位置向量 PE :
PE(pos, 2i) = sin(pos / 10000^(2i / d_model)) PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))
这看起来很复杂,我们把它拆解一下:
- PE(pos, i) :代表位置 pos 的位置向量中,第 i 个维度的值。
- pos :Token 在句子中的位置,从 0 开始( 0, 1, 2, … )。
- i :向量中的维度索引,从 0 开始( 0, 1, 2, …, 767 )。公式中用 2i 和 2i+1 来区分处理向量中的偶数维度和奇数维度。
- d_model :嵌入向量的总维度,在我们的例子中是 768 。
直观理解:
您可以把这 768 个维度想象成一个乐队里的 768 个乐手。
- 前几个维度 (i 很小) : 2i / d_model 的值很小,分母 10000^(…) 接近 1,所以 pos / … 的值很大。这意味着它们演奏的是 高频波 (像短笛),波形变化非常快。从位置0到位置1,它们的值可能就经历了一个完整的周期。
- 后几个维度 (i 很大) : 2i / d_model 的值接近 1,分母 10000^(…) 非常大。这意味着它们演奏的是 超低频波 (像低音提琴),波形变化极其缓慢。从位置0到位置100,它们的值可能都只是微微变动。
结果就是 :对于每一个位置 pos ,它都会得到一个由 768 个不同频率的 sin/cos 值组成的、独一无二的“交响乐指纹”。
这个方法的绝妙之处:相对位置
这个公式最神奇的地方在于,它使得 任意两个位置之间的关系,可以通过一个线性变换来表示 。
因为三角函数有恒等式: sin(α+β) = sin(α)cos(β) + cos(α)sin(β) cos(α+β) = cos(α)cos(β) - sin(α)sin(β)
这意味着,位置 pos+k 的编码向量,可以被表示为位置 pos 编码向量的一个线性函数。模型在处理数据时,不需要去死记硬背“位置5是什么”、“位置6是什么”。它只需要学会一个通用的“线性变换”,这个变换就代表了“向后移动一位”这个相对关系。
这使得模型可以非常轻松地理解词与词之间的相对距离,并且能很好地泛化到任意长度的句子。
最后一步:注入位置信息
现在我们有两个形状完全相同的矩阵:
- 词嵌入矩阵 :形状为 [6, 768] ,包含了每个 Token 的 语义 。
- 位置编码矩阵 :形状为 [6, 768] ,我们通过上述公式为 pos=0 到 pos=5 计算出了 6 个 768 维的 位置向量 ,并将它们堆叠起来。
操作 :将这两个矩阵进行 逐元素相加 (Element-wise Addition) 。
Final_Input_Matrix = Word_Embedding_Matrix + Positional_Encoding_Matrix
最终产物 :我们得到了一个最终的输入矩阵,形状仍然是 [6, 768] 。
现在,这个矩阵的每一行向量,都神奇地同时包含了两个信息:
- 它是什么 (语义) :来自词嵌入。
- 它在哪里 (位置) :来自位置编码。
这个最终的 [6, 768] 矩阵,才是被送入 Transformer 第一个编码器模块(Multi-Head Attention)的真正输入。它已经准备好,可以开始进行复杂的特征提取和上下文理解了。
关键区别:Encoding vs. Embedding
这篇文章做了一个非常重要的区分,这也是很多人会混淆的:
-
位置编码 (Position Encoding) :
- 方法 :通过一个固定的 数学公式 (比如 sin/cos 三角函数)直接计算出来。
- 特点 : 不需要学习 。只要你知道位置和公式,就能算出唯一的编码。
- 代表模型 :原始的 Transformer 模型。
-
位置嵌入 (Position Embedding) :
- 方法 :像词嵌入(Token Embedding)一样,它也是一个 可学习的向量矩阵 。
- 特点 :模型在预训练阶段,需要像学习词义一样, 学习 出每个位置(比如第1位、第2位…)应该用什么样的向量来表示。
- 代表模型 :BERT。
简单记 :Encoding 是“算”出来的,Embedding 是“学”出来的。
BERT 的“学徒式”位置嵌入及其缺陷
BERT 的做法,这是一种 绝对位置编码 (Absolute Position Encoding) 。
- 做法 :BERT 准备了一个 [512, 768] 的位置嵌入矩阵(假设模型最大长度是512,维度是768)。当一个词在第 i 个位置时,就从这个矩阵中取出第 i 行的向量,和这个词本身的词向量相加。
- 优点 :通过学习,模型确实能理解“相邻的位置向量更相似”这种相对关系(如图中可视化的那样,向量是平滑过渡的)。
- 致命缺点(文章重点) : 没有外推性 (Extrapolability) 。
- 因为 BERT 只学习了 512 个位置的向量,所以它天生就无法处理超过 512 个词的句子。如果你给它一个 600 词的句子,它到第 513 个词时就“懵了”,因为它根本没学过第 513 个位置应该长什么样。
LLM 时代的选择:回归“公式派”并追求“相对性”
文章指出,随着 ChatGPT 的成功,大家发现要处理越来越长的上下文(比如几万字的文档),BERT 那种“学徒式”的固定长度位置嵌入行不通了。于是,研究方向又回到了原始 Transformer 的“公式派”思路上,并发展出了更高级的 相对位置编码 (Relative Position Encoding) 。
核心思想转变 :模型需要知道的,可能不是“这个词在句子的绝对第100位”,而是“这个词在我当前关注的词的 左边3个位置 ”。 相对距离 比绝对位置更重要。
文章重点介绍了两种主流的、基于公式的相对位置编码方案:
- RoPE (Rotary Position Embedding) - 旋转位置编码
- 这是文章的重中之重,也是目前几乎所有主流开源LLM(如 LLaMA, Qwen, Yi)的选择。
- 核心思想 :它不再是把位置向量“加”到词向量上,而是在 Attention 计算的内部,利用复数的思想,将位置信息通过**“旋转”**的方式融入到 Query 和 Key 向量中。
- 怎么理解“旋转” :你可以想象每个词向量是一个二维平面上的指针。当一个词在位置 m ,另一个词在位置 n 时,RoPE 通过一个旋转矩阵,将这两个词的向量各自旋转 mθ 和 nθ 度。当计算它们俩的注意力分数(点积)时,这个分数的结果只跟它们的 相对角度差 (m-n)θ 有关,而跟它们各自的绝对角度 mθ 和 nθ 无关。
- 优点 :
- 极好的外推性 :因为模型学到的是相对关系,所以无论句子多长,它都能计算出相对位置,轻松处理超长文本。
- 性能优异 :在各种长文本任务上表现非常好。
- ALiBi (Attention with Linear Biases) - 线性偏置注意力
- 这是另一种聪明的方案,被 BLOOM、Baichuan2-13B 等模型采用。
- 核心思想 :它甚至完全抛弃了位置向量。它的做法更直接:在计算完 Attention 分数矩阵后,直接给这个矩阵加上一个 固定的、不可学习的“偏置” (bias) 。
- 这个偏置矩阵长什么样 :它是一个斜对角线矩阵。规则很简单:两个词的距离越远,加在这个位置上的惩罚项(一个负数)就越大。
- 效果 :这相当于告诉模型:“你别老关注那些离得很远的词,优先关注离得近的词。”
- 优点 :
- 计算量极小 :只是一个简单的矩阵加法。
- 外推性也很好 :因为这个惩罚规则对于任意长度的句子都适用。
RoPE (旋转位置编码) 的精髓
核心思想一句话总结:RoPE 不再是给词向量“穿上”位置的外套(相加),而是让词向量根据自己的位置“转动”一个角度。
一个生动的比喻:时钟上的指针
想象一下,我们有一个巨大的钟面,上面没有数字,只有一个中心点。
-
词向量 (Word Vector) :
- 每一个词的初始向量(比如“帮”的向量),就是一根从中心点出发,指向某个方向的 指针 。指针的 长度 代表了词的“语义强度”,指针的 方向 代表了词的“语义内容”。
- 比如,“帮”的指针可能指向2点钟方向,“生成”的指针可能指向5点钟方向。
-
传统位置编码 (相加) :
- 传统方法(比如BERT)是,再造一根代表“位置1”的指针,然后把“帮”的指针和“位置1”的指针通过向量相加(平行四边形法则)得到一根新的指针。
- 这种方法简单,但如果位置太多,这些“位置指针”就需要提前学好,没学过的位置就不知道怎么办了(没有外推性)。
-
RoPE 的做法 (旋转) :
- RoPE 说:“我们别搞那么多新指针了,就用原来的词向量指针。”
- 它的规则是: 一个词在哪个位置,就把它的指针顺时针旋转多少度。
- “请”在位置 0,它的指针 不旋转 。
- “帮”在位置 1,把它的指针 旋转 10 度 。
- “我”在位置 2,把它的指针 旋转 20 度 。
- “生成”在位置 3,把它的指针 旋转 30 度 。
- …以此类推。
关键来了:为什么要这么做?
Attention 机制的核心是计算两个词向量之间的 点积 (Dot Product) ,这在几何上等于 向量A长度 × 向量B长度 × cos(A和B的夹角) 。它衡量的是两个向量的 相似度或相关性 。
现在我们来看,经过 RoPE 旋转后,计算两个词的相关性会发生什么:
- 我们想计算位置1的“帮”和位置3的“生成”之间的相关性。
- “帮”的指针旋转了 10 度,“生成”的指针旋转了 30 度。
- 它们旋转后的 夹角 是多少?是它们 原始的夹角 ,再加上一个 (30 - 10) = 20 度的 相对角度差 。
- 同理,计算位置0的“请”和位置2的“我”的相关性时,它们的相对角度差也是 (20 - 0) = 20 度。
结论: 经过 RoPE 旋转后,任意两个词向量的点积结果,同时取决于它们 原始的语义相似度 (原始夹角)和它们的 相对位置距离 (旋转角度差)。模型在计算中,自然而然地就知道了“生成”在“帮”后面2个位置。
从 2D 比喻到高维现实
上面的比喻是在一个 2D 平面上旋转。但我们的词向量是 768 维甚至更高维的,怎么办呢?
RoPE 的做法非常聪明: 成对处理,分组旋转!
-
维度配对 :它把 768 维的向量,两两一组,看作 384 个独立的 2D 平面。
- 第 0 维和第 1 维,组成第 1 个“钟面”。
- 第 2 维和第 3 维,组成第 2 个“钟面”。
- …
- 第 766 维和第 767 维,组成第 384 个“钟面”。
-
不同速度旋转 :RoPE 不会让所有的“钟面”都转一样的速度。它规定:
- 第 1 个钟面 (低维) :转得 飞快 。比如,位置0转0度,位置1转90度,位置2转180度…
- 第 2 个钟面 :转得 慢一点 。比如,位置0转0度,位置1转45度,位置2转90度…
- 第 384 个钟面 (高维) :转得 极慢 。可能位置100才转了1度。
为什么要这样?
这就像我们测量距离用不同的尺子。
- 转得快的钟面(高频信息) :对 近距离 的词语位置关系非常敏感。它能精确分辨出“你”和“我”是紧挨着的,还是隔了一个词。
- 转得慢的钟面(低频信息) :用来感知 远距离 的词语位置关系。它可能分不清第100个词和第101个词,但能清晰地感知到第100个词和第500个词之间的遥远距离。
通过这种多尺度的旋转,模型就获得了对相对位置关系的全面感知能力。
RoPE 的巨大优势
现在我们可以回头看懂为什么 RoPE 成为了主流:
- 极强的外推性 :因为旋转是一个 公式 ,所以无论你的句子有多长,哪怕是1万个词,我都能算出第9999个词应该旋转多少度。它不像 BERT 那样只能处理预先学好的512个位置。这是它能处理长文本的关键。
- 只编码相对位置 :点积的结果只和相对位置差有关,这非常符合语言的直觉。
- 计算高效且稳定 :旋转操作不会改变向量的长度(L2模),这有助于保持训练的稳定性。
总结
所以,当您下次看到 RoPE 时,请忘掉那些复杂的欧拉公式和复数,记住这个画面:
拿到一个词向量,根据它所在的位置,把它切成几百个二维“小指针”,然后让这些“小指针”按照各自不同的预设速度,“转”到一个新的角度。最后再把它们拼回来,得到一个既包含原始语义、又蕴含了丰富相对位置信息的新向量,送去做 Attention 计算。
RoPE推导
核心在于证明:经过 RoPE 变换后,两个向量(Query 和 Key)的点积,其结果只与它们的 相对位置 (m-n) 有关,而与它们的绝对位置 m 和 n 无关。
第一步:从高维降到二维,一切开始的地方
我们先别管 768 维那么复杂的情况。假设我们的词向量就是一个简单的 二维向量 q = (x, y) 。
在数学中,处理二维旋转最方便的工具是 复数 。我们可以把这个二维向量 q 看作一个复数 q = x + iy 。
关键工具:欧拉公式 e^(iθ) = cos(θ) + i * sin(θ) 这个公式告诉我们,一个复数乘以 e^(iθ) ,就相当于把它在复平面上 逆时针旋转 θ 角度 。
RoPE 的核心操作 :
RoPE 要对一个在位置 m 的向量 q 进行位置编码,操作就是把它乘以 e^(imθ) 。这里的 θ 是一个预先设定的、代表旋转“单位速度”的常数。
- 位置为 m 的向量 q ,旋转后得到 q_m = q * e^(imθ)
- 位置为 n 的向量 k ,旋转后得到 k_n = k * e^(inθ)
第二步:计算旋转后向量的点积
在二维向量中, q = (q_x, q_y) 和 k = (k_x, k_y) 的点积是 q_xk_x + q_yk_y 。
在复数中,这个操作等价于 Re(q * k*) ,其中 k* 是 k 的 共轭复数 ( k* = k_x - i*k_y ), Re() 代表取实部。
现在,我们来计算旋转后的 q_m 和 k_n 的点积:
Dot(q_m, k_n) = Re(q_m * k_n*)
代入我们第一步的旋转公式:
= Re( (q * e^(imθ)) * (k * e^(inθ))* )
根据共轭复数的性质 (ab) = a* * b* 和 (e^(iθ))* = e^(-iθ) ,我们展开 (k * e^(inθ))* :
= Re( (q * e^(imθ)) * (k* * e^(-inθ)) )
现在,我们把 q 和 k* 放在一起,把指数项放在一起:
= Re( (q * k*) * e^(imθ - inθ) )
= Re( (q * k*) * e^(i(m-n)θ) )
请在这里停一下,这是最关键的一步!
您看到了吗?最终的点积公式里, q 和 k 的绝对位置 m 和 n 消失了,只剩下了它们的差值 (m-n) !
这意味着,无论 m 和 n 是 1 和 3,还是 101 和 103,只要它们的相对距离 (m-n) 相同, e^(i(m-n)θ) 这一项就完全一样。点积的结果也就只受原始向量 q 、 k 和它们的相对位置 (m-n) 影响。
我们已经成功地在二维情况下证明了 RoPE 的核心性质。
第三步:从二维扩展到高维
现在的问题是,我们的词向量是 768 维的,不是 2 维的。怎么办?
RoPE 的方法是: 把高维向量两两一组,拆成一堆二维向量来处理。
一个 768 维的向量 q = (q_0, q_1, q_2, q_3, …, q_766, q_767) ,RoPE 会这样看待它:
- 把 (q_0, q_1) 看作第 1 个二维向量(复数)。
- 把 (q_2, q_3) 看作第 2 个二维向量(复数)。
- …
- 把 (q_766, q_767) 看作第 384 个二维向量(复数)。
接下来,RoPE 对这 384 个二维向量 分别进行旋转 。
但是,它不会用同一个旋转速度 θ 。它会为每一对向量分配一个 不同的旋转速度 θ_j (其中 j 是分组的索引,从 0 到 383)。
θ_j = 10000^(-2j / d_model)
- 对于第一组 (q_0, q_1) , j=0 , θ_0 的值很大,旋转速度 最快 。
- 对于最后一组 (q_766, q_767) , j=383 , θ_383 的值很小,旋转速度 最慢 。
所以,对于一个在位置 m 的 768 维向量 q ,RoPE 的完整操作是:
- 将 q 拆分成 384 个二维向量 q_j = (q_{2j}, q_{2j+1}) 。
- 对每一个 q_j ,都用它自己的旋转速度 θ_j 进行旋转,得到 q_{m,j} = q_j * e^(imθ_j) 。
- 最后再把这 384 个旋转后的二维向量拼回一个 768 维的向量。
第四步:高维点积的最终推导
高维向量的点积,等于其所有维度对应相乘后再求和。由于我们是两两分组的,这等价于:
Dot(q_m, k_n) = Σ [ Dot(q_{m,j}, k_{n,j}) ] (从 j=0 到 383 求和)
我们从第二步已经知道,对于每一组 j :
Dot(q_{m,j}, k_{n,j}) = Re( (q_j * k_j*) * e^(i(m-n)θ_j) )
所以,最终两个 768 维向量的点积就是:
Dot(q_m, k_n) = Σ [ Re( (q_j * k_j*) * e^(i(m-n)θ_j) ) ]
最终结论: 这个最终公式的每一项,都只和原始向量 q_j 、 k_j 以及相对位置 (m-n) 有关。因此,它们的总和,也必然只和原始向量及相对位置有关。
这就从数学上严格证明了,RoPE 这种设计,巧妙地将绝对位置信息 m 和 n 编码进了向量,但在最关键的 Attention 点积计算中,这些绝对位置信息又神奇地“抵消”掉了,只留下了模型真正需要的相对位置信息 (m-n) 。
RoPE在LLM中具体位置
1. RoPE 最终输出的是什么?
RoPE 本身 没有一个独立的、明确的“输出” 。它不是一个像 Add & Norm 那样界限清晰的“层”。
更准确地说,RoPE 是一种 操作(Operation) ,这个操作被直接**嵌入(Injected)**到了 Attention 机制的计算流程中。
-
传统方法 (如 BERT 或原始 Transformer) :
- Input -> Token Embedding -> Positional Encoding (一个独立的相加步骤) -> Attention Layer
- 在这个流程中,有一个明确的步骤产生了“加入了位置信息的向量”。
-
RoPE 的方法 :
- Input -> Token Embedding -> Attention Layer
- 在 Attention Layer 内部 ,当模型根据输入的词向量计算出 Query (Q) 和 Key (K) 向量之后, RoPE 操作才开始介入 。
- RoPE 会 分别对 Q 和 K 向量进行“旋转” ,得到旋转后的 Q_rot 和 K_rot 。
- 然后,模型用 Q_rot 和 K_rot 去计算注意力分数。
所以,RoPE 的“输出”就是那个被旋转过的 Q_rot 和 K_rot 矩阵,但它们是 Attention 计算过程中的 中间变量 ,而不是一个独立的、会传递给下一层的输出。
2. 没有单独的 Position Encoding 过程
在使用了 RoPE 的模型(如 LLaMA)的架构图中,你再也找不到那个在 Token Embedding 之后、 Attention 之前,画着一个 + 号的 Positional Encoding 模块了。
那个经典的 X = X_embed + X_pos 的 显式相加步骤被彻底移除了 。
位置信息不再是预先“添加”到词向量上,而是在 Attention 计算时“动态注入”的。
3. 直接合入到 Attention 计算
这正是 RoPE 的核心设计理念。
让我们把 Self-Attention 的计算流程再细化一下,看看 RoPE 在哪里“动手脚”:
标准 Self-Attention 流程:
- 输入矩阵 X (已经加好了传统位置编码)。
- 通过三个不同的权重矩阵 W_q , W_k , W_v ,计算出 Q , K , V :
- Q = X * W_q
- K = X * W_k
- V = X * W_v
- 计算注意力分数: Score = softmax( (Q * K^T) / sqrt(d_k) )
- 计算最终输出: Output = Score * V
集成了 RoPE 的 Self-Attention 流程:
- 输入矩阵 X (只经过 Token Embedding, 没有 位置编码)。
- 通过三个不同的权重矩阵 W_q , W_k , W_v ,计算出原始的 Q , K , V :
- Q_orig = X * W_q
- K_orig = X * W_k
- V = X * W_v (注意:V 向量通常不应用 RoPE,因为它只提供内容,不参与位置匹配)
- RoPE 操作介入 :
- 对 Q_orig 应用 RoPE 旋转,得到 Q_rot 。
- 对 K_orig 应用 RoPE 旋转,得到 K_rot 。
- 计算注意力分数: Score = softmax( (Q_rot * K_rot^T) / sqrt(d_k) )
- 计算最终输出: Output = Score * V
通过这个对比,您可以清晰地看到,RoPE 就像一个“插件”,被精准地安装在了 Q 和 K 生成之后、Attention 分数计算之前。它改变了 Attention 机制的“匹配规则”,使其从“匹配绝对位置的语义”变成了“匹配相对位置的语义”。
参考资料:
https://zhuanlan.zhihu.com/p/664214907
更多推荐


所有评论(0)