Skip to main content
Qualcomm AI Runtime(QAIRT)SDK 提供用于示例应用开发的 C++ API。 Qualcomm AI Engine Direct(QNN)和 Qualcomm 神经处理引擎 SDK(SNPE)都提供了示例。这些示例可帮助您开始应用 开发。以下说明介绍如何构建、运行和浏览 源代码,并演示使用 QNN 或 SNPE API 运行模型的工作流程。

构建并运行 QNN 示例应用

qnn-sample-app 位于 ${QNN_SDK_ROOT}/examples/QNN/SampleApp, 其中 QNN_SDK_ROOT 指 QNN SDK 解压后的路径。

设置 QAIRT SDK

要为 QNN 示例应用设置工具链,请完成以下步骤:
  1. 下载 Qualcomm AI Runtime SDK
  2. 解压 SDK。
  3. 安装 SDK。 按照构建 QIM SDK 安装 SDK,其中包含 所需的交叉编译工具链。
    • 这些库使用 GCC-11.2 编译。
    • 使用 SDK 安装路径设置 SDK_PATH 环境变量。后续步骤将使用该安装路径(/path/to/extracted/toolchain)进行编译。

构建 QNN 示例应用

完成以下步骤以构建 QNN 示例应用。
  1. 进入示例应用目录。
  2. 为 GCC 工具链设置环境变量。
  3. 构建应用。
    此操作会创建两个文件夹。
    • bin:包含各平台的 qnn-sample-app 二进制文件,分别位于各自的目录中。
    • obj:包含构建和链接可执行文件所用的所有目标文件。

在 Linux(基于 Yocto)上运行 QNN 示例应用

构建好的 qnn-sample-app 可执行文件可以使用任意 QNN 后端运行模型。对于基于 Yocto scarthgap 的设备,可使用 aarch64-oe-linux-gcc11.2 的后端。
  1. 将产物推送到目标设备。
    如果设备上尚不存在 /etc/apps/ 目录,请先创建它。
  2. 在主机上,使用 AI Hub 导出模型。 例如,要导出 InceptionV3 QNN 模型,请运行以下命令:
    请为目标设备上正在使用的相同 SDK 版本生成上下文二进制文件。
  3. 将导出的 InceptionV3 QNN 模型推送到目标设备。 将模型保存到 export_assets/inception_v3-qnn_context_binary-w8a8-<CHIPSET>。以下示例使用 QCS6490 作为芯片组。
    当提示输入密码时,输入 oelinux123
  4. 在主机上生成用于推理的虚拟输入文件,并将其传输到目标设备。 a. 在 Python 环境中运行以下命令。
    b. 将 input.raw 文件传输到目标设备:
  5. 从主机通过 SSH 连接到目标设备。
  6. 创建 input_list.txt
  7. 运行应用。
    请根据所选模型更新模型名称和 input_list。
    要查看帮助信息,请运行:

命令行参数

必需参数
  • --model:QNN 网络模型的路径。与 --retrieve_context 互斥。
  • --retrieve_context:缓存二进制文件的路径,用于加载已保存的 上下文和执行图。与 --model 互斥。
  • --backend:用于运行模型的 QNN 后端的路径。
  • --input_list:列出网络输入的文件路径。对于多个 图,请提供以逗号分隔的输入文件列表。
可选参数
  • --debug:保存所有网络层的输出。
  • --output_dir:输出目录(默认:./output)。
  • --output_data_type:输出数据类型(float_only、native_only、float_and_native)。
  • --input_data_type:输入数据类型(float 或 native)。
  • --op_packages:以逗号分隔的算子包和接口提供者列表。
  • --profiling_level:性能分析级别(basic 或 detailed)。
  • --save_context:将后端上下文和图元数据保存到二进制文件。
  • --num_inferences:要执行的推理次数。
  • --log_level:最高日志级别(error、warn、info、verbose)。
  • --system_library:libQnnSystem.so 的路径,用于上下文加载期间的反射 API。
  • --version:打印 QNN SDK 版本。
  • --help:显示帮助信息。

工作流程与 API 使用

使用以下推荐模式来开发使用 QNN API 的 C++ 应用。
  1. 加载必备的共享库。
  2. 使用 QNN API。 a. 使用 QNN 接口获取函数指针。
    b. 设置日志记录。
    c. 初始化后端。
    d. 初始化性能分析。
    e. 创建设备。
    f. 注册算子包。
    g. 创建上下文。
    h. 准备图。
    i. 最终化图。
    j. 将上下文保存为二进制文件。
    k. 从缓存的二进制文件加载上下文。
    l. 运行图。
    m. 释放上下文。
    n. 终止后端。

加载必备的共享库

QNN SDK 提供多种共享库以访问后端, 应用需要按需加载它们才能运行网络。 可通过以下方式之一在 QNN 中创建网络。
  • 在应用中直接使用 QNN API 构建网络。
  • 使用 QNN 转换器生成 QNN 网络的共享库。
qnn-sample-app 使用共享库方式。该网络可以 使用 SDK 中提供的某个 QNN 转换器生成,并 使用 qnn-model-lib-generator 编译为共享库。
对于 Windows 用户,在以下说明中请将所有 .so 文件替换为对应的 .dll 文件。有关更多详细信息,请参见 平台差异。

