227、【AI】【模型部署】基座模型研究:RoPE 旋转位置编码

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

标题

227、【AI】【模型部署】基座模型研究:RoPE 旋转位置编码

背景

上篇 blog

【AI】【模型部署】基座模型研究:RMSNorm 与残差(归一化为什么必要)

拆了每层的第一个零件:Qwen2RMSNorm 只做"算 mean(x²) → 乘 rsqrt(variance+eps) → 乘可学习权重",不减均值;手写实现与源码输出误差 0 ,与 LayerNorm 的差异(输出均值 0.0003 vs 3.4e-9)正体现"只归尺度";它放在子层之前(pre-norm)并与残差配合。归一化后的向量接着进入注意力,而注意力有个天生缺陷:它对顺序无感 ------把"猫吃鱼"和"鱼吃猫"的 token 打乱,注意力算出的结果一样。位置信息得额外注入,本篇讲 Qwen2 用的方案:RoPE(旋转位置编码)

模型部署

注意力的计算是 q 与 k 做点积:谁和谁相关,只看向量本身。要让模型知道"谁在前谁在后",就得把位置"编"进 q 和 k。RoPE(Rotary Position Embedding,旋转位置编码) 的思路很巧妙:按位置把向量旋转一个角度------位置不同,旋转角度不同;两个向量做点积时,角度差自然带出相对距离。


🧩 源码:频率是怎么算的

Qwen2RotaryEmbeddingmodeling_qwen2.py:51-102)初始化时算一组"逆频率"(compute_default_rope_parameters:71-87):

python 复制代码
base = config.rope_parameters["rope_theta"]
dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float) / dim))

实测:rope_theta=1000000.0(config 里那个字段),head_dim=64,于是 inv_freq 长度 32 (64 的一半),前五个值约 [1.0000, 0.6494, 0.4217, 0.2738, 0.1778]------不同维度用不同频率:低维转得快、高维转得慢,像时钟的秒针与时针。

把公式手抄一遍,与模型里的值对比(实测):

python 复制代码
base = cfg.rope_parameters["rope_theta"]
dim = cfg.hidden_size // cfg.num_attention_heads
inv = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float) / dim))
print("手算 inv_freq == 源码:", torch.allclose(inv, rot.inv_freq))

实测 True------公式没有隐藏项:inv_freq[i] = 1 / theta^(2i/dim)i 从 0 到 31,指数 2i/dim 让相邻维度按几何级数拉开频率。


🧩 前向:位置决定旋转角

拿到逆频率后,对每个位置 pos 计算 freqs = pos × inv_freq,再取 cos/sin(实测):

python 复制代码
pos = torch.arange(0, 8).unsqueeze(0)
x = torch.randn(1, 8, 64)
cos, sin = rot(x, pos)
print("cos 形状:", tuple(cos.shape))
print("位置0 cos[:4]:", cos[0, 0, :4])
print("位置1 cos[:4]:", cos[0, 1, :4])

实测:cos 形状 (1, 8, 64)------每个位置一组 cos;位置 0 的 cos 全是 1 (旋转 0 度等于没转),位置 1 的 cos 变成 [0.5403, 0.7965, 0.9124, 0.9627]------位置越靠后,角度越大,各维旋转幅度不同。


🧩 rotate_half 与 apply:怎么把旋转加到向量上

真正施加旋转靠两个函数。rotate_halfmodeling_qwen2.py:105-109)把向量的前后两半"交叉换位并取负":

