做位置编码做得久的人,心里大概率都攒着一堆“这几种方法到底是不是一回事”的疑问。RoPE 拿旋转矩阵把位置揉进向量,ALiBi 直接在注意力分数上扣一个线性偏置,Sinusoidal 用一串三角函数给每个位置发身份证,可学习位置编码则干脆把位置向量丢给网络自己学。它们看起来各有各的道理,但放到一起总觉得缺一张总图:到底有没有一个统一框架,能把 RoPE、ALiBi、Sinusoidal、T5 Bias、可学习位置编码全塞进去,并且解释清楚它们各自的取舍?GRAPE 就是冲着这个“终极统一”去的,它用群论把位置编码重新讲了一遍。这篇文章我想把 GRAPE 的逻辑掰开揉碎,讲清楚群论为什么适合干这件事,RoPE 和 ALiBi 在群论视角下各自是什么,以及 GRAPE 的落地思路和我在实际训练中踩过的坑。适合对 Transformer 位置编码有一定了解、但想往原理层面再进一步的同学。1. 位置编码的“补丁史”:为什么我们一直缺一张总图1.1 绝对位置、相对位置和“第三种”位置先把已有的位置编码按直觉分一下类。最早的正弦位置编码属于“绝对位置派”,每个位置 pos 生成一串三角函数特征,相当于给序列里的每个座位发一个固定门牌号。RoPE 属于“相对位置派”,它的核心思想是 query 和 key 的点积只依赖两者的相对偏移,也就是说注意力关心的是“你在我左边第三格还是右边第五格”,而不是“你具体坐在 7 号座位还是 12 号座位”。ALiBi 和 T5 Bias 也都在做相对位置,只不过直接在 logits 上加偏置,一个用线性递减,一个用分桶。但实际用的时候你会发现,“绝对”和“相对”的边界其实很模糊。绝对正弦编码在自注意力里做矩阵乘法之后,位置信息会被自动转换成相对形式,这个性质早就有论文分析过。反过来,RoPE 虽然只依赖相对偏移,但不同位置的绝对相位信息也没有完全丢失,因为旋转角度的起点是固定的。真正常见的第三种,是“可学习位置编码”,它不预设任何结构,embedding 表直接随模型训练,最后学成什么样完全取决于任务。你说它是绝对位置还是相对位置?都有可能,取决于数据把表训成了什么样子。这么一分,问题就出来了。我们有一堆方法,各有各的直觉,但没有人能说清楚它们共享什么样的代数结构。于是新方法出来后,大家只能靠实验对比来判断“这个新方法比 RoPE 好在哪”,而不是靠推导说明“它其实是 RoPE 在某个群结构上的推广”。GRAPE 想补的就是这张总图:能不能找到一个足够抽象的数学对象,让上面所有方法都变成这个对象的具体实例。1.2 注意力公式里那一点点偏置,决定了大模型的长度行为这里需要回到注意力机制本身。不管是哪类位置编码,最终都要进入 attention logits 的计算:score(q, k, i, j) = (q_i^T k_j) / sqrt(d) + bias_{i, j}位置编码影响的只有两个地方:一是通过变换 q 和 k 的向量表示(比如 RoPE 旋转、正弦加法、可学习向量相加),二是直接改 bias 项(比如 ALiBi、T5 Bias)。前者可以统称为“表示改造”,后者是“logits 注入”。两种路径在梯度传播性质上差别很大:表示改造会连带影响 value 的加权过程,logits 注入则只影响注意力权重,不影响 value 本身的语义。但不管用哪条路径,最终位置信息都会被压缩成同一件事——模型能感知到的“距离”。我自己的经验是,很多外推问题(长文本泛化)其实不是 attention 计算不了长距离,而是位置编码给的距离信号在训练长度之外失真了。RoPE 在超过训练长度后旋转矩阵本身不会爆炸,但它和高频信息叠加后,query-key 点积的相对角度分布会变得训练时没见过;ALiBi 则因为线性偏置会一直往下减,长距离的注意力权重被压得过于稀疏。位置编码本质上是在控制“距离感”,GRAPE 的视角则把这种“距离感”解释成某个群作用下的不变性与变换规律。有了这个视角,外推问题就不再是玄学,而是一个可以被代数工具分析的几何问题。2. 群论视角的第一课:位置不是数字,是某个集合上的“动作”2.1 旋转群与平移群:最直观的两个玩具群论听起来吓人,但这里只需要抓住一个直觉:群是一组动作的集合,这些动作可以复合,可以求逆,复合之后还是这个集合里的动作。比如旋转群 SO(2),它包含所有二维旋转,先转 30 度再转 45 度,等于直接转 75 度,逆动作是反向转。平移群同理,先向右移 2 格再向右移 3 格,等于向右移 5 格,逆动作是向左移。位置编码关心的就是“相对位移”这个操作。站在位置 i 的 token 要理解位置 j 的 token,关键在于知道“从 i 到 j 需要做什么动作”。这个动作天然有群结构:连续做两个相对位移等价于做一个合成的相对位移,反过来也有逆位移。这就比单纯记一个整数距离强得多,因为群结构规定好了动作之间怎么组合、怎么求逆,而这些规则恰恰是 Transformer 在长距离推理中最需要的东西。为什么要用群而不是直接用数字距离?数字距离比如 |i - j|,只告诉你差了多少,没告诉你方向,也没告诉你这个差值在更复杂的几何结构上怎么组合。旋转群上的“距离”是两个角度的差,平移群上的“距离”是两个位置的差,但这两个“差”所满足的代数性质完全不同。GRAPE 的出发点很简单:先把所有位置映射到某个群上的元素,再用群结构去定义位置之间的关系。不同的群选择,直接决定了位置编码能表达什么样的位置关系。2.2 位置编码的数学身份:从标量到群表示具体到实现层面,群元素只是一个抽象符号,模型没法直接拿它做矩阵乘法。我们需要把群元素映射成实数矩阵或者向量,并且保证这种映射“保持群结构”,也就是两个群元素相乘的结果,映射后等于两个矩阵相乘的结果。这种保持结构的映射,数学上叫群的表示。把位置编码放到这个语言下看,RoPE 就是一个非常标准的群表示:它对每个相对位移(群元素)都构造一个旋转矩阵,矩阵乘法把位置偏移合成在一起。Sinusoidal 位置编码也可以看作平移群在二维旋转表示下的特征映射,经典公式里的 sin/cos 其实就是旋转群的特征函数,只是它把每个位置编码成了一个高维旋转向量。ALiBi 不是表示,但它是群元素到实数的一个函数,它保留了群元素的某种序结构,只是没有用矩阵乘法,而是用函数取值直接充当距离衰减信号。这就引出了 GRAPE 的核心主张:位置编码 = 位置空间到某个群 G 的映射 + G 的表示或函数的选择。绝对位置、相对位置、可学习位置全是这个公式的特例。群论在这里不是硬造一个数学外衣,而是真正提供了一个“生成位置编码”的语法——你可以设计新的群、新的表示、新的距离函数,而不是只会在 sin/cos 和线性偏置之间二选一。3. 让 GRAPE 真正“统一”:RoPE、ALiBi、Sinusoidal 如何坐进同一张表3.1 RoPE 为什么是 SO(2) 表示,它表达的群结构是什么RoPE 的做法现在大家都很熟:把 query 和 key 按维度分成两两一组,每组当作二维平面上的一个点,然后用与位置相关的角度旋转它。具体来说,第 t 个 token 的第 d 维分组,旋转角度是 t 乘上一个基频 theta_d。于是两个位置 t 和 s 的 query-key 点积,会出现一个相对角度差 (t - s) * theta_d。这背后对应的群就是二维旋转群 SO(2)。每个相对位置都是群里的一个旋转动作,动作的复合就是“先旋转 (t-s) 再旋转 (s-r) 等于旋转 (t-r)”。RoPE 选择的是把同一个 SO(2) 群作用在每一组二维分块上,不同分组使用不同的基频 theta_d,所以整体表示是 SO(2) 的若干个不可约表示的直和。从这个视角看,RoPE 在群论上非常“干净”:它满足群同态,旋转矩阵的乘法严格对应位置偏移的加法。也正因如此,RoPE 在相对位置推理上表现稳定,超过训练长度时矩阵本身不会失稳,真正要担心的是基频分布带来的高频外推失真。GRAPE 框架下,RoPE 的改进方向也变得清晰:要么改变群结构(例如换成更大的旋转群或离散旋转子群),要么改变基频的分布方式,而不是瞎调旋转矩阵的类别。3.2 ALiBi 是半群上的单调函数,不是例外ALiBi 的公式特别简单:在注意力 logits 上加上 -m * |i - j|,其中 m 是一个和注意力头相关的斜率。很多讨论只把它当作一个工程技巧,认为它和 RoPE 是两条路线。但在群论视角下,ALiBi 完全可以纳入同一张表——它只是选了一个特别简单的“群”和特别简单的函数。确切地说,ALiBi 用的不是完整旋转群,而是自然数平移半群上的一个单调函数。半群和群的区别在于少了逆元素:你只知道“向右移动”的操作,没有反向移动构成群。在这个半群上,ALiBi 定义了一个函数 f(i, j) = -m * |i - j|,它满足一种“距离衰减”的函数性质,但不需要满足群同态。所以 ALiBi 是“群函数”写法而不是“群表示”写法,两者都落在 GRAPE 的语法里:你选了群/半群 G,然后选一个从 G 到实数的函数来调制注意力偏置。这带来一个有意思的推论:ALiBi 和 RoPE 不是不可比较的两派,而是同一框架下“函数路径”和“表示路径”的两个不同选择。GRAPE 甚至可以让你混用——对某一部分注意力头走 RoPE 的旋转表示,对另一部分头走 ALiBi 的线性衰减函数。我自己做实验时发现,这种混用在中等规模模型上确实能提升外推稳定度,只是要注意两部分信号的比例别失衡。3.3 Sinusoidal 和可学习位置编码的群覆盖解释Sinusoidal 位置编码看起来是一堆 sin/cos,但它同样有群论身份。经典正弦编码可以看作平移群在二维旋转表示下的特征映射:每个位置 pos 在维度 2d 和 2d+1 上对应一对 (sin(pos / base^{2d/D}), cos(pos / base^{2d/D})),这恰好是复数平面上的旋转单位向量。不同维度对应不同频率,构成一组傅里叶特征。所以它本质上是把平移群打到多个旋转子群上的表示,而且这个表示是可逆的——理论上你能从编码反推位置。可学习位置编码则更自由:它没有预设群表示,而是直接学一组嵌入向量。从 GRAPE 视角看,可学习位置编码等价于“让网络自己学一个位置空间的覆盖嵌入”,未必满足严格的群结构。它的表达能力更强,但代价是失去了外推保证。训练长度 2048 的模型,你硬塞 4096 的位置,可学习位置嵌入表直接越界,这是它最不擅长外推的根本原因。GRAPE 至少解释清楚了为什么“可学习”不等于“任意学”——你想要好的外推,就得保留群结构,而不是只靠数据硬拟合。下面这张表是我整理的位置编码群论速查表,方便对照理解:方法底层代数结构群表示/函数写法位置关系表达外推特性Sinusoidal平移群多个二维旋转表示直和绝对位置作为相对角度的编码受限于基频选择,可部分外推RoPESO(2) 旋转群按维度分组的旋转矩阵直和相对偏移通过角度差表达矩阵稳定,但高频可能失真ALiBi自然数半群半群上的线性衰减函数相对距离的单调负偏置偏置递减稳定,长距离易稀疏T5 Bias相对偏移集合分段常值函数分桶相对距离偏置超出桶范围不再区分可学习位置无固定群结构自由嵌入表任意位置向量基本无法外推4. GRAPE 的落地思路:如何在 Transformer 里实际使用群论4.1 群元素采样:连续群太大,必须离散化理论归理论,真要在一个 Transformer 里把群论用起来,第一个拦路虎就是群元素太多了。以 SO(2) 为例,它有连续无穷多个旋转角度,但序列长度最多几千,位置数量有限。GRAPE 的思路不是去处理整个连续群,而是采样一个足够覆盖实际位置的有限子群或有限子集。具体做法一般是:选择整数 N 作为循环群的阶,把位置 t 映射到群元素 g_t = e^{i * 2π * t / N}(复数表示),也就是把位置均匀分布在单位圆上。这样 g_t 和 g_s 的“群距离”由它们的乘法差值 g_t * g_s^{-1} 决定,不再由简单的整数差决定。采样密度决定了你能表达多少种不同的相对位置:N 太小,两个不同位置差会被映射到同一个群元素附近,相对位置区分度下降;N 太大,又会导致模型看到的位置循环周期过长,等于硬学了一个几乎线性的映射,失去了群结构的优势。我在一个模拟项目里试过不同 N,经验是 N 取训练长度的 2 到 4 倍效果较好。太小会让位置在圆上挤成一团,注意力对相邻位置的区分变差;太大则近似退化成绝对位置差,群结构的周期优势体现不出来。这个比例不是定理,只是一个比较稳的经验区间。4.2 从群元素到注意力偏置:特征函数与距离度量拿到群元素之后,GRAPE 需要把群元素变成模型能用的信号。有两条路可走,对应前面说的“表示路径”和“函数路径”。表示路径的做法是:把每个 token 的位置映射为群元素,然后用这个群元素的表示矩阵去变换 query 和 key。用复数表示最方便,每个位置 t 对应一个单位复数,然后构造对应的 2D 旋转矩阵去做和 RoPE 类似的旋转操作。这时相对偏移 (t-s) 不再直接进矩阵,而是以 g_t * g_s^{-1} 的形式出现,它在复数域上的实部就是 cos(2π(t-s)/N),虚部是 sin(2π(t-s)/N),天然带着周期结构。函数路径的做法是:直接在注意力 logits 上加一个基于群距离的偏置。GRAPE 可以用一个单调递减函数 f(d(g_t, g_s^{-1})) 来替代 ALiBi 的线性偏置。群距离 d 可以选单位圆上的测地距离,也可以选 |1 - g_t * g_s^{-1}| 这类复数模差。这样位置编码变成了一张可学习的距离-偏置映射表,但映射的输入不是原始整数距离,而是群距离,所以自动继承了群结构的组合性质。两种路径我都试过,函数路径的调试成本更低,因为它不改 query/key 的数值分布,和现有注意力架构兼容性更好;表示路径的容量上限更高,但数值稳定性要额外小心,尤其是旋转角度的初始化不当,很容易在深层模型里引起训练不稳定。4.3 一个可跑的伪代码与参数设置下面给一个 GRAPE 函数路径的最小实现思路,可以作为替换 ALiBi 的起点。这部分代码只是示意,重点在于让你看清整个数路是怎么流转的:import numpy as np import torch import torch.nn as nn class GroupDistanceBias(nn.Module): def __init__(self, num_heads, max_len, group_order=2048, num_buckets=32): super().__init__() self.group_order = group_order # 可学习的桶偏置,类似 T5 Bias 的桶表,但输入是群距离 self.bucket_bias = nn.Parameter(torch.zeros(num_heads, num_buckets)) # 可学习的桶边界,初始按群距离对数均匀切分 self.bucket_edges = nn.Parameter( torch.linspace(0.1, 2.0, num_buckets - 1) ) def group_distance(self, pos_i, pos_j): # 将位置映射到单位圆上的复数 theta_i = 2 * np.pi * pos_i / self.group_order theta_j = 2 * np.pi * pos_j / self.group_order g_i = torch.exp(1j * theta_i) g_j = torch.exp(1j * theta_j) # 群距离用 |1 - g_j * conj(g_i)| 表示 diff = torch.abs(1.0 - g_j * torch.conj(g_i)) return diff def forward(self, pos_i, pos_j): dist = self.group_distance(pos_i, pos_j) # [batch, seq, seq] # 把距离分桶 edges = torch.sort(torch.sigmoid(self.bucket_edges))[0] bucket_idx = torch.bucketize(dist, edges) bias = self.bucket_bias[:, bucket_idx] # [heads, batch, seq, seq] return bias这里有几个参数要注意。group_order 控制群周期的长度,我建议设成训练序列长度的 2 倍左右,并不是越长越好。bucket_edges 初始化成对数均匀分布,是希望近距离分桶更细、远距离分桶更粗,这是从 T5 Bias 的经验里继承过来的。bucket_bias 的初始化建议为 0,让模型自己决定距离偏置的符号和幅度,避免一开始就把注意力分布带偏。真正训练时,我建议先在一个相对小规模的模型(比如 6 层、8 头)上验证群距离偏置是否正常收敛,再搬到更大模型上。直接上大模型调参,你会分不清到底是群选择的问题还是优化器的问题。5. 实测观察与容易踩的坑5.1 外推长度的变化规律我在一个 400M 参数规模的自回归语言模型上做了对比实验,训练长度固定在 2048,然后分别用 ALiBi、RoPE 和 GRAPE 距离偏置测试 4096、8192 长度的困惑度。ALiBi 在 4096 长度上困惑度上升明显,但还可用;到 8192 时注意力变得稀疏,很多长距离 token 几乎没有被关注到。RoPE 在 4096 表现不错,8192 时出现少量下降,但整体比 ALiBi 稳。GRAPE 距离偏置在 4096 和 8192 都保持了相对平稳的困惑度,没有出现突然崩溃。GRAPE 的周期结构起了作用。单位圆上的群距离天然有一个最大边界(两点相距最大是半圆),超过这个距离后,位置关系开始“折返”,模型看到的距离信号不再无限增长。这与 ALiBi 的无限负偏置形成鲜明对比。直观理解就是:位置关系被卷在一个有限范围里,模型不需要外推出一个从未见过的距离值,而只需要在训练时见过的群距离范围内做插值。但要注意,这种外推红利不是免费的。折返效应在长距离上会引入位置歧义——两个距离很远的 token 会被映射成中等距离,这会轻微损害超长距离的精确推理。我的实测结果是,在需要精确长距离位置判断的任务上(比如“找到第 3000 个 token 之后的内容”),GRAPE 比 RoPE 略差。所以它适合“大体了解长距离关系”的场景,不适合“精确定位于超长位置”的场景。5.2 群类型选择、可学习参数初始化和数值稳定性GRAPE 落地最容易被忽略的坑是数值稳定性。单位复数乘法虽然漂亮,但如果你不谨慎处理,复数的实部和虚部很容易在反向传播中出现 NaN,尤其是在混合精度训练下。解决方法是把复数运算拆成实数矩阵运算,用 2x2 旋转矩阵代替复指数,在实现上稍微多几行代码,但稳定性会好一个数量级。第二个坑是群类型选择。SO(2) 是最常用的选择,但它只表达一维连续旋转关系,对“位置”这种序列关系够用,对二维网格、图结构之类的更复杂位置关系就不够了。如果将来要把位置编码扩展到图片或者图数据,GRAPE 的框架其实允许你换更大的群,比如 SO(3) 或者对称群。但目前 NLP 场景里,SO(2) 的性价比最高,不要一上来就上复杂群,调试成本会急剧增加。第三个坑是 bucket_edges 的可学习性质。距离分桶的边界如果完全自由学习,很容易在训练初期就收敛到一个次优配置,而且不同头学到完全不同的分桶方式,降低了模型的归纳稳定性。我的建议是:前 10% 的训练步数冻结 bucket_edges,只训练 bucket_bias,等注意力分布相对稳定后再放开边界参数。这样虽然牺牲了一点点灵活性,但训练曲线会平滑很多。从整个位置编码的发展来看,GRAPE 提供的与其说是一个新方法,不如说是一套“位置编码的设计语法”。它让你不再对着 RoPE、ALiBi、Sinusoidal 一个一个调,而是先想清楚:你要用什么群结构去组织位置关系,你希望这个关系以表示形式还是函数形式进入注意力。想明白这两件事,位置编码的设计就从打补丁变成了搭积木。我现在做新模型的位置编码设计,都会先拿 GRAPE 这套思路做一轮“群结构诊断”,再决定具体用哪个实现。这个视角一旦建立,你再看那些层出不穷的新位置编码,心里就有底多了。
做位置编码做得久的人,心里大概率都攒着一堆“这几种方法到底是不是一回事”的疑问。RoPE 拿旋转矩阵把位置揉进向量,ALiBi 直接在注意力分数上扣一个线性偏置,Sinusoidal 用一串三角函数给每个位置发身份证,可学习位置编码则干脆把位置向量丢给网络自己学。它们看起来各有各的道理,但放到一起总觉得缺一张总图:到底有没有一个统一框架,能把 RoPE、ALiBi、Sinusoidal、T5 Bias、可学习位置编码全塞进去,并且解释清楚它们各自的取舍?GRAPE 就是冲着这个“终极统一”去的,它用群论把位置编码重新讲了一遍。这篇文章我想把 GRAPE 的逻辑掰开揉碎,讲清楚群论为什么适合干这件事,RoPE 和 ALiBi 在群论视角下各自是什么,以及 GRAPE 的落地思路和我在实际训练中踩过的坑。适合对 Transformer 位置编码有一定了解、但想往原理层面再进一步的同学。1. 位置编码的“补丁史”:为什么我们一直缺一张总图1.1 绝对位置、相对位置和“第三种”位置先把已有的位置编码按直觉分一下类。最早的正弦位置编码属于“绝对位置派”,每个位置 pos 生成一串三角函数特征,相当于给序列里的每个座位发一个固定门牌号。RoPE 属于“相对位置派”,它的核心思想是 query 和 key 的点积只依赖两者的相对偏移,也就是说注意力关心的是“你在我左边第三格还是右边第五格”,而不是“你具体坐在 7 号座位还是 12 号座位”。ALiBi 和 T5 Bias 也都在做相对位置,只不过直接在 logits 上加偏置,一个用线性递减,一个用分桶。但实际用的时候你会发现,“绝对”和“相对”的边界其实很模糊。绝对正弦编码在自注意力里做矩阵乘法之后,位置信息会被自动转换成相对形式,这个性质早就有论文分析过。反过来,RoPE 虽然只依赖相对偏移,但不同位置的绝对相位信息也没有完全丢失,因为旋转角度的起点是固定的。真正常见的第三种,是“可学习位置编码”,它不预设任何结构,embedding 表直接随模型训练,最后学成什么样完全取决于任务。你说它是绝对位置还是相对位置?都有可能,取决于数据把表训成了什么样子。这么一分,问题就出来了。我们有一堆方法,各有各的直觉,但没有人能说清楚它们共享什么样的代数结构。于是新方法出来后,大家只能靠实验对比来判断“这个新方法比 RoPE 好在哪”,而不是靠推导说明“它其实是 RoPE 在某个群结构上的推广”。GRAPE 想补的就是这张总图:能不能找到一个足够抽象的数学对象,让上面所有方法都变成这个对象的具体实例。1.2 注意力公式里那一点点偏置,决定了大模型的长度行为这里需要回到注意力机制本身。不管是哪类位置编码,最终都要进入 attention logits 的计算:score(q, k, i, j) = (q_i^T k_j) / sqrt(d) + bias_{i, j}位置编码影响的只有两个地方:一是通过变换 q 和 k 的向量表示(比如 RoPE 旋转、正弦加法、可学习向量相加),二是直接改 bias 项(比如 ALiBi、T5 Bias)。前者可以统称为“表示改造”,后者是“logits 注入”。两种路径在梯度传播性质上差别很大:表示改造会连带影响 value 的加权过程,logits 注入则只影响注意力权重,不影响 value 本身的语义。但不管用哪条路径,最终位置信息都会被压缩成同一件事——模型能感知到的“距离”。我自己的经验是,很多外推问题(长文本泛化)其实不是 attention 计算不了长距离,而是位置编码给的距离信号在训练长度之外失真了。RoPE 在超过训练长度后旋转矩阵本身不会爆炸,但它和高频信息叠加后,query-key 点积的相对角度分布会变得训练时没见过;ALiBi 则因为线性偏置会一直往下减,长距离的注意力权重被压得过于稀疏。位置编码本质上是在控制“距离感”,GRAPE 的视角则把这种“距离感”解释成某个群作用下的不变性与变换规律。有了这个视角,外推问题就不再是玄学,而是一个可以被代数工具分析的几何问题。2. 群论视角的第一课:位置不是数字,是某个集合上的“动作”2.1 旋转群与平移群:最直观的两个玩具群论听起来吓人,但这里只需要抓住一个直觉:群是一组动作的集合,这些动作可以复合,可以求逆,复合之后还是这个集合里的动作。比如旋转群 SO(2),它包含所有二维旋转,先转 30 度再转 45 度,等于直接转 75 度,逆动作是反向转。平移群同理,先向右移 2 格再向右移 3 格,等于向右移 5 格,逆动作是向左移。位置编码关心的就是“相对位移”这个操作。站在位置 i 的 token 要理解位置 j 的 token,关键在于知道“从 i 到 j 需要做什么动作”。这个动作天然有群结构:连续做两个相对位移等价于做一个合成的相对位移,反过来也有逆位移。这就比单纯记一个整数距离强得多,因为群结构规定好了动作之间怎么组合、怎么求逆,而这些规则恰恰是 Transformer 在长距离推理中最需要的东西。为什么要用群而不是直接用数字距离?数字距离比如 |i - j|,只告诉你差了多少,没告诉你方向,也没告诉你这个差值在更复杂的几何结构上怎么组合。旋转群上的“距离”是两个角度的差,平移群上的“距离”是两个位置的差,但这两个“差”所满足的代数性质完全不同。GRAPE 的出发点很简单:先把所有位置映射到某个群上的元素,再用群结构去定义位置之间的关系。不同的群选择,直接决定了位置编码能表达什么样的位置关系。2.2 位置编码的数学身份:从标量到群表示具体到实现层面,群元素只是一个抽象符号,模型没法直接拿它做矩阵乘法。我们需要把群元素映射成实数矩阵或者向量,并且保证这种映射“保持群结构”,也就是两个群元素相乘的结果,映射后等于两个矩阵相乘的结果。这种保持结构的映射,数学上叫群的表示。把位置编码放到这个语言下看,RoPE 就是一个非常标准的群表示:它对每个相对位移(群元素)都构造一个旋转矩阵,矩阵乘法把位置偏移合成在一起。Sinusoidal 位置编码也可以看作平移群在二维旋转表示下的特征映射,经典公式里的 sin/cos 其实就是旋转群的特征函数,只是它把每个位置编码成了一个高维旋转向量。ALiBi 不是表示,但它是群元素到实数的一个函数,它保留了群元素的某种序结构,只是没有用矩阵乘法,而是用函数取值直接充当距离衰减信号。这就引出了 GRAPE 的核心主张:位置编码 = 位置空间到某个群 G 的映射 + G 的表示或函数的选择。绝对位置、相对位置、可学习位置全是这个公式的特例。群论在这里不是硬造一个数学外衣,而是真正提供了一个“生成位置编码”的语法——你可以设计新的群、新的表示、新的距离函数,而不是只会在 sin/cos 和线性偏置之间二选一。3. 让 GRAPE 真正“统一”:RoPE、ALiBi、Sinusoidal 如何坐进同一张表3.1 RoPE 为什么是 SO(2) 表示,它表达的群结构是什么RoPE 的做法现在大家都很熟:把 query 和 key 按维度分成两两一组,每组当作二维平面上的一个点,然后用与位置相关的角度旋转它。具体来说,第 t 个 token 的第 d 维分组,旋转角度是 t 乘上一个基频 theta_d。于是两个位置 t 和 s 的 query-key 点积,会出现一个相对角度差 (t - s) * theta_d。这背后对应的群就是二维旋转群 SO(2)。每个相对位置都是群里的一个旋转动作,动作的复合就是“先旋转 (t-s) 再旋转 (s-r) 等于旋转 (t-r)”。RoPE 选择的是把同一个 SO(2) 群作用在每一组二维分块上,不同分组使用不同的基频 theta_d,所以整体表示是 SO(2) 的若干个不可约表示的直和。从这个视角看,RoPE 在群论上非常“干净”:它满足群同态,旋转矩阵的乘法严格对应位置偏移的加法。也正因如此,RoPE 在相对位置推理上表现稳定,超过训练长度时矩阵本身不会失稳,真正要担心的是基频分布带来的高频外推失真。GRAPE 框架下,RoPE 的改进方向也变得清晰:要么改变群结构(例如换成更大的旋转群或离散旋转子群),要么改变基频的分布方式,而不是瞎调旋转矩阵的类别。3.2 ALiBi 是半群上的单调函数,不是例外ALiBi 的公式特别简单:在注意力 logits 上加上 -m * |i - j|,其中 m 是一个和注意力头相关的斜率。很多讨论只把它当作一个工程技巧,认为它和 RoPE 是两条路线。但在群论视角下,ALiBi 完全可以纳入同一张表——它只是选了一个特别简单的“群”和特别简单的函数。确切地说,ALiBi 用的不是完整旋转群,而是自然数平移半群上的一个单调函数。半群和群的区别在于少了逆元素:你只知道“向右移动”的操作,没有反向移动构成群。在这个半群上,ALiBi 定义了一个函数 f(i, j) = -m * |i - j|,它满足一种“距离衰减”的函数性质,但不需要满足群同态。所以 ALiBi 是“群函数”写法而不是“群表示”写法,两者都落在 GRAPE 的语法里:你选了群/半群 G,然后选一个从 G 到实数的函数来调制注意力偏置。这带来一个有意思的推论:ALiBi 和 RoPE 不是不可比较的两派,而是同一框架下“函数路径”和“表示路径”的两个不同选择。GRAPE 甚至可以让你混用——对某一部分注意力头走 RoPE 的旋转表示,对另一部分头走 ALiBi 的线性衰减函数。我自己做实验时发现,这种混用在中等规模模型上确实能提升外推稳定度,只是要注意两部分信号的比例别失衡。3.3 Sinusoidal 和可学习位置编码的群覆盖解释Sinusoidal 位置编码看起来是一堆 sin/cos,但它同样有群论身份。经典正弦编码可以看作平移群在二维旋转表示下的特征映射:每个位置 pos 在维度 2d 和 2d+1 上对应一对 (sin(pos / base^{2d/D}), cos(pos / base^{2d/D})),这恰好是复数平面上的旋转单位向量。不同维度对应不同频率,构成一组傅里叶特征。所以它本质上是把平移群打到多个旋转子群上的表示,而且这个表示是可逆的——理论上你能从编码反推位置。可学习位置编码则更自由:它没有预设群表示,而是直接学一组嵌入向量。从 GRAPE 视角看,可学习位置编码等价于“让网络自己学一个位置空间的覆盖嵌入”,未必满足严格的群结构。它的表达能力更强,但代价是失去了外推保证。训练长度 2048 的模型,你硬塞 4096 的位置,可学习位置嵌入表直接越界,这是它最不擅长外推的根本原因。GRAPE 至少解释清楚了为什么“可学习”不等于“任意学”——你想要好的外推,就得保留群结构,而不是只靠数据硬拟合。下面这张表是我整理的位置编码群论速查表,方便对照理解:方法底层代数结构群表示/函数写法位置关系表达外推特性Sinusoidal平移群多个二维旋转表示直和绝对位置作为相对角度的编码受限于基频选择,可部分外推RoPESO(2) 旋转群按维度分组的旋转矩阵直和相对偏移通过角度差表达矩阵稳定,但高频可能失真ALiBi自然数半群半群上的线性衰减函数相对距离的单调负偏置偏置递减稳定,长距离易稀疏T5 Bias相对偏移集合分段常值函数分桶相对距离偏置超出桶范围不再区分可学习位置无固定群结构自由嵌入表任意位置向量基本无法外推4. GRAPE 的落地思路:如何在 Transformer 里实际使用群论4.1 群元素采样:连续群太大,必须离散化理论归理论,真要在一个 Transformer 里把群论用起来,第一个拦路虎就是群元素太多了。以 SO(2) 为例,它有连续无穷多个旋转角度,但序列长度最多几千,位置数量有限。GRAPE 的思路不是去处理整个连续群,而是采样一个足够覆盖实际位置的有限子群或有限子集。具体做法一般是:选择整数 N 作为循环群的阶,把位置 t 映射到群元素 g_t = e^{i * 2π * t / N}(复数表示),也就是把位置均匀分布在单位圆上。这样 g_t 和 g_s 的“群距离”由它们的乘法差值 g_t * g_s^{-1} 决定,不再由简单的整数差决定。采样密度决定了你能表达多少种不同的相对位置:N 太小,两个不同位置差会被映射到同一个群元素附近,相对位置区分度下降;N 太大,又会导致模型看到的位置循环周期过长,等于硬学了一个几乎线性的映射,失去了群结构的优势。我在一个模拟项目里试过不同 N,经验是 N 取训练长度的 2 到 4 倍效果较好。太小会让位置在圆上挤成一团,注意力对相邻位置的区分变差;太大则近似退化成绝对位置差,群结构的周期优势体现不出来。这个比例不是定理,只是一个比较稳的经验区间。4.2 从群元素到注意力偏置:特征函数与距离度量拿到群元素之后,GRAPE 需要把群元素变成模型能用的信号。有两条路可走,对应前面说的“表示路径”和“函数路径”。表示路径的做法是:把每个 token 的位置映射为群元素,然后用这个群元素的表示矩阵去变换 query 和 key。用复数表示最方便,每个位置 t 对应一个单位复数,然后构造对应的 2D 旋转矩阵去做和 RoPE 类似的旋转操作。这时相对偏移 (t-s) 不再直接进矩阵,而是以 g_t * g_s^{-1} 的形式出现,它在复数域上的实部就是 cos(2π(t-s)/N),虚部是 sin(2π(t-s)/N),天然带着周期结构。函数路径的做法是:直接在注意力 logits 上加一个基于群距离的偏置。GRAPE 可以用一个单调递减函数 f(d(g_t, g_s^{-1})) 来替代 ALiBi 的线性偏置。群距离 d 可以选单位圆上的测地距离,也可以选 |1 - g_t * g_s^{-1}| 这类复数模差。这样位置编码变成了一张可学习的距离-偏置映射表,但映射的输入不是原始整数距离,而是群距离,所以自动继承了群结构的组合性质。两种路径我都试过,函数路径的调试成本更低,因为它不改 query/key 的数值分布,和现有注意力架构兼容性更好;表示路径的容量上限更高,但数值稳定性要额外小心,尤其是旋转角度的初始化不当,很容易在深层模型里引起训练不稳定。4.3 一个可跑的伪代码与参数设置下面给一个 GRAPE 函数路径的最小实现思路,可以作为替换 ALiBi 的起点。这部分代码只是示意,重点在于让你看清整个数路是怎么流转的:import numpy as np import torch import torch.nn as nn class GroupDistanceBias(nn.Module): def __init__(self, num_heads, max_len, group_order=2048, num_buckets=32): super().__init__() self.group_order = group_order # 可学习的桶偏置,类似 T5 Bias 的桶表,但输入是群距离 self.bucket_bias = nn.Parameter(torch.zeros(num_heads, num_buckets)) # 可学习的桶边界,初始按群距离对数均匀切分 self.bucket_edges = nn.Parameter( torch.linspace(0.1, 2.0, num_buckets - 1) ) def group_distance(self, pos_i, pos_j): # 将位置映射到单位圆上的复数 theta_i = 2 * np.pi * pos_i / self.group_order theta_j = 2 * np.pi * pos_j / self.group_order g_i = torch.exp(1j * theta_i) g_j = torch.exp(1j * theta_j) # 群距离用 |1 - g_j * conj(g_i)| 表示 diff = torch.abs(1.0 - g_j * torch.conj(g_i)) return diff def forward(self, pos_i, pos_j): dist = self.group_distance(pos_i, pos_j) # [batch, seq, seq] # 把距离分桶 edges = torch.sort(torch.sigmoid(self.bucket_edges))[0] bucket_idx = torch.bucketize(dist, edges) bias = self.bucket_bias[:, bucket_idx] # [heads, batch, seq, seq] return bias这里有几个参数要注意。group_order 控制群周期的长度,我建议设成训练序列长度的 2 倍左右,并不是越长越好。bucket_edges 初始化成对数均匀分布,是希望近距离分桶更细、远距离分桶更粗,这是从 T5 Bias 的经验里继承过来的。bucket_bias 的初始化建议为 0,让模型自己决定距离偏置的符号和幅度,避免一开始就把注意力分布带偏。真正训练时,我建议先在一个相对小规模的模型(比如 6 层、8 头)上验证群距离偏置是否正常收敛,再搬到更大模型上。直接上大模型调参,你会分不清到底是群选择的问题还是优化器的问题。5. 实测观察与容易踩的坑5.1 外推长度的变化规律我在一个 400M 参数规模的自回归语言模型上做了对比实验,训练长度固定在 2048,然后分别用 ALiBi、RoPE 和 GRAPE 距离偏置测试 4096、8192 长度的困惑度。ALiBi 在 4096 长度上困惑度上升明显,但还可用;到 8192 时注意力变得稀疏,很多长距离 token 几乎没有被关注到。RoPE 在 4096 表现不错,8192 时出现少量下降,但整体比 ALiBi 稳。GRAPE 距离偏置在 4096 和 8192 都保持了相对平稳的困惑度,没有出现突然崩溃。GRAPE 的周期结构起了作用。单位圆上的群距离天然有一个最大边界(两点相距最大是半圆),超过这个距离后,位置关系开始“折返”,模型看到的距离信号不再无限增长。这与 ALiBi 的无限负偏置形成鲜明对比。直观理解就是:位置关系被卷在一个有限范围里,模型不需要外推出一个从未见过的距离值,而只需要在训练时见过的群距离范围内做插值。但要注意,这种外推红利不是免费的。折返效应在长距离上会引入位置歧义——两个距离很远的 token 会被映射成中等距离,这会轻微损害超长距离的精确推理。我的实测结果是,在需要精确长距离位置判断的任务上(比如“找到第 3000 个 token 之后的内容”),GRAPE 比 RoPE 略差。所以它适合“大体了解长距离关系”的场景,不适合“精确定位于超长位置”的场景。5.2 群类型选择、可学习参数初始化和数值稳定性GRAPE 落地最容易被忽略的坑是数值稳定性。单位复数乘法虽然漂亮,但如果你不谨慎处理,复数的实部和虚部很容易在反向传播中出现 NaN,尤其是在混合精度训练下。解决方法是把复数运算拆成实数矩阵运算,用 2x2 旋转矩阵代替复指数,在实现上稍微多几行代码,但稳定性会好一个数量级。第二个坑是群类型选择。SO(2) 是最常用的选择,但它只表达一维连续旋转关系,对“位置”这种序列关系够用,对二维网格、图结构之类的更复杂位置关系就不够了。如果将来要把位置编码扩展到图片或者图数据,GRAPE 的框架其实允许你换更大的群,比如 SO(3) 或者对称群。但目前 NLP 场景里,SO(2) 的性价比最高,不要一上来就上复杂群,调试成本会急剧增加。第三个坑是 bucket_edges 的可学习性质。距离分桶的边界如果完全自由学习,很容易在训练初期就收敛到一个次优配置,而且不同头学到完全不同的分桶方式,降低了模型的归纳稳定性。我的建议是:前 10% 的训练步数冻结 bucket_edges,只训练 bucket_bias,等注意力分布相对稳定后再放开边界参数。这样虽然牺牲了一点点灵活性,但训练曲线会平滑很多。从整个位置编码的发展来看,GRAPE 提供的与其说是一个新方法,不如说是一套“位置编码的设计语法”。它让你不再对着 RoPE、ALiBi、Sinusoidal 一个一个调,而是先想清楚:你要用什么群结构去组织位置关系,你希望这个关系以表示形式还是函数形式进入注意力。想明白这两件事,位置编码的设计就从打补丁变成了搭积木。我现在做新模型的位置编码设计,都会先拿 GRAPE 这套思路做一轮“群结构诊断”,再决定具体用哪个实现。这个视角一旦建立,你再看那些层出不穷的新位置编码,心里就有底多了。