加载后端

QNN SDK 中提供了包括 CPU、GPU、HTP 和 DSP 在内的 各种后端的共享库。每个实现 QNN API 的后端都会 公开所有必要的符号,这些符号可以通过动态加载 机制访问。 以名为 libQnnSampleBackend.so 的示例后端共享库 为例,可按如下方式动态加载:
要以共享库形式加载模型,我们以名为 libQnnSampleModel.so 的示例模型 共享库为例,可按如下方式动态 加载:
可选地,为了从缓存的二进制文件创建上下文并执行图, 应用可以使用 QnnSystem API 来检索与上下文关联的 元数据。QnnSystem API 可以通过加载 libQnnSystem.so 共享库来访问,如下所示:

解析共享库中的符号

共享库成功加载后,我们即可继续 解析访问 QNN API 所需的全部符号。 以下代码片段展示了在共享库中解析符号的 模板:
以下代码片段展示了如何解析实际 QNN API 的示例:
qnn-sample-app 源代码中,所有必要的符号都被解析并 存储在如下所示的 QnnFunctionPointers 类型的结构体中:
上述结构体可以在 ${QNN_SDK_ROOT}/examples/QNN/SampleApp/SampleApp/src/SampleApp.hpp 中找到。 本教程的其余部分将假定存在一个 名为 m_qnnFunctionPointersQnnFunctionPointers 类型变量, 其中包含有效的函数指针。

QNN API 的用法

本节演示在客户端应用中使用 QNN API。

使用 QNN Interface 获取函数指针

可以使用 QNN Interface 机制来建立指向后端中 QNN API 的函数 指针表,而不必逐个手动解析每个 API 的 符号,这使符号解析变得简单。QNN Interface 的使用方式如下:
可以使用 QNN System Interface 来解析与 QNN System API 相关的所有符号,如下所示:

设置日志记录

日志记录可以在后端初始化之前、后端 共享库动态加载完成之后进行设置。 要初始化日志记录,必须定义一个 QnnLog_Callback_t 类型的 回调。示例定义如下:
上述回调可以与最高日志级别一起注册到后端。以下是以 QNN_LOG_LEVEL_INFO 作为最高日志级别进行初始化的示例代码:

初始化后端

日志记录成功初始化后,即可按如下方式初始化 后端:

初始化性能分析

如果需要性能分析,在后端初始化后可以设置性能分析 句柄。该性能分析句柄可在之后 用于任何支持性能分析的 API。 可以在后端以 basic 性能分析级别创建性能分析句柄, 如下所示:

创建设备

可以按如下方式创建设备:
按照 QNN HTP Backend API 中的定义设置 devConfig

注册算子包

算子包(op package)是向后端提供包含算子的库的方式。它们 可按如下方式注册:

创建上下文

可以按如下方式在后端中创建上下文:

准备图

qnn-sample-app 依赖某个转换器的输出在后端 创建 QNN 网络。composeGraphsFnHandle 映射到 模型共享库中的 QnnModel_composeGraphs API,该 API 将 qnn_wrapper_api::GraphInfo_t*** 作为参数之一。 函数 composeGraphsFnHandle 会对后端进行必要的调用 以创建网络。它还会将执行图所需的所有必要 信息(例如与图相关的输入和输出张量的 信息)写入结构体 graphsInfo,如以下代码块所示:
此时,上下文将包含 libQnnSampleModel.so 中存在的 所有图。

最终化图

可以按如下方式对上一步中添加的图进行最终化 处理:

将上下文保存为二进制文件

当上下文中的所有图都最终化后,用户应用 可以选择将上下文保存为二进制文件以供将来使用。保存上下文的 优势在于,将来可以直接检索该上下文来执行其中包含的图, 而无需再次进行最终化处理。这将在执行网络时 为初始化节省大量时间。 可以按如下方式保存上下文:

从缓存的二进制文件加载上下文

与上一步类似,已保存为二进制文件的上下文可以 被加载,从而避免每次都创建新的上下文。 以下代码片段演示了这一步骤:

运行图

当上下文创建完成、图已添加并最终化, 或从二进制文件中检索到上下文后,即可执行上下文中的一个 或多个图。 运行图包括:
  1. 设置输入和输出张量。
  2. 将输入数据填充到输入张量中。
  3. 调用后端中的执行方法。
  4. 获取输出并保存。
以下代码片段演示了这一过程:
IOTensor 是随源代码提供的实用工具, 位于 ${QNN_SDK_ROOT}/examples/QNN/SampleApp/SampleApp/src/Utils/IOTensor.cpp。 它公开了几个有助于执行图的方法, 这些方法在前面的代码片段中已使用:
  1. setupInputAndOutputTensors 用于设置与输入 和输出张量相关的结构。
  2. populateInputTensors 用于将输入数据复制到输入张量 结构中。
  3. tearDownInputAndOutputTensors 用于清理与 输入和输出张量关联的资源。
有关这些 API 的更多详细信息,请参见 IOTensor 源代码。

释放上下文

所有执行完成后,可以按如下方式释放 上下文:

终止后端

可以按如下方式终止后端:

SNPE 示例应用

有关使用 SNPE 的 C++ API 和示例应用执行,请参见 Qualcomm AI Runtime SDK 文档