python 复制代码
def rotate_half(x):
    x1 = x[..., : x.shape[-1] // 2]
    x2 = x[..., x.shape[-1] // 2 :]
    return torch.cat((-x2, x1), dim=-1)

apply_rotary_pos_embmodeling_qwen2.py:113-135)则用"乘法 + 旋转"组合完成旋转:

python 复制代码
def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
    cos = cos.unsqueeze(unsqueeze_dim)
    sin = sin.unsqueeze(unsqueeze_dim)
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed

手写一遍并对比(实测):rotate_half 与源码逐元素相等q 旋转后与源码最大误差 0.0 。这段形式看着像"三角函数",本质就是二维旋转矩阵 [cos, -sin; sin, cos] 作用在成对的维度上。

把一对维度 (x1, x2) 看成二维向量,旋转角 θ 的旋转矩阵作用后:x1' = x1·cosθ - x2·sinθx2' = x1·sinθ + x2·cosθ。源码里的 (x·cos) + (rotate_half(x)·sin) 正是它的向量化写法------rotate_half 提供 (-x2, x1),乘 sin 后与 x·cos 相加即得上述结果。Qwen2 把 64 维拆成 32 对 ,每对用不同频率旋转;unsqueeze_dim=1 则把 cos/sin 插到"头"这一维,好广播到 (batch, heads, seq, dim) 的 q/k 上。


🧩 实测:点积只认相对位置

RoPE 最核心的性质可以用点积验证:同一个 q、k 放到不同位置旋转后做点积(实测):

python 复制代码
print("q@pos5 · k@pos3 =", round(dot(5, 3), 5))
print("q@pos2 · k@pos0 =", round(dot(2, 0), 5))
print("q@pos0 · k@pos2 =", round(dot(0, 2), 5))

实测:q@pos5 · k@pos3q@pos2 · k@pos0 都等于 5.64291 ------两组位置距离都是 2,点积完全相同;而 q@pos0 · k@pos2(距离 -2)是 8.86717,方向反了结果就不同。这直接证明:旋转后 q·k 只依赖相对距离,与绝对位置无关。


🧩 为什么旋转能编码位置

把 q、k 都按各自位置旋转后做点积:位置 m 的 q 与位置 n 的 k,点积结果只取决于角度差 (即 m-n)------绝对位置被旋转"消掉",留下的是相对距离。这正是注意力想要的:判断"两个 token 离多远"比"各自在第几位"更有用。

这也是 Qwen2 支持长上下文(config 里 max_position_embeddings=32768)的基础:位置信息不是查表加进去的固定向量,而是按公式算出的旋转角,可以外推到更长位置。


🧩 rope_theta 为什么是 100 万

公式 inv_freq = 1 / theta^(2i/dim) 里,theta 越大,频率衰减越慢、可区分的位置范围越广。Qwen2 用 rope_theta=1000000(不少小模型用 10000),配合 max_position_embeddings=32768 支撑长上下文;把 theta 调大,也是把模型"外推"到更长位置时的常用手段之一。本模型能覆盖 32768 个位置,正是 theta 与那 32 个频率共同支撑的结果。


📊 RoPE 的特点小结

特点 说明
相对位置 旋转后点积只依赖 m-n(本篇实测:距离同为 2 时点积相等)
无额外参数 靠 cos/sin 公式现算,不占权重
可外推 位置是算出来的角度,不是查表向量
作用对象 只旋转 q、k,不碰 v

正因为不占参数、又天然表达相对距离,RoPE 成了当下开源大模型(Qwen、Llama 等)的主流位置编码。

另外注意:RoPE 只作用于 q 和 kv 保持原样------因为位置只影响"谁和谁相关",不影响"取什么内容"。

这也是"位置编码"与"位置嵌入"的分野:传统方案给 token 向量加一个位置向量,RoPE 则不动 token 本身,只在注意力的 q/k 上做旋转。


📌 一句话记忆

RoPE 用"按位置旋转"给注意力注入位置:inv_freq = 1/(theta^(2i/dim))rope_theta=1e6head_dim=64 → 32 个频率),位置 pos 决定旋转角,rotate_half + apply_rotary_pos_emb 把旋转作用到 q/k 上(手写与源码误差 0);旋转后 q·k 只依赖相对位置------位置 0 的 cos 全 1(没转),位置越靠后角度越大。


OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog

【数学】【统计】均方根、方差与标准差

相关推荐
Omics Pro1 小时前
1个月2轮融资!长寿虚拟细胞
数据库·人工智能·算法·机器学习·自然语言处理
residual_fan1 小时前
航空发动机故障诊断专用智能体(六):实际机队健康管理中的应用考量与展望
人工智能·算法·数据挖掘·数据分析
能源革命1 小时前
AI 日报 2026-09-19
人工智能
天远Date Lab1 小时前
零信任架构实战:基于天远二手车VIN估值构建自动化资产定价网关
人工智能·ai·工具分享
掘金泥石流1 小时前
上下文即服务:AI 应用的竞争,为什么会从入口转向 Context?
人工智能·架构·agent
知几蜗牛1 小时前
平均延迟很快用户还在卡?用AIPerf看懂P99尾延迟
人工智能
打工仔折腾 AI1 小时前
用 Shell 脚本自动部署 mysqld_exporter 并接入 Prometheus 远程抓取
人工智能·后端·python·性能优化·ai agent 实战
tiger8651 小时前
大语言模型面试和题解,梳理中国主流开源 LLM 系列的发展脉络、技术路线与工程取舍
人工智能·gpt·深度学习·算法·自然语言处理·面试·transformer
墨林陌1 小时前
AI 热点日报(2026-09-20):谷歌Gemini首次越狱入侵三家公司,阶跃星辰发布6000亿参数Step 5
人工智能