Repository: microsoft/onnxruntime
File: onnxruntime/core/session/plugin_ep/ep_plugin_provider_interfaces.cc (C++)

Code before:
  }

  return result;
}

Status PluginExecutionProvider::FusedNodeState::AddFusedNode(const Node& fused_node, /*out*/ EpNode*& added_ep_node) {
  std::unique_ptr<EpNode> unique_ep_fused_node = nullptr;
  ORT_RETURN_IF_ERROR(EpNode::Create(fused_node, /*parent graph*/ nullptr, this->value_infos, unique_ep_fused_node));
  this->nodes.push_back(std::move(unique_ep_fused_node));
  added_ep_node = this->nodes.back().get();
  return Status::OK();
}

/// <summary>
/// Converts the EPContext nodes provided by the plugin EP (OrtNode instances) to onnxruntime::Node instances.
/// Note that the EP plugin uses the model editor API to create the OrtNode instances.
/// </summary>
/// <param name="ep_name">Name of the plugin EP.</param>
/// <param name="fused_nodes">fused nodes provided by ORT.</param>
/// <param name="plugin_ep_context_nodes">EPContext nodes provided by the plugin EP.</param>
/// <param name="result_nodes">Output parameter set to the resulting array of EPContext nodes.</param>
/// <param name="result_node_args">Output parameter that stores the NodeArgs used by the EPContext nodes.</param>
/// <returns>A status indicating success or an error.</returns>
static Status ConvertEpContextNodes(const std::string& ep_name, const std::vector<IExecutionProvider::FusedNodeAndGraph>& fused_nodes, const std::vector<OrtNode*> plugin_ep_context_nodes,
                                    /*out*/ std::vector<std::unique_ptr<Node>>& result_nodes,
                                    /*out*/ std::vector<std::unique_ptr<NodeArg>>& result_node_args) {
#if !defined(ORT_MINIMAL_BUILD) || defined(ORT_EXTENDED_MINIMAL_BUILD) || defined(ORT_MINIMAL_BUILD_CUSTOM_OPS)
  if (plugin_ep_context_nodes.empty()) {
    return Status::OK();  // No EPContext nodes.
  }

  std::vector<std::unique_ptr<Node>> ep_context_nodes_holder;
  std::vector<std::unique_ptr<NodeArg>> ep_context_node_args_holder;

  ep_context_nodes_holder.reserve(plugin_ep_context_nodes.size());
  int index = -1;
  for (const OrtNode* ort_node : plugin_ep_context_nodes) {
    ++index;
    auto& fused_node_filtered_graph = fused_nodes[index].filtered_graph;
    ORT_RETURN_IF_NOT(ort_node != nullptr, ep_name, ": OrtEp::Compile() returned a NULL EPContext node.");

    const ModelEditorNode* editor_node = ModelEditorNode::ToInternal(ort_node);
    ORT_RETURN_IF_NOT(editor_node != nullptr, ep_name, ": OrtEp::Compile() returned OrtNode objects ",
                      "that were not created with OrtModelEditorApi.");

    // Create NodeArg for each input/output.
    std::vector<NodeArg*> input_node_args;
    std::vector<NodeArg*> output_node_args;

    input_node_args.reserve(editor_node->input_names.size());
    output_node_args.reserve(editor_node->output_names.size());

Review comment (suggestion):
hm, I wonder if the original `ORT_ENFORCE` checks are ok to keep. if those conditions aren't met something is not as expected, right? I suppose logging an error is ok too, but ORT_ENFORCE is louder.

Code after:

      for (const EpNode* ep_node : node_grouping.nodes) {
        node_set.insert(&ep_node->GetInternalNode());
      }

      // We now require the OrtEp to only provide individual groups of supported nodes that each maps to exactly
      // one ComputeCapability. Calling utils::CreateSupportedPartitions() may create multiple ComputeCapability
      // instances, and if so, log an error and return.
      //
      // TODO(adrianlizarraga): Do not use the heavy-weight CreateSupportedPartitions just to check if the user
      // provided a single partition. Use utils::MakeCapability() and create a new helper to check that there are no
      // unsupported nodes in any path between supported nodes.
      std::vector<std::unique_ptr<ComputeCapability>> capabilities = utils::CreateSupportedPartitions(
          graph_viewer, node_set, /*stop_ops*/ {}, PluginEpMetaDefNameFunctor(generator, graph_viewer, this->Type()),
          this->Type(), this->Type(), /*node_unit_map*/ nullptr,
          node_grouping.fusion_options.drop_constant_initializers);

      if (capabilities.size() != 1) {
        LOGS(logger, ERROR) << "OrtEp::GetCapability() for " << Type() << " set nodes that cannot be fused together. "
                            << "Please ensure that the nodes provided to EpGraphSupportInfo_AddNodesToFuse() do not "
                            << "have an unsupported node in any path between two of the supported nodes.";
        return {};
      }

      // Log an error if the nodes in node_set do not match the nodes in capabilities[0]. We expect this to always
      // be true because we've already checked that the EP did not try to claim nodes already assigned to another EP.
      // TODO(adrianlizarraga): This check can be removed when we stop using utils::CreateSupportedPartitions() above.
      std::vector<NodeIndex>& capability_node_indices = capabilities[0]->sub_graph->nodes;
      std::unordered_set<NodeIndex> capability_node_indices_set(capability_node_indices.begin(),
                                                                capability_node_indices.end());

      if (node_set.size() != capability_node_indices_set.size()) {
        LOGS(logger, ERROR) << "OrtEp::GetCapability() for " << Type()
                            << " set nodes that cannot all be fused together.";
        return {};
      }

      result.push_back(std::move(capabilities[0]));
    } else {
      LOGS(logger, ERROR) << "PluginExecutionProvider::GetCapability() has invalid NodeGroupingKind: "
                          << static_cast<int>(node_grouping.kind);
      return {};
    }
  }

  return result;
}

Status PluginExecutionProvider::FusedNodeState::AddFusedNode(const Node& fused_node, /*out*/ EpNode*& added_ep_node) {
  std::unique_ptr<EpNode> unique_ep_fused_node = nullptr;
  ORT_RETURN_IF_ERROR(EpNode::Create(fused_node, /*parent graph*/ nullptr, this->value_infos, unique_ep_fused_node));