qnn-debug
MNN QNN(高通 HTP/NPU)后端的问题定位、修复与算子适配。覆盖运行时报错(1002/6000/1003/6004)、模型转换/算子校验失败、推理结果异常、精度不达标、以及性能问题;并指导为 QNN 新增/适配算子。核心方法:用 MNN2QNNModel --dump_intermediate_outputs 一次性导出 QNN 全部中间张量,与 CPU(fwd=0) 基线逐张量对比,直接定位首个出错算子(旧的 testMNNFromOnnx.py 截断二分已降为回退手段);区分“真 bug/量化/HTP fp16 精度”;新增算子先查 SDK 算子文档(MasterOpDef/HtpOpDefSupplement)再实现。
DeepseekModel
官方收录技能
质量 优秀 · 90
v1.0.0
获取
https://deepseekmodel.com/api/download.php?id=alibaba-mnn-skills-qnn-debug-skill-md&format=skill
下载 .skill
标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name qnn-debug description MNN QNN(高通 HTP/NPU)后端的问题定位、修复与算子适配。覆盖运行时报错(1002/6000/1003/6004)、模型转换/算子校验失败、推理结果异常、精度不达标、以及性能问题;并指导为 QNN 新增/适配算子。核心方法:用 MNN2QNNModel --dump_intermediate_outputs 一次性导出 QNN 全部中间张量,与 CPU(fwd=0) 基线逐张量对比,直接定位首个出错算子(旧的 testMNNFromOnnx.py 截断二分已降为回退手段);区分“真 bug/量化/HTP fp16 精度”;新增算子先查 SDK 算子文档(MasterOpDef/HtpOpDefSupplement)再实现。 MNN QNN 后端 定位 / 修复 / 适配 SKILL 触发条件 :用户报告 QNN / NPU(高通 HTP)后端任何问题,或要求适配算子。常见表述:"QNN 结果不对/NPU 精度差"、"QNN 报错 1002/6000/1003/6004"、"graphFinalize/graphExecute 失败"、"模型转换失败 / validateOpConfig failed"、"LLM 在 NPU 上输出乱码"、"QNN 性能差"、"给 QNN 加/支持 XXX 算子"、"某算子 QNN 不支持回落 CPU"。 概述 QNN 后端有 两条执行路径 ,排查前先分清在跑哪条(详见 reference.md · 两条执行路径 ): 在线 finalize 路径 :加载普通 .mnn (视觉/CNN),运行时逐算子构图 → graphFinalize → 整图执行。入口多为 ModuleBasic 。报错码常见 1002(finalize)/6000(execute) 。 离线预编译路径 :LLM 经 llmexport.py → generate_llm_qnn.py 生成预编译 QNN 二进制, llm_demo 加载运行。报错码常见 1003/6004(运行时 IO 与图定义不一致) 、转换期 validateOpConfig failed 。 本 SKILL 覆盖三类工作,可组合: A. 数值/精度 & 报错定位 (主线): 建立可信基线 → dump 全部中间张量比对定位首个出错点(回退:截断二分)→ 读误差模式并数值验证 → 区分真 bug / 量化 / HTP fp16 精度 → 打桩定位到代码 。 B. 新增/适配算子 : 先查 SDK 算子文档(MasterOpDef/HtpOpDefSupplement/SupportedOps) → 照现有算子模板实现并注册 → 二分探测验证 。见 新增 / 适配 QNN 算子 。 C. 性能定位 :开 QNN Profile → 找过多 quant/dequant、数据搬运、CPU fallback → 优化。见 性能定位与优化 。 问题分诊(先按症状选路径) 症状 / 错误 路径 去哪一节 error 1002 graphFinalize 失败 / could not create op 在线 步骤 3.5 + 新增/适配算子 error 6000 graphExecute 失败 / 输入全零、结果恒为 bias 在线 先试 shapeMutable=false (见关键约束) + 步骤 3.5 结果不对 / 精度差 / 与 CPU 不一致 在线 步骤 0→4 :dump 中间张量比对(回退:截断二分) error 1003/6004 运行时报错(离线/LLM) 离线 运行时报错 1003/6004 validateOpConfig failed / 转换失败(离线/LLM) 离线 新增/适配算子 (同查 OpDef) + reference 案例 8 LLM 输出乱码 / 数值异常(能跑不报错) 离线 reference 案例 9 · 量化精度 性能不达预期 两者 性能定位与优化 要新增/适配某算子 两者 新增/适配算子 A/B 常交织:定位到"某 OpType 没实现/实现有 bug"就转 B,补完再用 A 验证。 核心原则 先建立可信基线 :任何对比都先确认 CPU fp32 与 ONNX 一致( TEST_SUCCESS / diff ≈ 1e-4)。参考 txt 与 input.txt 必须是 同一次 生成的,否则会误判(见坑 1)。 一次性 dump 全部中间张量 (首选,替代反复截断):用 MNN2QNNModel <sdk> <soc> <arch> <model.mnn> <out> --dump_intermediate_outputs 生成 debug 版模型,它把 每个 QNN native 激活提升为 APP_READ 图输出;在真机上跑一遍即把全部中间张量连同 manifest_*.tsv 落盘,再与 CPU 基线 逐张量 比对,一次定位首个出错算子——不必每探一个点就重转一次模型。截断二分( testMNNFromOnnx.py <model> <tensor> )保留为 回退手段 (dump 跑不起来、或想快速二分少数点时用)。见 步骤 1 。 看误差模式,别只看 diff 数值 :每通道常数、全常数、转置、NaN 各有明确含义(见 reference.md )。用 Python 对照 bias/权重等 数值验证 你的假设,而不是猜。 三方对比区分“bug”还是“精度” :QNN-fp16、 CPU-fp16 (fp16 地板,fp32 累加)、CPU-fp32(可信基线)一起对比( qnn_probe.sh 一次给全)。真 bug 会在某算子处 QNN 突跳而 CPU-fp16 不跳 ;fp16 精度问题是 平滑累积 、经 Pool/GAP 会 下降 。需要时再拿 OpenCL-fp16 交叉验证。 能定位就停 :定位到首个出错算子 + 数值证据即可下结论;改代码前先 git blame 看该处是否近期改动。 闭环沉淀 :每次非平凡定位/修复/适配完成后,若有可复用经验, 主动 按 复盘:回写 reference 追加案例——让本 skill 越用越强。 关键约束 严禁访问 schema/private/ 和 source/internal/ 。 结果异常先试一招 :若模型输入形状固定,先用 shapeMutable=false ( Session_Input_Inside )跑一遍。QNN 在线路径在 Session_Input_User ( shapeMutable=true ,多数入口默认)下有 输入不被拷入、首个算子吃全零 的已知问题; shapeMutable=false 零代码规避。详见 reference.md 案例 1 。 两套构建目录别搞混 : build/ (macOS 主机构建, MNN_QNN=OFF )只提供 MNNConvert 等主机工具;真机 libMNN.so 来自 project/android/build_64 ( MNN_QNN=ON ,NDK arm64)。 改后端代码后要在 build_64 里 make MNN 并 push 它产出的 libMNN.so ,push 错目录的库是最常见的“修了没效果”原因。 前置依赖(环境准备) 依赖 用途 检测 adb + 真机 设备已连接, /data/local/tmp/MNN/ 下已就绪 ModuleBasic.out 、 libMNN.so 、 libQnnHtp*.so 、 libc++_shared.so 等 adb devices ; adb shell ls /data/local/tmp/MNN/ 主机 MNNConvert ONNX→MNN 转换(截断后重转) ls build/MNNConvert (或 which mnnconvert ) python 环境 testMNNFromOnnx.py 依赖 onnx onnxruntime numpy python3 -c "import onnx,onnxruntime,numpy" Android QNN 构建 改后端代码后重编 libMNN.so grep MNN_QNN: project/android/build_64/CMakeCache.txt 应为 ON 设备上运行都要 export LD_LIBRARY_PATH=. (在 /data/local/tmp/MNN/ 下)。 必背:ModuleBasic 命令与后端编号 ./ModuleBasic.out <model.mnn> <dir> <runMask> <forwardType> <loops> <threads> <precision> forwardType :CPU= 0 ,OpenCL= 3 , QNN= 5 (QNN 注册为 MNN_FORWARD_NN ,见 QNNBackend.cpp 的 QNN_FORWARD_TYPE ) precision :Normal= 0 ,High= 1 ,Low= 2 ;QNN 里 mUseFP16 = (precision != High) dir 内需有 input.txt 、 input.json 和 <outputName>.txt 参考;比对阈值为 1% ( absMaxV*0.01 < diffmax 判失败) 用户给的复现命令示例: ./ModuleBasic.out onnx/test.mnn onnx 0 5 1 4 2 (QNN, fp16)。 必背:MNN2QNNModel + 中间张量 dump(定位主力,见步骤 1) MNN2QNNModel <qnnSDKPath> <socId> <hexagonArch> <src.mnn> <outDir> [totalShapeNum] [shape...] [--dump_intermediate_outputs] 常见 SoC:8Gen2→ socId 43 / arch 73 ,8Gen3→ 57 / 75 ,8Elite→ 69 / 79 。 加 --dump_intermediate_outputs 生成 debug 版 离线模型( outDir/<name>.mnn + .bin ):它把 QNN 图里每个 native 张量提升为 APP_READ 输出; 该标志已烘焙进模型 ,运行时无需再传任何 flag,跑一遍即自动 dump。 对普通 CNN 同样适用 :MNN2QNNModel 接受任意 .mnn ,所以在线路径的模型也能用这条离线 dump 通道拿到全部中间张量(在线 ModuleBasic 本身不透传 dump flag)。 输出落盘:默认写到模型旁的 qnn_intermediate_outputs/ ;设环境变量 MNN_QNN_DUMP_DIR 改目录。每次执行产出一个 manifest_NNNNNN.tsv + 每张量一个 raw 文件。详见 reference.md · QNN 中间张量 dump 。 在线路径若要在自己的 runner 里开 dump: backendConfig.flags = MNN_QNN_DUMP_INTERMEDIATE_OUTPUTS ( 1<<16 ,见 MNNForwardType.h )。 ModuleBasic.out 未透传该 flag,故在线模型走上面的 MNN2QNNModel 通道最省事。 离线 / LLM 路径的准备(仅 LLM/NPU 预编译模型需要) source $QAIRT /bin/envsetup.sh # 设 QNN_SDK_ROOT 等 # 1) 导出 NPU 版 MNN(量化):关键 --generate_for_npu --quant_bit 4 --act_bit=16 --sym --smooth --hqq python3 transformers/llm/export/llmexport.py --path <model> -- export mnn --dst_path <out> --generate_for_npu ... # 2) MNN → QNN 预编译离线图(报错→转"转换期校验失败") python3 transformers/llm/export/npu/generate_llm_qnn.py --model <mnn> --soc_id=57 --dsp_arch=v75 # 3) 设备运行 adb shell "cd /data/local/tmp && LD_LIBRARY_PATH=. ./llm_demo <model>/config_qnn.json prompt.txt" 除 libMNN.so 外,离线路径还需 push QNN SDK 运行库: libQnnHtp.so / libQnnSystem.so / libQnnHtpV<arch>Stub.so / libQnnHtpV<arch>Skel.so (来自 $QNN_SDK_ROOT/lib/{aarch64-android,hexagon-v<arch>/unsigned} )。 定位流程 步骤 0 · 建立可信基线 cd build # 主机构建目录,有 MNNConvert python3 ../tools/script/testMNNFromOnnx.py ../models/<model>.onnx # 全模型:生成 onnx/ 参考 + convert_cache.mnn,并做 CPU 自检 末尾应打印 TEST_SUCCESS 。把 convert_cache.mnn → onnx/test.mnn ,连同 input*.txt/input.json/<out>.txt push 到设备,跑 CPU fwd=0 prec=1 确认 diff≈1e-4。 至此确认模型/转换无误,问题在 QNN。 ⚠️ 源 onnx 必须放在 onnx/ 目录之外 (如 cp build/onnx/test.onnx build/src_model.onnx )——否则 testMNNFromOnnx.py 把它拷成 onnx/test.onnx 时报 SameFileError。多输入模型会生成 input0.txt/input1.txt/… ,push 时用 onnx/input*.txt 覆盖。 步骤 1 · 一次性 dump 全部中间张量并比对(首选) 一趟 dump 拿到 QNN 全部中间激活,再与 CPU 基线逐张量比,直接找出 第一个 出错算子——取代"每探一个点就重转一次"的截断循环。 生成 debug 模型并跑一遍 (CNN / LLM 通用): MNN2QNNModel $QNN_SDK_ROOT 57 75 onnx/test.mnn dbg --dump_intermediate_outputs # soc/arch 按真机改 adb push dbg/test.mnn dbg/test.bin /data/local/tmp/MNN/ # 连同 input.txt/input.json/.bin adb shell "cd /data/local/tmp/MNN && LD_LIBRARY_PATH=. MNN_QNN_DUMP_DIR=dump ./ModuleBasic.out test.mnn . 0 5 1 4 2" adb pull /data/local/tmp/MNN/dump ./dump # manifest_*.tsv + 每张量一个 raw 读 manifest 逐张量对比 CPU : manifest_NNNNNN.tsv 每行给出 name / file / data_type / dimensions / quant_encoding / scale / offset 。raw 文件是 QNN 布局(NHWC)+ QNN dtype ,比对前要按 manifest 的 quant(反量化 f = (q - offset) * scale )与布局还原,再和 CPU-fp32 基线( testMNNFromOnnx.py 全模型跑出的中间张量,或 MNNDump2Json 的张量表)对齐。张量名形如 t42 ,可回查 MNN 张量表定位到具体 op。 找突跳点 :按图执行顺序扫每个张量的 diff,第一个" 输入好、输出坏 "且 QNN 远大于 CPU-fp16 地板的即首个出错算子;随后转 步骤 2/3 读误差模式、区分 bug/精度。 细节(manifest 字段、布局/量化还原、局限)见 reference.md · QNN 中间张量 dump 。 回退:截断 + 二分(dump 跑不起来 / 只想快速二分少数点时) 先看图节点顺序: python3 -c "import onnx;m=onnx.load('src_model.onnx');[print(i,n.op_type,list(n.output)) for i,n in enumerate(m.graph.node)]" ;或 MNNDump2Json 看 MNN 侧执行顺序(MNN 名与 ONNX 名可能不同)。 用 scripts/qnn_probe.sh <tensor> [<tensor> ...] 对中间张量截断→转换→push→ 一次并排跑 QNN-fp16 / CPU-fp32 / CPU-fp16 ,对节点序号二分找"输入好、输出坏"的第一个算子。 脚本已 自动注入 shapeMutable=false (否则 QNN 输入不进去,见关键约束)。 陷阱 见 坑 2/坑 3 :换模型前 rm -f .tempcache (QNN 图缓存,脚本已带);单独取 QNN 输出要单独跑 fwd=5 再 cat output/0_0.txt (同一条命令里跑 CPU 会覆盖它)。 步骤 2 · 读误差模式 + 数值验证 把 QNN 输出与参考 reshape 后用 numpy 比对,对照 误差模式速查 。例如“每通道 std=0 的常数”几乎一定是 conv 收到全零输入 → 输出==bias ,可与 ONNX 里该 conv 的 bias 逐通道核对确认。 步骤 3 · 区分“真 bug”还是“HTP fp16 精度” 最简单:直接看 qnn_probe.sh 已经并排给出的 CPU-fp16 列——它是"行为良好的 fp16 地板"(CPU/OpenCL 的 fp16 都用 fp32 累加器)。某点 QNN-fp16 ≫ CPU-fp16 且突跳 = 真 bug; 同步平滑增长 = fp16 累积。 需要 OpenCL 做交叉验证时,再用 scripts/cmp_probe.sh (设备需 libMNN_CL.so )一次输出 QNN 与 OpenCL 的 diff。判据: 现象 结论 某算子处 QNN 误差 突跳 、OpenCL 不跳 该算子 真 bug ,深挖它 QNN 与 OpenCL 平滑同步 增长,经 GlobalAveragePool/Pool 后误差 下降 随机精度噪声,非离散 bug QNN 比 OpenCL 同精度大 ~2.5×/层 并随深度放大 HTP fp16 累加 (OpenCL fp16 用 fp32 累加器) QNN High 与 Low 结果几乎相同 HTP 忽略 fp32 请求 ,底层纯 fp16(见 reference) 步骤 3.5 · graphFinalize / graphExecute 失败时:启用 QNN 错误日志 如果 QNN 报 error code 1002(graphFinalize 失败)或 6000(graphExecute 失败),需要启用 QNN 内部日志来获取详细错误信息: 启用 log callback :在 QNNBackend.cpp 中找到 QnnLog_create 或 log level 设置处,将级别改为 QNN_LOG_LEVEL_ERROR (1)或更详细的级别(2=WARN, 3=INFO, 4=DEBUG)。 重编并测试 : cd project/android/build_64 && make MNN -j8 && adb push libMNN.so /data/local/tmp/MNN/ 查看日志 :运行测试时 grep QNN_LOG ,关注: could not create op → 某算子约束不满足,查 MasterOpDef.html Wrong number of Inputs → 输入数量不对 Op creation failure, total_inputs=N → 检查各 Input 的类型(F16Crouton=fp16, PlainFloat=fp32) 查 SDK 算子文档确认约束 :SDK 根取自编译配置—— SDK=$(grep -i QNN_SDK_ROOT project/android/build_64/CMakeCache.txt | head -1 | cut -d= -f2) (或 $QNN_SDK_ROOT ),再进 $SDK/docs/QNN/ (老版)或 $SDK/docs/QAIRT-Docs/QNN/ (2.48) 下的 OpDef/ 。同目录 HtpOpDefSupplement.html 是 HTP 专属约束权威来源, SupportedOps.html 是各后端支持列表。搜算子名确认输入数量、类型、维度约束。 详见 reference.md · QNN 错误日志 和 QNN 算子约束查询 。 步骤 4 · 打桩定位到代码 / 给结论
Agent 识别该技能的关键词,点击任意一个即可复制。
该技能未提供触发词。
下载的 .skill 包内含以下字段。
| 字段 | 说明 |
|---|---|
| format | 格式标识(skill/v1) |
| skill_id | 技能唯一 ID |
| name | 技能名称 |
| version | 版本号 |
| description | 技能描述 |
| category | 所属分类(数组) |
| trigger_words | 触发词列表 |
| tags | 标签列表 |
| source | 来源标识 |
| source_url | 来源链接(本页地址) |
| exported_at | 导出时间(每次下载生成) |
| system_prompt | 系统提示词正文 |
| model_config | 模型参数:provider / model / temperature / max_tokens / top_p |
| examples | 示例 |
| install_guide | 各平台导入说明(Coze / Dify / Claude / 自定义框架) |