diff --git a/onnxruntime/core/providers/shared_library/provider_interfaces.h b/onnxruntime/core/providers/shared_library/provider_interfaces.h index 06403b025f..17669b78d8 100644 --- a/onnxruntime/core/providers/shared_library/provider_interfaces.h +++ b/onnxruntime/core/providers/shared_library/provider_interfaces.h @@ -313,6 +313,7 @@ struct ProviderHost { virtual void AttributeProto__operator_delete(ONNX_NAMESPACE::AttributeProto* p) = 0; virtual void AttributeProto__operator_assign(ONNX_NAMESPACE::AttributeProto* p, const ONNX_NAMESPACE::AttributeProto& v) = 0; + virtual const std::string& AttributeProto__name(const ONNX_NAMESPACE::AttributeProto* p) const = 0; virtual ONNX_NAMESPACE::AttributeProto_AttributeType AttributeProto__type(const ONNX_NAMESPACE::AttributeProto* p) = 0; virtual int AttributeProto__ints_size(const ONNX_NAMESPACE::AttributeProto* p) = 0; virtual int AttributeProto__floats_size(const ONNX_NAMESPACE::AttributeProto* p) = 0; @@ -360,6 +361,13 @@ struct ProviderHost { virtual void ModelProto__set_ir_version(ONNX_NAMESPACE::ModelProto* p, int64_t value) = 0; + // NodeProto + virtual std::unique_ptr NodeProto__construct() = 0; + virtual void NodeProto__operator_delete(ONNX_NAMESPACE::NodeProto* p) = 0; + virtual void NodeProto__operator_assign(ONNX_NAMESPACE::NodeProto* p, const ONNX_NAMESPACE::NodeProto& v) = 0; + virtual int NodeProto__attribute_size(ONNX_NAMESPACE::NodeProto* p) = 0; + virtual const ONNX_NAMESPACE::AttributeProto& NodeProto__attribute(const ONNX_NAMESPACE::NodeProto* p, int index) const = 0; + // TensorProto virtual std::unique_ptr TensorProto__construct() = 0; virtual void TensorProto__operator_delete(ONNX_NAMESPACE::TensorProto* p) = 0; @@ -586,6 +594,7 @@ struct ProviderHost { virtual ConstPointerContainer> Node__InputDefs(const Node* p) noexcept = 0; virtual ConstPointerContainer> Node__OutputDefs(const Node* p) noexcept = 0; virtual NodeIndex Node__Index(const Node* p) noexcept = 0; + virtual std::vector> Node__GetSubgraphs(const Node* p) const noexcept = 0; virtual void Node__ToProto(const Node* p, ONNX_NAMESPACE::NodeProto& proto, bool update_subgraphs = false) = 0; @@ -627,6 +636,8 @@ struct ProviderHost { virtual std::unique_ptr NodeAttributes__end(const NodeAttributes* p) = 0; virtual std::unique_ptr NodeAttributes__find(const NodeAttributes* p, const std::string& key) = 0; virtual void NodeAttributes__insert(NodeAttributes* p, const NodeAttributes& v) = 0; + virtual void NodeAttributes__emplace(NodeAttributes* p, const std::string& k, const ONNX_NAMESPACE::AttributeProto& v) = 0; + virtual void NodeAttributes__reserve(NodeAttributes* p, size_t size) = 0; // Model virtual void Model__operator_delete(Model* p) = 0; @@ -649,6 +660,8 @@ struct ProviderHost { virtual const std::vector& Graph__GetInputs(const Graph* p) noexcept = 0; virtual bool Graph__GetInitializedTensor(const Graph* p, const std::string& tensor_name, const ONNX_NAMESPACE::TensorProto*& value) = 0; + virtual const Node* Graph__ParentNode(const Graph* p) const = 0; + // GraphViewer virtual void GraphViewer__operator_delete(GraphViewer* p) = 0; virtual std::unique_ptr GraphViewer__CreateModel(const GraphViewer* p, const logging::Logger& logger) = 0; @@ -660,7 +673,9 @@ struct ProviderHost { virtual const NodeArg* GraphViewer__GetNodeArg(const GraphViewer* p, const std::string& name) = 0; virtual bool GraphViewer__IsSubgraph(const GraphViewer* p) = 0; + virtual const Graph& GraphViewer__GetGraph(const GraphViewer* p) const = 0; virtual bool GraphViewer__IsConstantInitializer(const GraphViewer* p, const std::string& name, bool check_outer_scope) = 0; + virtual const Node* GraphViewer__ParentNode(const GraphViewer* p) = 0; virtual int GraphViewer__NumberOfNodes(const GraphViewer* p) noexcept = 0; virtual int GraphViewer__MaxNodeIndex(const GraphViewer* p) noexcept = 0; diff --git a/onnxruntime/core/providers/shared_library/provider_wrappedtypes.h b/onnxruntime/core/providers/shared_library/provider_wrappedtypes.h index 6e43ff20be..597ee06850 100644 --- a/onnxruntime/core/providers/shared_library/provider_wrappedtypes.h +++ b/onnxruntime/core/providers/shared_library/provider_wrappedtypes.h @@ -57,6 +57,7 @@ struct AttributeProto final { void operator=(const AttributeProto& v) { g_host->AttributeProto__operator_assign(this, v); } static void operator delete(void* p) { g_host->AttributeProto__operator_delete(reinterpret_cast(p)); } + const std::string& name() const { return g_host->AttributeProto__name(this); } AttributeProto_AttributeType type() const { return g_host->AttributeProto__type(this); } int ints_size() const { return g_host->AttributeProto__ints_size(this); } int floats_size() const { return g_host->AttributeProto__floats_size(this); } @@ -136,6 +137,17 @@ struct ModelProto final { void operator=(const ModelProto&) = delete; }; +struct NodeProto final { + static std::unique_ptr Create() { return g_host->NodeProto__construct(); } + static void operator delete(void* p) { g_host->NodeProto__operator_delete(reinterpret_cast(p)); } + void operator=(const NodeProto& v) { g_host->NodeProto__operator_assign(this, v); } + int attribute_size() { return g_host->NodeProto__attribute_size(this); } + const AttributeProto& attribute(int index) const { return g_host->NodeProto__attribute(this, index); } + + NodeProto() = delete; + NodeProto(const NodeProto&) = delete; +}; + struct TensorProto final { static std::unique_ptr Create() { return g_host->TensorProto__construct(); } static void operator delete(void* p) { g_host->TensorProto__operator_delete(reinterpret_cast(p)); } @@ -574,6 +586,8 @@ struct Node final { ConstPointerContainer> OutputDefs() const noexcept { return g_host->Node__OutputDefs(this); } NodeIndex Index() const noexcept { return g_host->Node__Index(this); } + std::vector> GetSubgraphs() const noexcept { return g_host->Node__GetSubgraphs(this); } + void ToProto(ONNX_NAMESPACE::NodeProto& proto, bool update_subgraphs = false) const { return g_host->Node__ToProto(this, proto, update_subgraphs); } const NodeAttributes& GetAttributes() const noexcept { return g_host->Node__GetAttributes(this); } @@ -646,6 +660,8 @@ struct NodeAttributes final { IteratorHolder> end() const { return g_host->NodeAttributes__end(this); } IteratorHolder> find(const std::string& key) const { return g_host->NodeAttributes__find(this, key); } void insert(const NodeAttributes& v) { return g_host->NodeAttributes__insert(this, v); } + void emplace(const std::string& k, const ONNX_NAMESPACE::AttributeProto& v) { g_host->NodeAttributes__emplace(this, k, v); } + void reserve(size_t size) { g_host->NodeAttributes__reserve(this, size); } NodeAttributes() = delete; NodeAttributes(const NodeAttributes&) = delete; @@ -680,6 +696,8 @@ struct Graph final { bool GetInitializedTensor(const std::string& tensor_name, const ONNX_NAMESPACE::TensorProto*& value) const { return g_host->Graph__GetInitializedTensor(this, tensor_name, value); } + const Node* ParentNode() const { return g_host->Graph__ParentNode(this); } + PROVIDER_DISALLOW_ALL(Graph) }; @@ -695,7 +713,9 @@ struct GraphViewer final { const NodeArg* GetNodeArg(const std::string& name) const { return g_host->GraphViewer__GetNodeArg(this, name); } bool IsSubgraph() const { return g_host->GraphViewer__IsSubgraph(this); } + const Graph& GetGraph() const { return g_host->GraphViewer__GetGraph(this); } bool IsConstantInitializer(const std::string& name, bool check_outer_scope) const { return g_host->GraphViewer__IsConstantInitializer(this, name, check_outer_scope); } + const Node* ParentNode() const { return g_host->GraphViewer__ParentNode(this); } int NumberOfNodes() const noexcept { return g_host->GraphViewer__NumberOfNodes(this); } int MaxNodeIndex() const noexcept { return g_host->GraphViewer__MaxNodeIndex(this); } diff --git a/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.cc b/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.cc index c8d426f1ec..9ae1305de1 100644 --- a/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.cc +++ b/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.cc @@ -555,6 +555,46 @@ Status TensorrtExecutionProvider::SetComputeStream(void* stream) { return Status::OK(); } +// Check the graph is the subgraph of control flow op +bool TensorrtExecutionProvider::IsSubGraphOfControlFlowOp(const GraphViewer& graph) const { + if (graph.IsSubgraph()) { + const auto& node = graph.ParentNode(); + if (control_flow_op_set_.find(node->OpType()) != control_flow_op_set_.end()) { + return true; + } + } + return false; +} + + +// Check whether all the nodes of the graph are assigned to specific ep +bool TensorrtExecutionProvider::AllNodesAssignedToSpecificEP(const GraphViewer& graph, const std::string& provider_type) const { + const int number_of_ort_nodes = graph.NumberOfNodes(); + std::vector nodes_vector(number_of_ort_nodes); + std::iota(std::begin(nodes_vector), std::end(nodes_vector), 0); + const std::vector& node_index = graph.GetNodesInTopologicalOrder(); + for (const auto& index : nodes_vector) { + const auto& node = graph.GetNode(node_index[index]); + if (node->GetExecutionProviderType() != provider_type) { + return false; + } + } + + return number_of_ort_nodes != 0; +} + +// Check whether all the nodes of subgraph are supported +bool TensorrtExecutionProvider::IsSubGraphFullySupported(SubGraphCollection_t supported_nodes_vector, const int number_of_ort_nodes) const { + int number_of_trt_nodes = 0; + for (const auto& group : supported_nodes_vector) { + if (!group.first.empty()) { + number_of_trt_nodes += static_cast(group.first.size()); + } + } + + return number_of_trt_nodes == number_of_ort_nodes; +} + std::unique_ptr TensorrtExecutionProvider::GetSubGraph(SubGraph_t graph_nodes_index, const GraphViewer& graph) const { const std::vector& node_index = graph.GetNodesInTopologicalOrder(); std::unordered_set node_set; @@ -767,7 +807,29 @@ SubGraphCollection_t TensorrtExecutionProvider::GetSupportedList(SubGraphCollect subgraph_output_names.push_back(name); } } - graph_build.AddNode(node->Name(), node->OpType(), node->Description(), inputs, outputs, &node->GetAttributes(), node->Domain()); + + // If the node has subgraph, it's possible that the ORT graph of that subgraph and the GraphProto in the node attributes are not in sync because of graph optimization. + // Therefore, we need to force GraphProto attributes to be updated in order to get the valid GraphProto. + if (node->GetAttributes().size() > 0) { + auto node_proto = ONNX_NAMESPACE::NodeProto::Create(); + // we need to update any GraphProto attributes for subgraphs so that any changes made by things + // such as the optimizers are captured. otherwise we can end up saving an invalid graph. + node->ToProto(*node_proto, /* update_subgraphs */ true); + const int num_attributes = node_proto->attribute_size(); + auto node_attributes = ONNX_NAMESPACE::NodeAttributes::Create(); + node_attributes->reserve(num_attributes); + + for (int i = 0; i < num_attributes; ++i) { + auto& attr = node_proto->attribute(i); + node_attributes->emplace(attr.name(), attr); + } + + // The GraphProto attributes are the updated ones. + graph_build.AddNode(node->Name(), node->OpType(), node->Description(), inputs, outputs, node_attributes.get(), node->Domain()); + } else { + // The GraphProto attributes are the original ones. + graph_build.AddNode(node->Name(), node->OpType(), node->Description(), inputs, outputs, &node->GetAttributes(), node->Domain()); + } } ORT_ENFORCE(graph_build.Resolve().IsOK()); @@ -984,16 +1046,36 @@ TensorrtExecutionProvider::GetCapability(const GraphViewer& graph, const int number_of_ort_nodes = graph.NumberOfNodes(); std::vector nodes_vector(number_of_ort_nodes); std::iota(std::begin(nodes_vector), std::end(nodes_vector), 0); + std::vector filtered_nodes_vector; const std::vector& node_index = graph.GetNodesInTopologicalOrder(); - - // We currently exclude "If" and "Loop" control flow ops from original node vector before calling TensorRT parser. - // The reason is, these control flow ops have subgraph which might contain TRT fused node after ORT partition. - // If this is the case, TensorRT parser will complain the non-recognized TRT fused node and fail. for (const auto& index : nodes_vector) { const auto& node = graph.GetNode(node_index[index]); - if (node->OpType() == "If" || node->OpType() == "Loop" || node->OpType() == "Scan") { - continue; + + /* If current node is control flow op, we take different approach based on following four cases: + * + * (1) control flow op is supported by TRT, and its subgraphs are all supported by TRT. Assign this node to TRT. + * (2) control flow op is supported by TRT, but not all its subgraphs supported by TRT. Don't assign this node to TRT. + * (3) control flow op is not supported by TRT, but its subgraphs all supported by TRT. Don't assign this node to TRT. + * (4) control flow op is not supported by TRT, and not all its subgraphs supported by TRT. Don't assign this node to TRT. + * + * For cases 2, 3, 4, even though the control flow op is not assigned to TRT, any portion of its subgraphs that can run in TRT will be still fused and assigned to TRT EP. + */ + if (control_flow_op_set_.find(node->OpType()) != control_flow_op_set_.end()) { + auto sub_graphs = node->GetSubgraphs(); + if (sub_graphs.size() != 0) { + bool all_subgraphs_are_supported = true; + for (auto sub_graph : sub_graphs) { + if (!AllNodesAssignedToSpecificEP(*(sub_graph->CreateGraphViewer()), kTensorrtExecutionProvider)) { + all_subgraphs_are_supported = false; + break; + } + } + if (!all_subgraphs_are_supported) { + // if not all its subgraphs are supported, we need to exclude this control flow op + continue; + } + } } filtered_nodes_vector.push_back(index); } @@ -1035,6 +1117,52 @@ TensorrtExecutionProvider::GetCapability(const GraphViewer& graph, // Construct subgraph capability from node list std::vector> result; + + // Handle the case where the graph is subgraph of control flow op. + // The purpose is to make control flow op as well as its subgraphs run on TRT. + // Here we need to check whether subgraph is fully supported by TRT and don't fuse the nodes of the subgraph until control flow op level. + if (IsSubGraphOfControlFlowOp(graph) && IsSubGraphFullySupported(supported_nodes_vector, number_of_ort_nodes)) { + const std::vector& node_index = graph.GetNodesInTopologicalOrder(); + bool all_subgraphs_are_supported = true; + + // "If" control flow op has two subgraph bodies, "then" body and "else" body respectively. + // Check its parent node's another subgraph to see whether that subgraph is also fully supported by TRT. + if (graph.ParentNode()->OpType() == "If") { + all_subgraphs_are_supported = false; + SubGraphCollection_t subgraph_supported_nodes_vector; + auto sub_graphs = graph.ParentNode()->GetSubgraphs(); + for (auto sub_graph : sub_graphs) { + if (sub_graph.get() != &graph.GetGraph()) { + auto sub_graph_veiwer = sub_graph->CreateGraphViewer(); + const int number_of_ort_subgraph_nodes = sub_graph_veiwer->NumberOfNodes(); + std::vector subgraph_nodes_vector(number_of_ort_subgraph_nodes); + std::iota(std::begin(subgraph_nodes_vector), std::end(subgraph_nodes_vector), 0); + SubGraphCollection_t parser_subgraph_nodes_vector = {{subgraph_nodes_vector, false}}; + bool subgraph_early_termination = false; + subgraph_supported_nodes_vector = GetSupportedList(parser_subgraph_nodes_vector, 0, max_partition_iterations_, *sub_graph_veiwer, &subgraph_early_termination); + all_subgraphs_are_supported = IsSubGraphFullySupported(subgraph_supported_nodes_vector, number_of_ort_subgraph_nodes); + break; + } + } + } + + if (all_subgraphs_are_supported) { + // We want the subgraph nodes to be assigned to TRT EP but don't want them to be fused until later at the control flow op level. + // Simply request the subgraph nodes with a single ComputeCapability for each with no MetaDef (i.e. what the default implementation for IExecutionProvider::GetCapability does). + for (const auto& group : supported_nodes_vector) { + if (!group.first.empty()) { + for (const auto& index : group.first) { + std::unique_ptr sub_graph = onnxruntime::IndexedSubGraph::Create(); + sub_graph->Nodes().push_back(node_index[index]); + result.push_back(ComputeCapability::Create(std::move(sub_graph))); + } + } + } + LOGS_DEFAULT(INFO) << "[TensorRT EP] Whole graph will run on TensorRT execution provider"; + return result; + } + } + int number_of_trt_nodes = 0; for (const auto& group : supported_nodes_vector) { if (!group.first.empty()) { diff --git a/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.h b/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.h index c74543b5c2..76ee443ca5 100644 --- a/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.h +++ b/onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.h @@ -175,6 +175,7 @@ class TensorrtExecutionProvider : public IExecutionProvider { int (*engine_decryption_)(const char*, char*, size_t*); int (*engine_encryption_)(const char*, char*, size_t); + std::unordered_set control_flow_op_set_ = {"If", "Loop", "Scan"}; std::unordered_map> parsers_; std::unordered_map> engines_; std::unordered_map> contexts_; @@ -206,5 +207,15 @@ class TensorrtExecutionProvider : public IExecutionProvider { should be protected by a lock when invoked by multiple threads concurrently. */ std::unique_lock GetApiLock() const; + + /**Check the graph is the subgraph of control flow op*/ + bool IsSubGraphOfControlFlowOp(const GraphViewer& graph) const; + + /**Check whether all the nodes of the graph are assigned to specific ep*/ + bool AllNodesAssignedToSpecificEP(const GraphViewer& graph, const std::string& provider_type) const; + + /**Check whether all the nodes of subgraph are supported*/ + bool IsSubGraphFullySupported(SubGraphCollection_t supported_nodes_vector, const int number_of_ort_nodes) const; + }; } // namespace onnxruntime diff --git a/onnxruntime/core/session/provider_bridge_ort.cc b/onnxruntime/core/session/provider_bridge_ort.cc index bd75d1ff31..32ff604ca0 100644 --- a/onnxruntime/core/session/provider_bridge_ort.cc +++ b/onnxruntime/core/session/provider_bridge_ort.cc @@ -385,6 +385,7 @@ struct ProviderHostImpl : ProviderHost { void AttributeProto__operator_delete(ONNX_NAMESPACE::AttributeProto* p) override { delete p; } void AttributeProto__operator_assign(ONNX_NAMESPACE::AttributeProto* p, const ONNX_NAMESPACE::AttributeProto& v) override { *p = v; } + const std::string& AttributeProto__name(const ONNX_NAMESPACE::AttributeProto* p) const override { return p->name(); } ONNX_NAMESPACE::AttributeProto_AttributeType AttributeProto__type(const ONNX_NAMESPACE::AttributeProto* p) override { return p->type(); } int AttributeProto__ints_size(const ONNX_NAMESPACE::AttributeProto* p) override { return p->ints_size(); } int AttributeProto__floats_size(const ONNX_NAMESPACE::AttributeProto* p) override { return p->floats_size(); } @@ -433,6 +434,13 @@ struct ProviderHostImpl : ProviderHost { void ModelProto__set_ir_version(ONNX_NAMESPACE::ModelProto* p, int64_t value) override { p->set_ir_version(value); } + // NodeProto (wrapped) + std::unique_ptr NodeProto__construct() override { return std::make_unique(); } + void NodeProto__operator_delete(ONNX_NAMESPACE::NodeProto* p) override { delete p; } + void NodeProto__operator_assign(ONNX_NAMESPACE::NodeProto* p, const ONNX_NAMESPACE::NodeProto& v) override { *p = v; } + int NodeProto__attribute_size(ONNX_NAMESPACE::NodeProto* p) override { return p->attribute_size();} + const ONNX_NAMESPACE::AttributeProto& NodeProto__attribute(const ONNX_NAMESPACE::NodeProto* p, int index) const override { return p->attribute(index); } + // TensorProto (wrapped) std::unique_ptr TensorProto__construct() override { return std::make_unique(); } void TensorProto__operator_delete(ONNX_NAMESPACE::TensorProto* p) override { delete p; } @@ -669,6 +677,7 @@ struct ProviderHostImpl : ProviderHost { ConstPointerContainer> Node__InputDefs(const Node* p) noexcept override { return p->InputDefs(); } ConstPointerContainer> Node__OutputDefs(const Node* p) noexcept override { return p->OutputDefs(); } NodeIndex Node__Index(const Node* p) noexcept override { return p->Index(); } + std::vector> Node__GetSubgraphs(const Node* p) const noexcept override { return p->GetSubgraphs(); } void Node__ToProto(const Node* p, ONNX_NAMESPACE::NodeProto& proto, bool update_subgraphs = false) override { p->ToProto(proto, update_subgraphs); } @@ -715,6 +724,8 @@ struct ProviderHostImpl : ProviderHost { return std::make_unique(p->find(key)); } void NodeAttributes__insert(NodeAttributes* p, const NodeAttributes& v) override { return p->insert(v.begin(), v.end()); } + void NodeAttributes__emplace(NodeAttributes* p, const std::string& k, const ONNX_NAMESPACE::AttributeProto& v) override { p->emplace(k, v); } + void NodeAttributes__reserve(NodeAttributes* p, size_t size) override { p->reserve(size); } // Model (wrapped) void Model__operator_delete(Model* p) override { delete p; } @@ -739,6 +750,8 @@ struct ProviderHostImpl : ProviderHost { const std::vector& Graph__GetInputs(const Graph* p) noexcept override { return p->GetInputs(); } bool Graph__GetInitializedTensor(const Graph* p, const std::string& tensor_name, const ONNX_NAMESPACE::TensorProto*& value) override { return p->GetInitializedTensor(tensor_name, value); } + const Node* Graph__ParentNode(const Graph* p) const override { return p->ParentNode(); } + // GraphViewer (wrapped) void GraphViewer__operator_delete(GraphViewer* p) override { delete p; } std::unique_ptr GraphViewer__CreateModel(const GraphViewer* graph_viewer, const logging::Logger& logger) override { @@ -758,7 +771,9 @@ struct ProviderHostImpl : ProviderHost { const NodeArg* GraphViewer__GetNodeArg(const GraphViewer* p, const std::string& name) override { return p->GetNodeArg(name); } bool GraphViewer__IsSubgraph(const GraphViewer* p) override { return p->IsSubgraph(); } + const Graph& GraphViewer__GetGraph(const GraphViewer* p) const override { return p->GetGraph(); } bool GraphViewer__IsConstantInitializer(const GraphViewer* p, const std::string& name, bool check_outer_scope) override { return p->IsConstantInitializer(name, check_outer_scope); } + const Node* GraphViewer__ParentNode(const GraphViewer* p) override { return p->ParentNode(); } int GraphViewer__NumberOfNodes(const GraphViewer* p) noexcept override { return p->NumberOfNodes(); } int GraphViewer__MaxNodeIndex(const GraphViewer* p) noexcept override { return p->MaxNodeIndex(); } diff --git a/onnxruntime/test/framework/inference_session_test.cc b/onnxruntime/test/framework/inference_session_test.cc index 4ccf5298eb..bb6dda9ffd 100644 --- a/onnxruntime/test/framework/inference_session_test.cc +++ b/onnxruntime/test/framework/inference_session_test.cc @@ -1526,6 +1526,22 @@ TEST(InferenceSessionTests, Test3LayerNestedSubgraph) { status = session_object.Run(run_options, feeds, output_names, &fetches); ASSERT_TRUE(status.IsOK()); VerifyOutputs(fetches, expected_dims, expected_values); + +#if USE_TENSORRT + // previous run with graph being optimized, one of If node’s both subgraphs become empty, so TRT EP won’t assign this If node to TRT and later ORT assign it to CUDA. + // we also want to test graph not being optimized and TRT EP should also be able to run it and make the whole graph run on TRT. + so.graph_optimization_level = TransformerLevel::Default; + InferenceSession session_object_2{so, GetEnvironment()}; + ASSERT_STATUS_OK(session_object_2.RegisterExecutionProvider(DefaultTensorrtExecutionProvider())); + status = session_object_2.Load(model_file_name); + ASSERT_TRUE(status.IsOK()); + status = session_object_2.Initialize(); + ASSERT_TRUE(status.IsOK()); + // Now run + status = session_object_2.Run(run_options, feeds, output_names, &fetches); + ASSERT_TRUE(status.IsOK()); + VerifyOutputs(fetches, expected_dims, expected_values); +#endif } TEST(InferenceSessionTests, Test2LayerNestedSubgraph) {