同样是跑在 Hexagon NPU 上,自研 Hexagon 后端早期构建 decode 仅 1.5 t/s(4B App 实测;0.6B llm_bench 9.6 t/s),经过  pisces312/MNN fork 持续优化后,0.6B 已提升到  11.65 t/s(近 8 倍改善)。而高通官方 QNN SDK 路线却能跑出  32 t/s——仍是 Hexagon 的约 2.7 倍。这篇文章讲清楚两条 NPU 路线的本质区别、QNN 离线模型的使用方式,以及为什么它才是骁龙 NPU 跑 LLM 的最优姿势。

为什么有了 Hexagon 还要 QNN

MNN 有两条把计算 offload 到骁龙 NPU 的路线:

MNN Hexagon(自研)MNN QNN(官方 SDK)
代码位置source/backend/hexagon/source/backend/qnn/
后端类型MNN_FORWARD_HEXAGON(type=10)MNN_FORWARD_NN(type=5)
算子实现自研 HVX/HMX kernel(htp-ops-lib高通 QNN HTP runtime
部署形态stub + skel 两个 so,FastRPC 调用离线 context binary + plugin 算子
KV cache 管理无 HTP 图内 KV 管理;decode 每 token 全量权重经 DMA 流进 VTCM(受固件带宽墙限制)HTP 图内管理,零搬运
适用场景大 batch prefill(长文档总结)decode(逐 token 生成)

两条路线在硬件上最终都跑在 Hexagon DSP 上,但架构完全不同。Hexagon 后端是 MNN 自己写 DSP kernel、通过 FastRPC 逐个算子调用;QNN 后端是把整张图交给高通的 QNN runtime 编译和执行,KV cache、权重调度这些细节全部由 QNN 内部优化。

关键差异在 decode:Hexagon 是通用 HVX DSP,KV cache 不在图内管理,decode 每 token 都要把全量权重经 DMA 从 DDR 流进 VTCM(仅 8MB,放不下 300MB~2GB 权重);零售机固件在 NoC/DDR QoS 层对 unsigned PD 的 DMA 流量封顶 ~2.8GB/s,这是代码层无法突破的墙。QNN 则把 KV cache 放在 HTP 图内自生自灭,用专用 tensor 单元执行,decode 几乎不与 CPU 交互。这就是为什么同是 0.6B、同用 App 短对话口径,QNN decode(32 t/s)是 Hexagon(~1.5 t/s)的约 21 倍(若统一到 llm_bench,Hexagon 0.6B 仍有 9.6 t/s,QNN 优势约为其 3 倍+,QNN llm_bench 待补)。

在线构图 vs 离线构图:LLM 必须走离线

QNN 有两种使用模式:

  • 在线构图:运行时把 MNN 图翻译成 QNN 图,即时编译执行。只支持静态形状的常规模型(如 CNN 分类/检测),对 LLM 这种动态形状 + 融合算子的模型直接崩溃。
  • 离线构图:提前在 PC 上把模型编译成 QNN context binary(.bin),运行时由 plugin 算子直接加载执行。LLM 只能走这条路
一句话:App 设置里选  npu backend = 在线构图(LLM 必崩);QNN 离线模型的  backend_type 是  cpu,计算实际在 NPU 上由 plugin 算子驱动。初学者最容易在这里搞混。

离线模型是怎么生成的

Qwen3-0.6B QNN 离线模型的生成分四步:

  1. NPU 专用导出llmexport.py 加 --generate_for_npu --seperate_embed --disable_transformer_c4 --sym --omni --hqq --quant_bit 4 --quant_block 64 --act_bit=16。几个关键开关:
  • --generate_for_npu:在模型里传播 activation 量化信息
  • --seperate_embed:embedding 和 lm_head 单独存,CPU 侧执行
  • --disable_transformer_c4:关掉 Hexagon 专用的 C4 布局,QNN 不认识
  • --omni:用 OmniQuant 做权重量化校准,精度比纯 HQQ 更好
  1. compilefornpu:把 MNN 模型编译成 QNN 图,按 --max_history_token 2048 生成两档位(prefill 128 token / decode 1 token)。KV cache 内置在 QNN Attention 里。
  2. qnn-model-lib-generator:把 QNN 图编译成可加载的 context library。
  3. qnn-context-binary-generator:生成最终的 HTP context binary(graph0.bin),这就是跑在 NPU 上的东西。

产物里,graph0.bin 是真正的 NPU 可执行文件(含所有权重和两档位图),llm.mnn 是一个约 2.3KB 的 plugin op 模型(含 state/seq_len 元数据),作用是告诉 MNN:"加载这个 bin 文件,剩下的交给 QNN"。

⚠️  转换链路曾有两个导致真机首条消息 SIGSEGV 的 upstream bug,均已在本 fork(`https://github.com/pisces312/MNN`)修复
1.  compilefornpu 整图路径  _compileWholeModule 不写  state/ seq_len attr → KV  clientBuf 为空 →  graphExecute 返回 6004 →  Llm::sample 采样段错误;
2. 动态  StridedSlice 负 index 被 QNN 钳为 0 → 短 prompt logits 越界段错误;
3. 另含  QNNStridedSlice 负 index wrap 修复(已提交 upstream 级质量)。
用本 fork 的  compilefornpu / 已编译工具链即可直接跑通,无需手动打补丁;上述修复的细节见  qnn-device-crash-analysis_20260809_1031.md

真机性能:三方对比

同一台设备(荣耀 SM8850 / 骁龙 8 Elite Gen 5 / HTP V81),同一个模型(Qwen3-0.6B Q4 g64),三种后端实测:

指标CPU fp16 (4 线程)Hexagon cDSP(0.6B / 4B)QNN HTP (NPU)
Prefill (短)306 t/s223 / 48 t/s13~21 t/s
Decode70 t/s11.65 / 1.41 t/s(App)32 t/s
首字延迟 (20 tok)~0.07s~0.11s~1.6s
Hexagon 数字均为  [pisces312/MNN](https://github.com/pisces312/MNN) fork 当前版本 App 短对话实测。0.6B decode 从早期 ~1.5 t/s 提升到  11.65 t/s(近 8 倍),已进入可用区间;4B 受固件 DMA 带宽墙约束,改善有限(1.5 → 1.41)。llm_bench 口径下 0.6B 为 9.6 t/s(早期构建,fork 版待复测)。根因分析详见姊妹篇《从 1.5 到 11.65:MNN Hexagon DSP 后端让大模型跑上骁龙》。

怎么解读:

  • Prefill 是 CPU 的强项:大矩阵乘法吃内存带宽,CPU 带宽优势明显。NPU 的 prefill 受 context binary 加载开销和 KV cache 初始化影响,短 prompt 下偏慢。但如果是几百 token 的长 prompt(文档总结、RAG),Hexagon 的 prefill 能到 2000+ t/s,远超 CPU。
  • Decode 是 QNN 的主场:32 t/s ≈ 每秒 20+ 个中文字,完全可用。是 Hexagon 0.6B 的约 2.7 倍(32 vs 11.65),达到 CPU 的近一半。相比早期构建(Hexagon ~1.5 t/s,差距 21 倍),fork 优化后差距大幅收窄。
  • Hexagon decode(0.6B)已可用,4B 仍受限:0.6B 当前 fork 达到 11.65 t/s(App 短对话),每秒约 7~8 个中文字;但 4B 仅 1.41 t/s。根因是零售设备固件在 NoC/DDR QoS 层对 unsigned PD 的 DMA 流量封顶 ~2.8GB/s——fork 通过优化算子调度将 0.6B 推到了带宽墙附近的极限,而 4B 权重总量更大、DMA 压力更高,改善空间有限(详见姊妹篇《骁龙手机端侧大模型低功耗推理:MNN Hexagon cDSP 后端实录》)。
  • 模型越大,NPU 越赚:0.6B 上 CPU 还能赢,但到 4B 级 CPU decode 会因带宽瓶颈急剧下降,而 HTP 的 tensor 单元和片上缓存能维持稳定吞吐。4B 的 QNN 转换正在进行中。

Qwen3-0.6B QNN HTP App 实测截图(提问"NPU 是什么",输出 203 tokens):

Qwen3-0.6B QNN HTP 实测:decode 32.56 t/s

NPU 的真正价值:不是更快,是更可持续

性能之外,NPU 存在的核心理由是能效和不占 CPU

CPU fp16QNN HTP
典型功耗3~5W(全核满载)0.5~1W
CPU 占用占满所有核,UI 卡顿几乎为零,UI 流畅
热降频2~3 分钟后触发,速度断崖下跌低功耗,长时间稳定
后台任务挤占系统资源完全不影响

对用户来说,NPU 的体验是:

  • 长时间对话不卡:一场 10 分钟的深度对话,CPU 可能后半段因热降频明显变慢,HTP 全程稳定
  • 边推理边干别的:模型生成回复时,你可以流畅刷网页、切 App、回消息
  • 省电:同样的推理量,HTP 功耗只有 CPU 的 1/3~1/5

即使 0.6B 上 CPU 速度更快,如果你频繁使用、长时间对话,NPU 的体验反而更好。

怎么用

下载

不想自己编译 App? 直接从  GitHub Releases 下载预编译的 APK(v0.8.3.3),安装即可使用。APK 已内置 QNN + Hexagon 后端支持,无需从源码构建。

部署

把模型目录整个放到手机的 /sdcard/mnn-models/ 下,然后在 App 的"我的模型"里就能看到。

注意:在模型设置里 不要把 backend 改成 npu(那是在线构图入口,LLM 必崩)。QNN 离线模型的 backend_type 默认就是 cpu,plugin 算子内部会自动走 HTP——保持默认即可。

早期版本要求额外推送  llm.mnn.weight(plugin 虽不读,但  llm.cpp 会无条件  checkFile),现已通过  external weight 标记解决:HF 上发布的模型  config.json 均已更新,按目录整体推送即可,无需单独补权重文件。

写在最后

端侧 NPU 跑 LLM 这件事,不是"开个开关就变快"那么简单。Hexagon 后端和 QNN 后端是两条完全不同的技术路线,各有适用场景:

  • 长文档总结、RAG 检索 → Hexagon prefill(2000+ t/s)
  • 逐 token 对话生成 → QNN HTP decode(32 t/s,低功耗)
  • 小模型短时使用 → CPU 直接上(最快,省心)

真正的端侧 AI 体验,不是把所有计算都扔给 NPU,而是根据工作负载的特点,在 CPU / Hexagon / QNN 之间做智能调度。MNN 的多后端架构恰好提供了这种灵活性——同一个模型、同一个 App、同一个引擎,切换 backend 就是一行配置的事。

---

*环境:MNN(2026-08 master)/ QAIRT SDK 2.39.0 / SM8850(骁龙 8 Elite Gen 5,HTP V81)/ Android 16*

更多推荐