Qwen3-VL-4B 在 X1 上为什么会错框:一次从执行图到端侧结果的修复实验

2026 年 9 月 25 日 · QCS8550 / Hexagon v73 · 文中所有延迟均在 X1 实测

在六张公开 COCO 图片中,原厂量化版 Qwen3-VL-4B 对人物和公共汽车都给出了偏离目标的框。模型能够加载,能够回答 JSON,一次请求只要两三秒;如果只检查“能否生成”,这类定位偏差很容易被忽略。

我们沿着图像进入 NPU、视觉特征进入语言模型、位置编码进入注意力图、KV cache 跨 token 延续的路径逐项核查,最后得到一条独立重导出的量化 NPU 执行链。它在一组清晰的 COCO 图片上把通过数从 0/6 提升到 6/6;为公开展示而另选的六张 CC BY 图片上,从 3/6 提升到 5/6,热请求 P50 为 3.80 秒。这说明已测试的执行链错位得到改善;它不是对 COCO 全集的准确率估计,也不是原厂 AIDEM 包的原位修改。

两条端侧执行链的结构对比

图 1. 上图是本次复现的原厂示例/AidGen 路径;下图是重导出路径。灰色后 18 层和输出头沿用原厂标准 context。图只画出已验证的接口和执行关系,不代表已经逐节点解析原始 AIDEM 内部。

问题为什么不像一个普通的“模型不认识物体”

X1 上此前有一套厂商 Qwen2.5-VL-3B 量化包,使用 AidGen 常驻推理。厂商 Qwen3-VL-4B 量化包在本文公开的 COCO 样本上有明显错框;另一条保留完整模型结构的 GenieX + GGUF Q4_0 混合推理路径在同组六图达到 5/6,因此我们优先核查 4B 的转换与运行链路,而非简单断言“4B 天生不会框物体”。但不同推理引擎、图像处理和量化也会影响答案;这些现象本身不是因果证明。

后来重新导出的完整 FP16 版本在 X1 跑通三路视觉特征和位置编码,但分片加载与生成开销很高,只适合作为正确性参考。目标因此变得具体:保留完整模型语义,把核心图放在 NPU 上,热请求尽量达到 5–6 秒;CPU 只处理分词、图像预处理、位置表、KV 编排和采样等外围工作。本文实验都使用保存的图片。

真正缺失的是什么:DeepStack 与交错 MRoPE

Qwen3-VL-4B-Instruct 的配置写着 deepstack_visual_indexes: [5, 11, 17]。这三个数字是视觉编码器取特征的层位,不是语言模型注入的层号。视觉塔除了最终的主特征,还从中间层取出三路 DeepStack 特征。Transformers 的实现把它们加到语言模型前 三个 decoder 层之后对应的视觉 token 位置:


图像 → ViT 主特征 ───────────────────→ 语言模型输入的视觉 token

├─ ViT 第 5 层特征 ───────────→ decoder 第 0 层后注入

├─ ViT 第 11 层特征 ──────────→ decoder 第 1 层后注入

└─ ViT 第 17 层特征 ──────────→ decoder 第 2 层后注入

这不是把三张图片拼起来,也不是生成后对框做几次修正。它改变语言模型内部前三层的隐藏状态。我们做过受控消融:依次把首三层的 DeepStack 输入置零,隐藏状态相对 L2 误差约为 0.24、0.30、0.26。这个实验说明三路特征有实质数值影响,不能单凭它证明所有错框都由缺失 DeepStack 造成。

同一个模型的 rope_scaling 还要求 mrope_interleaved: true,以时间、高度、宽度位置构造交错的多维旋转位置编码。对于这里固定的 640×480 单图,我们使用 15×20 的合并视觉网格,即 300 个视觉 token。把这些 token 当成 300 个普通文本位置,会让注意力看到错误的空间关系。

我们对保存的原厂 AIDEM 包做了只读执行接口观测:视觉图是 3 输入、1 输出;首 18 层语言图为 40 输入、37 输出,其中除 embedding、cos、sin、mask 外是 KV 张量,没有三路独立 DeepStack 输入。厂商示例也只把一份主视觉 embedding 送进语言模型。这说明本次可见执行链没有把三路特征传到应注入的位置;原始 AIDEM 的每个内部节点并未全部解析,不能由接口推断其所有内部细节。

