Build and run the QNN sample app
Theqnn-sample-app is located at ${QNN_SDK_ROOT}/examples/QNN/SampleApp,
where QNN_SDK_ROOT refers to the path where the QNN SDK has been extracted.
Set up the QAIRT SDK
To set up the toolchain for the QNN sample app, complete the following steps:- Download the Qualcomm AI Runtime SDK.
-
Extract and unzip the SDK.
-
Install the SDK.
Follow Build QIM SDK to install the SDK, which contains the
required cross-compiler toolchain.
- The libraries are compiled with GCC-11.2.
- Set the SDK_PATH environment variable with SDK installation path. Later steps use the installation path (/path/to/extracted/toolchain) for the compilation.
Build the QNN sample app
Complete the following steps to build the QNN sample app.-
Go to the sample app directory.
-
Set the environment variable for the GCC toolchain.
-
Build the application.
This creates two folders.
bin: Containsqnn-sample-appbinaries for each platform within their respective directories.obj: Contains all object files used in building and linking the executable.
Run the QNN sample app on Linux (Yocto-based)
The builtqnn-sample-app executable can run a model with any
QNN backend. For Yocto scarthgap-based devices, backends are available
for aarch64-oe-linux-gcc11.2.
-
Push the artifacts to the target device.
Create the
/etc/apps/directory if it does not already exist on the device. -
On the host computer, use AI Hub to export a model.
For example, to export the InceptionV3 QNN model, run the following commands:
Generate the context binary for the same SDK version in use on the target device.
-
Push the exported InceptionV3 QNN model to the target device.
Save the model to
export_assets/inception_v3-qnn_context_binary-w8a8-<CHIPSET>. The following example usesQCS6490as the chipset.When prompted to enter the password, enteroelinux123. -
On the host computer, generate a dummy input file for inference and transfer it to the target device.
a. Run the following commands in the Python environment.
b. Transfer the
input.rawfile to the target device: -
From the host computer, SSH into the target device.
-
Create
input_list.txt. -
Run the app.
For help context, run:Update the model name and input_list as per the selected model.
Command line arguments
Required arguments--model: Path to the QNN network model. Mutually exclusive with--retrieve_context.--retrieve_context: Path to a cached binary for loading a saved context and execution graphs. Mutually exclusive with--model.--backend: Path to a QNN backend to run the model.--input_list: Path to a file listing network inputs. For multiple graphs, provide a comma-separated list of input files.
--debug: Save output from all network layers.--output_dir: Directory for outputs (default: ./output).--output_data_type: Output data type (float_only, native_only, float_and_native).--input_data_type: Input data type (float or native).--op_packages: Comma-separated list of op packages and interface providers.--profiling_level: Profiling level (basic or detailed).--save_context: Save backend context and graph metadata to a binary file.--num_inferences: Number of inferences to perform.--log_level: Max logging level (error, warn, info, verbose).--system_library: Path to libQnnSystem.so for reflection APIs during context loading.--version: Print QNN SDK version.--help: Display help message.
Workflow and API usage
Use the following recommended pattern to develop C++ applications using QNN APIs.- Load prerequisite shared libraries.
-
Use QNN APIs.
a. Use QNN interface to obtain function pointers.
b. Set up logging.
c. Initialize backend.
d. Initialize profiling.
e. Create device.
f. Register op packages.
g. Create context.
h. Prepare graphs.
i. Finalize graphs.
j. Save context into a binary.
k. Load context from a cached binary.
l. Run graphs.
m. Free context.
n. Terminate backend.
Load prerequisite shared libraries
QNN SDK provides various shared libraries to access backends and applications have to load them as needed to run a network. Create a network in QNN in one of the following ways.- Build the network directly in your application using QNN APIs.
- Use QNN converters to produce a shared library of a QNN network.
qnn-sample-app uses the shared library option. This network can
be produced using one of the QNN converters available in the SDK, and
compiled into a shared library using qnn-model-lib-generator.
For Windows users, replace all
.so files with the analogous
.dll file in the following instructions. For more details, see
platform differences.Loading a backend
Shared libraries for various backends including CPU, GPU, HTP, and DSP are available in the QNN SDK. Every backend that implements QNN APIs exposes all necessary symbols that can be accessed using dynamic loading mechanism. Consider a sample backend shared library named libQnnSampleBackend.so, which can be dynamically loaded as shown below:Resolving symbols in shared libraries
After the shared libraries are successfully loaded, we can proceed to resolve all necessary symbols to access QNN APIs. The below code snippet shows a template to resolve a symbol in a shared library:${QNN_SDK_ROOT}/examples/QNN/SampleApp/SampleApp/src/SampleApp.hpp.
The rest of the tutorial will assume a variable
named m_qnnFunctionPointers of type QnnFunctionPointers that
contains valid function pointers.
Usage of QNN APIs
This section demonstrates the usage of QNN APIs in a client application.Use QNN Interface to obtain function pointers
QNN Interface mechanism can be used to set up a table of function pointers to QNN APIs in the backend instead of manually resolving symbols to each and every API, which makes resolving symbols easy. QNN Interface can be used as below:Set up logging
Logging can be set up before a backed is initialized and after a backend shared library has been dynamically loaded. To initialize logging, a callback of type QnnLog_Callback_t has to be defined. An example is defined below:Initialize backend
Once logging has been successfully initialized, backend can be initialized as shown below:Initialize Profiling
If profiling is desired, after the backend is initialized, a profile handle can be set up. This profile handle can be used at a later point in any API that supports profiling. A profile handle can be created in the backend with basic profiling level as shown below:Create device
Device can be created as shown below:Register op packages
Op packages are way to supply libraries containing ops to backends. They can be registered as shown below:Create context
A context can be created in a backend as shown below:Prepare graphs
qnn-sample-app relies on the output from one of the converters to create a QNN network in the backend. composeGraphsFnHandle is mapped to QnnModel_composeGraphs API in the model shared library, which takes qnn_wrapper_api::GraphInfo_t*** as one of the parameters. The function composeGraphsFnHandle will make necessary calls to the backend to create a network(s). It also writes all necessary information, like information about input and output tensors related to the graph, required to execute a graph into the structure graphsInfo as shown in the following code block:Finalize Graphs
Graphs that were added in the previous step can be finalized as shown below:Save context into a binary
After all the graphs in a context are finalized, the user application may choose to save the context into a binary for future use. The advantage of saving a context is that it can be retrieved in the future for execution of graphs contained within it without having to finalize them again. This will save considerable time for initialization during execution of a network. The context can be saved as shown below:Load context from a cached binary
A context that was saved into a binary, like in the previous step, can be loaded as an alternative to creating a new context every time. The code snippet below demonstrates this step:Run graphs
After a context has been created, graphs have been added and finalized, or alternatively, after a context has been retrieved from a binary, one or more graphs in the context can be executed. Running a graph involves:- Setting up input and output tensors.
- Populating input data into input tensors.
- Calling the execute method in the backend.
- Obtaining outputs and saving them.
${QNN_SDK_ROOT}/examples/QNN/SampleApp/SampleApp/src/Utils/IOTensor.cpp.
It exposes a few methods that help with the execution of a graph, which
were used in the previous code snippet:
- setupInputAndOutputTensors to set up structures related to input and output tensors.
- populateInputTensors to copy input data into input tensor structures.
- tearDownInputAndOutputTensors to clean up resources associated with input and output tensors.

