Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e1735d4
update
chilo-ms Dec 3, 2025
3a3e63d
fix compile error
chilo-ms Dec 3, 2025
982d6dc
lintrunner -a
chilo-ms Dec 3, 2025
72fff82
add back test Check_Graph_GetSubgraph()
chilo-ms Dec 3, 2025
eb8c938
Add implementation for GetConsumerNodes() for MINIMAL_BUILD
chilo-ms Dec 3, 2025
ade76c5
Add check node in EpGraph when getting GetProducerInfo/GetConsumerInfo
chilo-ms Dec 8, 2025
059c918
Log warning if the node is outside of the subgraph
chilo-ms Dec 9, 2025
0eafb99
Make EpGraph create parent node EpNode if the graph is a subgraph of …
chilo-ms Dec 16, 2025
d94ba2a
add macro for non minimal build
chilo-ms Dec 18, 2025
3bdca16
Handle outer scope initializers for the subgraph
chilo-ms Dec 19, 2025
0f96d23
update ort_graph_to_proto.h
chilo-ms Jan 5, 2026
cfadecd
Merge branch 'main' into chi/update_graph_view_api
chilo-ms Jan 5, 2026
373d828
update ort_graph_to_proto.h
chilo-ms Jan 5, 2026
2e81538
use graph.GetInitializer() instead of graph.GetConstantInitializer()
chilo-ms Jan 5, 2026
d245852
update ort_graph_to_proto.h to include missing initializers
chilo-ms Jan 13, 2026
b863464
Use the unified implementation for node arg to consumer nodes across …
chilo-ms Jan 14, 2026
492efa3
address reviewer's comment
chilo-ms Jan 14, 2026
53f1553
add comments to functions
chilo-ms Jan 14, 2026
7fbaa70
address reveiwer's comments
chilo-ms Jan 15, 2026
428b2e9
address reviewr's comments
chilo-ms Jan 16, 2026
e9c1f8e
address reviewer's comment
chilo-ms Jan 19, 2026
e833a4a
Revert the code in GraphViewer so that it stays the old behavior that…
chilo-ms Jan 19, 2026
78774d1
address reviewer's comments
chilo-ms Jan 19, 2026
ef35e91
address reviewer's comments
chilo-ms Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions include/onnxruntime/core/providers/utils/ort_graph_to_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
#define INCLUDE_ONNXRUNTIME_CORE_PROVIDERS_UTILS_ORT_GRAPH_TO_PROTO_H_

#include <functional>
#include <optional>
#include "core/session/onnxruntime_cxx_api.h"
#include "onnx/onnx_pb.h"

