don't create optimizer instances until EP requests it by calling GetOptimizerByName

This commit is contained in:
Chi Lo 2025-02-06 15:16:26 -08:00
parent 2b81789507
commit 0c10cd43d5
10 changed files with 276 additions and 136 deletions

View file

@ -3,23 +3,77 @@
#include "core/optimizer/graph_optimizer_registry.h"
#include "core/optimizer/graph_transformer_utils.h"
#include "core/optimizer/selection_and_optimization_func.h"
#include "core/optimizer/qdq_transformer/constant_folding_dq_node.h"
using namespace onnxruntime;
using namespace ::onnxruntime::common;
namespace onnxruntime {
GraphOptimizerRegistry::GraphOptimizerRegistry() {
logger_ = &logging::LoggingManager::DefaultLogger();
}
common::Status GraphOptimizerRegistry::Register(std::unique_ptr<GraphTransformer> transformer) {
const auto& name = transformer->Name();
if (name_to_transformer_map_.find(name) != name_to_transformer_map_.end()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "This transformer is already registered " + name);
if (name_to_transformer_map_.find(name) != name_to_transformer_map_.end() &&
name_to_transformer_map_.at(name)) {
LOGS(*logger_, WARNING) << "This optimizer is already created and registered " << name;
return Status::OK();
}
name_to_transformer_map_[name] = transformer.get();
transformer_list_.push_back(std::move(transformer));
if (name == kCONSTANT_FOLDING_DQ) {
transformer_name_to_selection_func_[name] = ConstantFoldingDQ_selection;
}
return Status::OK();
}
common::Status GraphOptimizerRegistry::AddPredefinedOptimizerNames(std::vector<std::string>& optimizer_names) {
for (auto name : optimizer_names) {
if (name_to_transformer_map_.find(name) != name_to_transformer_map_.end()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "This transformer name is already added " + name);
}
name_to_transformer_map_[name] = nullptr; // The transformer will be instantizted only when EP requests it
}
return Status::OK();
}
std::unique_ptr<GraphTransformer> GraphOptimizerRegistry::CreateOptimizer(std::string& name, std::unordered_map<std::string, std::string>& key_value_configs) {
std::unique_ptr<GraphTransformer> transformer;
if (name == kCONSTANT_FOLDING_DQ) {
const InlinedHashSet<NodeIndex> node_index_set = {};
return std::make_unique<ConstantFoldingDQ>(*cpu_ep_, false /*skip_dequantize_linear*/,
session_options_->config_options, node_index_set);
}
LOGS(*logger_, WARNING) << "Can't create optimizer " << name;
return transformer;
}
std::optional<std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)>> GraphOptimizerRegistry::GetSelectionFunc(std::string& name,
std::unordered_map<std::string, std::string>& key_value_configs) const {
if (name_to_transformer_map_.find(name) == name_to_transformer_map_.end()) {
LOGS(*logger_, WARNING) << "Can't find optimizer " << name;
return std::nullopt;
}
// Create and register if the transformer instance is not created.
if (!name_to_transformer_map_.at(name)) {
auto new_transformer = Get()->CreateOptimizer(name, key_value_configs);
Get()->Register(std::move(new_transformer));
}
auto lookup = transformer_name_to_selection_func_.find(name);
if (lookup != transformer_name_to_selection_func_.end()) {
return transformer_name_to_selection_func_.at(name);
}
return std::nullopt;
}
GraphTransformer* GraphOptimizerRegistry::GetTransformerByName(std::string& name) const {
if (name_to_transformer_map_.find(name) != name_to_transformer_map_.end()) {
return name_to_transformer_map_.at(name);
@ -27,7 +81,7 @@ GraphTransformer* GraphOptimizerRegistry::GetTransformerByName(std::string& name
return nullptr;
}
// Registers all the predefined transformers for EP
// Create and register all the predefined transformers for EP
common::Status GraphOptimizerRegistry::AddPredefinedOptimizers(
const onnxruntime::SessionOptions& sess_options,
const onnxruntime::IExecutionProvider& cpu_ep,
@ -56,6 +110,16 @@ common::Status GraphOptimizerRegistry::ApplyTransformer(Graph& graph, std::strin
return Status::OK();
}
common::Status GraphOptimizerRegistry::AddCpuEpReference(onnxruntime::IExecutionProvider* cpu_ep) {
cpu_ep_ = cpu_ep;
return Status::OK();
}
common::Status GraphOptimizerRegistry::AddSessionOptionsReference(onnxruntime::SessionOptions* session_options) {
session_options_ = session_options;
return Status::OK();
}
// Initialize static members
std::shared_ptr<GraphOptimizerRegistry> onnxruntime::GraphOptimizerRegistry::graph_optimizer_registry = nullptr;
std::mutex GraphOptimizerRegistry::registry_mutex;

View file

@ -5,16 +5,22 @@
#include "core/common/inlined_containers.h"
#include "core/common/logging/logging.h"
//#include "core/common/common.h"
#include "core/optimizer/graph_transformer.h"
#include "core/framework/execution_providers.h"
#include "core/framework/compute_capability.h"
namespace onnxruntime {
/**
* A registration/lookup class for re-usable optimizers for EPs.
*/
class GraphOptimizerRegistry {
public:
explicit GraphOptimizerRegistry() {}
explicit GraphOptimizerRegistry();
GraphOptimizerRegistry(const GraphOptimizerRegistry&) = delete;
/**
* Get GraphOptimizerRegistry instance as a singleton.
*/
static std::shared_ptr<GraphOptimizerRegistry> Get() {
if (!graph_optimizer_registry) { // First Check (without locking)
std::lock_guard<std::mutex> lock(registry_mutex);
@ -25,21 +31,66 @@ class GraphOptimizerRegistry {
return graph_optimizer_registry;
}
common::Status AddPredefinedOptimizers(const onnxruntime::SessionOptions& sess_options,
const onnxruntime::IExecutionProvider& cpu_ep,
const logging::Logger& logger);
/**
* Register all the predefined optimizer names, only name not the optimizer instance.
*
* The optimizer will later be instantizted only when EP requests it by calling GetOptimizerByName in provider bridge.
*/
common::Status GraphOptimizerRegistry::AddPredefinedOptimizerNames(std::vector<std::string>& optimizer_names);
/**
* Create and register all predefined optimizers.
*/
common::Status AddPredefinedOptimizers(const onnxruntime::SessionOptions& sess_options,
const onnxruntime::IExecutionProvider& cpu_ep,
const logging::Logger& logger);
/**
* Create optimizer instance.
*/
std::unique_ptr<GraphTransformer> CreateOptimizer(std::string& name, std::unordered_map<std::string, std::string>& key_value_configs);
/**
* Get optimizer by name.
*/
GraphTransformer* GraphOptimizerRegistry::GetTransformerByName(std::string& name) const;
/**
* Run the optimizer.
*/
common::Status ApplyTransformer(Graph& graph, std::string& name,
const logging::Logger& logger) const;
/**
* Register optimizer and its optimization selection function.
*/
common::Status Register(std::unique_ptr<GraphTransformer> transformer);
// Get transformer by name. Return nullptr if not found.
GraphTransformer* GetTransformerByName(std::string& name) const;
/**
* Get optimizer selection function requested by EP. If the optimizer name can't be found, return nullopt.
*
* Please note that this function also creates and registers the optimizer if its instance is not existed.
*/
std::optional<std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)>> GraphOptimizerRegistry::GetSelectionFunc(std::string& name,
std::unordered_map<std::string, std::string>& key_value_configs) const;
/**
* Add CPU EP reference from InferenceSession as it's needed for some optimizers, ex: ConstantFoldingDQ.
*/
common::Status AddCpuEpReference(onnxruntime::IExecutionProvider* cpu_ep);
/**
* Add Session Options reference from InferenceSession as it's needed for some optimizers, ex: ConstantFoldingDQ.
*/
common::Status AddSessionOptionsReference(onnxruntime::SessionOptions* session_options);
private:
InlinedVector<std::unique_ptr<GraphTransformer>> transformer_list_;
InlinedHashMap<std::string, GraphTransformer*> name_to_transformer_map_;
InlinedHashMap<std::string, std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)>> transformer_name_to_selection_func_;
const logging::Logger* logger_;
onnxruntime::IExecutionProvider* cpu_ep_;
onnxruntime::SessionOptions* session_options_;
static std::shared_ptr<GraphOptimizerRegistry> graph_optimizer_registry;
static std::mutex registry_mutex;

View file

@ -1,18 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include <limits>
#include "core/optimizer/qdq_transformer/constant_folding_dq_node.h"
#include "core/optimizer/initializer.h"
#include "core/optimizer/utils.h"
#include "core/optimizer/graph_optimizer_registry.h"
#include "core/graph/graph_utils.h"
#include "core/optimizer/optimizer_execution_frame.h"
#include "core/optimizer/utils.h"
#include "core/framework/op_kernel.h"
#include "core/framework/tensorprotoutils.h"
using namespace onnxruntime::common;
namespace onnxruntime {

View file

@ -12,15 +12,13 @@
namespace onnxruntime {
/**
@class ConstantFolding
@class ConstantFoldingDQ
Transformer that traverses the graph top-down and performs constant folding, i.e.,
it statically computes parts of the graph that rely only on constant initializers.
It's the derived class from ConstantFolding.
*/
class ConstantFoldingDQ : public ConstantFolding {
public:
/*! Constant folding will not be applied to nodes that have one of initializers from excluded_initializers as input.
For pre-training, the trainable weights are those initializers to be excluded.
\param execution_provider Execution provider instance to execute constant folding.
*/
ConstantFoldingDQ(const IExecutionProvider& execution_provider,

View file

@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "selection_and_optimization_func.h"
#include "core/optimizer/graph_optimizer_registry.h"
#include "core/graph/graph_utils.h"
#include "core/framework/compute_capability.h"
#include "core/optimizer/qdq_transformer/constant_folding_dq_node.h"
namespace onnxruntime {
std::vector<std::unique_ptr<ComputeCapability>> ConstantFoldingDQ_selection(const GraphViewer& graph_viewer) {
std::vector<std::unique_ptr<ComputeCapability>> result;
std::unique_ptr<IndexedSubGraph> sub_graph = std::make_unique<IndexedSubGraph>();
const std::vector<NodeIndex>& node_index = graph_viewer.GetNodesInTopologicalOrder(ExecutionOrder::PRIORITY_BASED /*priority-based topological sort*/);
InitializedTensorSet constant_inputs;
const InlinedHashSet<std::string> excluded_initializers;
// Select DequantizeLinear node where all inputs are constant
for (const auto& index : node_index) {
const auto& node = graph_viewer.GetNode(index);
if (node->OpType() != "DequantizeLinear") {
continue;
}
if (!graph_utils::AllNodeInputsAreConstant(graph_viewer.GetGraph(), *node, constant_inputs, excluded_initializers)) {
continue;
}
sub_graph->nodes.push_back(index);
}
result.push_back(std::make_unique<ComputeCapability>(std::move(sub_graph)));
result.back()->optimization_func = ConstantFoldingDQ_optimization;
return result;
}
Status ConstantFoldingDQ_optimization(Graph& graph, const ComputeCapability& optimization_cc, ComputeCapability& cc_to_update) {
std::string optimizer_name = kCONSTANT_FOLDING_DQ;
auto logger = const_cast<logging::Logger*>(&logging::LoggingManager::DefaultLogger());
std::unordered_set<std::string> original_initializers_to_remove;
std::unordered_set<std::string> new_initializers_to_add;
InlinedHashSet<NodeIndex> dq_node_index_set;
// iterate node_to_optimize to:
// 1. get original initializers to remove
// 2. add new initializers
// 3. create dq node index set
for (const auto& index : optimization_cc.sub_graph->nodes) {
auto node = graph.GetNode(index);
if (node->OpType() != "DequantizeLinear") {
continue;
}
auto input_0 = node->InputDefs()[0];
auto output_0 = node->OutputDefs()[0];
original_initializers_to_remove.insert(input_0->Name());
new_initializers_to_add.insert(output_0->Name());
dq_node_index_set.insert(index);
}
auto optimizer_registry = onnxruntime::GraphOptimizerRegistry::Get();
ConstantFoldingDQ* transformer = static_cast<ConstantFoldingDQ*>(optimizer_registry->GetTransformerByName(optimizer_name));
transformer->UpdateNodeIndexSet(dq_node_index_set);
// apply constant folding on DQ nodes
optimizer_registry->ApplyTransformer(graph, optimizer_name, *logger);
// update the overall ComputeCapability
std::vector<onnxruntime::NodeIndex> updated_nodes;
for (auto index : cc_to_update.sub_graph->nodes) {
if (dq_node_index_set.find(index) != dq_node_index_set.end()) {
continue;
}
updated_nodes.push_back(index);
}
cc_to_update.sub_graph->nodes = updated_nodes;
auto original_meta_def = cc_to_update.sub_graph->GetMetaDef();
std::unique_ptr<IndexedSubGraph::MetaDef> updated_meta_def = std::make_unique<IndexedSubGraph::MetaDef>();
updated_meta_def->name = original_meta_def->name;
updated_meta_def->domain = original_meta_def->domain;
updated_meta_def->since_version = original_meta_def->since_version;
updated_meta_def->status = original_meta_def->status;
updated_meta_def->inputs = original_meta_def->inputs;
updated_meta_def->outputs = original_meta_def->outputs;
updated_meta_def->attributes = original_meta_def->attributes;
updated_meta_def->doc_string = original_meta_def->doc_string;
#if !defined(ORT_MINIMAL_BUILD)
updated_meta_def->type_and_shape_inference_function = original_meta_def->type_and_shape_inference_function;
#endif
for (auto constant_initializer : original_meta_def->constant_initializers) {
if (original_initializers_to_remove.find(constant_initializer) != original_initializers_to_remove.end()) {
continue;
}
updated_meta_def->constant_initializers.push_back(constant_initializer);
}
for (auto constant_initializer : new_initializers_to_add) {
updated_meta_def->constant_initializers.push_back(constant_initializer);
}
cc_to_update.sub_graph->SetMetaDef(std::move(updated_meta_def));
return Status::OK();
}
} // namespace onnxruntime

View file

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "core/framework/compute_capability.h"
#include "core/graph/graph_viewer.h"
namespace onnxruntime {
static const std::string kCONSTANT_FOLDING_DQ = "ConstantFoldingDQ";
// ConstantFoldingDQ selection function
std::vector<std::unique_ptr<ComputeCapability>> ConstantFoldingDQ_selection(const GraphViewer& graph_viewer);
// ConstantFoldingDQ optimization function
Status ConstantFoldingDQ_optimization(Graph& graph, const ComputeCapability& optimization_cc, ComputeCapability& cc_to_update);
} // namespace onnxruntime

View file

@ -143,6 +143,7 @@ struct ProviderHost {
virtual const OrtApiBase* OrtGetApiBase() = 0;
virtual Status GetOptimizerByName(const std::string& name,
std::unordered_map<std::string, std::string>& key_value_configs,
std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)>& selection_func) = 0;
virtual void* HeapAllocate(size_t size) = 0;

View file

@ -2670,7 +2670,8 @@ TensorrtExecutionProvider::GetCapability(const GraphViewer& graph,
*/
std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)> selection_func;
auto status = g_host->GetOptimizerByName("ConstantFoldingDQ", selection_func);
std::unordered_map<std::string, std::string> key_value_configs = {};
auto status = g_host->GetOptimizerByName("ConstantFoldingDQ", key_value_configs, selection_func);
std::vector<std::unique_ptr<ComputeCapability>> selection_cc;
if (selection_func) {
selection_cc = selection_func(graph);

View file

@ -1845,11 +1845,20 @@ common::Status InferenceSession::Initialize() {
record_runtime_optimization_produced_op_schema,
*session_logger_));
// register predefined transformers for EP
// Register predefined optimizer names for EPs.
// It won't create optimizer instance until EP later requests it by calling GetOptimizerByName in provider bridge
std::vector<std::string> predefined_optimizer_names_for_ep;
predefined_optimizer_names_for_ep.push_back("ConstantFoldingDQ");
auto graph_optimizer_registry = onnxruntime::GraphOptimizerRegistry::Get();
graph_optimizer_registry->AddPredefinedOptimizerNames(predefined_optimizer_names_for_ep);
graph_optimizer_registry->AddCpuEpReference(execution_providers_.Get(onnxruntime::kCpuExecutionProvider));
// Don't create optimizer instances upfront.
/*
graph_optimizer_registry->AddPredefinedOptimizers(session_options_,
*execution_providers_. Get(onnxruntime::kCpuExecutionProvider),
*session_logger_);
*/
#ifdef USE_DML
const IExecutionProvider* dmlExecutionProvider = execution_providers_.Get(kDmlExecutionProvider);

View file

@ -4,6 +4,7 @@
// This is the Onnxruntime side of the bridge to allow providers to be built as a DLL
// It implements onnxruntime::ProviderHost
#include <optional>
#include "core/common/inlined_containers.h"
#include "core/common/path_string.h"
#include "core/framework/allocator_utils.h"
@ -37,8 +38,6 @@
#include "core/framework/model_metadef_id_generator.h"
#include "core/optimizer/qdq_transformer/selectors_actions/qdq_selectors.h"
#include "core/optimizer/qdq_transformer/selectors_actions/shared/utils.h"
#include "core/optimizer/qdq_transformer/constant_folding_dq_node.h"
#include "core/session/onnxruntime_session_options_config_keys.h"
#include "core/session/onnxruntime_c_api.h"
#include "core/common/string_helper.h"
@ -215,119 +214,17 @@ struct ProviderHostImpl : ProviderHost {
const OrtApiBase* OrtGetApiBase() override { return ::OrtGetApiBase(); }
Status GetOptimizerByName(const std::string& name,
std::unordered_map<std::string, std::string>& key_value_configs,
std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)>& selection_func) override {
//static const GraphTransformerManager& graph_transformer_mgr = transformer_mgr;
std::string optimizer_name(name);
// Pre-defined graph transformers/optimizers
static const std::string kEP_GRAPH_TRANSFORMER_CONSTANT_FOLDING_DQ = "ConstantFoldingDQ";
auto optimizer_registry = onnxruntime::GraphOptimizerRegistry::Get();
auto func = optimizer_registry->GetSelectionFunc(optimizer_name, key_value_configs);
// ConstantFoldingDQ optimization function
auto constant_folding_dq_optimization = [&](Graph& graph, const ComputeCapability& optimization_cc, ComputeCapability& cc_to_update) -> Status {
std::string optimizer_name = kEP_GRAPH_TRANSFORMER_CONSTANT_FOLDING_DQ;
auto logger = const_cast<logging::Logger*>(&logging::LoggingManager::DefaultLogger());
std::unordered_set<std::string> original_initializers_to_remove;
std::unordered_set<std::string> new_initializers_to_add;
InlinedHashSet<NodeIndex> dq_node_index_set;
// iterate node_to_optimize to:
// 1. get original initializers to remove
// 2. add new initializers
// 3. create dq node index set
for (const auto& index : optimization_cc.sub_graph->nodes) {
auto node = graph.GetNode(index);
if (node->OpType() != "DequantizeLinear") {
continue;
}
auto input_0 = node->InputDefs()[0];
auto output_0 = node->OutputDefs()[0];
original_initializers_to_remove.insert(input_0->Name());
new_initializers_to_add.insert(output_0->Name());
dq_node_index_set.insert(index);
}
auto optimizer_registry = onnxruntime::GraphOptimizerRegistry::Get();
ConstantFoldingDQ* transformer = static_cast<ConstantFoldingDQ*>(optimizer_registry->GetTransformerByName(optimizer_name));
transformer->UpdateNodeIndexSet(dq_node_index_set);
// apply constant folding on DQ nodes
optimizer_registry->ApplyTransformer(graph, optimizer_name, *logger);
// update the overall ComputeCapability
std::vector<onnxruntime::NodeIndex> updated_nodes;
for (auto index : cc_to_update.sub_graph->nodes) {
if (dq_node_index_set.find(index) != dq_node_index_set.end()) {
continue;
}
updated_nodes.push_back(index);
}
cc_to_update.sub_graph->nodes = updated_nodes;
auto original_meta_def = cc_to_update.sub_graph->GetMetaDef();
std::unique_ptr<IndexedSubGraph::MetaDef> updated_meta_def = std::make_unique<IndexedSubGraph::MetaDef>();
updated_meta_def->name = original_meta_def->name;
updated_meta_def->domain = original_meta_def->domain;
updated_meta_def->since_version = original_meta_def->since_version;
updated_meta_def->status = original_meta_def->status;
updated_meta_def->inputs = original_meta_def->inputs;
updated_meta_def->outputs = original_meta_def->outputs;
updated_meta_def->attributes = original_meta_def->attributes;
updated_meta_def->doc_string = original_meta_def->doc_string;
#if !defined(ORT_MINIMAL_BUILD)
updated_meta_def->type_and_shape_inference_function = original_meta_def->type_and_shape_inference_function;
#endif
for (auto constant_initializer : original_meta_def->constant_initializers) {
if (original_initializers_to_remove.find(constant_initializer) != original_initializers_to_remove.end()) {
continue;
}
updated_meta_def->constant_initializers.push_back(constant_initializer);
}
for (auto constant_initializer : new_initializers_to_add) {
updated_meta_def->constant_initializers.push_back(constant_initializer);
}
cc_to_update.sub_graph->SetMetaDef(std::move(updated_meta_def));
return Status::OK();
};
// ConstantFoldingDQ selection function
auto constant_folding_dq_selection = [&](const GraphViewer& graph_viewer) -> std::vector<std::unique_ptr<ComputeCapability>> {
std::vector<std::unique_ptr<ComputeCapability>> result;
std::unique_ptr<IndexedSubGraph> sub_graph = std::make_unique<IndexedSubGraph>();
const std::vector<NodeIndex>& node_index = graph_viewer.GetNodesInTopologicalOrder(ExecutionOrder::PRIORITY_BASED /*priority-based topological sort*/);
InitializedTensorSet constant_inputs;
const InlinedHashSet<std::string> excluded_initializers;
// Select DequantizeLinear node where all inputs are constant
for (const auto& index : node_index) {
const auto& node = graph_viewer.GetNode(index);
if (node->OpType() != "DequantizeLinear") {
continue;
}
if (!graph_utils::AllNodeInputsAreConstant(graph_viewer.GetGraph(), *node, constant_inputs, excluded_initializers)) {
continue;
}
sub_graph->nodes.push_back(index);
}
result.push_back(std::make_unique<ComputeCapability>(std::move(sub_graph)));
result.back()->optimization_func = constant_folding_dq_optimization;
return result;
};
// Optimizer lookup table
static std::unordered_map<std::string, std::function<std::vector<std::unique_ptr<ComputeCapability>>(const GraphViewer&)>> optimizer_to_selection_function;
if (optimizer_to_selection_function.find(kEP_GRAPH_TRANSFORMER_CONSTANT_FOLDING_DQ) == optimizer_to_selection_function.end()) {
optimizer_to_selection_function[kEP_GRAPH_TRANSFORMER_CONSTANT_FOLDING_DQ] = constant_folding_dq_selection;
if (func.has_value()) {
selection_func = func.value();
}
auto look_up = optimizer_to_selection_function.find(optimizer_name);
if (look_up != optimizer_to_selection_function.end()) {
selection_func = optimizer_to_selection_function[optimizer_name];
}
return Status::OK();
};