大模型推理-高通qnn基础
一、高通ai 软件的介绍
Qualcomm®AI Engine Direct SDK(qnn)
提供较低级别、高度可定制的统一API,通过单独的库加速所有AI加速器核心上的AI模型, 可以直接用于针对特定的加速器核心或从流行的运行时(包括Qualcomm Neural Processing SDK、TensorFlow Lite和ONNX运行时)委派工作负载。
Qualcomm 神经处理SDK
一个支持异构计算、系统级配置的一体化SDK,旨在将AI工作负载引导到我们平台上的所有加速器核心。为开发人员提供灵活性,包括核心间协作支持和其他高级功能。
AIMET- AI模型效率工具包
是一个为训练好的神经网络模型提供高级量化和压缩技术的库。
Qualcomm AI Hub
Qualcomm于2024年2月发布 平台,旨在为开发者提供优化和验证过的AI模型,以便在Snapdragon®和Qualcomm®设备上运行。这个平台不仅支持移动计算、汽车、物联网等多个领域,还提供一个强大的模型库,截至目前为止包括Whisper、ControlNet、Stable Diffusion和Baichuan 7B等超过109个热门AI模型,这些模型覆盖了视觉、音频和语音应用,并且已经在Qualcomm AI Hub、GitHub和Hugging Face上提供。
本次主要是介绍QNN SDK.
QNN SDK 可以将模型文件(例如从 pytorch 构建)转换成高通的指定格式文件,然后可以在高通的多种各种设备处理器(CPU、GPU、DSP、HTP 或 LPAI)上运行我们转换好的模型。
先看下高通的软件整体架构图。
可以看到支持很多的后端CPU, GPU, HTP, cDSP, HTA,这些后端在QNN中都支持模型的运行。
这个是QNN的软件架构
device设备
硬件加速器平台的软件抽象。提供关联首选硬件加速器资源以执行用户组合图所需的所有构造。平台可能分为多个设备。设备可能有多个核心。基于不同的硬件后端,qnn 提供统一的接口实现host和device之间的操作。支持的设备类型有windows,linux, android, qnx(台架)。
Backend 后端
后端是一个顶级 API 组件,它托管和管理图形组合和执行所需的大部分后端资源,包括存储所有可用操作的操作注册表。
Context 上下文
表示维持用户应用程序所需的所有 Qualcomm® AI Engine Direct 组件的构造。托管用户提供的网络并允许将构造的实体缓存到序列化对象中以供将来使用。它通过提供可共享的内存空间来实现多个图形之间的互操作性,在该空间中,可以在图形之间交换张量。
Graph 图形
Qualcomm® AI Engine Direct 表示可加载网络模型的方式。由表示操作的节点和将它们互连以组成有向无环图的张量组成。 Qualcomm® AI Engine Direct 图形构造支持执行网络模型初始化、优化和执行的 API。
Operation Package registry 操作包注册表
维护可用于执行模型的所有操作记录的注册表。这些操作可以是内置的,也可以由用户作为自定义操作提供。
二、QNN详解
2.1 整体流程图
这是qnn sdk 将一个模型加载到运行的流程图。那从图中可以知道,首先我们需要离线的将模型转换成qnn可以运行的文件,这个转换过程可以在windows 或者linux(wsl也可以)进行模型的离线转换。
2.2 软件包的构成
Qnn的软件包下载路径,这里面可以下载各个版本的qnn-sdk.
https://code.qualcomm.com/nextev-usa-inc/automotive-ai-sdk-for-qualcomm-neural-networks-zip-2-0_qti-tools_oem/tree/r23104.2
先看下解压的sdk的目录构成,
2.2.1 SDK的目录构成
Benchmarks: 这个是测试模型的性能数据的库。
Bin: 这里面主要是qnn的Android, linux, windows各个平台,各个高通后端cpu, gpu, npu等平台的可执行文件,主要是执行进行模型的转换,量化等一系列操作。
Docs: 主要是sdk的说明文档。
Example: 这里主要是实现的例子,转换模型,数据处理,模型运行的时候可以参考
Include: 主要是各个头文件,高通没有开放源码,只是提供了头文件,方便接口查看。
Lib: 这个主要是各个so 文件,在转换,执行时需要的库文件。
share: 这个应该是一些共享的文件之类。
2.2.2 bin文件夹
在 x86 的 linux 端可以看到其提供的相关功能,在 bin/x86_64-linux-clang 如下所示,可以根据自己平台决定如何使用, android, Windows等是类似的结构。
#模型编译和运行
qnn-net-run #用于运行 QNN 模型,并测试其推理性能
qnn-model-lib-generator # 用于生成 QNN 兼容的模型库(.so 文件)
qnn-throughput-net-run #类似 qnn-net-run,但专注于吞吐量测试
#调试 & 解析
qnn-architecture-checker # 检查模型是否适用于 QNN 硬件架构
qnn-profile-viewer #分析 QNN 运行时的性能数据
qnn-platform-validator #验证 QNN 运行环境是否正确配置
#模型格式转换
qnn-onnx-converter 将 ONNX 模型转换为 QNN 兼容格式。
qnn-tflite-converter 转换 TensorFlow Lite (TFLite) 模型为 QNN 兼容格式。
qnn-tensorflow-converter
qnn-pytorch-converter
#模型量化 & 解析
qnn-quantization-checker #检查模型的量化信息,确保适用于 QNN 运行环境
qnn-context-binary-generator #生成 QNN 运行时所需的 context 文件
qnn-context-binary-utility #解析和管理 QNN context 二进制文件
#精度分析
qnn-accuracy-debugger # 调试和分析模型精度问题
qnn-accuracy-evaluator #评估 QNN 模型的精度
qnn-hypertuner #用于调整 QNN 运行参数,以优化性能
#其他
qnn-netron 可视化 QNN 计算图(类似 Netron)
qnn-rpc-server #用于远程调用 QNN 运行时
qnn-op-package-generator # 用于自定义算子(Op)封装
# Hexagon DSP(高通的专用 DSP 处理器)相关
hexagon-llvm-objcopy # 用于处理 Hexagon 目标文件
hexagon-llvm-objdump #用于处理 Hexagon 目标文件
hexagon-readelf #用于查看 Hexagon ELF 文件的信息
2.3 模型转换
这个是模型转换的实现整体流程示意图。
qnn_core_api关键的类与关系如下:
QnnModel:该类类似于给定上下文中的 QnnGraph 及其张量。初始化时应提供上下文,并在其中创建新的 QnnGraph。有关这些类 API 的更多详细信息,请参阅 QnnModel.hpp、QnnWrapperUtils.hpp
GraphConfigInfo:此结构用于从客户端传递 QNN 图配置列表(如果适用)。有关可用图配置选项的详细信息,请参阅 QnnGraph API。
GraphInfo:此结构用于将构造的图及其输入和输出张量传达给客户端。
QnnModel_composeGraphs:负责使用 QnnModel 类在提供的 QNN 后端上构造qnn 图。它将通过 graphsInfo 返回构造的图。
QnnModel_freeGraphsInfo:仅在不再使用图时才应调用。
2.4 qnn 的后端
Qnn 支持的后端设备有CPU, GPU, HTP, DSP,HTA, (HTP MCP 支持在linux 和qnx 上运行)
QNN HTP 支持在所有 Qualcomm SoC 上运行量化 8 位和量化 16 位网络。QNN HTP Quant 运行时支持的操作列表可在“支持的操作”中的“后端支持 HTP”列下查看,QNN HTP 支持在部分 Qualcomm SoC 上使用 float16 数学运行 float32 网络。
QNN 仅支持每个进程独享一个后端库。这些库必须是相同的 SDK 版本,并且具有匹配的 Hexagon 架构版本。为了便于说明,图表中将使用 V73 Hexagon 架构库;非 V73 工件也适用相同的准则。QNN 支持的库布局如下所示。
为防止 QNN 库布局不正确,Qualcomm 建议如下:
-
每个库的一份副本应存在于单个进程(后端、接口、骨架等)中。
-
后端库 (libQnnHtp.so) 应使用 dlopen 明确加载,而不是作为依赖项动态链接。
-
库应彼此位于同一目录中(只要正确设置 ADSP_LIBRARY_PATH 以查找库,stub就是例外)。
-
不要重命名库以加载多个副本,因为不支持此操作。
QNN 不支持在单个进程中访问 QNN 后端库 (libQnnHtp.so) 的多个副本。下面描述了两种不同的布局,其中设备上存在多个后端库。
对于要加载的两个后端库,要么明确地从与第一个库所在目录不同的目录中加载库的第二个副本,要么在进程执行期间创建重复的文件系统(对于 Android 目标,则为 adb remount)。无论哪种情况,都不支持上面显示的两种工件布局。
2.5 专业名称解释
cdsp:
高通CDSP(Compute Digital Signal Processor)是高通Snapdragon平台上专门用来做通用计算的硬件加速单元,与主机CPU相比,它通常以较低的时钟速度运行,并提供更多的并行指令级。这使得DSP在功耗方面成为CPU的更好替代品。因此,将尽可能多的大型计算密集型任务移植到DSP上,可以降低设备的整体功耗。
CDSP的主要用途包括摄像头、视频的图像增强相关处理,计算机视觉、增强、虚拟现实处理,以及深度学习硬件加速。在SOC架构中,CDSP位于红色框内。CDSP的硬件架构包括Hexagon Scalar Core,其中包含4个或更多的DSP硬件线程,每个DSP硬件线程都可以访问Hexagon标量单元。此外,Qualcomm Hexagon Vector eXtensions-HVX(“Hexagon矢量扩展”)是Hexagon 680 DSP的典型特性,能够在执行图像处理应用中的计算负载中发挥重要作用,比如虚拟现实、增强现实、图像处理、视频处理、计算视觉等。
高通SoC的AI加速硬件都是基于DSP的,指令方面则是SIMD扩展。高通自2013年导入DSP加速,一直沿用至今。DSP是通用的加速器,可以对应标量(Scalar)、向量(Vector)和张量(Tensor)。而我们所说的AI芯片一般只对应张量。标量运算一般对应的是CPU,通常是串行数据。向量和张量对应的并行数据计算,传统的CPU不太胜任。
HVX(Hexagon Vector eXtensions)
高通在第六代Hexagon DSP中引入的一种矢量处理功能,它为标量DSP单元添加了128字节的矢量处理功能,即在HVX编程的时候很多处理都要128对齐。标量硬件线程通过访问HVX寄存器文件(也称为HVX上下文)来使用HVX协处理器。HVX意味着你可以将视频和摄像机任务从CPU转移到Hexagon DSP,以实现低功耗的快速图像处理。长期以来,用于宽矢量处理的新型Hexagon VX内核一直很吸引人的用例,因为它们消耗大量电能,因此可以从CPU上卸载计算机视觉(CV)和视频。借助HVX内核,Hexagon的设计师增加了宽矢量处理,以实现更高的性能和更低的功耗。
HMX(Hexagon Matrix eXtensions)
是高通HTP(Hexagon Tensor Processor)中的一部分,作为HTP上专门用于深度学习MAC计算的硬件模块暂时不对客户开放进行可编程。HMX是高通继续改进HTA(Hexagon Tensor Accelerator),改进为HTP时加入的,所谓HTP就是加入了HMX即Hexagon Matrix eXtensions,仍然是基于标量DSP而加入的协处理器。
三、模型精度和量化
3.1 量化模式
非量化模型文件使用 32 位浮点表示模型权重参数。量化模型文件使用定点表示网络参数,通常为 8 位权重和 8 位或 32 位的偏置。定点表示与 Tensorflow 量化模型中使用的相同。
在量化或非量化模型之间进行选择:
GPU - 选择非量化模型。量化模型目前与 GPU 后端不兼容。
DSP - 选择量化模型。在 DSP 后端运行时需要量化模型。
HTP - 选择量化模型。在 HTP 后端运行时需要量化模型。
HTA - 选择量化模型。在 HTA 后端运行时需要量化模型。以下是图片中的文字内容:
对高通SM8650平台的AI Engine中各设备支持的数据精度如下:
CPU: INT8, FP32
GPU: FP16, FP32
HTP: INT4, INT8, INT16(W8A16/W16A16), FP16, 混合精度(INT4-INT8-INT16-FP16)
在量化方面,QNN对模型使用的是定点数(Fixed-point)量化,对weight可使用4bit/8bit量化,对activation可使用8bit/16bit量化,对bias可使用8bit/32bit量化(注意bias不支持16bit)。
注意:定点数据类型在DSP领域很常用,包含了整数部分和小数部分(都用INT来表示),用一部分bit表示整数部分,一部分表示小数部分,因此并不等同于INT数据类型,也不同于FLOAT数据类型。
这个是8bit的量化示意图
针对数据差异,建议权重做对称量化,激活输入做非对称量化,可以获取更好的效果。
原因:在神经网络模型的量化过程中,常采用对权重量化使用对称量化,而对激活输入量化使用非对称量化的策略。这种组合方式有助于在模型精度和计算效率之间取得平衡。
对称量化是指将数据的正负范围以零为中心进行均匀量化,适用于数据分布接近对称的情况。模型的权重通常呈现对称分布,因此对称量化能够有效地减少量化误差,并简化计算复杂度。
非对称量化允许数据在非对称范围内进行量化,适用于数据分布偏移的情况。激活输入的数据分布往往是不对称的,可能存在偏移。采用非对称量化可以更准确地捕捉这些偏移,减少量化引入的误差,从而提高模型的精度。然而,非对称量化需要额外存储零点信息,并在推理时进行相应的计算。
对于qnn 自身的量化能力其实是比较弱的,想要得到更好的量化效果,可以使用AIMET,利用aimet得到的encodings 文件,作为qnn-onnx-converter的输入,可以得到更好的量化效果。
当精度值设置为 QNN_PRECISION_FLOAT16 时,QNN HTP 后端会将 QnnGraph_execute() 中用户提供的 float32 输入转换为 float16,并使用 float16 数学运算执行图形。最终输出以 float32 输出结果。
注意QNN HTP 不支持将精度值设置为 QNN_PRECISION_FLOAT32, 如果未设置精度值,QNN HTP 后端会假定客户端希望运行量化网络。
3.2 量化注意要点
-
如果不配置量化的算法和精度,默认使用的min-max 量化方法,采用的是W8A8的量化精度。
-
可以在配置文件中针对某些算子进行数据类型的设置,和量化位数的设置,没有设置的算子就是默认使用fp32数据类型和默认的量化位数进行量化。
3. converter模型需要加上--input_list.txt 这个文件是指定量化数据的路径,得到的就是量化模型。不指定这个参数就是得到非量化模型。
4. 当在converter的时候,如果在coverter的命令行中指定了–act_bw/–weight_bw/–bias_bw, 那么配置文件中的配置不会生效,以显式指定的这个参数为准。
5. Qnn支持混合精度的量化,类似下面的这个示意图:
6. 当加上converter的时候, 加上–float_bw 16时,所有的权重和激活数据类型都会被设置成fp16, 配置文件中的数据类型会被忽略,也就是不进行模型量化。
7. 量化精度支持到0.0001
8. 支持的量化方式有 tf(非对称量化), symmetric, enhanced(对称量化), and tf adjusted(非对称量化),量化方法都是min_max的计算方式。
9. 量化参数设置:
偏置参数设置--bias_bw 8(32), --act_bw 8 (16), --weight_bw 8.(这个是qnn sdk 2.12 的参数说明,不同版本可能会有差异。)
四、模型Benchmarking
1. 说明
提供了脚本qnn_bench.py 进行模型运行性能的测试。能快速比较不同模型的整体性能,对比 CPU vs GPU vs DSP 后端的整体推理时间,比较不同 batch size、量化方式的性能差异。从而可以知道各个配置的性能结果。
Profiling主要是性能分析,知道每一个模型,每一层网络结果,每一个算子的性能数据,为性能分析提供详实的数据。benchmark 主要是获取模型的整体数性能数据。
usage: qnn_bench.py [-h] -c CONFIG_FILE [-o OUTPUT_BASE_DIR_OVERRIDE][-v DEVICE_ID_OVERRIDE] [-r HOST_NAME][-t DEVICE_OS_TYPE_OVERRIDE] [-d] [-s SLEEP][-n ITERATIONS] [-p PERFPROFILE][--backend_config BACKEND_CONFIG] [-l PROFILINGLEVEL][-json] [-be BACKEND [BACKEND ...]] [--htp_serialized][--dsp_type {v65,v66,v68,v69,v73}][--arm_prepare] [--use_signed_skel] [--discard_output][--test_duration TEST_DURATION] [--enable_cache][--shared_buffer] [--clean_artifacts] [--cdsp_id {0,1}]Run the qnn_benchrequired arguments:-c CONFIG_FILE, --config_file CONFIG_FILEPath to a valid config fileRefer to sample config file config_help.json present at <SDK_ROOT>/benchmarks/QNN/to know details on how to fill parameters in config fileoptional arguments:-o OUTPUT_BASE_DIR_OVERRIDE, --output_base_dir_override OUTPUT_BASE_DIR_OVERRIDESets the output base directory.-v DEVICE_ID_OVERRIDE, --device_id_override DEVICE_ID_OVERRIDEUse this device ID instead of the one supplied in config file.-r HOST_NAME, --host_name HOST_NAMEHostname/IP of remote machine to which devices are connected.-t DEVICE_OS_TYPE_OVERRIDE, --device_os_type_override DEVICE_OS_TYPE_OVERRIDESpecify the target OS type, valid options are['aarch64-android', 'aarch64-windows-msvc', 'aarch64-qnx','aarch64-oe-linux-gcc9.3', 'aarch64-oe-linux-gcc8.2', 'aarch64-ubuntu-gcc7.5']-d, --debug Set to turn on debug log-s SLEEP, --sleep SLEEPSet number of seconds to sleep between runs e.g. 20 seconds-n ITERATIONS, --iterations ITERATIONSSet the number of iterations to execute for calculatin
2. 这个是qnx的配置文件示例:
{"Name":"InceptionV3","HostRootPath": "inception_v3.repo","HostResultsDir":"inception_v3.repo/results","DevicePath":"/data/local/tmp/qnnbm.repo","Devices":["e9b27753"],"Runs":3,"Model": {"Name": "INCEPTION_V3","qnn_model": "../../examples/Models/InceptionV3/model_libs/aarch64-qnx/libInception_v3.so","InputList": "../../examples/Models/InceptionV3/data/target_raw_list.txt","Data": ["../../examples/Models/InceptionV3/data/cropped"]},"Backends":["CPU"],"Measurements": ["timing"]}
3. 运行示例
Running inceptionV3_sample.json (CPU backend)
cd $QNN_SDK_ROOT/benchmarks/QNN
python3.6 qnn_bench.py -c inceptionV3_sample.json# Running inceptionV3_quantized_sample.json (HTP backend by generating context binary on X86)
cd $QNN_SDK_ROOT/benchmarks/QNN
python3.6 qnn_bench.py -c inceptionV3_quantized_sample.json --dsp_type v68 --htp_serialized