news 2026/9/23 7:29:34

Day 18·3 MRoPE——3D 位置编码给视觉留的席位

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 18·3 MRoPE——3D 位置编码给视觉留的席位

本地数值推演验证:本文的 1D-vs-3D MRoPE 相位差为数值推演/静态锚定;图像端到端真机推理未在本篇执行(边界见文内)

一句话导读:MRoPE 三维位置编码:mrope_section [24,20,20] 把 64 个频率槽分给时间、高、宽三轴,为视觉 token 预留席位;数值推演把 3D 改回 1D 的相位偏差,图像端到端真机验证不在本篇范围。

Qwen3-VL 的推理在结构上与纯文本引擎只差一层:位置编码不是 1D 而是 3Dconfig.json里写着mrope_section: [24, 20, 20]head_dim = 128,于是同一个 rotary 空间被切成三组频率,分别跟"时间、高、宽"三套坐标相乘。本篇先讲清这套坐标怎么进代码,再用数值实验回答一个"改动后果"问题:如果有人把 MRoPE 强行改回 1D,旋转角会错多少?并如实说明边界:本日只做位置编码层面的数值与代码推演,图像/视频输入链路的端到端真机验证不在本日范围(视频媒体/H.264 模块未完成、默认不启用,本仓库不对此做"已实测可用"的任何表述)。

1. 知识点:文本位置不够用的时候

1.1 回顾:RoPE 做了什么

Day 8-1《自注意力数学》把 Q·Kᵀ·softmax·V 讲透了,这里只补位置编码这一层(在 技术文档 的 RoPE 小节也有对应推导)。标准 RoPE 的做法:对每个 head 的 Q/K,把head_dim=128half = 64分成前后两半,dimj与 dimj+half组成一对做 2D 旋转:

freq[j] = 1 / θ^(j/half) θ = 5e6(rope_theta),j = 0..63 angle = pos * freq[j] q[j]' = q[j]*cos(angle) - q[j+half]*sin(angle) q[j+half]' = q[j+half]*cos(angle) + q[j]*sin(angle)

旋转角度随j递减:j=0每挪一个位置转 ~1 弧度,j=63每挪一个位置只转 ~2e-7 弧度。低 j 负责"大概在第几格",高 j 负责"精确到第几格"

1.2 多模态的困境:一行图钉怎么排号

一段图文混合 prompt 里,视觉区是一个g_h × g_w网格(比如 7×7 的 49 个视觉 token)。若按文本顺序硬排 1D 序号,会发生两件怪事:

  1. 同一行内"挨着"的两个视觉 token 在语义上不是邻居——图里 (row, col) 与 (row, col+1) 挨着,但 (row, 5) 与 (row+1, 0) 在 1D 序号上也挨着;
  2. 文本与视觉在同一个位置空间里抢序号:第 20 个文本 token 和第 20 个视觉 token 若拿到相同 pos,Q/K 里就有一对"假的同位"。

Qwen3-VL 的答案(官方实现Qwen3VLTextRotaryEmbedding+apply_rotary_pos_emb_mrope)是:给每个 token 三套坐标(pos_t, pos_h, pos_w),把half=64维的频率按(j % 3)循环分给三套坐标:

j % 3 == 0 → 用 pos_t(时间/文本轴) j % 3 == 1 → 用 pos_h(高轴) 前提 j < 3*sec[1] = 60 j % 3 == 2 → 用 pos_w(宽轴) 前提 j < 3*sec[2] = 60 j >= 60 → 全部退回 pos_t(sec[0]=24 ⇒ 3*24=72 > 64,t 轴覆盖到顶)

[24,20,20]的意思即:64 个频率槽按 3 个一组轮转,前 24 组"首槽"永远走 t 轴,其余槽在 h/w 界内走 h/w。文本 token 的三套坐标恒相等(t=h=w),于是数学上退化成普通 RoPE——这正是纯文本路径能原样跑的原因。

2. 对应代码:坐标从哪来、怎么用

2.1 视觉 token 的三维坐标

多模态 prefill(st_qwen_model_multimodal_prefill_ex)为每个 token 建三个 int 数组(vllm_safetensors.c11361–11363),随后按 token 类型填(11389–11428):

if (tid == img_id || tid == vid_id) { /* 视觉 token */ int g_t = grids[r*3+0], g_h = grids[r*3+1], g_w = grids[r*3+2]; int flat = vis_idx - r_start[r]; /* region 内第几个视觉 token */ int frame = g_t > 1 ? flat / (g_h*g_w) : 0; /* 帧序(frame-major) */ int ph = (flat_cycle / g_w) % g_h; /* 行序(row-major) */ int pw = flat_cycle % g_w; pos_t_arr[i] = text_pos + frame; /* t = 文本基准 + 帧 */ pos_h_arr[i] = text_pos + ph; /* h = 文本基准 + 行 */ pos_w_arr[i] = text_pos + pw; /* w = 文本基准 + 列 */ ... text_pos += (g_h > g_w ? g_h : g_w); /* region 结束推进文本位 */ } else { pos_t_arr[i] = pos_h_arr[i] = pos_w_arr[i] = text_pos; /* 文本 = 1D */ text_pos++; }

一句话:视觉区的坐标 = 文本基准位置 + 网格里的 (帧, 行, 列) 偏移,偏移量就是 MRoPE 让视觉"看到形状"的机制(11404–11412 行的注释也印证 frame-major 后 row-major 的铺排与官方meshgrid一致)。

2.2 按轴取角:cos/sin 逐 token 现算旋转表

在 11515–11558 行逐 token 现算(每 token 只算一次,Q/K 所有 head 复用):

