0.8MB 跑通 Qwen|第 18-3 篇:MRoPE——推理引擎的 3D 位置编码给视觉留的席位

简介: 本篇详解Qwen3-VL多模态推理中MRoPE三维位置编码机制:通过`mrope_section=[24,20,20]`将64维旋转频率分给时间/高/宽三轴,支持视觉token网格定位;并数值推演“强行降维至1D”的相位偏差——7×7图像下最高达4.71弧度(近270°),40/64维严重失准,导致注意力失效。纯文本因t=h=w自动退化,故该bug无法被文本测试捕获。(239字)

系列:《0.8MB 跑通 Qwen:从零实现 ARM 零依赖纯 C 推理引擎》(30 天 × 90 篇) | 适配模型:Qwen3-VL-8B-Instruct(千问3_VL_8B_Instruct)· Qwen3-VL-2B-Instruct · Qwen3-30B-A3B | 测试设备:RK3588(4×Cortex-A76 + 4×Cortex-A55,aarch64)

系列总纲:《0.8MB 跑通 Qwen》30 天 90 篇 · 总纲(阿里云社区)

上一篇:18-2《tokenizer.json → vocab.bin:build_vocab_bin.py 在干嘛》| 下一篇:19-1

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

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

关键词:手搓 Qwen 推理引擎、千问大模型推理、MRoPE、3D位置编码、视觉 token、多模态、mrope_section、Qwen3-VL、零依赖纯 C

导语:纯文本引擎只需要一条时间轴,而 Qwen3-VL 的推理多出两个维度。这篇讲 MRoPE 如何用一组三维频率槽,把旋转位置编码的空间分给时间、高、宽三套坐标;并用数值推演回答"强行改回 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_dim=128 按 half = 64 分成前后两半,dim j 与 dim j+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.c 11361–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.json 的 mrope_section 解析(1485–1499 行);GGUF 路径无此元数据时兜底 [24,20,20](vllm_gguf.c 705–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) pos1=(16,16,16)
max |cos3-cos1| delta = 1.9999 , max |sin3-sin1| delta = 1.4220
sample token #24 (pos3=(16,19,19)): 40/64 rotary pairs differ
grid=(gt=1, gh=28, gw=28) n_vis=784 text_pos=16
pos: pos_t [16,16]  pos_h [16,43]  pos_w [16,43]
worst |angle| diff = 21.217409 rad @ j=1  token#756 pos3=(16,43,16) pos1=(16,16,16)
max |cos3-cos1| delta = 1.9999 , max |sin3-sin1| delta = 1.7550
sample token #392 (pos3=(16,30,16)): 20/64 rotary pairs differ
grid=(gt=2, gh=7, gw=7) n_vis=98 text_pos=16     /* 多帧示例:仅演示数值 */
pos: pos_t [16,17]  pos_h [16,22]  pos_w [16,22]
worst |angle| diff = 4.714980 rad @ j=1  (与 gt=1 同源:h/w 最大偏移 6 格)

三组数据各自讲了一个点:

  1. 7×7(49 视觉 token):位置差 6 格在 j=1 上产生 4.71 rad ≈ 270° 的相位差——cos 差 1.9999 说明 Q/K 里这组维度几乎反相:该对齐的没对齐,该正交的变平行。中间那个 token(第 24 个,位于 (3,3))两轴都偏,40/64 维全错。
  2. 28×28(784 token,贴近高分辨率动态网格量级):ph 最大 27 格 → j=1 相位差 21.2 rad。注意这是"同一个 token 在两套坐标系里的相位差",多绕几圈后 cos/sin 各取极值,Q/K 点积里这些维的信息基本报废。而中段 token #392 在 (14,0) 列上只有 h 偏 → 只错 20/64 维——错多少取决于它在网格里的 (行,列),不是均匀伤害。
  3. gt=2 多帧:t 轴只差 1 格(帧差),h/w 与 gt=1 相同。这条专门演示 t/h/w 三轴是分维注入的:同一网格,帧号只污染 j%3==0 那 24 个槽,行/列各污染自己的 20 个槽。gt>1(视频帧维)在本仓库仅服务于媒体输入路径(视频/H.264 模块未完成、默认不启用),此处只做数值演示,不做端到端行为断言。

