Skip to main content
The Qualcomm AI Runtime (QAIRT) SDK provides C++ APIs for sample application development. Samples are available for both Qualcomm AI Engine Direct (QNN) and Qualcomm Neural Processing Engine SDK (SNPE). The samples help you begin application development. The following instructions describe how to build, run, and navigate the source code. They demonstrate the workflow for utilizing QNN or SNPE APIs to run a model.

Build and run the QNN sample app

The qnn-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:
  1. Download the Qualcomm AI Runtime SDK.
  2. Extract and unzip the SDK.
  3. 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.
  1. Go to the sample app directory.
  2. Set the environment variable for the GCC toolchain.
  3. Build the application.
    This creates two folders.
    • bin: Contains qnn-sample-app binaries 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 built qnn-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.
  1. Push the artifacts to the target device.
    Create the /etc/apps/ directory if it does not already exist on the device.
  2. 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.
  3. 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 uses QCS6490 as the chipset.
    When prompted to enter the password, enter oelinux123.
  4. 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.raw file to the target device:
  5. From the host computer, SSH into the target device.
  6. Create input_list.txt.
  7. Run the app.
    Update the model name and input_list as per the selected model.
    For help context, run:

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.
Optional arguments
  • --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.
  1. Load prerequisite shared libraries.
  2. 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:
To load a model as a shared library, let’s consider a sample model shared library named libQnnSampleModel.so, which can be dynamically loaded as shown below:
Optionally, to create a context from a cached binary and execute graphs, applications can make use of QnnSystem API to retrieve metadata associated with the context. QnnSystem API can be accessed by loading the libQnnSystem.so shared library 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:
The below code snippet shows an example of how to resolve an actual QNN API:
In qnn-sample-app source code, all necessary symbols are resolved and stored in a struct of type QnnFunctionPointers shown below:
The above structure can be found in ${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:
QNN System Interface can be used to resolve all symbols related to QNN System APIs as shown 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:
The above callback can be registered with the backend along with a maximum log level. Sample code to initialize with a max log level of QNN_LOG_LEVEL_INFO:

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:
Set devConfig as defined here in QNN HTP Backend API

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:
At this point, the context will contain all the graphs that were present in libQnnSampleModel.so.

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:
  1. Setting up input and output tensors.
  2. Populating input data into input tensors.
  3. Calling the execute method in the backend.
  4. Obtaining outputs and saving them.
This is demonstrated using the code snippet below:
IOTensor is a utility provided with the source code at ${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:
  1. setupInputAndOutputTensors to set up structures related to input and output tensors.
  2. populateInputTensors to copy input data into input tensor structures.
  3. tearDownInputAndOutputTensors to clean up resources associated with input and output tensors.
See the IOTensor source code for more details about these APIs.

Free context

After all the execution is completed, the context can be freed as shown below:

Terminate backend

Backend can be terminated as shown below:

SNPE sample app

For C++ API and sample app execution using SNPE, see the Qualcomm AI Runtime SDK documentation.