我们还抓取原厂示例传给语言图的真实 cos 张量,用同一 HF 位置编码实现比较:它与普通一维顺序位置的 RMSE 约 1.67×10⁻⁵,与正确三维交错 MRoPE 的 RMSE 约 0.547。这支持“示例链的位置编码不匹配”的判断,但仅靠位置表差异不能证明全部定位偏差的根因。

如何重建一条能在 X1 常驻运行的量化链

我们没有继续把厂商 AIDEM 当作可随意改线的 ONNX 图。先在 AidGen 正常加载的边界保留其传入 QNN 的标准 context 副本,核对后 18 层和输出头的张量形状、量化 scale/offset 及 KV 布局。用保存的原生输入独立重放,后半段 74 份输出字节逐一相同;输出头的 prefill/decode 重放也逐字节相同。这给了我们复用后半段快路径的依据。

新链路的前半段来自重新导出的完整视觉塔与首 18 层图:

  1. 保持 640×480 图像及 300 个视觉 token;视觉图产生主特征和三路 DeepStack。当前最终候选的视觉图用 FP16。

  2. 首 18 层的新图显式接收 DeepStack 与视觉位置 mask,在前三层注入,并使用交错 MRoPE。prefill、decode 分图,序列上限 2048。

  3. 主投影权重使用 W4;新首 18 层的 attention 历史 KV 使用经过 v73 实际图审计的 16 位输入。后 18 层保持原厂 8 位 KV,隐藏状态跨分片按真实 scale/offset 重定标,输出头复用原厂 context。

  4. 视觉、首片、后片、输出头常驻加载;每个请求清空逻辑状态,避免跨请求 KV 污染。图像、token、MRoPE 表和进程通信由 X1 CPU 编排,核心模型图在 QNN HTP/NPU 执行。

“W4/KV16”是便于识别候选的简称,不表示整条链每个算子都是 W4A16,也不表示后 18 层 KV16。实际图审计显示新首片有 W4 投影、部分 FP16 norm、混合精度激活;原厂后片维持自身量化契约。我们曾发现默认转换虽然喂入了 16 位历史 KV,却在 attention MatMul 内重新量化为 8 位。后来使用对称零点 -32768 并限制量化步进,在 v73 上确认 attention 的 RHS 真正为 16 位。仅凭文件名或 converter 选项认定精度,会把这一步错过。

这一路线也不是一次性成功。FP16 视觉、局部 W8 投影和残差 FP16 都经过数值与性能闸门。局部 FP16 残差虽能降低小图的数值误差,却让三层图的热 prefill 中位从约 17.3 毫秒升至 37.8 毫秒;最终没有采用全量残差方案。本文同图对照使用的候选是已在 X1 执行并能常驻服务的 FP16 视觉 + W4 首 18 层/KV16 + 原厂后 18 层/KV8 + 原厂头。

同图前后对照:先看图,再看分数

我们把图片、目标名称、官方实例框和提示词都在看模型输出前冻结。输入均为 COCO val2017 中的 640×480 图片。统一中文提示词为:


请找到图中的{目标},输出JSON:{"found":true,"box":[x1,y1,x2,y2]},

坐标使用0到1000归一化坐标。找不到则found为false。仅输出一行JSON,不要Markdown或解释。

归一化框还原到 640×480 后,必须同时满足 IoU ≥ 0.5、预测框中心落在官方实例框内。未找到、错框、无效 JSON、超时和进程失败分别统计;它们不能混成“精度不佳”。原厂链把同一源图缩放到 448×448 后送模型,重导出链使用 640×480。这个前后对照评价的是整套部署方案,不是控制住图像分辨率和所有权重后的单因素消融。

下面这组专为文章配图挑选:COCO 和原 Flickr 页面都标为 CC BY 2.0;筛选标准是单个标注目标、物体显眼、图片为 640×480,并且在运行这六张图之前冻结清单。绿色实线为 COCO 框,粉色虚线为模型框。单张可点开查看:人、猫、狗、公共汽车、厢式货车、椅子。

六张 COCO 图片,原厂、重导出 NPU 与 GenieX 的同图对照

