Community编程与开发github.com

qnn-npu-compile

将ONNX模型编译为在骁龙手机NPU(Hexagon HTP,通过QNN/QAIRT SDK)上运行的格式,并配合sherpa-onnx或ONNX Runtime在Android应用中执行。当用户希望在搭载骁龙芯片的手机NPU上运行语音、音频、视觉或小型Transformer模型以提升速度或降低功耗时使用。

qnn-npu-compile 是什么?

该技能提供了一套将ONNX模型编译为Qualcomm NPU(Hexagon HTP)可执行格式的完整流程,主要面向Android应用中的推理加速。它并非自动化脚本,而是需要开发者根据每个模型手动调整的步骤化指南,源自sherpa-onnx项目已验证的CI工作流。技能覆盖从评估NPU适用性(静态形状、芯片家族限制、模型大小)、安装Linux x86-64工具链(QAIRT SDK、Android NDK)、导出固定形状的ONNX、使用qnn-onnx-converter进行量化与编译,到生成特定芯片(如SM8750 v79)的上下文二进制文件的全过程。运行阶段支持通过sherpa-onnx的QnnConfig或ONNX Runtime的QNN执行提供程序加载,并强调在真实设备上进行输出一致性、速度和功耗验证。特别值得注意的是,它指出了常见陷阱(动态形状、int64输入、CPU回退、SDK版本绑定),并提供了不使用本地Linux的替代方案(Qualcomm AI Hub)。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/chencohensolutions/claude-skills/tree/HEAD/plugins/qnn-npu-compile/skills/qnn-npu-compile

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

为Qualcomm NPU(QNN HTP)编译模型

这是一个流程,而非脚本:每个模型都需要自己的导出和检查。它遵循sherpa-onnx编译其已发布的NPU模型的方式(k2-fsa/sherpa-onnx,.github/workflows/export-*-qnn.yaml,.github/scripts/export-qnn/,scripts/qnn/),这些方式已知是有效的。在开始之前,请阅读那里的匹配工作流;进行适配,而非发明。

0. 判断NPU是否值得使用

  • 先在目标手机上测量: 模型当前的CPU占用率和墙钟时间(/proc/<pid>/stat每2秒滴答一次,top -H -p <pid>每线程),以及如果目标是功耗的话,在固定任务中的电池消耗。NPU在大型、稳定的矩阵计算(编码器、长批量传递)中收益明显;对于在1-2个线程上运行的小模型,收益可能很小。
  • 仅支持静态形状。 NPU运行固定大小的图:固定批次(1)、固定输入长度(分块大小,或Whisper编码器的30秒)。输入长度变化的模型需要固定分块、填充或多个图。
  • 每个构建对应一个芯片家族。 上下文二进制文件是为一种Hexagon架构(v73、v75、v79、v81…)编译的,只能在该架构上运行;其他手机(以及Exynos/Tensor/MediaTek)保持CPU路径。始终计划CPU回退。
  • 大小限制。 非常大的模型(超过约1B参数,fp16)可能无法放入一个上下文:它们需要8位权重(w8a16)和/或分割成多个图。sherpa-onnx自己的Whisper构建止步于medium(769M)。

芯片(来自sherpa-onnx scripts/qnn/device_info.py):SM8550 Snapdragon 8 Gen 2 (v73),SM8650 8 Gen 3 (v75), SM8750 8 Elite (v79),SM8850 (v81),QCS9100 (v73)。SM8350 (v68) 没有fp16:跳过它。通过 adb shell getprop ro.soc.model 查找手机的芯片。

1. 工具(仅Linux x86-64)

QNN SDK(QAIRT)运行在Linux x86-64(或Windows)上,不支持macOS。在Mac上:使用Linux虚拟机/云盒子,或使用 --platform linux/amd64 的Docker(模拟,速度慢)。sherpa-onnx为其构建了一个Docker镜像(Dockerfile-2.40,qnn-sdk-docker-build.yaml)。

  • QAIRT SDK,例如2.40.0.251030:来自Qualcomm(免费账户),或sherpa-onnx的CI下载的副本(huggingface.co/csukuangfj/qnn-toolkit,v2.40.0.251030.zip)。检查其许可证是否允许你的使用。
  • source qairt/<version>/bin/envsetup.sh(设置 QNN_SDK_ROOT),然后 sudo ${QNN_SDK_ROOT}/bin/check-linux-dependency.sh 和 python3 ${QNN_SDK_ROOT}/bin/check-python-dependency。
  • Python 3.10 venv,包含 numpy<2 onnx==1.17.0 onnxruntime onnxsim 以及模型自身的导出依赖。
  • Android NDK在 PATH 中(ndk-build),用于aarch64模型库;LDFLAGS="-Wl,-z,max-page-size=16384"(16 KB页,Android 15+需要)。

2. 为NPU导出ONNX

