本地数值推演验证本文的 1D-vs-3D MRoPE 相位差为数值推演/静态锚定图像端到端真机推理未在本篇执行边界见文内一句话导读MRoPE 三维位置编码mrope_section [24,20,20] 把 64 个频率槽分给时间、高、宽三轴为视觉 token 预留席位数值推演把 3D 改回 1D 的相位偏差图像端到端真机验证不在本篇范围。Qwen3-VL 的推理在结构上与纯文本引擎只差一层位置编码不是 1D 而是 3D。config.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_dim128按half 64分成前后两半dimj与 dimjhalf组成一对做 2D 旋转freq[j] 1 / θ^(j/half) θ 5e6rope_thetaj 0..63 angle pos * freq[j] q[j] q[j]*cos(angle) - q[jhalf]*sin(angle) q[jhalf] q[jhalf]*cos(angle) q[j]*sin(angle)旋转角度随j递减j0每挪一个位置转 ~1 弧度j63每挪一个位置只转 ~2e-7 弧度。低 j 负责大概在第几格高 j 负责精确到第几格。1.2 多模态的困境一行图钉怎么排号一段图文混合 prompt 里视觉区是一个g_h × g_w的网格比如 7×7 的 49 个视觉 token。若按文本顺序硬排 1D 序号会发生两件怪事同一行内挨着的两个视觉 token 在语义上不是邻居——图里 (row, col) 与 (row, col1) 挨着但 (row, 5) 与 (row1, 0) 在 1D 序号上也挨着文本与视觉在同一个位置空间里抢序号第 20 个文本 token 和第 20 个视觉 token 若拿到相同 posQ/K 里就有一对假的同位。Qwen3-VL 的答案官方实现Qwen3VLTextRotaryEmbeddingapply_rotary_pos_emb_mrope是给每个 token 三套坐标(pos_t, pos_h, pos_w)把half64维的频率按(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_tsec[0]24 ⇒ 3*2472 64t 轴覆盖到顶[24,20,20]的意思即64 个频率槽按 3 个一组轮转前 24 组首槽永远走 t 轴其余槽在 h/w 界内走 h/w。文本 token 的三套坐标恒相等thw于是数学上退化成普通 RoPE——这正是纯文本路径能原样跑的原因。2. 对应代码坐标从哪来、怎么用2.1 视觉 token 的三维坐标多模态 prefillst_qwen_model_multimodal_prefill_ex为每个 token 建三个 int 数组vllm_safetensors.c11361–11363随后按 token 类型填11389–11428if (tid img_id || tid vid_id) { /* 视觉 token */ int g_t grids[r*30], g_h grids[r*31], g_w grids[r*32]; 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_worker11133 行起用线程池按 head 并行套 rotate_half。section 来源两处safetensors 路径从config.json的mrope_section解析1485–1499 行GGUF 路径无此元数据时兜底[24,20,20]vllm_gguf.c705–707 行。2.3 文本路径的假 3Ddyn_mrope文本 prefill / decode 走的是dyn_mrope7794 行起注释讲得很直白7796–7806text-only 时三套位置相等mrope_section 的 interleaving 是 no-op正确的文本 RoPE 应该旋转全部hd维配对为 (j, jhd/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_posframe, text_posph, text_pospw)1D 化 视觉区所有 token 一律坐文本位text_pos最激进的 1D 方案视觉区被当成一个文本位置。3.1 受影响维度40/64 恒成立j在 0..63j%31 且 j60有 20 个、j%32 且 j60有 20 个——即64 个频率槽里 40 个有能力被 h/w 坐标改写其余 24 个永远走 t。这个比例与 section[24,20,20]一一对应。3.2 三组标定网格的数值grid(gt1, gh7, gw7) n_vis49 text_pos16 sections[24,20,20] pos: pos_t [16,16] pos_h [16,22] pos_w [16,22] dims affected by 3D: 40/64 (bound_h60 bound_w60) worst |angle| diff 4.714980 rad j1 token#42 pos3(16,22,16) pos关键词MRoPE、3D位置编码、视觉 token、多模态、mrope_section
