Skip to content

Commit

Permalink
[QNN EP] Enable QNN Saver for debugging issues (#17747)
Browse files Browse the repository at this point in the history
### Description
- Enables option to use the QNN Saver backend for dumping QNN API calls
to file.
- Adds logic to read environment variable
`ORT_UNIT_TEST_ENABLE_QNN_SAVER` from QNN EP unit tests. If enabled,
unit tests will use the QNN Saver backend and dump files to
`./saver_output/`.


### Motivation and Context
QNN Saver makes it easier to debug issues when unit tests fail. The
output files generated by QNN Saver can be used to replay the exact QNN
API calls that lead to a specific error condition.

QNN Saver dumps QNN API calls (and weights) to disk.
- saver_output/saver_output.c: C file containing all QNN API calls.
- saver_output/params.bin: binary file containing all
input/output/parameter tensor data provided during tensor creation, op
config validation, and graph execution.

Enabling the QNN Saver backend has 2 note-worthy effects:
  1. All QNN API calls will succeed.
  2. Inference output returns dummy data.
 
Because the output files from QNN Saver are always overwritten, it is
recommended to run individual unit tests via the `--gtest_filter`
command-line option.

Example (linux):
```shell
$ ORT_UNIT_TEST_ENABLE_QNN_SAVER=1 ./onnxruntime_test_all --gtest_filter=QnnHTPBackendTests.Resize_DownSample_Linear_AlignCorners
```
  • Loading branch information
adrianlizarraga authored Oct 3, 2023
1 parent 992f3e4 commit 8e6019a
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 81 deletions.
3 changes: 3 additions & 0 deletions include/onnxruntime/core/session/onnxruntime_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -3597,6 +3597,9 @@ struct OrtApi {
* "rpc_control_latency": QNN RPC control latency.
* "htp_performance_mode": QNN performance mode, options: "burst", "balanced", "default", "high_performance",
* "high_power_saver", "low_balanced", "low_power_saver", "power_saver", "sustained_high_performance". Default to "default".
* "qnn_saver_path": File path to the QNN Saver backend library. If specified, QNN Saver will be enabled and will
* dump QNN API calls to disk for replay/debugging. QNN Saver produces incorrect model inference results and
* may alter model/EP partitioning. Use only for debugging.
*
* SNPE supported keys:
* "runtime": SNPE runtime engine, options: "CPU", "CPU_FLOAT32", "GPU", "GPU_FLOAT32_16_HYBRID", "GPU_FLOAT16",
Expand Down
200 changes: 139 additions & 61 deletions onnxruntime/core/providers/qnn/builder/qnn_backend_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,20 @@ typedef Qnn_ErrorHandle_t (*QnnSystemInterfaceGetProvidersFn_t)(const QnnSystemI

constexpr const char* QNN_PROVIDER = "ORTQNNEP";

static Qnn_Version_t GetQnnInterfaceApiVersion(const QnnInterface_t* qnn_interface) {
return qnn_interface->apiVersion.coreApiVersion;
}

static Qnn_Version_t GetQnnInterfaceApiVersion(const QnnSystemInterface_t* qnn_interface) {
return qnn_interface->systemApiVersion;
}

template <typename F, class T>
Status QnnBackendManager::GetQnnInterfaceProviders(const char* lib_path,
const char* interface_provider_name,
void** backend_lib_handle,
T*** interface_providers,
uint32_t& num_providers) {
Status QnnBackendManager::GetQnnInterfaceProvider(const char* lib_path,
const char* interface_provider_name,
void** backend_lib_handle,
Qnn_Version_t req_version,
T** interface_provider) {
std::string error_msg;
*backend_lib_handle = LoadLib(lib_path,
static_cast<int>(DlOpenFlag::DL_NOW) | static_cast<int>(DlOpenFlag::DL_LOCAL),
Expand All @@ -47,10 +55,36 @@ Status QnnBackendManager::GetQnnInterfaceProviders(const char* lib_path,
GetInterfaceProviders = ResolveSymbol<F>(*backend_lib_handle, interface_provider_name, *logger_);
ORT_RETURN_IF(nullptr == GetInterfaceProviders, "Failed to get QNN providers!");

auto result = GetInterfaceProviders((const T***)interface_providers, &num_providers);
T** interface_providers{nullptr};
uint32_t num_providers{0};

auto result = GetInterfaceProviders((const T***)&interface_providers, &num_providers);
ORT_RETURN_IF((QNN_SUCCESS != result || nullptr == *interface_providers || 0 == num_providers),
"Failed to get QNN providers.");

bool found_valid_interface{false};
for (size_t pIdx = 0; pIdx < num_providers; pIdx++) {
Qnn_Version_t interface_version = GetQnnInterfaceApiVersion(interface_providers[pIdx]);

LOGS_DEFAULT(VERBOSE) << lib_path << " interface version: " << interface_version.major << "."
<< interface_version.minor << "." << interface_version.patch;

// Check the interface's API version against the required version.
// Major versions must match. The interface's minor version must be greater OR equal with a suitable patch version.
if (interface_version.major == req_version.major) {
bool minor_and_patch_version_ok = (interface_version.minor > req_version.minor) ||
(interface_version.minor == req_version.minor &&
interface_version.patch >= req_version.patch);
if (minor_and_patch_version_ok) {
found_valid_interface = true;
*interface_provider = interface_providers[pIdx];
break;
}
}
}

ORT_RETURN_IF_NOT(found_valid_interface, "Unable to find a valid interface for ", lib_path);

return Status::OK();
}

Expand All @@ -76,38 +110,89 @@ void QnnBackendManager::SetQnnBackendType(uint32_t backend_id) {
}

Status QnnBackendManager::LoadBackend() {
QnnInterface_t** interface_providers{nullptr};
uint32_t num_providers{0};
auto rt = GetQnnInterfaceProviders<QnnInterfaceGetProvidersFn_t,
QnnInterface_t>(backend_path_.c_str(),
"QnnInterface_getProviders",
&backend_lib_handle_,
&interface_providers,
num_providers);
QnnInterface_t* backend_interface_provider{nullptr};
auto rt = GetQnnInterfaceProvider<QnnInterfaceGetProvidersFn_t,
QnnInterface_t>(backend_path_.c_str(),
"QnnInterface_getProviders",
&backend_lib_handle_,
{QNN_API_VERSION_MAJOR,
QNN_API_VERSION_MINOR,
QNN_API_VERSION_PATCH},
&backend_interface_provider);
ORT_RETURN_IF_ERROR(rt);
qnn_interface_ = backend_interface_provider->QNN_INTERFACE_VER_NAME;
auto backend_id = backend_interface_provider->backendId;
SetQnnBackendType(backend_id);

bool found_valid_interface{false};
LOGS_DEFAULT(VERBOSE) << "QNN_API_VERSION_MAJOR: " << QNN_API_VERSION_MAJOR
<< " QNN_API_VERSION_MINOR: " << QNN_API_VERSION_MINOR;
for (size_t pIdx = 0; pIdx < num_providers; pIdx++) {
LOGS_DEFAULT(VERBOSE) << "interface_providers major: " << interface_providers[pIdx]->apiVersion.coreApiVersion.major
<< " interface_providers minor: " << interface_providers[pIdx]->apiVersion.coreApiVersion.minor;
if (QNN_API_VERSION_MAJOR == interface_providers[pIdx]->apiVersion.coreApiVersion.major &&
QNN_API_VERSION_MINOR <= interface_providers[pIdx]->apiVersion.coreApiVersion.minor) {
found_valid_interface = true;
qnn_interface_ = interface_providers[pIdx]->QNN_INTERFACE_VER_NAME;
auto backend_id = interface_providers[pIdx]->backendId;
SetQnnBackendType(backend_id);

LOGS_DEFAULT(INFO) << "Found valid interface, version: " << QNN_API_VERSION_MAJOR
<< "." << QNN_API_VERSION_MINOR
<< " backend provider name: " << interface_providers[pIdx]->providerName
<< " backend id: " << backend_id;
break;
Qnn_Version_t backend_interface_version = GetQnnInterfaceApiVersion(backend_interface_provider);
LOGS_DEFAULT(INFO) << "Found valid interface, version: " << backend_interface_version.major
<< "." << backend_interface_version.minor << "." << backend_interface_version.patch
<< " backend provider name: " << backend_interface_provider->providerName
<< " backend id: " << backend_id;

return Status::OK();
}

// Loads the intended backend (e.g., HTP, CPU, etc) to get its type, and then
// sets QNN Saver as the active backend. QNN op builders will still see the intended backend (e.g., HTP)
// as the backend type to ensure they emit the expected QNN API calls.
//
// QNN Saver is a "debugging" backend that serializes all QNN API calls (and weights) into local files.
// This information can be used to debug issues by replaying QNN API calls with another backend.
Status QnnBackendManager::LoadQnnSaverBackend() {
void* backend_lib_handle = nullptr;

// Helper that unloads the intended backend library handle when the `unload_backend_lib` variable
// goes out of scope. Similar to `defer` in other languages.
auto unload_backend_lib = gsl::finally([&] {
if (backend_lib_handle != nullptr) {
auto result = UnloadLib(backend_lib_handle);
if (Status::OK() != result) {
ORT_THROW("Failed to unload backend library.");
}
}
}
});

// Load the intended backend (e.g., HTP, CPU) to ensure it is valid and to get its type.
QnnInterface_t* backend_interface_provider{nullptr};
auto rt = GetQnnInterfaceProvider<QnnInterfaceGetProvidersFn_t,
QnnInterface_t>(backend_path_.c_str(),
"QnnInterface_getProviders",
&backend_lib_handle,
{QNN_API_VERSION_MAJOR,
QNN_API_VERSION_MINOR,
QNN_API_VERSION_PATCH},
&backend_interface_provider);
ORT_RETURN_IF_ERROR(rt);

ORT_RETURN_IF_NOT(found_valid_interface, "Unable to find a valid interface.");
// Set the "intended" backend type so that QNN builders still make the expected QNN API calls.
auto backend_id = backend_interface_provider->backendId;
SetQnnBackendType(backend_id);

// Load the QNN Saver backend and set it as the activate backend.
QnnInterface_t* saver_interface_provider{nullptr};
auto saver_rt = GetQnnInterfaceProvider<QnnInterfaceGetProvidersFn_t,
QnnInterface_t>(qnn_saver_path_.c_str(),
"QnnInterface_getProviders",
&backend_lib_handle_, // NOTE: QNN Saver library handle is set
{QNN_API_VERSION_MAJOR,
QNN_API_VERSION_MINOR,
QNN_API_VERSION_PATCH},
&saver_interface_provider);
ORT_RETURN_IF_ERROR(saver_rt);
qnn_interface_ = saver_interface_provider->QNN_INTERFACE_VER_NAME; // NOTE: QNN Saver will provide the interfaces

Qnn_Version_t backend_interface_version = GetQnnInterfaceApiVersion(backend_interface_provider);
Qnn_Version_t saver_interface_version = GetQnnInterfaceApiVersion(saver_interface_provider);

LOGS_DEFAULT(INFO) << "Using QNN Saver version: " << saver_interface_version.major << "."
<< saver_interface_version.minor << "." << saver_interface_version.patch
<< " provider name : " << saver_interface_provider->providerName;

LOGS_DEFAULT(INFO) << "Intended backend provider name: " << backend_interface_provider->providerName
<< " backend id: " << backend_id
<< " interface version: " << backend_interface_version.major
<< "." << backend_interface_version.minor << "." << backend_interface_version.patch;

return Status::OK();
}
Expand All @@ -120,34 +205,22 @@ Status QnnBackendManager::LoadQnnSystemLib() {
#endif // #ifdef _WIN32
std::filesystem::path lib_file_path(backend_path_.c_str());
std::string sys_file_path(lib_file_path.remove_filename().string() + system_lib_file);
QnnSystemInterface_t** system_interface_providers{nullptr};
uint32_t num_providers = 0;
auto rt = GetQnnInterfaceProviders<QnnSystemInterfaceGetProvidersFn_t,
QnnSystemInterface_t>(sys_file_path.c_str(),
"QnnSystemInterface_getProviders",
&system_lib_handle_,
&system_interface_providers,
num_providers);
QnnSystemInterface_t* system_interface_provider{nullptr};
auto rt = GetQnnInterfaceProvider<QnnSystemInterfaceGetProvidersFn_t,
QnnSystemInterface_t>(sys_file_path.c_str(),
"QnnSystemInterface_getProviders",
&system_lib_handle_,
{QNN_SYSTEM_API_VERSION_MAJOR,
QNN_SYSTEM_API_VERSION_MINOR,
QNN_SYSTEM_API_VERSION_PATCH},
&system_interface_provider);
ORT_RETURN_IF_ERROR(rt);
Qnn_Version_t system_interface_version = GetQnnInterfaceApiVersion(system_interface_provider);
qnn_sys_interface_ = system_interface_provider->QNN_SYSTEM_INTERFACE_VER_NAME;

bool found_valid_interface{false};
for (size_t pIdx = 0; pIdx < num_providers; pIdx++) {
LOGS_DEFAULT(VERBOSE) << "system_interface_providers major: " << system_interface_providers[pIdx]->systemApiVersion.major
<< " system_interface_providers minor: " << system_interface_providers[pIdx]->systemApiVersion.minor;
int64_t systems_version_major = static_cast<int64_t>(system_interface_providers[pIdx]->systemApiVersion.major);
int64_t systems_version_minor = static_cast<int64_t>(system_interface_providers[pIdx]->systemApiVersion.minor);
if (systems_version_major == QNN_SYSTEM_API_VERSION_MAJOR &&
systems_version_minor >= QNN_SYSTEM_API_VERSION_MINOR) {
found_valid_interface = true;
qnn_sys_interface_ = system_interface_providers[pIdx]->QNN_SYSTEM_INTERFACE_VER_NAME;
LOGS_DEFAULT(INFO) << "Found valid system interface, version: " << QNN_API_VERSION_MAJOR
<< "." << QNN_API_VERSION_MINOR
<< " backend provider name: " << system_interface_providers[pIdx]->providerName;
break;
}
}

ORT_RETURN_IF_NOT(found_valid_interface, "Unable to find a valid system interface.");
LOGS_DEFAULT(INFO) << "Found valid system interface, version: " << system_interface_version.major
<< "." << system_interface_version.minor
<< " backend provider name: " << system_interface_provider->providerName;

return Status::OK();
}
Expand Down Expand Up @@ -643,7 +716,12 @@ Status QnnBackendManager::SetupBackend(const logging::Logger& logger, bool load_
return Status::OK();
}

ORT_RETURN_IF_ERROR(LoadBackend());
if (qnn_saver_path_.empty()) {
ORT_RETURN_IF_ERROR(LoadBackend());
} else {
ORT_RETURN_IF_ERROR(LoadQnnSaverBackend());
}

LOGS(logger, VERBOSE) << "LoadBackend succeed.";

if (load_from_cached_context) {
Expand Down
21 changes: 13 additions & 8 deletions onnxruntime/core/providers/qnn/builder/qnn_backend_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ class QnnModel;

class QnnBackendManager {
public:
QnnBackendManager(std::string backend_path,
QnnBackendManager(std::string&& backend_path,
ProfilingLevel profiling_level,
uint32_t rpc_control_latency,
HtpPerformanceMode htp_performance_mode)
HtpPerformanceMode htp_performance_mode,
std::string&& qnn_saver_path)
: backend_path_(backend_path),
profiling_level_(profiling_level),
rpc_control_latency_(rpc_control_latency),
htp_performance_mode_(htp_performance_mode) {
htp_performance_mode_(htp_performance_mode),
qnn_saver_path_(qnn_saver_path) {
}
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(QnnBackendManager);

Expand Down Expand Up @@ -140,6 +142,8 @@ class QnnBackendManager {

Status LoadQnnSystemLib();

Status LoadQnnSaverBackend();

Status UnloadLib(void* handle);

void* LibFunction(void* handle, const char* symbol, std::string& error_msg);
Expand All @@ -155,11 +159,11 @@ class QnnBackendManager {
}

template <typename F, class T>
Status GetQnnInterfaceProviders(const char* lib_path,
const char* interface_provider_name,
void** backend_lib_handle,
T*** interface_providers,
uint32_t& num_providers);
Status GetQnnInterfaceProvider(const char* lib_path,
const char* interface_provider_name,
void** backend_lib_handle,
Qnn_Version_t req_version,
T** interface_provider);

bool IsDevicePropertySupported();

Expand Down Expand Up @@ -210,6 +214,7 @@ class QnnBackendManager {
#ifdef _WIN32
std::set<HMODULE> mod_handles_;
#endif
const std::string qnn_saver_path_;
};

} // namespace qnn
Expand Down
24 changes: 18 additions & 6 deletions onnxruntime/core/providers/qnn/qnn_execution_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ QNNExecutionProvider::QNNExecutionProvider(const ProviderOptions& provider_optio
static const std::string BACKEND_PATH = "backend_path";
auto backend_path_pos = runtime_options_.find(BACKEND_PATH);

std::string backend_path;
if (backend_path_pos != runtime_options_.end()) {
backend_path_ = backend_path_pos->second;
LOGS_DEFAULT(VERBOSE) << "Backend path: " << backend_path_;
backend_path = backend_path_pos->second;
LOGS_DEFAULT(VERBOSE) << "Backend path: " << backend_path;
} else {
LOGS_DEFAULT(ERROR) << "No backend path provided.";
}
Expand All @@ -131,10 +132,21 @@ QNNExecutionProvider::QNNExecutionProvider(const ProviderOptions& provider_optio
ParseHtpPerformanceMode(htp_performance_mode_pos->second);
}

qnn_backend_manager_ = std::make_unique<qnn::QnnBackendManager>(backend_path_,
profiling_level_,
rpc_control_latency_,
htp_performance_mode_);
// Enable use of QNN Saver if the user provides a path the QNN Saver backend library.
static const std::string QNN_SAVER_PATH_KEY = "qnn_saver_path";
std::string qnn_saver_path;
auto qnn_saver_path_pos = runtime_options_.find(QNN_SAVER_PATH_KEY);
if (qnn_saver_path_pos != runtime_options_.end()) {
qnn_saver_path = qnn_saver_path_pos->second;
LOGS_DEFAULT(VERBOSE) << "User specified QNN Saver path: " << qnn_saver_path;
}

qnn_backend_manager_ = std::make_unique<qnn::QnnBackendManager>(
std::move(backend_path),
profiling_level_,
rpc_control_latency_,
htp_performance_mode_,
std::move(qnn_saver_path));
}

bool QNNExecutionProvider::IsNodeSupported(qnn::QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit,
Expand Down
1 change: 0 additions & 1 deletion onnxruntime/core/providers/qnn/qnn_execution_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class QNNExecutionProvider : public IExecutionProvider {

private:
ProviderOptions runtime_options_;
std::string backend_path_;
qnn::ProfilingLevel profiling_level_ = qnn::ProfilingLevel::OFF;
qnn::HtpPerformanceMode htp_performance_mode_ = qnn::HtpPerformanceMode::kHtpDefault;
std::unique_ptr<qnn::QnnBackendManager> qnn_backend_manager_;
Expand Down
Loading

0 comments on commit 8e6019a

Please sign in to comment.