从原始检查点(PyTorch)重新导出,而不是从为CPU制作的ONNX导出:

  • --dynamic-batch 0(批次1),ONNX中不使用int8量化(--enable-int8-quantization 0:改为在QNN中量化),--use-int32-inputs 1(没有int64张量),固定分块/长度。
  • 简化(onnxsim)并检查每个输入/输出都有固定形状(sherpa-onnx scripts/pyannote/segmentation/show-onnx.py)。
  • 在一些真实输入上,使用ONNX Runtime(onnxruntime)在CPU上运行ONNX,与原始结果对比:输出相同(或文本相同)。

示例:流式Zipformer2(icefall zipformer/export-onnx-streaming.py,参见sherpa-onnx scripts/zipformer-transducer/x-asr/qnn/export-streaming.sh);Whisper(sherpa-onnx scripts/whisper/qnn/export_onnx.py,编码器和解码器作为单独的图)。

3. 转换、构建、编译(每个图,每个芯片)

对于每个ONNX图 $m(例如编码器、解码器、joiner):

# a) ONNX → QNN C++ 图 + 权重。fp16激活:--act_bitwidth 16 --bias_bitwidth 32。
#    对于8位权重(大模型):通过带有真实输入的校准 --input_list 添加量化。
qnn-onnx-converter --input_network $m.onnx --output_path out/$m.cpp --act_bitwidth 16 --bias_bitwidth 32

# b) 为芯片生成HTP配置(sherpa-onnx scripts/qnn/generate_config.py:soc_id,dsp_arch,vtcm)。
python3 generate_config.py --soc SM8750 --graph-name $m --output-dir out/config-$m --qnn-sdk-root $QNN_SDK_ROOT

# c) 模型库:x86_64(在主机上编译上下文)和aarch64-android。
python3 $QNN_SDK_ROOT/bin/x86_64-linux-clang/qnn-model-lib-generator -c out/$m.cpp -b out/$m.bin -o out/model_libs

# d) 为芯片生成上下文二进制文件:手机加载的内容。
$QNN_SDK_ROOT/bin/x86_64-linux-clang/qnn-context-binary-generator \
  --backend $QNN_SDK_ROOT/lib/x86_64-linux-clang/libQnnHtp.so \
  --model out/model_libs/x86_64-linux-clang/lib$m.so \
  --output_dir out/binary --binary_file $m \
  --config_file out/config-$m/htp_backend_extensions.json

保留转换器和生成器的日志:不支持的操作和量化警告会显示在那里。

4. 在手机上运行

  • 为Android aarch64运送QNN运行时库(libQnnHtp.so,libQnnHtpV79Skel.so以及芯片对应的存根,libQnnSystem.so;sherpa-onnx将其发布为 qnn-libs-<version>.tar.bz2)。Skel库在DSP上运行:sherpa-onnx的 OnlineRecognizer.prependAdspLibraryPath(dir)(或 ADSP_LIBRARY_PATH)必须指向其文件夹。
  • sherpa-onnx:在模型配置上使用 QnnConfig(backendLib = ".../libQnnHtp.so",contextBinary = ".../$m.bin",systemLib = ".../libQnnSystem.so")(例如 OnlineTransducerModelConfig(...,qnnConfig = ...)),provider = "qnn"。
  • ONNX Runtime替代方案:QNN执行提供程序(QNNExecutionProvider,backend_path=libQnnHtp.so),它也可以自行缓存上下文二进制文件。
  • 按设备选择:如果 ro.soc.model 匹配编译过的芯片且库加载成功,则使用NPU;否则使用CPU。绝不要假设。

5. 验证

  1. 相同输出: 在CPU和NPU上运行相同的音频/输入;对于语音,在10个以上的真实片段上计算词错误率。16位激活通常无损;8位权重必须检查。
  2. 速度: 手机上的实时因子和首次输出延迟。
  3. 功耗: 进程的CPU占用率,以及在固定30-60分钟任务中电池的下降情况(CPU vs NPU,屏幕关闭,相同的亮度和网络)。重复测试;热状态会改变结果。
  4. 在模型旁边记录结果(芯片、SDK版本、模型提交、量化方式、数值)。

常见陷阱

  • 动态形状、int64输入、不支持的操作(某些注意力或控制流操作):在导出中修复(包装模型,固定长度),而不是在QNN中。
  • 上下文二进制文件与SDK版本家族绑定:使用与运行时库相同的QAIRT版本构建。
  • 模型若有部分操作回退到CPU,会无声地失去大部分收益:检查日志中的CPU分区。
  • 许可证:模型许可证(重新编译的形态分发相当于分发模型)和QNN SDK许可证。
  • 不使用本地Linux的替代路线:Qualcomm AI Hub(云端编译并在真实设备上分析;需要账户;检查其条款是否适用于你的应用)。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能