3.3 结论(为什么"1D 化"必乱)

把 MRoPE 改回 1D,等于视觉 token 的位置信息只剩一个标量:网格的形状(行/列/帧)从旋转相位里整个消失。上面数值显示,哪怕是 7×7 的小网格,也有 40/64 维在低 j 上出现近反相的错误旋转——注意力打分在这些维上等同噪声。由于模型训练时视觉 token 就是按 3D 相位学出来的,推理时给错相位,attention 就"认不出"图内结构。纯文本不受影响(t=h=w 恒等,见 §2.3),这正是"改动后果"实验里最值得记的一条:这个 bug 只在视觉输入下显形,文本回归测试测不出来。

4. 学员调试任务

  • A 档(板端/本地动手):
    1. 跑 mrope_divergence.py 1 7 7 16 与 1 28 28 16,复现 3.2 的 worst/affected 数字;
    2. 把 3D 口径改成"逐 token 递增的 1D 序号"(即视觉 token 按先后拿到 text_pos, text_pos+1, …),对比它与本日"整体坐 text_pos"口径的 worst 差异谁大谁小——体会"1D 化"并不是只有一种写法;
    3. 改 sections:把 [24,20,20] 换成 [64,0,0],跑脚本确认"受影响维数归零、与 1D 完全一致"——这就是把引擎 mrope_sections 配置设成单组时的退化行为(对应源码 11536–11537 的 else 分支)。
  • B 档(纯读源码):读 vllm_safetensors.c 11389–11428 与 11515–11558,回答:① 为什么 bound_h 取 sec[1]*3=60 而不是 half=64?若把界放宽到 64,j=61(j%3==1)这类槽会被 h 轴接管,会对 section 语义造成什么影响?② text_pos += max(g_h, g_w)(11418 行)为什么是"取大"而不是 g_h+g_w?③ 为什么每 token 的 cos/sin 要现算一次而不是像文本路径那样预生成整表(提示:pos_h/w 随 token 在网格里的位置变化,128 维下文本表有多少种 pos、网格里有多少种 (h,w) 组合)?

预期输出:一张"1D 化 × 网格尺寸 → worst 相位差 / 受影响维数"的小表,并能口头解释为什么文本回归测不出这个 bug。

收尾

  • 本篇源码点名:vllm_safetensors.c(三数组构建 11361–11428、axis 规则 11515–11558、rotate_half worker 11131–11145、文本退化 dyn_mrope 7794–7806、section 解析 1485–1499)、vllm_gguf.c(GGUF 兜底 sections 705–707)
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:位置编码就位,接下来是真正的"黑箱部分"——注意力在板子上怎么排内存、多轮 KV cache 怎么滚。Day 19 我们从 GQA/注意力布局讲起,并第一次正面讨论视觉/媒体输入链路(视频媒体模块未完成、默认不启用的边界会在相关篇目如实标注)。
相关文章
|
3天前
|
芯片 AI芯片
0.8MB 跑通 Qwen|第 2-3 篇:推理引擎的设备画像——识别 RK3588,而不是"猜"(vllm_device.c)
本文实测RK3588设备画像机制:通过启发式匹配`/proc/device-tree/model`识别板型,自动生成含NPU支持、量化推荐等配置的`VDevProfile`;支持`--device`人工覆盖,验证画像如何精准驱动引擎决策。(239字)
 0.8MB 跑通 Qwen|第 2-3 篇:推理引擎的设备画像——识别 RK3588,而不是"猜"(vllm_device.c)