图 2. 这六张的原厂链路为 3/6,重导出 NPU 与 GenieX 各为 5/6。第五张是重要的失败例:COCO 将这辆零食配送车标成 truck,三条链都回答未找到。它保留在图中,没有因为修复版失败而被替换。

逐图结果(IoU;括号内为判定):

  • 人 · COCO 547336:原厂 0.349(错框);重导出 0.968(通过);GenieX 0.968(通过)。

  • 猫 · COCO 520531:原厂 0.585(通过);重导出 0.980(通过);GenieX 0.980(通过)。

  • 狗 · COCO 139872:原厂 0.541(通过);重导出 0.970(通过);GenieX 0.963(通过)。

  • 公共汽车 · COCO 566758:原厂 0.296(错框);重导出 0.968(通过);GenieX 0.974(通过)。

  • 卡车 · COCO 196759:三条路线均未找到。

  • 椅子 · COCO 527427:原厂 0.502(通过);重导出 0.884(通过);GenieX 0.867(通过)。

通过数:原厂 3/6;重导出 NPU 5/6;GenieX 5/6。

“卡车”可能涉及目标词与厢式车外观之间的语义差异,也可能是模型识别本身的问题;没有经过控制变量复测,不能指定单一原因。重导出版本在 found:false 后仍附了一个 box 字段;评分以 found 为准,没有把这个多余框画成预测。原厂的猫、狗、椅子在这个阈值下也能过,故“修复前完全不会框物体”同样不准确。图中的猫和狗原厂框明显偏大,但仍有足够重叠;IoU 与可用性应一起看。

此前还有一组单独冻结的六张清晰 COCO 图片,目标为人、猫、狗、瓶子、杯子、椅子,原厂 0/6,重导出与 GenieX 均为 6/6;重导出逐图 IoU 为 0.979、0.935、0.934、0.946、0.974、0.759。我们把它作为第二组小规模诊断证据,而不是把两组六图合并成总体准确率。那组原照片只用于内部诊断,本文不转载带框图;原始清单、结果和评分保留在工程实验目录。这两组都经过人工挑选,绝不是 COCO val2017 全集评测。

速度:热请求进入目标区间,冷启动仍要另算

本次可公开六图在 X1 上的实测如下。三条路径的计时边界不同,数字不能用来给推理引擎做严格的“倍数排名”。

六图计时(P50 / P95):

  • 原厂 4B AIDEM + C++ AidGen QNN240:3/6;2.09 / 2.34 秒。模型已加载,在原生入口内计时,含图片读入和生成,不含冷加载。

  • 重导出 4B 量化 NPU 常驻链:5/6;3.80 / 3.97 秒。本地保存图的完整热请求,含预处理、分词、本地通信和解码,不含现场采集或网络。

  • GenieX GGUF Q4_0 + FP16 mmproj(llama_cpp:HTP0):5/6;4.12 / 4.27 秒。从 model.generate 开始,提示词模板构建在计时前;本地 NPU+CPU 混合执行。

重导出链在这六次中首 token 约 1.22–1.26 秒出现;每次生成 22–24 token,热请求均在 4 秒内结束。冷 client 启动到模型就绪为 10.32 秒;GenieX 的本次冷模型加载为 5.59 秒,都不能藏进热请求结果。前一组六图同 NPU 候选曾测得冷就绪 6.98 秒、热 P50 3.75 秒,说明冷启动有波动;服务常驻才使热请求数字有应用意义。所有图片来自文件,现场采集与外部服务调用未计入。

运行时优化优先检查逐 token KV 输入复制和模型驻留。最终六图计时包含本地预处理、分词、进程通信和解码;具体速度结论只对应表中所列的 X1 测量边界。

为什么量化仍是关键?同一前一组六图中,完整 FP16 4B 虽然 6/6,但每张独立进程的 generate 阶段中位约 83.28 秒;GenieX Q4_0 也 6/6,generate P50 约 4.12 秒。它们的计时边界和引擎不同,不能直接换算为本次量化的加速倍数。能够确认的是:新常驻 NPU 链在实际 X1 上同时给出了几秒级热请求与清晰目标上的正确框,而 FP16 方案没有交互价值。

