
1. 从位置这件事说起为什么Transformer需要位置编码如果你接触过Transformer架构一定绕不开一个核心矛盾自注意力机制本身是置换不变的。换句话说你把输入序列里的词打乱顺序注意力算出来的结果只是跟着换了个位置模型本身完全感知不到谁在前谁在后。这跟RNN那种天然按时间步递推的结构完全不同——RNN自带顺序感而Transformer是一张白纸。所以位置编码Positional Encoding本质上是在给这张白纸补上顺序这个维度。最早的做法是正弦余弦编码用不同频率的三角函数给每个位置生成一个固定向量直接加到词嵌入上。后来有了可学习的位置嵌入、相对位置编码、旋转位置编码RoPE、ALiBi等等方案层出不穷。但问题来了这些方案看起来各成一派正弦编码是加性的、RoPE是乘性的、ALiBi是加在注意力分数上的偏置它们之间到底有没有统一的数学框架能不能用一套语言把所有这些位置编码都描述出来GRAPE这篇工作给出的答案是能而且这个统一框架来自群论。它把位置编码看作群作用在表示空间上的一种实现用群表示论的视角把加性、乘性、相对、绝对这些看似割裂的方案全部纳入同一个体系。这不是简单的综述式统一而是给出了一个可推导、可扩展的生成框架——你可以用它造出新的位置编码而不只是归类已有的。这篇文章我会从群论的基本直觉讲起拆解GRAPE的核心构造对比它和RoPE、ALiBi这些主流方案的关系然后给出实操层面的复现思路和踩坑经验。适合对Transformer底层机制感兴趣、想深入理解位置编码本质的读者也适合正在做长上下文建模、需要自己设计位置编码的工程同学。2. 群论视角的直觉铺垫位置编码到底在编码什么2.1 把位置翻译成群作用先建立一个最朴素的直觉。假设序列长度是N位置集合就是{0, 1, 2, ..., N-1}。当我们说位置编码时实际上是在做一件事给每个位置i分配一个向量表示使得这个表示能反映位置之间的结构关系。这个结构关系是什么最核心的是平移关系位置i和位置j之间的关系本质上由差值i-j决定。位置5和位置3的关系跟位置105和位置103的关系应该是一样的——都是相差2。这种平移不变性就是群论里平移群的语言。所以位置编码真正要编码的不是我在第几个位置这个绝对坐标而是平移群如何作用在表示空间上。绝对位置编码是把这个群作用具体化成一个向量相对位置编码是直接编码群元素差值RoPE是把群作用实现成旋转矩阵。它们描述的是同一个东西的不同侧面。2.2 为什么群论是终极统一的合适语言群论之所以适合做这个统一是因为它天然处理对称性和变换。位置编码要满足的几个核心性质用群论语言表述会变得非常干净平移等变性位置编码应该满足 PE(iΔ) 和 PE(i) 之间存在某种由Δ决定的一致变换关系。这正是群作用的定义。组合性从位置i到位置j的变换应该等于从i到k再到j的变换的复合。这是群的结合律。可逆性位置变换应该可逆对应群元素的逆元存在。用群论框架你可以把位置编码的设计问题转化为选择一个群G选择G的一个表示ρ然后让位置编码实现这个表示。不同的选择就对应不同的位置编码方案。这个视角的威力在于它把设计位置编码从拍脑袋想公式变成了选群和选表示的结构化问题。2.3 加性、乘性、偏置三种实现方式的群论归属在GRAPE之前主流方案大致分三类用群论视角看它们的归属其实很清晰方案类型代表方法群作用实现方式作用位置加性绝对编码正弦余弦编码平移群在向量空间的加法表示词嵌入相加乘性相对编码RoPE旋转群SO(2)的表示注意力Q/K相乘加性偏置编码ALiBi平移群在标量上的表示注意力分数相加GRAPE的贡献在于指出这三类其实都是同一个群表示框架下的不同投影。加性和乘性的区别本质上是群表示选择在向量空间还是矩阵空间绝对和相对的区别本质上是编码群元素本身还是编码群作用的轨道。3. GRAPE的核心构造群表示如何生成位置编码3.1 从群元素到编码矩阵的映射GRAPE的核心构造可以拆成三步。第一步是选定一个群G通常取平移群或者它的离散子群。第二步是选定G的一个表示ρ把群元素g映射到矩阵ρ(g)。第三步是定义位置编码为这个表示的某种函数。具体来说对于位置iGRAPE定义其编码为ρ(g_i)其中g_i是群中对应位置i的元素。当群是平移群时g_i就是平移i步这个操作ρ(g_i)就是平移i步对应的矩阵。这个矩阵作用在表示空间上就实现了位置的编码。这个构造的精妙之处在于它自动满足了平移等变性ρ(g_i)·ρ(g_j) ρ(g_i·g_j) ρ(g_{ij})这是群同态的性质。也就是说位置i和位置j的编码复合起来自然对应位置ij的编码。这个性质是很多位置编码方案想要但难以保证的而GRAPE通过群表示的结构直接免费获得了它。3.2 为什么选择矩阵表示而不是向量表示这里有个关键的设计选择GRAPE用矩阵表示而不是向量表示。为什么向量表示比如正弦编码的问题是它只能实现加性作用。位置i的编码是一个向量v_i作用方式是x v_i。这种作用方式下两个位置编码的复合是v_i v_j对应位置ij的编码是v_{ij}。要保证v_i v_j v_{ij}这要求编码是线性的限制了表达能力。矩阵表示比如RoPE的旋转矩阵允许乘性作用x → R_i · x。复合是R_i · R_j R_{ij}这自动成立因为旋转矩阵的乘法对应角度相加。矩阵表示的表达能力更强能实现更丰富的群作用。GRAPE选择矩阵表示是因为它要统一的对象里既有加性也有乘性方案。用矩阵表示作为统一框架加性方案可以看作矩阵表示在特定子空间上的投影乘性方案是矩阵表示的直接实现。这样统一框架的覆盖面才够广。3.3 群表示的可约性与位置编码的分解群表示论里有个重要概念叫可约性一个表示如果能分解成更小的表示的直和就叫可约的。GRAPE利用这个概念来解释位置编码的维度分配问题。在多头注意力里每个头可以有不同的位置编码行为。用群表示的语言这相当于把整个表示空间分解成多个不可约表示的直和每个头负责一个子空间。不同子空间可以对应不同的群表示从而实现不同的位置感知模式。这个视角解释了一个实践中的现象为什么RoPE在不同维度上用不同频率的旋转因为那实际上是把表示空间分解成多个二维旋转子空间每个子空间对应一个不同频率的旋转表示。频率的选择就是不可约表示的选择。GRAPE把这个直觉推广到了更一般的群和表示上。4. GRAPE与RoPE、ALiBi的对照统一框架下的再理解4.1 RoPE是GRAPE在旋转群下的特例RoPE的核心操作是把query和key向量按二维一组进行旋转旋转角度与位置成正比。用GRAPE的语言这相当于群G取旋转群SO(2)的N个副本N是维度的一半表示ρ把位置i映射到一组旋转矩阵每个副本的旋转角度是i·θ_kθ_k是第k个副本的频率位置编码就是这组旋转矩阵RoPE的相对位置性质——注意力分数只依赖相对位置——在GRAPE框架下就是旋转群表示的性质R_i^T · R_j R_{j-i}只依赖差值。这个性质不是RoPE特有的技巧而是旋转群表示的必然结果。GRAPE的推广在于它允许你选择比SO(2)更一般的群。比如你可以选SO(3)做三维旋转或者选更复杂的李群。这为设计新的位置编码打开了空间。4.2 ALiBi是GRAPE在标量表示下的投影ALiBi的做法更简单在注意力分数上直接加一个与相对距离成正比的偏置距离越远偏置越负。用GRAPE的语言这相当于群G取平移群表示ρ把位置i映射到一个标量而不是矩阵这个标量作用在注意力分数上实现为加法ALiBi是GRAPE框架下最简表示的特例一维标量表示。它的表达能力有限只能表达距离衰减但计算极简。GRAPE把它纳入框架说明统一框架不排斥简单方案而是能解释简单方案为什么简单、简单在哪里。4.3 统一框架带来的新设计空间GRAPE最大的价值不是归类而是生成。一旦你有了群表示这个框架就可以系统性地探索新的位置编码换群从平移群换到其他群比如仿射群、置换群换表示从标量表示换到高维矩阵表示从可约表示换到不可约表示换作用位置从词嵌入换到注意力分数从Q/K换到输出这个设计空间比拍脑袋想公式大得多而且每个选择都有群论的性质做保证。比如你选了一个群表示平移等变性、组合性这些性质就自动满足不用单独验证。5. 复现GRAPE从理论到代码的落地路径5.1 环境准备与依赖选择复现GRAPE不需要特殊的深度学习框架PyTorch就够了。核心是实现群表示矩阵的生成和乘法。我建议的依赖配置python 3.9 torch 2.0 numpy 1.24 einops 0.6 # 用于张量重排处理多头维度很方便einops这个库值得单独提一下。GRAPE涉及大量的维度重排把维度分组、把群表示作用到特定子空间用einops的rearrange和einsum比手写permutereshape清晰得多也不容易出错。5.2 群表示矩阵的构造以旋转群SO(2)为例构造位置编码矩阵的核心代码如下import torch import math def build_rotation_encoding(seq_len, dim, base10000.0): 构造SO(2)群表示的位置编码矩阵。 seq_len: 序列长度 dim: 表示维度必须是偶数 base: 频率基数 返回: (seq_len, dim, dim) 的编码矩阵 assert dim % 2 0, 维度必须是偶数才能两两分组做旋转 half_dim dim // 2 # 频率不同子空间用不同频率对应不同的不可约表示 freqs base ** (-torch.arange(half_dim).float() / half_dim) positions torch.arange(seq_len).float() # 每个位置在每个子空间的角度 angles positions[:, None] * freqs[None, :] # (seq_len, half_dim) cos_a torch.cos(angles) sin_a torch.sin(angles) # 构造块对角旋转矩阵 # 每个2x2块是 [[cos, -sin], [sin, cos]] encoding torch.zeros(seq_len, dim, dim) for k in range(half_dim): encoding[:, 2*k, 2*k] cos_a[:, k] encoding[:, 2*k, 2*k1] -sin_a[:, k] encoding[:, 2*k1, 2*k] sin_a[:, k] encoding[:, 2*k1, 2*k1] cos_a[:, k] return encoding这段代码的关键点是块对角结构。每个二维子空间独立旋转整体是一个块对角矩阵。这个结构保证了不同子空间之间不干扰每个子空间对应一个独立的不可约表示。5.3 把编码作用到注意力上有了编码矩阵作用到注意力上的方式和RoPE类似但更通用def apply_grape_encoding(q, k, encoding): q, k: (batch, heads, seq_len, head_dim) encoding: (seq_len, dim, dim)dim head_dim # 对每个位置的q和k应用对应的编码矩阵 # 用einsum做批量矩阵乘法 q_encoded torch.einsum(b h s d, s d e - b h s e, q, encoding) k_encoded torch.einsum(b h s d, s d e - b h s e, k, encoding) return q_encoded, k_encoded这里有个性能上的注意点如果序列很长encoding矩阵是(seq_len, dim, dim)内存占用是O(seq_len · dim²)。当dim128、seq_len8192时这个矩阵有8192×128×128≈1.3亿个元素float32下约500MB。这个开销在长序列场景下不可忽视。优化思路是利用块对角结构的稀疏性只存储和计算非零块。上面的构造里实际只有half_dim个2×2块非零存储量可以降到O(seq_len · dim)。实现上可以用稀疏矩阵或者直接手写旋转操作避免构造完整矩阵。5.4 验证平移等变性复现完成后一定要验证群表示的核心性质——平移等变性。测试方法def test_translation_equivariance(encoding, i, j): 验证 R_i R_j R_{ij} R_i encoding[i] R_j encoding[j] R_ij encoding[i j] diff (R_i R_j - R_ij).abs().max() return diff.item() # 实测差值应该在1e-5量级浮点误差范围内这个测试看起来简单但它是GRAPE正确性的核心。如果这个性质不成立说明群表示构造有问题后续所有推导都不成立。我在复现时踩过一个坑一开始频率设置用了错误的公式导致R_i R_j和R_{ij}对不上排查了半天才发现是频率基数的问题。6. 实操中的坑与经验长上下文场景的注意事项6.1 频率外推训练短、推理长的核心难题GRAPE框架下位置编码的频率选择直接决定了外推能力。训练时序列长度是L_train推理时可能遇到L_test L_train。如果频率设计不当超出训练范围的位置编码会失真导致性能骤降。这个问题的本质是群表示在训练范围内是良定义的但超出范围后表示的性质可能不再保持。比如旋转角度超出训练范围后旋转矩阵可能进入没见过的区域模型无法正确处理。实操中的经验是低频分量负责长距离高频分量负责短距离。外推时低频分量相对稳定角度变化慢高频分量容易失真。所以外推策略通常是调整频率基数让高频分量在推理时降频扩展有效范围。这个思路和RoPE的NTK-aware插值是一致的GRAPE框架下可以更系统地分析。6.2 维度分配多少维度给位置编码GRAPE框架下位置编码占用的维度是一个可调参数。全部维度都给位置编码像RoPE那样是一种选择只给一部分维度是另一种选择。这个选择影响模型的表达能力和计算开销。我的经验是位置编码维度不宜过低但也不必全占。太低比如只给16维会导致位置信息表达不足长距离依赖建模能力下降全占比如128维全给位置会挤占内容表示的维度影响语义建模。实践中给一半到三分之二的维度是比较平衡的选择。这个结论在GRAPE框架下有理论支撑位置编码对应群表示的子空间内容表示对应其余子空间。两个子空间的维度分配本质上是表示空间分解的选择。分解得太细每个子空间表达能力不足分解得太粗位置和内容纠缠在一起难以解耦。6.3 多头异质性不同头用不同的群表示多头注意力天然适合GRAPE框架因为每个头可以对应不同的群表示。实践中可以这样设计一部分头用高频旋转关注局部位置关系一部分头用低频旋转关注全局位置关系一部分头用ALiBi式的标量偏置关注距离衰减这种异质性设计让模型同时具备多种位置感知模式。实测下来这种设计在长文本任务上比所有头用同一种编码更稳。原因是不同任务、不同层对位置信息的粒度需求不同异质性提供了更大的灵活性。不过要注意异质性设计会增加超参数调优的复杂度。头数、每类头的数量、频率分配这些都是需要调的超参数。建议从简单的均匀设计开始确认基线后再逐步引入异质性。6.4 数值稳定性旋转矩阵的累积误差长序列场景下旋转角度会变得很大位置×频率cos和sin的数值精度会下降。特别是当位置达到几万、几十万时角度可能达到10^5量级float32的精度在这么大的角度上会明显不足。解决方案有两个一是用float64计算编码矩阵只在构造编码时用不影响主计算二是用角度取模的方式把角度控制在[0, 2π)范围内。第二种更高效但要注意取模后的角度对应的旋转矩阵和原角度相同因为旋转是周期的所以数学上等价。# 角度取模保持数值稳定 angles angles % (2 * math.pi)这个操作在长序列场景下是必须的否则训练后期会出现loss突然发散的情况。我踩过这个坑一开始以为是学习率问题调了半天才发现是角度精度问题。7. 从GRAPE看位置编码的未来设计方向GRAPE框架最大的启发是位置编码的设计不应该停留在试公式的层面而应该从群表示的结构出发系统性地探索设计空间。这个视角下几个值得关注的方向非交换群的应用。目前主流方案用的都是交换群平移群、旋转群都是交换的群元素的复合与顺序无关。但自然语言的位置关系未必是交换的——先A后B和先B后A可能对应不同的语义。非交换群比如置换群、矩阵群可能能表达更丰富的位置关系。GRAPE框架天然支持非交换群这是它比RoPE更一般的地方。自适应群表示。目前群表示是预先固定的训练中不变化。但不同任务、不同层可能需要不同的群表示。让群表示成为可学习的参数或者根据输入动态选择群表示是一个有潜力的方向。这相当于把选群这件事也交给模型去学。与状态空间模型的结合。状态空间模型如Mamba天然处理序列但它的位置感知机制和Transformer不同。GRAPE的群表示框架能否迁移到状态空间模型上是一个开放问题。初步的想法是状态空间模型的递推结构本身就隐含了平移群的作用用GRAPE的语言可以更清晰地分析它的位置感知能力。多模态场景的位置编码。文本是一维序列位置关系简单。但图像是二维的、视频是三维的位置关系复杂得多。GRAPE框架下二维位置对应二维平移群三维对应三维平移群群表示的选择空间更大。多模态场景可能是GRAPE框架真正发挥威力的地方。我在实际项目中试过把GRAPE的思路用到二维图像的位置编码上初步结果是二维旋转群表示比简单的位置嵌入在图像分类任务上有小幅提升但计算开销增加明显。这个方向的性价比还需要更多实验来验证。最后分享一个实操心得GRAPE这类理论性较强的工作复现时不要一上来就追求完整实现。先实现最简单的特例比如SO(2)旋转验证平移等变性确认框架正确后再逐步推广到更一般的群。理论工作的价值在于框架而框架的正确性可以通过特例来验证。跳过特例直接上一般实现很容易在细节上翻车而且难以定位问题。