|
1天前
|
缓存 人工智能 索引
0.8MB 跑通 Qwen|第 12-1 篇:LLM 推理的跨进程恢复——服务重启了,会话不能断
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实现在RK3588上部署Qwen3-VL等多尺寸模型。本文详解`--disk-kv`机制:将KV Cache落盘持久化,支持进程重启后按前缀精准恢复,实现“会话不断连”,2K上下文prefill加速达23.6×,真正打通边缘AI服务可用性最后一环。(239字)
|
1天前
|
缓存
0.8MB 跑通 Qwen|第 8-3 篇:推理引擎的 KV 缓存结构——按 token 还是按头存
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测。本文精析KV缓存的token-major布局设计原理,揭示其如何兼顾prefill与decode访存效率,直击带宽瓶颈。(239字)
|
17天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
在AI技术快速落地办公场景的当下,市面上绝大多数智能工具依旧停留在问答对话、文本摘要、简单文案改写层面,只能完成碎片化单点任务。企业员工处理一份完整业务工作时,往往需要同时开启多款工具,文档处理、数据分析、素材创作、网页制作分属不同软件,中间内容需要反复复制粘贴,即便借助通用大模型生成内容,输出成果还需要大量二次格式调整,才能达到交付标准。同时企业内部的数据分散在聊天记录、审批单据、本地文档、业务平台,普通AI工具无法直接读取内部业务信息,每一次任务都要人工复述背景条件,法务、财务、研发这类垂直岗位,还需要编写冗长提示词才能拿到相对可用结果,AI办公落地的实际门槛长期居高不下。千问办公Qwen
427 9
|
1天前
|
缓存 JSON 安全
《0.8MB 跑通 Qwen:从零实现 ARM 零依赖纯 C 推理引擎》系列教程 · 总纲
本系列教程以开源引擎Kestrel为教材,30天90篇纯C实战,聚焦ARM边缘端LLM推理:从零实现mmap加载、NEON加速GEMM、q8 KV缓存、稀疏注意力、磁盘KV恢复、推测解码、OpenAI兼容服务及国密SM2/3/4全栈安全,全程代码驱动、调试验证。(239字)
|
1天前
|
C语言 C++
0.8MB 跑通 Qwen|第 17-1 篇:safetensors → VQF——推理引擎的 vqf_write 一次成型
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型(2B/8B/30B),在RK3588平台实测通过。核心突破:safetensors→VQF转换与推理共用同一量化路径,实现位级可复现,体积压缩至2.2–4.2GB,真机重转SHA256完全一致。(239字)
|
1天前
|
定位技术 C语言 C++
0.8MB 跑通 Qwen|第 7-1 篇:大模型量化的 Q4_0 格式——与 GGUF 对齐的 4bit 布局
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测通过。详解GGUF对齐的Q4_0量化:32个float压缩至18字节(f16 scale + 16 nibble),剖析nibble打包规则与11倍误差代价,为端侧高效部署夯实底层基础。(239字)
|
1天前
|
JSON 测试技术 API
0.8MB 跑通 Qwen|第 20-2 篇:推理引擎的路由与自研最小 JSON
本系列《0.8MB跑通Qwen》用纯C手搓零依赖推理引擎,适配Qwen3-VL多模型,在RK3588(aarch64)实测通过。核心含strcmp路由表与自研轻量JSON解析器,精准拦截非法请求(如`msgs`→400),严守兼容边界,代码精简可控,专注教学与边缘部署。(239字)
|
1天前
|
C++
0.8MB 跑通 Qwen|第 10-2 篇:推理引擎的 sparse top-k 块选择——"只看该看的"到底怎么选
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上高效运行Qwen3-VL多模态模型。本文深度剖析sparse_attn_head的top-k块选择机制——探针抽样、prefill重要性预留、贪心补满与recency保险四段代码,揭示长上下文稀疏注意力如何精准“找针”,并实证k=1时板端翻车根源。(239字)
|
1天前
|
调度
0.8MB 跑通 Qwen|第 13-3 篇:推理引擎的回退与收益边界——哪些场景 spec 才划算
本文精析Speculative Decoding在ARM端的收益边界:基于RK3588实测,揭示“62%命中率仍变慢”的根源——单步Decode仅44ms时,验证开销与KV回滚反致负增益;明确开启条件:长上下文、高重复性、大模型、贪婪采样。零依赖纯C,适配Qwen3-VL系列。

热门文章

最新文章