[QNN EP] Return INVALID_GRAPH if failed to load from context binary (#18485)

### Description
[QNN EP] Return INVALID_GRAPH if failed to load from context binary

### Motivation and Context
Make sure QNN EP return INVALID_GRAPH if error encountered with the
context binary file
This commit is contained in:
Hector Li 2023-11-24 20:41:27 -08:00 committed by GitHub
parent 2f608338cb
commit a2fd8a6fc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 238 additions and 206 deletions

View file

@ -60,10 +60,10 @@ Status CreateNodeArgs(const std::vector<std::string>& names,
return Status::OK();
}
Status QnnCacheModelHandler::GetEpContextFromModel(const std::string& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model,
const logging::Logger& logger) {
Status GetEpContextFromModel(const onnxruntime::PathString& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model,
const logging::Logger& logger) {
using namespace onnxruntime;
std::shared_ptr<Model> model;
ORT_RETURN_IF_ERROR(Model::Load(ToPathString(ctx_onnx_model_path), model, {}, logger));
@ -74,10 +74,10 @@ Status QnnCacheModelHandler::GetEpContextFromModel(const std::string& ctx_onnx_m
qnn_model);
}
Status QnnCacheModelHandler::GetEpContextFromGraph(const onnxruntime::GraphViewer& graph_viewer,
const std::string& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model) {
Status GetEpContextFromGraph(const onnxruntime::GraphViewer& graph_viewer,
const onnxruntime::PathString& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model) {
const auto& node = graph_viewer.Nodes().begin();
NodeAttrHelper node_helper(*node);
bool is_embed_mode = node_helper.Get(EMBED_MODE, true);
@ -89,11 +89,11 @@ Status QnnCacheModelHandler::GetEpContextFromGraph(const onnxruntime::GraphViewe
}
std::string external_qnn_context_binary_file_name = node_helper.Get(EP_CACHE_CONTEXT, "");
std::filesystem::path folder_path = std::filesystem::path(ctx_onnx_model_path).parent_path();
std::filesystem::path context_binary_path = folder_path.append(external_qnn_context_binary_file_name);
std::string context_binary_path(std::filesystem::path(ctx_onnx_model_path).parent_path().string() +
"/" + external_qnn_context_binary_file_name);
size_t buffer_size{0};
std::ifstream cache_file(context_binary_path.c_str(), std::ifstream::binary);
std::ifstream cache_file(context_binary_path.string().c_str(), std::ifstream::binary);
ORT_RETURN_IF(!cache_file || !cache_file.good(), "Failed to open cache file.");
cache_file.seekg(0, cache_file.end);
@ -112,114 +112,122 @@ Status QnnCacheModelHandler::GetEpContextFromGraph(const onnxruntime::GraphViewe
qnn_model);
}
Status QnnCacheModelHandler::GetMetadataFromEpContextModel(const std::string& ctx_onnx_model_path,
std::string& model_name,
std::string& model_description,
std::string& graph_partition_name,
std::string& cache_source,
const logging::Logger& logger) {
if (!is_metadata_ready_) {
using namespace onnxruntime;
std::shared_ptr<Model> model;
ORT_RETURN_IF_ERROR(Model::Load(ToPathString(ctx_onnx_model_path), model, {}, logger));
const auto& graph = GraphViewer(model->MainGraph());
const auto& node = graph.Nodes().begin();
NodeAttrHelper node_helper(*node);
model_name_ = graph.Name();
model_description_ = graph.Description();
graph_partition_name_ = node_helper.Get(PARTITION_NAME, "");
cache_source_ = node_helper.Get(SOURCE, "");
is_metadata_ready_ = true;
Status LoadQnnCtxFromOnnxModel(const onnxruntime::GraphViewer& graph_viewer,
const onnxruntime::PathString& ctx_onnx_model_path,
bool is_qnn_ctx_model,
bool is_ctx_cache_file_exist,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model,
const logging::Logger& logger) {
Status status;
if (is_qnn_ctx_model) {
status = GetEpContextFromGraph(graph_viewer, ctx_onnx_model_path, qnn_backend_manager, qnn_model);
} else if (is_ctx_cache_file_exist) {
status = GetEpContextFromModel(ctx_onnx_model_path, qnn_backend_manager, qnn_model, logger);
}
if (!status.IsOK()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, "Failed to load from EpContextModel. ", status.ErrorMessage());
}
model_name = model_name_;
model_description = model_description_;
graph_partition_name = graph_partition_name_;
cache_source = cache_source_;
return Status::OK();
}
bool QnnCacheModelHandler::IsContextCacheFileExists(const std::string& customer_context_cache_path,
const std::string& model_description,
const onnxruntime::PathString& model_pathstring) {
// Avoid duplicate work
if (ctx_file_exists_) {
return ctx_file_exists_;
}
model_description_ = model_description;
// Use user provided context cache file path if exist, otherwise try model_file.onnx_ctx.onnx by default
if (customer_context_cache_path.empty()) {
context_cache_path_ = PathToUTF8String(model_pathstring) + "_qnn_ctx.onnx";
} else {
context_cache_path_ = customer_context_cache_path;
}
Status GetMetadataFromEpContextModel(const onnxruntime::PathString& ctx_onnx_model_path,
std::string& model_name,
std::string& model_description,
std::string& graph_partition_name,
std::string& cache_source,
const logging::Logger& logger) {
using namespace onnxruntime;
std::shared_ptr<Model> model;
ORT_RETURN_IF_ERROR(Model::Load(ctx_onnx_model_path, model, {}, logger));
const auto& graph = GraphViewer(model->MainGraph());
const auto& node = graph.Nodes().begin();
NodeAttrHelper node_helper(*node);
model_name = graph.Name();
model_description = graph.Description();
graph_partition_name = node_helper.Get(PARTITION_NAME, "");
cache_source = node_helper.Get(SOURCE, "");
ctx_file_exists_ = std::filesystem::is_regular_file(context_cache_path_) && std::filesystem::exists(context_cache_path_);
return ctx_file_exists_;
return Status::OK();
}
Status QnnCacheModelHandler::ValidateWithContextFile(const std::string& model_name,
const std::string& graph_partition_name,
const logging::Logger& logger) {
ORT_RETURN_IF(!ctx_file_exists_, "Qnn context binary file not exist for some reason!");
bool IsContextCacheFileExists(const std::string& customer_context_cache_path,
const onnxruntime::PathString& model_pathstring,
onnxruntime::PathString& context_cache_path) {
// Use user provided context cache file path if exist, otherwise try model_file.onnx_ctx.onnx by default
if (!customer_context_cache_path.empty()) {
context_cache_path = ToPathString(customer_context_cache_path);
} else if (!model_pathstring.empty()) {
context_cache_path = model_pathstring + ToPathString("_qnn_ctx.onnx");
}
return std::filesystem::is_regular_file(context_cache_path) && std::filesystem::exists(context_cache_path);
}
Status ValidateWithContextFile(const onnxruntime::PathString& context_cache_path,
const std::string& model_name,
const std::string& model_description,
const std::string& graph_partition_name,
const logging::Logger& logger) {
std::string model_name_from_ctx_cache;
std::string model_description_from_ctx_cache;
std::string graph_partition_name_from_ctx_cache;
std::string cache_source;
ORT_RETURN_IF_ERROR(GetMetadataFromEpContextModel(context_cache_path_,
model_name_from_ctx_cache,
model_description_from_ctx_cache,
graph_partition_name_from_ctx_cache,
cache_source,
logger));
auto status = GetMetadataFromEpContextModel(context_cache_path,
model_name_from_ctx_cache,
model_description_from_ctx_cache,
graph_partition_name_from_ctx_cache,
cache_source,
logger);
if (!status.IsOK()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, "Failed to get metadata from EpContextModel.");
}
// The source attribute from the skeleton onnx file indicate whether it's generated from QNN toolchain or ORT
if (cache_source != kQnnExecutionProvider) {
LOGS(logger, VERBOSE) << "Context binary cache is not generated by Ort.";
return Status::OK();
}
ORT_RETURN_IF(model_name != model_name_from_ctx_cache,
"Model file name from context cache metadata: " + model_name_from_ctx_cache +
" is different with target: " + model_name +
". Please make sure the context binary file matches the model.");
if (model_name != model_name_from_ctx_cache ||
model_description != model_description_from_ctx_cache ||
graph_partition_name != graph_partition_name_from_ctx_cache) {
std::string message = onnxruntime::MakeString("Metadata mismatch. onnx: ",
model_name, " ", model_description, " ", graph_partition_name,
" vs epcontext: ",
model_name_from_ctx_cache, " ",
model_description_from_ctx_cache, " ",
graph_partition_name_from_ctx_cache);
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_GRAPH, message);
}
ORT_RETURN_IF(model_description_ != model_description_from_ctx_cache,
"Model description from context cache metadata: " + model_description_from_ctx_cache +
" is different with target: " + model_description_ +
". Please make sure the context binary file matches the model.");
ORT_RETURN_IF(graph_partition_name != graph_partition_name_from_ctx_cache && get_capability_round_2_,
"Graph name from context cache metadata: " + graph_partition_name_from_ctx_cache +
" is different with target: " + graph_partition_name +
". You may need to re-generate the context binary file.");
get_capability_round_2_ = true;
return Status::OK();
}
Status QnnCacheModelHandler::GenerateCtxCacheOnnxModel(unsigned char* buffer,
uint64_t buffer_size,
const std::string& sdk_build_version,
const std::vector<IExecutionProvider::FusedNodeAndGraph>& fused_nodes_and_graphs,
const std::unordered_map<std::string, std::unique_ptr<QnnModel>>& qnn_models,
const logging::Logger& logger) {
Status GenerateCtxCacheOnnxModel(const std::string model_name,
const std::string model_description,
unsigned char* buffer,
uint64_t buffer_size,
const std::string& sdk_build_version,
const std::vector<IExecutionProvider::FusedNodeAndGraph>& fused_nodes_and_graphs,
const std::unordered_map<std::string, std::unique_ptr<QnnModel>>& qnn_models,
const onnxruntime::PathString& context_cache_path,
bool qnn_context_embed_mode,
const logging::Logger& logger) {
std::unordered_map<std::string, int> domain_to_version = {{kOnnxDomain, 11}, {kMSDomain, 1}};
Model model(model_name_, false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
Model model(model_name, false, ModelMetaData(), PathString(), IOnnxRuntimeOpSchemaRegistryList(),
domain_to_version, {}, logger);
auto& graph = model.MainGraph();
graph.SetDescription(model_description_);
graph.SetDescription(model_description);
using namespace ONNX_NAMESPACE;
int index = 0;
// Still need more work to support multiple partition, it's out of EP's scope.
// Already have code to make sure it's single partition before this method get invoked.
for (const auto& fused_node_graph : fused_nodes_and_graphs) {
const onnxruntime::GraphViewer& graph_viewer(fused_node_graph.filtered_graph);
Node& fused_node = fused_node_graph.fused_node;
// graph_viewer.Name() is generated in GetCapability, e.g QNN_[hash_id]_[id]
// dump graph_viewer.Name() as metadata in context cache binary file, so that we can validate it in GetCapability
auto qnn_model_kv = qnn_models.find(fused_node.Name());
ORT_RETURN_IF(qnn_model_kv == qnn_models.end(), fused_node.Name(), " not exist in QnnModel table.");
@ -229,7 +237,7 @@ Status QnnCacheModelHandler::GenerateCtxCacheOnnxModel(unsigned char* buffer,
ORT_RETURN_IF_ERROR(CreateNodeArgs(qnn_model->GetInputNames(), qnn_model->GetInputsInfo(), inputs, graph));
ORT_RETURN_IF_ERROR(CreateNodeArgs(qnn_model->GetOutputNames(), qnn_model->GetOutputsInfo(), outputs, graph));
const std::string& graph_name = graph_viewer.Name();
const std::string& graph_name = fused_node.Name();
auto& ep_node = graph.AddNode(graph_name,
EPCONTEXT_OP,
"Onnx Qnn context binary cache for graph partition: " + graph_name,
@ -240,13 +248,13 @@ Status QnnCacheModelHandler::GenerateCtxCacheOnnxModel(unsigned char* buffer,
// Only dump the context buffer once since all QNN graph are in one single context
if (0 == index) {
if (qnn_context_embed_mode_) {
if (qnn_context_embed_mode) {
std::string cache_payload(buffer, buffer + buffer_size);
ep_node.AddAttribute(EP_CACHE_CONTEXT, cache_payload);
} else {
std::string context_cache_path(context_cache_path_ + "_" + graph_name + ".bin");
std::string context_cache_name(std::filesystem::path(context_cache_path).filename().string());
std::ofstream of_stream(context_cache_path.c_str(), std::ofstream::binary);
onnxruntime::PathString context_bin_path = context_cache_path + ToPathString("_" + graph_name + ".bin");
std::string context_cache_name(std::filesystem::path(context_bin_path).filename().string());
std::ofstream of_stream(context_bin_path.c_str(), std::ofstream::binary);
if (!of_stream) {
LOGS(logger, ERROR) << "Failed to open create context file.";
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Failed to open context cache file.");
@ -257,7 +265,7 @@ Status QnnCacheModelHandler::GenerateCtxCacheOnnxModel(unsigned char* buffer,
} else {
ep_node.AddAttribute(MAIN_CONTEXT, static_cast<int64_t>(0));
}
int64_t embed_mode = qnn_context_embed_mode_ ? static_cast<int64_t>(1) : static_cast<int64_t>(0);
int64_t embed_mode = qnn_context_embed_mode ? static_cast<int64_t>(1) : static_cast<int64_t>(0);
ep_node.AddAttribute(EMBED_MODE, embed_mode);
ep_node.AddAttribute(EP_SDK_VER, sdk_build_version);
ep_node.AddAttribute(PARTITION_NAME, graph_name);
@ -265,7 +273,7 @@ Status QnnCacheModelHandler::GenerateCtxCacheOnnxModel(unsigned char* buffer,
++index;
}
ORT_RETURN_IF_ERROR(graph.Resolve());
ORT_RETURN_IF_ERROR(Model::Save(model, context_cache_path_));
ORT_RETURN_IF_ERROR(Model::Save(model, context_cache_path));
return Status::OK();
}

View file

@ -38,77 +38,50 @@ Status CreateNodeArgs(const std::vector<std::string>& names,
std::vector<NodeArg*>& node_args,
onnxruntime::Graph& graph);
class QnnCacheModelHandler {
public:
QnnCacheModelHandler(bool qnn_context_embed_mode) : qnn_context_embed_mode_(qnn_context_embed_mode) {
}
ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(QnnCacheModelHandler);
bool IsContextCacheFileExists(const std::string& customer_context_cache_path,
const onnxruntime::PathString& model_pathstring,
onnxruntime::PathString& context_cache_path);
Status LoadQnnCtxFromOnnxModel(const onnxruntime::GraphViewer& graph_viewer,
const std::string& ctx_onnx_model_path,
bool is_qnn_ctx_model,
bool is_ctx_cache_file_exist,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model,
const logging::Logger& logger) {
if (is_qnn_ctx_model) {
return GetEpContextFromGraph(graph_viewer, ctx_onnx_model_path, qnn_backend_manager, qnn_model);
} else if (is_ctx_cache_file_exist) {
return GetEpContextFromModel(ctx_onnx_model_path, qnn_backend_manager, qnn_model, logger);
}
return Status::OK();
}
Status GetEpContextFromModel(const onnxruntime::PathString& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model,
const logging::Logger& logger);
bool IsContextCacheFileExists(const std::string& customer_context_cache_path,
const std::string& model_description,
const onnxruntime::PathString& model_pathstring);
Status GetEpContextFromGraph(const onnxruntime::GraphViewer& graph_viewer,
const onnxruntime::PathString& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model);
bool GetIsContextCacheFileExists() const {
return ctx_file_exists_;
}
Status ValidateWithContextFile(const std::string& model_name,
const std::string& graph_name,
const logging::Logger& logger);
Status GetMetadataFromEpContextModel(const std::string& ctx_onnx_model_path,
std::string& model_name,
std::string& model_description,
std::string& graph_partition_name,
std::string& cache_source,
const logging::Logger& logger);
Status GenerateCtxCacheOnnxModel(unsigned char* buffer,
uint64_t buffer_size,
const std::string& sdk_build_version,
const std::vector<IExecutionProvider::FusedNodeAndGraph>& fused_nodes_and_graphs,
const std::unordered_map<std::string, std::unique_ptr<QnnModel>>& qnn_models,
const logging::Logger& logger);
private:
Status GetEpContextFromModel(const std::string& ctx_onnx_model_path,
Status LoadQnnCtxFromOnnxModel(const onnxruntime::GraphViewer& graph_viewer,
const onnxruntime::PathString& ctx_onnx_model_path,
bool is_qnn_ctx_model,
bool is_ctx_cache_file_exist,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model,
const logging::Logger& logger);
Status GetEpContextFromGraph(const onnxruntime::GraphViewer& graph_viewer,
const std::string& ctx_onnx_model_path,
QnnBackendManager* qnn_backend_manager,
QnnModel& qnn_model);
Status ValidateWithContextFile(const onnxruntime::PathString& context_cache_path,
const std::string& model_name,
const std::string& model_description,
const std::string& graph_partition_name,
const logging::Logger& logger);
private:
bool is_metadata_ready_ = false;
// model_name_ to cache_source_ -- metadata get from generated Qnn context binary Onnx model
std::string model_name_ = "";
std::string model_description_ = "";
std::string graph_partition_name_ = "";
std::string cache_source_ = "";
std::string context_cache_path_ = "";
bool ctx_file_exists_ = false;
bool get_capability_round_2_ = false;
bool qnn_context_embed_mode_ = true;
}; // QnnCacheModelHandler
Status GetMetadataFromEpContextModel(const onnxruntime::PathString& ctx_onnx_model_path,
std::string& model_name,
std::string& model_description,
std::string& graph_partition_name,
std::string& cache_source,
const logging::Logger& logger);
Status GenerateCtxCacheOnnxModel(const std::string model_name,
const std::string model_description,
unsigned char* buffer,
uint64_t buffer_size,
const std::string& sdk_build_version,
const std::vector<IExecutionProvider::FusedNodeAndGraph>& fused_nodes_and_graphs,
const std::unordered_map<std::string, std::unique_ptr<QnnModel>>& qnn_models,
const onnxruntime::PathString& context_cache_path,
bool qnn_context_embed_mode,
const logging::Logger& logger);
} // namespace qnn
} // namespace onnxruntime

View file

@ -22,7 +22,6 @@ namespace onnxruntime {
namespace qnn {
class QnnModel;
class QnnCacheModelHandler;
class QnnBackendManager {
public:

View file

@ -16,20 +16,12 @@
#include "core/providers/qnn/builder/qnn_model_wrapper.h"
#include "core/providers/qnn/builder/op_builder_factory.h"
#include "core/providers/qnn/builder/qnn_def.h"
#include "core/providers/qnn/builder/onnx_ctx_model_helper.h"
namespace onnxruntime {
constexpr const char* QNN = "QNN";
std::string GetFileNameFromModelPath(onnxruntime::Path model_path) {
auto model_path_components = model_path.GetComponents();
// There's no model path if model loaded from buffer stead of file
if (model_path_components.empty()) {
return "";
}
return PathToUTF8String(model_path_components.back());
}
void QNNExecutionProvider::ParseProfilingLevel(std::string profiling_level_string) {
std::transform(profiling_level_string.begin(),
profiling_level_string.end(),
@ -134,16 +126,15 @@ QNNExecutionProvider::QNNExecutionProvider(const ProviderOptions& provider_optio
static const std::string CONTEXT_CACHE_PATH = "qnn_context_cache_path";
auto context_cache_path_pos = provider_options_map.find(CONTEXT_CACHE_PATH);
if (context_cache_path_pos != provider_options_map.end()) {
context_cache_path_ = context_cache_path_pos->second;
LOGS_DEFAULT(VERBOSE) << "User specified context cache path: " << context_cache_path_;
context_cache_path_cfg_ = context_cache_path_pos->second;
LOGS_DEFAULT(VERBOSE) << "User specified context cache path: " << context_cache_path_cfg_;
}
bool qnn_context_embed_mode = true;
static const std::string CONTEXT_CACHE_EMBED_MODE = "qnn_context_embed_mode";
auto context_cache_embed_mode_pos = provider_options_map.find(CONTEXT_CACHE_EMBED_MODE);
if (context_cache_embed_mode_pos != provider_options_map.end()) {
qnn_context_embed_mode = context_cache_embed_mode_pos->second == "1";
LOGS_DEFAULT(VERBOSE) << "User specified context cache embed mode: " << qnn_context_embed_mode;
qnn_context_embed_mode_ = context_cache_embed_mode_pos->second == "1";
LOGS_DEFAULT(VERBOSE) << "User specified context cache embed mode: " << qnn_context_embed_mode_;
}
static const std::string BACKEND_PATH = "backend_path";
@ -206,7 +197,6 @@ QNNExecutionProvider::QNNExecutionProvider(const ProviderOptions& provider_optio
htp_performance_mode_,
context_priority_,
std::move(qnn_saver_path));
qnn_cache_model_handler_ = std::make_unique<qnn::QnnCacheModelHandler>(qnn_context_embed_mode);
}
bool QNNExecutionProvider::IsNodeSupported(qnn::QnnModelWrapper& qnn_model_wrapper, const NodeUnit& node_unit,
@ -343,9 +333,10 @@ QNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_viewer
// This is for case: QDQ model + Onnx Qnn context cache model
if (context_cache_enabled_ && !is_qnn_ctx_model) {
load_from_cached_context = qnn_cache_model_handler_->IsContextCacheFileExists(context_cache_path_,
graph_viewer.Description(),
graph_viewer.ModelPath().ToPathString());
onnxruntime::PathString context_cache_path;
load_from_cached_context = qnn::IsContextCacheFileExists(context_cache_path_cfg_,
graph_viewer.ModelPath().ToPathString(),
context_cache_path);
}
// Load from cached context will load the QnnSystem lib and skip the Qnn context creation
@ -444,17 +435,6 @@ QNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_viewer
}
const size_t num_of_partitions = result.size();
if (!is_qnn_ctx_model && load_from_cached_context && 1 == num_of_partitions) {
rt = qnn_cache_model_handler_->ValidateWithContextFile(GetFileNameFromModelPath(graph_viewer.ModelPath()),
result[0]->sub_graph->GetMetaDef()->name,
logger);
if (Status::OK() != rt) {
LOGS(logger, ERROR) << "QNN failed to validate context cache metadata: " << rt.ErrorMessage();
return result;
}
}
const auto summary_msg = MakeString("Number of partitions supported by QNN EP: ", num_of_partitions,
", number of nodes in the graph: ", num_nodes_in_graph,
", number of nodes supported by QNN: ", num_of_supported_nodes);
@ -547,25 +527,38 @@ Status QNNExecutionProvider::Compile(const std::vector<FusedNodeAndGraph>& fused
bool is_qnn_ctx_model = false;
ORT_RETURN_IF_ERROR(qnn::IsFusedGraphHasCtxNode(fused_nodes_and_graphs, is_qnn_ctx_model));
bool is_ctx_file_exist = qnn_cache_model_handler_->GetIsContextCacheFileExists();
onnxruntime::PathString context_cache_path;
bool is_ctx_file_exist = qnn::IsContextCacheFileExists(context_cache_path_cfg_,
graph_viewer.ModelPath().ToPathString(),
context_cache_path);
const std::string& model_name = graph_viewer.GetGraph().Name();
const std::string& model_description = graph_viewer.GetGraph().Description();
const std::string& graph_meta_id = fused_node.Name();
if (fused_nodes_and_graphs.size() == 1 && !is_qnn_ctx_model && is_ctx_file_exist) {
ORT_RETURN_IF_ERROR(qnn::ValidateWithContextFile(context_cache_path,
model_name,
model_description,
graph_meta_id,
logger));
}
if (is_qnn_ctx_model || (context_cache_enabled_ && is_ctx_file_exist)) {
ORT_RETURN_IF(fused_nodes_and_graphs.size() != 1, "Only support single partition for context cache feature.");
std::unique_ptr<qnn::QnnModel> qnn_model = std::make_unique<qnn::QnnModel>(logger, qnn_backend_manager_.get());
// Load and execute from cached context if exist
ORT_RETURN_IF_ERROR(qnn_cache_model_handler_->LoadQnnCtxFromOnnxModel(graph_viewer,
context_cache_path_,
is_qnn_ctx_model,
is_ctx_file_exist,
qnn_backend_manager_.get(),
*(qnn_model.get()),
logger));
ORT_RETURN_IF_ERROR(qnn::LoadQnnCtxFromOnnxModel(graph_viewer,
context_cache_path,
is_qnn_ctx_model,
is_ctx_file_exist,
qnn_backend_manager_.get(),
*(qnn_model.get()),
logger));
ORT_RETURN_IF_ERROR(qnn_model->SetGraphInputOutputInfo(graph_viewer, fused_node));
ORT_RETURN_IF_ERROR(qnn_model->SetupQnnInputOutput());
// fused node name is QNNExecutionProvider_QNN_[hash_id]_[id]
// the name here should be same with context->node_name in compute_info
LOGS(logger, VERBOSE) << "fused node name: " << fused_node.Name();
qnn_models_.emplace(fused_node.Name(), std::move(qnn_model));
qnn_models_.emplace(graph_meta_id, std::move(qnn_model));
ORT_RETURN_IF_ERROR(CreateComputeFunc(node_compute_funcs, logger));
return Status::OK();
@ -576,12 +569,16 @@ Status QNNExecutionProvider::Compile(const std::vector<FusedNodeAndGraph>& fused
ORT_RETURN_IF(fused_nodes_and_graphs.size() != 1, "Only support single partition for context cache feature.");
uint64_t buffer_size(0);
auto context_buffer = qnn_backend_manager_->GetContextBinaryBuffer(buffer_size);
ORT_RETURN_IF_ERROR(qnn_cache_model_handler_->GenerateCtxCacheOnnxModel(context_buffer.get(),
buffer_size,
qnn_backend_manager_->GetSdkVersion(),
fused_nodes_and_graphs,
qnn_models_,
logger));
ORT_RETURN_IF_ERROR(qnn::GenerateCtxCacheOnnxModel(model_name,
model_description,
context_buffer.get(),
buffer_size,
qnn_backend_manager_->GetSdkVersion(),
fused_nodes_and_graphs,
qnn_models_,
context_cache_path,
qnn_context_embed_mode_,
logger));
}
return Status::OK();
}

View file

@ -8,7 +8,6 @@
#include <string>
#include "core/providers/qnn/builder/qnn_backend_manager.h"
#include "core/providers/qnn/builder/qnn_model.h"
#include "core/providers/qnn/builder/onnx_ctx_model_helper.h"
#include "core/providers/qnn/builder/qnn_graph_configs_helper.h"
namespace onnxruntime {
@ -71,10 +70,10 @@ class QNNExecutionProvider : public IExecutionProvider {
std::unordered_map<std::string, std::unique_ptr<qnn::QnnModel>> qnn_models_;
uint32_t rpc_control_latency_ = 0;
bool context_cache_enabled_ = false;
std::string context_cache_path_ = "";
std::string context_cache_path_cfg_ = "";
bool disable_cpu_ep_fallback_ = false; // True if CPU EP fallback has been disabled for this session.
std::unique_ptr<qnn::QnnCacheModelHandler> qnn_cache_model_handler_;
qnn::ContextPriority context_priority_ = qnn::ContextPriority::NORMAL;
bool qnn_context_embed_mode_ = true;
};
} // namespace onnxruntime

View file

@ -786,7 +786,7 @@ TEST_F(QnnHTPBackendTests, ContextBinaryCacheNonEmbedModeTest) {
// Check the Onnx skeleton file is generated
EXPECT_TRUE(std::filesystem::exists(context_binary_file.c_str()));
// Check the Qnn context cache binary file is generated
EXPECT_TRUE(std::filesystem::exists("qnn_context_cache_non_embed.onnx_QNN_8283143575221199085_1.bin"));
EXPECT_TRUE(std::filesystem::exists("qnn_context_cache_non_embed.onnx_QNNExecutionProvider_QNN_8283143575221199085_1_0.bin"));
// 2nd run loads and run from QDQ model + Onnx skeleton file + Qnn context cache binary file
TestQDQModelAccuracy(BuildOpTestCase<float>(op_type, {input_def}, {}, {}),
@ -806,6 +806,62 @@ TEST_F(QnnHTPBackendTests, ContextBinaryCacheNonEmbedModeTest) {
context_binary_file);
}
// Run QDQ model on HTP 2 times
// 1st run will generate the Onnx skeleton file + Qnn context cache binary file
// Then delete the context bin file to make the 2nd sesssion.Initialize() return the status with code INVALID_GRAPH
TEST_F(QnnHTPBackendTests, ContextBinaryCache_InvalidGraph) {
ProviderOptions provider_options;
#if defined(_WIN32)
provider_options["backend_path"] = "QnnHtp.dll";
#else
provider_options["backend_path"] = "libQnnHtp.so";
#endif
provider_options["qnn_context_cache_enable"] = "1";
const std::string context_binary_file = "./qnn_context_cache_non_embed.onnx";
provider_options["qnn_context_cache_path"] = context_binary_file;
provider_options["qnn_context_embed_mode"] = "0";
const TestInputDef<float> input_def({1, 2, 3}, false, -10.0f, 10.0f);
const std::string op_type = "Atan";
// Runs model with DQ-> Atan-> Q and compares the outputs of the CPU and QNN EPs.
// 1st run will generate the Onnx skeleton file + Qnn context cache binary file
TestQDQModelAccuracy(BuildOpTestCase<float>(op_type, {input_def}, {}, {}),
BuildQDQOpTestCase<uint8_t>(op_type, {input_def}, {}, {}),
provider_options,
14,
ExpectedEPNodeAssignment::All);
// Check the Onnx skeleton file is generated
EXPECT_TRUE(std::filesystem::exists(context_binary_file.c_str()));
// Check the Qnn context cache binary file is generated
std::filesystem::path context_bin = "qnn_context_cache_non_embed.onnx_QNNExecutionProvider_QNN_8283143575221199085_1_0.bin";
EXPECT_TRUE(std::filesystem::exists(context_bin));
// Delete the Qnn context cache binary file
EXPECT_TRUE(std::filesystem::remove(context_bin));
// loads and run from Onnx skeleton file + Qnn context cache binary file
onnx::ModelProto model_proto;
onnxruntime::Model qnn_ctx_model;
// Load the QNN context cache model from path specified
ASSERT_STATUS_OK(qnn_ctx_model.Load(ToPathString(context_binary_file), model_proto));
std::string qnn_ctx_model_data;
model_proto.SerializeToString(&qnn_ctx_model_data);
SessionOptions so;
so.session_logid = "qnn_ctx_model_logger";
RunOptions run_options;
run_options.run_tag = so.session_logid;
InferenceSessionWrapper session_object{so, GetEnvironment()};
std::string provider_type = kCpuExecutionProvider;
ASSERT_STATUS_OK(session_object.RegisterExecutionProvider(QnnExecutionProviderWithOptions(provider_options)));
ASSERT_STATUS_OK(session_object.Load(qnn_ctx_model_data.data(), static_cast<int>(qnn_ctx_model_data.size())));
// Verify the return status with code INVALID_GRAPH
ASSERT_TRUE(session_object.Initialize().Code() == common::StatusCode::INVALID_GRAPH);
}
// Run QDQ model on HTP with 2 inputs
// 1st run will generate the Qnn context cache onnx file
// 2nd run will load and run from QDQ model + Qnn context cache model