高通中的深度学习模型部署概述
- [1. 参考链接](#1. 参考链接)
- 2.简介
-
- [2.1 QNN](#2.1 QNN)
- [2.2 SNPE](#2.2 SNPE)
- [3. 两种格式对比与关系](#3. 两种格式对比与关系)
-
- [3.1 SNPE vs QNN vs AI Hub](#3.1 SNPE vs QNN vs AI Hub)
- [3.2 量化类型](#3.2 量化类型)
- [3.3 Hexagon NPU 代际](#3.3 Hexagon NPU 代际)
- [4. 高通深度学习模型部署的基本流程](#4. 高通深度学习模型部署的基本流程)
-
- [4.1 PyTorch 模型导出 ONNX](#4.1 PyTorch 模型导出 ONNX)
- [4.2 INT8 量化](#4.2 INT8 量化)
- [4.3 QNN 编译](#4.3 QNN 编译)
- [4.4 部署到设备](#4.4 部署到设备)
- [4.5 性能优化](#4.5 性能优化)
-
- [4.5.1 算子优化](#4.5.1 算子优化)
- [4.5.2 量化校准优化](#4.5.2 量化校准优化)
-
- [4.5.3 性能基准测试](#4.5.3 性能基准测试)
- [4.6 检查清单](#4.6 检查清单)
- [4.7 常见问题](#4.7 常见问题)
- [5. QNN和SNPE转化流程](#5. QNN和SNPE转化流程)
-
- [5.1 QNN部署流程](#5.1 QNN部署流程)
- [5.2 SNPE部署流程](#5.2 SNPE部署流程)
1. 参考链接
3Qualcomm SNPE/QNN 模型部署指南:从 PyTorch 到车规 NPU
7高通 QNN SDK 使用指南:从环境搭建到模型部署全流程
9高通SNPE安装与部署全攻略:从环境配置到模型转换跑通Demo
11高通SNPE 1.5安装与模型转换踩坑指南:从环境配置到DLC部署
13零基础上手:高通 SNPE 模型转换实战(从 ONNX 到 DLC)
14精度调试工具:SNPE Quantization Check、QNN Accuracy Analyzer、逐层对比
16使用 SNPE、QNN 或 QAIRT 运行 AI 模型
2.简介
在人工智能和机器学习领域,神经网络加速器(Neural Network Accelerators)是不可或缺的工具,它们可以显著提高深度学习应用的性能和效率。其中,QNN(Qualcomm Neural Processing Engine)和SNPE(Snapdragon Neural Processing Engine)作为两大主流神经网络加速器,各自在市场中占有一席之地。本文将从多个方面详细阐述QNN和SNPE之间的区别。
QNN和SNPE都是高通(Qualcomm)公司推出的神经网络加速器技术,旨在为其Snapdragon系列处理器提供深度学习加速支持。两者都能够帮助开发者在移动设备上实现高效的神经网络推理,但它们在技术实现、应用场景和性能特点上有所不同。
QNN和SNPE作为高通公司推出的两款神经网络加速器技术,在技术实现、应用场景和性能特点上各有优势。QNN以硬件加速为基础,适用于需要高性能计算能力的深度学习应用;而SNPE则以软件优化为主,为开发者提供了一套完整的神经网络推理解决方案。开发者可以根据自己的需求选择适合的神经网络加速器技术来实现高效的深度学习应用。
2.1 QNN
QNN是高通公司专为Snapdragon系列处理器设计的一款神经网络加速器。它采用硬件加速的方式,通过优化处理器架构和指令集,实现对神经网络的高效计算。QNN支持多种神经网络框架,如TensorFlow、PyTorch等,能够方便地将深度学习模型部署到高通芯片上。
QNN主要适用于需要高性能计算能力的深度学习应用,如图像识别、语音识别、自然语言处理等。通过QNN的硬件加速,这些应用可以在移动设备上实现更快的推理速度和更低的功耗。
QNN以硬件加速为基础,能够提供出色的计算性能和能效比。它支持多种神经网络框架和模型格式,使得开发者可以灵活地选择适合自己的深度学习解决方案。同时,QNN还具有较好的可扩展性,可以根据需求进行定制和优化。
2.2 SNPE
SNPE是高通公司推出的一款神经网络推理引擎,它可以在Snapdragon系列处理器上实现神经网络的快速推理。SNPE通过软件优化的方式,提供了一套完整的工具链,包括模型转换、优化和部署等功能。它支持多种神经网络模型格式,如ONNX、Caffe等,并可以与各种深度学习框架进行集成。
SNPE则更侧重于为开发者提供一套完整的神经网络推理解决方案。它不仅可以用于高性能的深度学习应用,还可以用于对实时性要求较高的场景,如自动驾驶、智能监控等。通过SNPE的模型转换和优化功能,开发者可以方便地将训练好的神经网络模型部署到高通芯片上,并实现高效的推理。
SNPE以软件优化为主,通过提供完整的工具链和丰富的API接口,使得开发者可以更加便捷地进行神经网络推理。它支持多种神经网络模型格式和深度学习框架,能够满足各种应用场景的需求。同时,SNPE还具有较好的兼容性和稳定性,可以确保在各种设备和操作系统上都能够正常运行。
3. 两种格式对比与关系
Qualcomm Snapdragon 平台的 Hexagon NPU 提供了强大的边缘 AI 推理能力,但模型部署流程复杂。本文档提供从 PyTorch 到 NPU 的完整部署路径,涵盖 INT8 量化、QNN 编译、性能优化。

3.1 SNPE vs QNN vs AI Hub
| 工具 | 定位 | 特点 | 推荐场景 |
|---|---|---|---|
| SNPE | 传统 SDK | 稳定,文档丰富,支持旧平台 | Snapdragon 855/865 |
| QNN | 新一代 SDK | 性能更优,支持新架构(Transformer) | Snapdragon 888/8 Gen 1+ |
| AI Hub | 云端编译服务 | 自动优化,一键部署 | 快速原型、量产 |
3.2 量化类型
| 精度 | 性能 | 精度损失 | 适用场景 |
|---|---|---|---|
| FP32 | 基线 | 0% | 模型调试 |
| FP16 | 1.5-2x | < 1% | 量化起点 |
| INT8 | 2-4x | 1-3% | 量产推荐 |
| INT4 | 4-8x | 3-10% | 大模型压缩 |
3.3 Hexagon NPU 代际
| 平台 | NPU 型号 | TOPS | 支持算子 | 量化支持 |
|---|---|---|---|---|
| Snapdragon 855 | Hexagon 690 | 7 | CNN 基础 | INT8 |
| Snapdragon 888 | Hexagon 780 | 26 | CNN + Transformer | INT8/INT16 |
| Snapdragon 8 Gen 1 | Hexagon 790 | 34 | 全算子 | INT8/FP16 |
| Snapdragon 8 Gen 2 | Hexagon 890 | 45 | 全算子 + LLM | INT8/INT4 |
| Snapdragon 8 Gen 3 | Hexagon 995 | 75 | 全算子 + LLM | INT8/INT4/INT16 |
4. 高通深度学习模型部署的基本流程
这个章节主要以案例的方式阐述一下深度学习模型是如何封装部署到高通嵌入式上的。
4.1 PyTorch 模型导出 ONNX
python
"""
PyTorch 模型导出 ONNX
关键要求:
1. 动态输入尺寸(batch, height, width)
2. 算子兼容性检查
3. 简化模型(去除后处理)
"""
import torch
import torch.nn as nn
import onnx
import onnxsim
class DMSModel(nn.Module):
"""示例 DMS 模型(简化版)"""
def __init__(self):
super().__init__()
self.backbone = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.Conv2d(32, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.Conv2d(64, 128, 3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
)
# 关键点回归
self.keypoint_head = nn.Conv2d(128, 68, 1) # 34 关键点 × 2 (x, y)
# 状态分类
self.class_head = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(128, 64),
nn.ReLU(inplace=True),
nn.Linear(64, 3) # 正常/疲劳/分心
)
def forward(self, x):
feat = self.backbone(x)
keypoints = self.keypoint_head(feat)
state = self.class_head(feat)
return keypoints, state
def export_to_onnx(model, output_path, input_size=(1, 3, 224, 224)):
"""导出模型到 ONNX
Args:
model: PyTorch 模型
output_path: 输出路径
input_size: 输入尺寸 (N, C, H, W)
"""
model.eval()
# 创建输入
dummy_input = torch.randn(*input_size)
# 动态维度
dynamic_axes = {
'input': {
0: 'batch_size',
2: 'height',
3: 'width'
},
'keypoints': {
0: 'batch_size'
},
'state': {
0: 'batch_size'
}
}
# 导出
torch.onnx.export(
model,
dummy_input,
output_path,
input_names=['input'],
output_names=['keypoints', 'state'],
dynamic_axes=dynamic_axes,
opset_version=13,
do_constant_folding=True
)
# 验证
onnx_model = onnx.load(output_path)
onnx.checker.check_model(onnx_model)
print(f"✓ ONNX 模型验证通过: {output_path}")
# 简化
onnx_model_simplified = onnxsim.simplify(onnx_model)
onnx.save(onnx_model_simplified, output_path)
print(f"✓ ONNX 模型已简化")
return output_path
# 实际导出
if __name__ == "__main__":
model = DMSModel()
export_to_onnx(model, "dms_model.onnx")
4.2 INT8 量化
python
"""
INT8 量化流程
关键步骤:
1. 准备校准数据集(Calibration Dataset)
2. 运行校准收集量化参数
3. 评估精度损失
"""
import numpy as np
import onnx
from onnxruntime.quantization import quantize_dynamic, quantize_static, QuantFormat, QuantType
from onnxruntime.quantization.shape_inference import quant_pre_process
def prepare_calibration_data(dataset_path: str, num_samples: int = 100):
"""准备校准数据
Args:
dataset_path: 数据集路径
num_samples: 校准样本数(推荐 100-500)
Returns:
calibration_data: 校准数据列表
"""
# 模拟数据(实际需要从真实数据集加载)
calibration_data = []
for i in range(num_samples):
# 生成随机输入(实际应从数据集读取)
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
# 保存为 .npy 文件
np.save(f"calibration_data/input_{i}.npy", input_data)
calibration_data.append(input_data)
return calibration_data
def quantize_to_int8(input_onnx_path, output_onnx_path, calibration_data_dir):
"""INT8 静态量化
Args:
input_onnx_path: 输入 ONNX 模型路径
output_onnx_path: 输出量化模型路径
calibration_data_dir: 校准数据目录
"""
# 预处理(插入形状推断节点)
preprocessed_path = input_onnx_path.replace('.onnx', '_preprocessed.onnx')
quant_pre_process(input_onnx_path, preprocessed_path)
# 创建校准数据读取器
def data_reader():
for i in range(100): # 假设有 100 个校准样本
input_data = np.load(f"{calibration_data_dir}/input_{i}.npy")
yield {'input': input_data}
# 静态量化
quantize_static(
model_input=preprocessed_path,
model_output=output_onnx_path,
calibration_data_reader=data_reader(),
quant_format=QuantFormat.QDQ, # Quantize-Dequantize 格式
per_channel=False, # 按层量化(简单),按通道量化(精度更高)
weight_type=QuantType.QInt8,
activation_type=QuantType.QInt8
)
print(f"✓ INT8 量化完成: {output_onnx_path}")
return output_onnx_path
def evaluate_quantization(original_path, quantized_path, test_data):
"""评估量化精度损失
Args:
original_path: 原始模型路径
quantized_path: 量化模型路径
test_data: 测试数据
Returns:
accuracy_loss: 精度损失 (%)
"""
import onnxruntime as ort
# 加载模型
original_session = ort.InferenceSession(original_path)
quantized_session = ort.InferenceSession(quantized_path)
# 推理
original_output = original_session.run(None, {'input': test_data})
quantized_output = quantized_session.run(None, {'input': test_data})
# 计算差异
diff = np.abs(original_output[0] - quantized_output[0]).mean()
print(f"平均输出差异: {diff:.6f}")
return diff
# 实际量化
if __name__ == "__main__":
# 1. 准备校准数据
prepare_calibration_data("calibration_data/", num_samples=100)
# 2. 量化
quantize_to_int8("dms_model.onnx", "dms_model_int8.onnx", "calibration_data/")
# 3. 评估
test_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
evaluate_quantization("dms_model.onnx", "dms_model_int8.onnx", test_data)
4.3 QNN 编译
bash
#!/bin/bash
# QNN 编译脚本
# 环境:Ubuntu 20.04, QNN SDK 2.20+
# 设置环境变量
export QNN_SDK_ROOT=/opt/qnn-sdk
export LD_LIBRARY_PATH=$QNN_SDK_ROOT/lib/x86_64-linux-clang:$LD_LIBRARY_PATH
# 1. ONNX 转 DLC
python $QNN_SDK_ROOT/bin/x86_64-linux-clang/onnx2dlc \
--input_model dms_model_int8.onnx \
--output_path dms_model.dlc
# 2. 模型验证
python $QNN_SDK_ROOT/bin/x86_64-linux-clang/qnn-net-run \
--model dms_model.dlc \
--input_list input_list.txt
# 3. 编译为 QNN Context Binary(针对特定平台)
python $QNN_SDK_ROOT/bin/x86_64-linux-clang/qnn-context-binary-generator \
--model dms_model.dlc \
--backend QnnCpu.so \
--output dms_model_cpu.so
# 4. 针对 Hexagon NPU 编译(需要目标平台库)
python $QNN_SDK_ROOT/bin/x86_64-linux-clang/qnn-context-binary-generator \
--model dms_model.dlc \
--backend QnnHtp.so \
--output dms_model_htp.so
echo "✓ QNN 编译完成"
4.4 部署到设备
cpp
/**
* QNN C++ 部署示例
*
* 环境:Android NDK r25, QNN SDK 2.20+
* 编译:ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk
*/
#include <iostream>
#include <vector>
#include <chrono>
#include "QnnInterface.hpp"
#include "QnnContext.hpp"
#include "PAL/Stream.hpp"
class DMSInference {
private:
QnnContextHandle_t context_;
QnnGraphHandle_t graph_;
QnnBackendHandle_t backend_;
public:
/**
* 初始化模型
*
* @param model_path QNN Context Binary 路径 (.so)
* @param device 目标设备 ("cpu", "htp", "gpu")
*/
bool Initialize(const std::string& model_path, const std::string& device) {
// 1. 加载后端
if (device == "htp") {
// Hexagon NPU
backend_ = LoadBackend("QnnHtp.so");
} else if (device == "gpu") {
// Adreno GPU
backend_ = LoadBackend("QnnGpu.so");
} else {
// CPU
backend_ = LoadBackend("QnnCpu.so");
}
if (!backend_) {
std::cerr << "✗ 后端加载失败" << std::endl;
return false;
}
// 2. 创建上下文
QnnContextCreate(&context_);
// 3. 加载模型
QnnContextBinaryLoad(context_, model_path.c_str());
// 4. 获取图句柄
QnnGraphCreate(context_, "dms_model", &graph_);
std::cout << "✓ 模型加载成功: " << model_path << std::endl;
return true;
}
/**
* 推理
*
* @param input_data 输入数据(RGB 图像,归一化)
* @param input_shape 输入形状 (N, C, H, W)
* @param keypoints 输出:关键点坐标
* @param state 输出:状态分类
*/
bool Infer(const float* input_data,
const std::vector<int>& input_shape,
std::vector<float>& keypoints,
std::vector<float>& state) {
// 1. 创建输入张量
QnnTensor_t input_tensor;
input_tensor.id = 0;
input_tensor.type = QNN_TENSOR_TYPE_APP_WRITE;
input_tensor.dataType = QNN_DATATYPE_FLOAT_32;
input_tensor.dimensions = input_shape.data();
input_tensor.rank = input_shape.size();
input_tensor.data = const_cast<float*>(input_data);
// 2. 执行推理
auto start = std::chrono::high_resolution_clock::now();
QnnError_t error = QnnGraphExecute(graph_, &input_tensor, 1);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
if (error != QNN_SUCCESS) {
std::cerr << "✗ 推理失败: " << error << std::endl;
return false;
}
// 3. 获取输出
// (简化实现,实际需要读取输出张量)
keypoints.resize(68); // 34 关键点 × 2
state.resize(3);
std::cout << "✓ 推理成功,耗时: " << duration.count() << " ms" << std::endl;
return true;
}
/**
* 清理
*/
void Cleanup() {
QnnGraphDestroy(graph_);
QnnContextDestroy(context_);
QnnBackendUnload(backend_);
std::cout << "✓ 资源释放完成" << std::endl;
}
private:
QnnBackendHandle_t LoadBackend(const std::string& backend_name) {
// 加载后端库
// (简化实现,实际需要 dlopen/dlsym)
return reinterpret_cast<QnnBackendHandle_t>(1);
}
};
// 使用示例
int main() {
DMSInference dms;
// 初始化(使用 Hexagon NPU)
if (!dms.Initialize("/data/local/tmp/dms_model_htp.so", "htp")) {
return -1;
}
// 准备输入(模拟)
std::vector<float> input_data(1 * 3 * 224 * 224, 0.5f);
std::vector<int> input_shape = {1, 3, 224, 224};
// 推理
std::vector<float> keypoints, state;
dms.Infer(input_data.data(), input_shape, keypoints, state);
// 清理
dms.Cleanup();
return 0;
}
4.5 性能优化
4.5.1 算子优化
python
"""
算子优化建议
关键:
1. 避免不支持的算子
2. 合并相邻算子
3. 使用 QNN 优化版算子
"""
# ❌ 避免:动态形状算子
class BadModel(nn.Module):
def forward(self, x):
# 动态 reshape(QNN 难以优化)
b, c, h, w = x.shape
x = x.view(b, c, -1)
return x
# ✅ 推荐:静态形状算子
class GoodModel(nn.Module):
def __init__(self):
super().__init__()
self.pool = nn.AdaptiveAvgPool2d(1) # 静态形状
def forward(self, x):
x = self.pool(x)
return x.flatten(1) # 已知形状
4.5.2 量化校准优化
python
def optimize_calibration(model, dataset, num_samples=500):
"""优化校准策略
Args:
model: 原始模型
dataset: 数据集
num_samples: 校准样本数
Returns:
optimal_samples: 最优校准样本索引
"""
from sklearn.cluster import KMeans
# 1. 提取特征
features = []
for i in range(min(len(dataset), 1000)):
sample = dataset[i]
# 提取中间层特征(简化)
feat = model.backbone(sample).flatten().detach().numpy()
features.append(feat)
features = np.array(features)
# 2. 聚类选择代表性样本
kmeans = KMeans(n_clusters=num_samples, random_state=42)
labels = kmeans.fit_predict(features)
# 3. 选择每个聚类的中心样本
optimal_indices = []
for cluster_id in range(num_samples):
cluster_samples = np.where(labels == cluster_id)[0]
if len(cluster_samples) > 0:
optimal_indices.append(cluster_samples[0])
print(f"✓ 选择 {len(optimal_indices)} 个代表性校准样本")
return optimal_indices
4.5.3 性能基准测试
python
import time
import statistics
def benchmark_qnn(model_path, device, num_runs=100):
"""性能基准测试
Args:
model_path: 模型路径
device: 设备 ("cpu", "htp", "gpu")
num_runs: 运行次数
Returns:
stats: 性能统计 dict
"""
dms = DMSInference()
dms.Initialize(model_path, device)
# 准备输入
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
input_shape = [1, 3, 224, 224]
latencies = []
# 预热
for _ in range(10):
dms.Infer(input_data.ctypes.data, input_shape)
# 测量
for _ in range(num_runs):
start = time.perf_counter()
dms.Infer(input_data.ctypes.data, input_shape)
end = time.perf_counter()
latencies.append((end - start) * 1000) # ms
# 统计
stats = {
'mean': statistics.mean(latencies),
'median': statistics.median(latencies),
'p95': sorted(latencies)[int(0.95 * len(latencies))],
'p99': sorted(latencies)[int(0.99 * len(latencies))],
'fps': 1000 / statistics.mean(latencies)
}
print(f"性能统计 ({device}):")
print(f" 平均延迟: {stats['mean']:.2f} ms")
print(f" 中位延迟: {stats['median']:.2f} ms")
print(f" P95 延迟: {stats['p95']:.2f} ms")
print(f" P99 延迟: {stats['p99']:.2f} ms")
print(f" 帧率: {stats['fps']:.1f} fps")
dms.Cleanup()
return stats
4.6 检查清单
| 检查项 | 要求 | 验证方法 |
|---|---|---|
| 算子兼容性 | 所有算子支持 INT8 | qnn-op-validation |
| 量化精度 | 损失 < 3% | 在验证集对比 FP32/INT8 |
| 内存占用 | 运行时内存 < 500MB | adb shell dumpsys meminfo |
| 启动时间 | 模型加载 < 1s | 计时测量 |
| 推理延迟 | 单帧 < 40ms | 基准测试 |
| 功耗 | 推理功耗 < 2W | adb shell cat /sys/class/power_supply/... |
| 稳定性 | 连续运行 24h 无崩溃 | 长期测试 |
4.7 常见问题
- 算子不支持
问题:QNN Error: Unsupported operator: LayerNorm
解决:
python
# 方法 1:替换为支持算子
# LayerNorm -> BatchNorm + Elementwise
class LayerNormReplacement(nn.Module):
def __init__(self, normalized_shape):
super().__init__()
self.bn = nn.BatchNorm2d(normalized_shape)
def forward(self, x):
return self.bn(x)
# 方法 2:等待 QNN SDK 更新(关注 Release Note)
- 量化精度下降严重
问题:INT8 量化后精度下降 > 10%
解决:
python
# 1. 增加校准样本
# 100 -> 500
# 2. 使用按通道量化
quantize_static(..., per_channel=True)
# 3. 敏感层保持 FP32
# 找出敏感层:逐层量化测试
sensitive_layers = identify_sensitive_layers(model)
# 在量化时跳过这些层
- 内存不足
问题:RuntimeError: Failed to allocate memory
解决:
python
# 1. 模型切片(Model Split)
# 将大模型拆分为多个小模型
# 2. 降低输入分辨率
# 224x224 -> 160x160
# 3. 使用更激进的量化
# INT8 -> INT4(精度损失更大)
5. QNN和SNPE转化流程
5.1 QNN部署流程
参考4-8&14-16
5.2 SNPE部署流程
参考9-13&14-16