int half = hd / 2; /* 64 */ int bound_h = sec[1] * 3; /* 60 */ int bound_w = sec[2] * 3; /* 60 */ for (int j = 0; j < half; j++) { int axis = pos_t; if ((j % 3) == 1 && j < bound_h) axis = pos_h; /* 11547 */ else if ((j % 3) == 2 && j < bound_w) axis = pos_w; /* 11548 */ float angle = (float)axis * freq; /* axis * freq[j] */ cos_tab[j] = cosf(angle); sin_tab[j] = sinf(angle); }

随后vllm_mrope_heads_worker(11133 行起)用线程池按 head 并行套 rotate_half。section 来源两处:safetensors 路径从config.jsonmrope_section解析(1485–1499 行);GGUF 路径无此元数据时兜底[24,20,20]vllm_gguf.c705–707 行)。

2.3 文本路径的"假 3D":dyn_mrope

文本 prefill / decode 走的是dyn_mrope(7794 行起),注释讲得很直白(7796–7806):

text-only 时三套位置相等,mrope_section 的 interleaving 是 no-op;正确的文本 RoPE 应该旋转全部hd维,配对为 (j, j+hd/2),频率 1/θ^(j/(hd/2))。

所以文本路径从不构造三数组,直接用单个pos(文本 prefill 用seq_len;多模态续写用 prefill 结束存的st->mrope_pos,见 9352–9353 与 11718 行)。这解释了为什么"3D 改 1D"对纯文本输出毫无影响——文本本来就只有一套 pos,而多模态的错误要到视觉 token 才暴露。

3. 改动后果:把 3D 改回 1D,旋转差多少

实测口径:数值复现脚本mrope_divergence.py(零依赖 python,按 11546–11548 的 axis 规则逐维算角),板端 RK3588 运行,2026-09-07。对照口径:3D 真值 = 视觉 token 用 (text_pos+frame, text_pos+ph, text_pos+pw);1D 化 = 视觉区所有 token 一律坐文本位text_pos(最激进的 1D 方案:视觉区被当成一个文本位置)。

3.1 受影响维度:40/64 恒成立

j在 0..63:j%3==1 且 j<60有 20 个、j%3==2 且 j<60有 20 个——即64 个频率槽里 40 个有能力被 h/w 坐标改写,其余 24 个永远走 t。这个比例与 section[24,20,20]一一对应。

3.2 三组标定网格的数值

grid=(gt=1, gh=7, gw=7) n_vis=49 text_pos=16 sections=[24,20,20] pos: pos_t [16,16] pos_h [16,22] pos_w [16,22] dims affected by 3D: 40/64 (bound_h=60 bound_w=60) worst |angle| diff = 4.714980 rad @ j=1 token#42 pos3=(16,22,16) pos

关键词:MRoPE、3D位置编码、视觉 token、多模态、mrope_section

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 7:29:29

第 1 篇:推理引擎到底在干什么(零门槛入门系列)

上一篇&#xff1a;《零门槛推理引擎入门》系列 总纲&#xff5c; 下一篇&#xff1a;第 2 篇《数字在内存里怎么排队》 一句话导读&#xff1a;把"大模型会说话"这件事拆开看&#xff0c;它只是一个"读数字 → 算 → 挑一个词 → 接回去再来一遍"的循环&…

作者头像 李华
网站建设 2026/9/23 7:29:29

光猫超级密码速查手册:3分钟搞定运维面试高频考点

光猫超级密码速查手册:3分钟搞定运维面试高频考点 官方文档翻了三页还没找到入口?别慌,这是大多数刚入行运维或网络工程师的通病。 面对【光猫超级密码】这个看似简单实则坑点密集的话题,死记硬背只会让你在面试中卡壳。 你需要的是这份浓缩了实战经验的【速查手册】,直接给你划重点、给方案、防踩坑。…

作者头像 李华
网站建设 2026/9/23 7:29:09

3个血泪坑:对数相乘最佳实践,新手必看的避坑指南

3个血泪坑:对数相乘最佳实践,新手必看的避坑指南 翻开官方文档看对数运算,是不是总觉得篇幅太长,核心逻辑被淹没在海量 API 说明里?很多开发者在计算涉及对数乘积的复杂公式时,往往因为浮点数精度或算法选择问题,导致线上数据偏差。其实,掌握 对数相乘…

作者头像 李华
网站建设 2026/9/23 7:29:08

3个核心考点搞定wangyuyun,面试不再背八股

3个核心考点搞定wangyuyun,面试不再背八股 刚结束一场后端面试,回来一看记录,手心全是汗。面试官没问什么高深的分布式锁,也没聊复杂的微服务架构,就盯着屏幕上的一个日志报错,问我对 StackTrace…

作者头像 李华
网站建设 2026/9/23 7:29:00

魅族note5参数性能优化:3步搞定卡顿,附完整示例

魅族note5参数性能优化:3步搞定卡顿,附完整示例 学会语法却不知怎么搭项目,这是很多开发者在拿到魅族 Note5 开发文档时的第一反应。别急,今天不聊虚的,直接上 完整示例 。我们聚焦于魅族 Note5 这款经典机型的性能调优,特别是针对其 Helio P10 处理器和 4GB RAM…

作者头像 李华
网站建设 2026/9/23 7:28:46

SpringBoot集成MinIO实现高效对象存储方案

1. MinIO与SpringBoot集成概述MinIO作为一款高性能的对象存储服务&#xff0c;已经成为云原生应用存储解决方案的热门选择。它与SpringBoot的集成能够为Java开发者提供简单高效的存储能力&#xff0c;特别适合需要处理图片、视频、文档等非结构化数据的应用场景。我在多个生产项…

作者头像 李华