Expand Down Expand Up @@ -320,6 +321,9 @@ Ort::Status OrtGraphToProto(const OrtGraph& graph,
// For values defined in an outer scope, just add the value info but not the initializer.
Comment thread
chilo-ms marked this conversation as resolved.
Outdated
if (is_from_outer_scope) {
value_infos.emplace(value_name, ort_value_info);
if (is_constant_initializer) {
initializer_value_infos.emplace(value_name, ort_value_info);
}
} else if (is_optional_graph_input) {
initializer_value_infos.emplace(value_name, ort_value_info);
} else if (is_constant_initializer) {
Expand Down Expand Up @@ -413,6 +417,13 @@ Ort::Status OrtGraphToProto(const OrtGraph& graph,
ORT_EP_UTILS_CXX_RETURN_IF_ERROR(OrtValueInfoToProto(value_info, *value_info_proto));
}

// There might be some initializers in the original OrtGraph that are not added yet.
Comment thread
adrianlizarraga marked this conversation as resolved.
Outdated
// Add those missing initializers and skip the ones that already in `initializer_value_infos`
std::vector<Ort::ConstValueInfo> ort_graph_initializers = ort_graph.GetInitializers();
for (const auto& initializer : ort_graph_initializers) {
initializer_value_infos.emplace(initializer.GetName(), initializer);
}

// Add initializers to GraphProto as TensorProto objects.
for (const auto& [initializer_name, initializer_value_info] : initializer_value_infos) {
std::vector<int64_t> initializer_dims;
Expand Down Expand Up @@ -492,8 +503,6 @@ Ort::Status OrtGraphToProto(const OrtGraph& graph,
try {
// Check that OrtGraph is a top-level graph (no parent node).
Comment thread
chilo-ms marked this conversation as resolved.
Outdated
Ort::ConstGraph ort_graph{&graph};
Ort::ConstNode parent_node = ort_graph.GetParentNode();
ORT_EP_UTILS_C_RETURN_IF(parent_node != nullptr, "Cannot serialize nested OrtGraph into a ModelProto");

// Set model description.
model_proto.set_doc_string("Serialized from OrtGraph");
Expand Down
3 changes: 2 additions & 1 deletion include/onnxruntime/core/session/onnxruntime_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -5917,7 +5917,8 @@ struct OrtApi {
/** \brief Returns an OrtGraph that contains a subset of nodes in the source OrtGraph.
*
* \note The lifetime of "dst_graph" is tied to that of "src_graph", as they both internally reference
* the same underlying graph.
* the same underlying graph. "dst_graph" preserves the input order of "src_graph", and
* its output order corresponds to the outputs produced by the nodes in "nodes" with the given order.
*
* \param[in] src_graph The source OrtGraph instance.
* \param[in] nodes A subset of the nodes/OrtNodes in 'graph'.
Expand Down
43 changes: 37 additions & 6 deletions onnxruntime/core/graph/ep_api_types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ Status EpValueInfo::GetProducerInfo(OrtValueInfo::ProducerInfo& producer_info) c
producer_info.output_index = 0;

if (graph_ == nullptr) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Unable to get producer node for OrtValueInfo '", name_,
return ORT_MAKE_STATUS(ONNXRUNTIME, NOT_FOUND, "Unable to get producer node for OrtValueInfo '", name_,
"' that is not owned by a OrtGraph.");
}

Expand All @@ -379,7 +379,15 @@ Status EpValueInfo::GetProducerInfo(OrtValueInfo::ProducerInfo& producer_info) c

const EpNode* ep_node = graph_->GetNode(node->Index());
if (ep_node == nullptr) {
return Status::OK(); // Node is not in this GraphViewer
producer_info.node = nullptr;
producer_info.output_index = 0;
#if !defined(ORT_MINIMAL_BUILD)
const auto& logger = graph_->GetGraphViewer().GetGraph().GetLogger();
LOGS(logger, WARNING) << "Unable to get producer node for OrtValueInfo '"
<< name_
<< "' that is not owned by an OrtGraph.";
#endif // !defined(ORT_MINIMAL_BUILD)
return Status::OK();
}

size_t output_index = 0;
Expand Down Expand Up @@ -539,10 +547,14 @@ void EpGraph::IndexToEpNodeMap::Resize(NodeIndex min_node_index, NodeIndex max_n
size_t num_elems = (max_node_index - min_node_index) + 1;

min_node_index_ = min_node_index;
max_node_index_ = max_node_index;
Comment thread
chilo-ms marked this conversation as resolved.
Outdated
nodes_.resize(num_elems, nullptr);
}

EpNode* EpGraph::IndexToEpNodeMap::GetEpNode(NodeIndex node_index) const {
if (node_index < min_node_index_ || node_index > max_node_index_) {
return nullptr;
}
size_t i = node_index - min_node_index_;
assert(i < nodes_.size());
Comment thread
chilo-ms marked this conversation as resolved.
return nodes_[i];
Expand All @@ -566,10 +578,10 @@ EpGraph::EpGraph(std::unique_ptr<GraphViewer> graph_viewer,
owned_indexed_sub_graph_(std::move(indexed_sub_graph)) {}

// Static class function to create a std::unique_ptr<EpGraph>.
Status EpGraph::Create(const GraphViewer& graph_viewer, /*out*/ std::unique_ptr<EpGraph>& result) {
Status EpGraph::Create(const GraphViewer& graph_viewer, /*out*/ std::unique_ptr<EpGraph>& result, bool create_parent_node) {
auto ep_graph = std::make_unique<EpGraph>(graph_viewer, PrivateTag{});

return CreateImpl(std::move(ep_graph), graph_viewer, result);
return CreateImpl(std::move(ep_graph), graph_viewer, result, create_parent_node);
}

// Static class function to create a std::unique_ptr<EpGraph>.
Expand All @@ -584,7 +596,8 @@ Status EpGraph::Create(std::unique_ptr<GraphViewer> src_graph_viewer,
return CreateImpl(std::move(ep_graph), graph_viewer, result);
}

Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer& graph_viewer, /*out*/ std::unique_ptr<EpGraph>& result) {
Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer& graph_viewer,
/*out*/ std::unique_ptr<EpGraph>& result, bool create_parent_node) {
AllocatorPtr initializer_allocator = CPUAllocator::DefaultInstance();
std::unordered_map<std::string, std::unique_ptr<EpValueInfo>> value_infos_map;

Expand Down Expand Up @@ -687,13 +700,23 @@ Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer&
}
}

std::unique_ptr<EpNode> ep_parent_node = nullptr;

// If this is a subgraph, add the OrtValueInfo and OrtValue objects that come from the outer scope.
// Wait until we have already processed OrtValueInfos consumed and produced by nodes so that we only add
// outer OrtValueInfo/OrtValue if they are actually used by the nodes in this GraphViewer.
if (graph_viewer.IsSubgraph()) {
gsl::not_null<const Graph*> parent_graph = graph_viewer.GetGraph().ParentGraph();
gsl::not_null<const Node*> parent_node = graph_viewer.ParentNode();

if (create_parent_node) {
std::unique_ptr<EpNode> ep_node = nullptr;

std::unordered_map<std::string, std::unique_ptr<EpValueInfo>> value_infos_map_tmp; // won't be used
ORT_RETURN_IF_ERROR(EpNode::Create(*parent_node, ep_graph.get(), value_infos_map_tmp, ep_node));
ep_parent_node = std::move(ep_node);
}

for (gsl::not_null<const NodeArg*> implicit_node_arg : parent_node->ImplicitInputDefs()) {
const std::string& implicit_name = implicit_node_arg->Name();
auto value_info_iter = value_infos_map.find(implicit_name);
Expand Down Expand Up @@ -741,6 +764,7 @@ Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer&
ep_graph->outer_scope_initializer_values_ = std::move(outer_scope_initializer_values);
ep_graph->inputs_ = std::move(graph_input_value_infos);
ep_graph->outputs_ = std::move(graph_output_value_infos);
ep_graph->parent_node_owned_ = std::move(ep_parent_node);

result = std::move(ep_graph);

Expand Down Expand Up @@ -872,7 +896,14 @@ Status EpGraph::GetNodes(gsl::span<const OrtNode*> dst) const {
}

Status EpGraph::GetParentNode(const OrtNode*& result) const {
result = parent_node_ != nullptr ? parent_node_->ToExternal() : nullptr;
if (parent_node_ != nullptr) {
result = parent_node_->ToExternal();
} else if (parent_node_owned_) {
result = parent_node_owned_->ToExternal();
} else {
result = nullptr;
}

return Status::OK();
}

Expand Down
22 changes: 18 additions & 4 deletions onnxruntime/core/graph/ep_api_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ struct EpGraph : public OrtGraph {

private:
NodeIndex min_node_index_ = 0;
NodeIndex max_node_index_ = 0;
std::vector<EpNode*> nodes_;
};

Expand All @@ -269,8 +270,16 @@ struct EpGraph : public OrtGraph {
/// </summary>
/// <param name="graph_viewer"></param>
/// <param name="result"></param>
/// <param name="create_parent_node">If the `graph_viewer` is a subgraph of a control flow op,
/// e.g. For/If/Scan op, and `create_parent_node` is set to true,
Comment thread
chilo-ms marked this conversation as resolved.
Outdated
/// then `result` EpGraph will create and own parent node's EpNode
/// instance. It's mainly used in EP's GetCapability() as it's
/// a bottom-up approach where inner-most subgraph will be constructed
/// first and by the time its parent node/graph hasn't be constructed yet.</param>
/// <returns></returns>
static Status Create(const GraphViewer& graph_viewer, /*out*/ std::unique_ptr<EpGraph>& result);
static Status Create(const GraphViewer& graph_viewer,
/*out*/ std::unique_ptr<EpGraph>& result,
bool create_parent_node = false);

/// <summary>
/// Creates an instance of EpGraph, which wraps a GraphViewer.
Expand Down Expand Up @@ -364,16 +373,21 @@ struct EpGraph : public OrtGraph {
private:
/// <summary>
/// The real implementation of creating an EpGraph instance.
/// Please use one of the above 'Create' functions that internally call this function, and avoid calling this function directly.
/// Please use one of the above 'Create' functions that internally call this function,
/// and avoid calling this function directly.
/// </summary>
/// <param name="ep_graph"></param>
/// <param name="graph_viewer"></param>
/// <param name="result"></param>
/// <param name="create_parent_node"></param>
/// <returns></returns>
static Status CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer& graph_viewer, /*out*/ std::unique_ptr<EpGraph>& result);
static Status CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer& graph_viewer,
/*out*/ std::unique_ptr<EpGraph>& result, bool create_parent_node = false);

const GraphViewer& graph_viewer_;
const EpNode* parent_node_ = nullptr;
const EpNode* parent_node_ = nullptr; // Keep the pointer to the parent node that
// is not owned by this graph
std::unique_ptr<EpNode> parent_node_owned_ = nullptr; // Hold the parent node created and owned by this graph
Comment thread
chilo-ms marked this conversation as resolved.
Outdated

std::unique_ptr<GraphViewer> owned_graph_viewer_ = nullptr;
std::unique_ptr<IndexedSubGraph> owned_indexed_sub_graph_ = nullptr;
Expand Down
6 changes: 4 additions & 2 deletions onnxruntime/core/graph/graph_viewer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,16 @@ GraphViewer::GraphViewer(const Graph& graph, const IndexedSubGraph* filter_info)
ORT_ENFORCE(node, "Mismatch between Graph and IndexedSubGraph. Node not found: ", node_idx);
const ONNX_NAMESPACE::TensorProto* tensor = nullptr;
for (const auto* node_input : node->InputDefs()) {
if (graph.GetInitializedTensor(node_input->Name(), tensor)) {
tensor = graph.GetInitializer(node_input->Name(), true);
Comment thread
chilo-ms marked this conversation as resolved.
Outdated
if (tensor) {
filtered_initializers_.insert({node_input->Name(), tensor});
}
}

// The implicit inputs for subgraphs (if any)
for (const auto* node_input : node->ImplicitInputDefs()) {
if (graph.GetInitializedTensor(node_input->Name(), tensor)) {
tensor = graph.GetInitializer(node_input->Name(), true);
if (tensor) {
filtered_initializers_.insert({node_input->Name(), tensor});
}
}
Expand Down
Loading
Loading