另一条可直接使用的方案:GenieX + GGUF

GenieX 路线使用 Unsloth 的 Qwen3-VL-4B-Instruct GGUF 中的 Qwen3-VL-4B-Instruct-Q4_0.gguf 与 mmproj-F16.gguf,在 X1 上由 Qualcomm GenieX 的 llama_cpp:HTP0 后端执行。GGUF 保留完整视觉—语言结构;mmproj-F16.gguf 提供视觉侧文件,Q4_0 是语言模型量化文件。后端由 NPU 与 CPU 协作,它不是对原厂 AIDEM 包的原位修补,也不是全图纯 NPU。我们固定 GenieX 0.6.1、两份文件的 revision 与 SHA256,在相同六图、目标名称和提示词上得到 5/6;未找到的仍是零食配送车。上表的 4.12/4.27 秒只计 model.generate,没有包含冷加载和提示词模板构建。

在已配置好本机 GenieX 包装器的 X1 上,单张保存图可这样复现;这份包装器处理了本机较旧 glibc 与 HTP 库路径的兼容问题,全新设备应先按 官方 GenieX Python 安装说明配置并确认 Hexagon 后端确实加载:


guide=/absolute/path/to/downloaded/qwen3-vl-4b-x1-npu

model_dir="$HOME/models/qwen3-vl-4b-geniex"

geniex_runner=/absolute/path/to/run-python

bash "$geniex_runner" "$guide/run_geniex_one.py" \

"$model_dir/Qwen3-VL-4B-Instruct-Q4_0.gguf" \

"$model_dir/mmproj-F16.gguf" \

"$guide/data/000000547336.jpg" '人'

模型固定版本的两文件下载、校验值、新 X1 的安装边界和输出示例都写在部署附录。如果目标是先把清晰物体框出来,GenieX 已有公开模型可下载,且这组六图效果与重导出链相当;若需要研究 DeepStack 与 MRoPE 对原厂链路的影响,则两条链的实验意义不同。

复现与可以得出的结论

模型来源、下载、校验和单图命令见修复版 NPU 与 GenieX 部署教程。GenieX GGUF 从上游下载;本文测试的完整修复版二进制和运行脚本发布于 lissajous/qwen3-vl-4b-x1-npu。其中后 18 层、输出头和 embedding 沿用原厂产物,视觉图和首 18 层为本工程重导出/编译。AidLux 的 Qwen3-VL-4B 页面对应本文视觉模型;另一 233 页面是纯文本 Qwen3-4B。发布包是独立重组的执行链,不是原厂 AIDEM 的原位补丁。

文章目录中的 data/case-manifest-final.json冻结了六张公开配图的 COCO ID、annotation ID、原图 SHA256、提示词、框和来源;data/ 还保存原厂、重导出与 GenieX 的输出、统一评分和图片。运行 python3 score_public_cases.py data 可从保存的输出重算表格;安装 Pillow 后运行 python3 render_three_way.py data images 可重画图 2。这些链接是工程内复现材料,不是依赖实时联网的推理服务。

本文可以支持的判断是:在已测试的 X1 / QNN240 路径上,原厂包的可见视觉到语言执行接口缺少三路 DeepStack 输入;示例链传入的位置张量与正确交错 MRoPE 不符;独立重导出链补齐它们并复用经重放验证的后半段后,在两组挑选的清晰 COCO 图上减少错框,且有实测的几秒级热请求。这些结果不能证明 DeepStack 是唯一根因,也不能证明原厂 AIDEM 已被原位修复;其他场景的准确率需要独立验证。

图片来源

图 2 的六张底图来自 COCO val2017,其 Flickr 页面及 COCO 元数据均标记为 CC BY 2.0。本文将原照片缩放、并排并叠加实例框和预测框,属改作;摄影师未参与或背书本实验。标题、作者和原作品链接如下,顺序与图 2 一致:

模型结构依据:Qwen3-VL 模型配置、Transformers Qwen3-VL 实现、Qwen3-VL 技术报告。上游导出参考为 Qualcomm AI Hub Models 的 Qwen3-VL-4B 定义;本文的厂商 AIDEM 实验结论来自上面链接的本地执行日志,不能用上游源码替代对 X1 实际运行的验证。