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

Code before:
        continue;
      }

      auto indexed_sub_graph = std::make_unique<IndexedSubGraph>();

      indexed_sub_graph->nodes.push_back(node.Index());
      result.push_back(std::make_unique<ComputeCapability>(std::move(indexed_sub_graph)));
    } else if (node_grouping.kind == OrtEpGraphSupportInfo::NodeGroupingKind::kFusedNode) {
      if (node_grouping.nodes.empty()) {
        // The EpGraphSupportInfo_AddNodesToFuse() C API should already return an error if the EP tries to provide
        // an empty array of nodes from OrtEp::GetCapability(). However, we check here too just in case this changes.
        LOGS(logger, ERROR) << "OrtEp::GetCapability() for " << Type() << " set an empty array of nodes "
                            << "when specifying supported nodes.";
        return {};
      }

      std::unordered_set<const Node*> node_set;
      node_set.reserve(node_grouping.nodes.size());

      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);

      // Check if utils::CreateSupportedPartitions returned zero results.
      // Happens if nodes have already been assigned to another EP.
      if (capabilities.empty()) {
        log_unsupported_node_info(node_grouping.nodes);
        continue;
      }

      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 if the nodes in node_set do not match the nodes in capabilities[0], which occurs when EP selects nodes
      // assigned to a different EP.

Review comment (suggestion):
I think the `capabilities.size() > 1` case could also happen with a node that is assigned to another EP. e.g., if such a node is in between two supported nodes.

a few options I can think - treat that as an error, ignore a node_grouping like that, or add a capability (or capabilities) comprised of nodes that aren't assigned to another EP. not sure which is best.

it might be more consistent to warn and then ignore the entire node_grouping if it contains any nodes assigned to another EPs in all cases (single node, group with node assigned to another EP that partitions the group, and group with node assigned to another EP that doesn't).

Code after:
        return {};
      }

      std::unordered_set<const Node*> node_set;
      node_set.reserve(node_grouping.nodes.size());

      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;