diff --git a/onnxruntime/core/providers/coreml/builders/impl/softmax_op_builder.cc b/onnxruntime/core/providers/coreml/builders/impl/softmax_op_builder.cc new file mode 100644 index 0000000000000..c454a2a779f6e --- /dev/null +++ b/onnxruntime/core/providers/coreml/builders/impl/softmax_op_builder.cc @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "core/providers/coreml/builders/impl/base_op_builder.h" + +#include "core/framework/tensorprotoutils.h" +#include "core/providers/common.h" +#include "core/providers/coreml/shape_utils.h" +#include "core/providers/shared/utils/utils.h" + +#ifdef __APPLE__ +#include "core/providers/coreml/builders/model_builder.h" +#endif +#include "core/providers/coreml/builders/op_builder_factory.h" + +namespace onnxruntime { +namespace coreml { + +class SoftmaxOpBuilder : public BaseOpBuilder { + // Add operator related +#ifdef __APPLE__ + private: + Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node, + const logging::Logger& logger) const override; +#endif + + // Operator support related + private: + bool IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& input_params, + const logging::Logger& logger) const override; +}; + +// Add operator related + +#ifdef __APPLE__ + +Status SoftmaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, + const Node& node, + const logging::Logger& logger) const { + std::unique_ptr layer = CreateNNLayer(model_builder, node); + const auto& input_name = node.InputDefs()[0]->Name(); + const auto& output_name = node.OutputDefs()[0]->Name(); + + std::vector data_shape; + ORT_RETURN_IF_NOT(GetStaticShape(*node.InputDefs()[0], data_shape, logger), "Failed to get input shape."); + + NodeAttrHelper helper(node); + int32_t axis_default_value = (node.SinceVersion() < 13) ? 1 : -1; + const auto axis = helper.Get("axis", axis_default_value); + const auto axis_nonnegative = HandleNegativeAxis(axis, data_shape.size()); + + if (node.SinceVersion() >= 13 || (data_shape.size() == 2)) { + auto* coreml_softmaxnd = layer->mutable_softmaxnd(); + coreml_softmaxnd->set_axis(axis); + *layer->mutable_input()->Add() = input_name; + *layer->mutable_output()->Add() = output_name; + model_builder.AddLayer(std::move(layer)); + } else { + // note: if opsets < 13, onnx Softmax coerces the input shape to be 2D based on axis. + // we need to manually reshape to 2D and apply SoftmaxND to axis -1 to achieve equivalent results for CoreML. + TensorShape input_shape(data_shape); + const auto size_to_dimension = input_shape.SizeToDimension(axis_nonnegative); + const auto size_from_dimension = input_shape.SizeFromDimension(axis_nonnegative); + + TensorShapeVector target_shape; + target_shape.push_back(size_to_dimension); + target_shape.push_back(size_from_dimension); + + const auto reshape1_output_name = model_builder.GetUniqueName(MakeString(node.Name(), "reshape1_output")); + { // Add reshape layer + const auto softmax_reshape1_layer_name = + model_builder.GetUniqueName(MakeString(node.Name(), "_Softmax_reshape1")); + auto reshape_layer = CreateNNLayer(softmax_reshape1_layer_name); + *reshape_layer->mutable_reshapestatic()->mutable_targetshape() = {target_shape.cbegin(), target_shape.cend()}; + *reshape_layer->mutable_input()->Add() = input_name; + *reshape_layer->mutable_output()->Add() = reshape1_output_name; + model_builder.AddLayer(std::move(reshape_layer)); + } + const auto softmax_output_name = model_builder.GetUniqueName(MakeString(node.Name(), "softmax_output")); + { + auto* coreml_softmaxnd = layer->mutable_softmaxnd(); + coreml_softmaxnd->set_axis(-1); + *layer->mutable_input()->Add() = reshape1_output_name; + *layer->mutable_output()->Add() = softmax_output_name; + model_builder.AddLayer(std::move(layer)); + } + { + // Add reshape back layer + const auto softmax_reshape2_layer_name = + model_builder.GetUniqueName(MakeString(node.Name(), "_Softmax_reshape2")); + auto reshape_layer = CreateNNLayer(softmax_reshape2_layer_name); + *reshape_layer->mutable_reshapestatic()->mutable_targetshape() = {data_shape.cbegin(), data_shape.cend()}; + *reshape_layer->mutable_input()->Add() = softmax_output_name; + *reshape_layer->mutable_output()->Add() = output_name; + model_builder.AddLayer(std::move(reshape_layer)); + } + } + + return Status::OK(); +} + +#endif + +// Operator support related + +bool SoftmaxOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& /* input_params */, + const logging::Logger& logger) const { + const auto& input_defs = node.InputDefs(); + std::vector input_shape; + if (!GetStaticShape(*input_defs[0], input_shape, logger)) + return false; + + const TensorShape shape(input_shape); + if (shape.Size() == 0) { + LOGS(logger, VERBOSE) << "Empty input data is not supported."; + return false; + } + + return true; +} + +void CreateSoftmaxOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) { + op_registrations.builders.push_back(std::make_unique()); + op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get()); +} + +} // namespace coreml +} // namespace onnxruntime diff --git a/onnxruntime/core/providers/coreml/builders/impl/split_op_builder.cc b/onnxruntime/core/providers/coreml/builders/impl/split_op_builder.cc new file mode 100644 index 0000000000000..815f68128ffaf --- /dev/null +++ b/onnxruntime/core/providers/coreml/builders/impl/split_op_builder.cc @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "core/providers/coreml/builders/impl/base_op_builder.h" + +#include "core/optimizer/initializer.h" +#include "core/providers/common.h" +#include "core/providers/coreml/builders/helper.h" +#include "core/providers/coreml/builders/op_builder_factory.h" +#include "core/providers/coreml/shape_utils.h" +#include "core/providers/shared/utils/utils.h" + +#if defined(__APPLE__) +#include "core/providers/coreml/builders/model_builder.h" +#endif + +namespace onnxruntime { +namespace coreml { + +class SplitOpBuilder : public BaseOpBuilder { + // Add operator related +#ifdef __APPLE__ + private: + void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const override; + + private: + Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node, + const logging::Logger& logger) const override; +#endif + + // Operator support related + private: + bool IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& input_params, + const logging::Logger& logger) const override; + + // Split opset 13- uses "split" as attribute. Currently it's not supported. + int GetMinSupportedOpSet(const Node& /* node */) const override { return 13; } +}; + +// Add operator related + +#ifdef __APPLE__ + +void SplitOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const { + const auto& input_defs = node.InputDefs(); + + if (input_defs.size() > 1 && input_defs[1]->Exists()) { // optional second input "split" + model_builder.AddInitializerToSkip(input_defs[1]->Name()); + } +} + +Status SplitOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, + const Node& node, + const logging::Logger& logger) const { + const auto& input_defs = node.InputDefs(); + + std::vector data_shape; + ORT_RETURN_IF_NOT(GetShape(*node.InputDefs()[0], data_shape, logger), "Failed to get input shape."); + + NodeAttrHelper helper(node); + const auto axis = helper.Get("axis", 0); + + // attribute introduced since opset 18 + uint64_t num_outputs; + + std::unique_ptr layer = CreateNNLayer(model_builder, node); + auto* coreml_splitnd = layer->mutable_splitnd(); + coreml_splitnd->set_axis(axis); + + if (input_defs.size() > 1) { + // if "split" is explicitly provided as an input + const auto& split_tensor = *model_builder.GetInitializerTensors().at(input_defs[1]->Name()); + Initializer unpacked_tensor(split_tensor); + auto split_span = unpacked_tensor.DataAsSpan(); + auto split_sizes = split_span.size(); + num_outputs = narrow(split_sizes); + for (size_t i = 0; i < split_sizes; i++) { + coreml_splitnd->add_splitsizes(split_span[i]); + } + } else if (node.SinceVersion() < 18) { + num_outputs = narrow(node.OutputDefs().size()); + coreml_splitnd->set_numsplits(num_outputs); + } else { + // note: for opset 18+ 'num_outputs' is a required attribute + num_outputs = narrow(helper.GetInt("num_outputs").value()); + // note: checked in IsOpSupportedImpl that ensures the dim value at splitting axis exists + auto split_dim_size = data_shape[HandleNegativeAxis(axis, data_shape.size())]; + uint64_t chunk_size = narrow((split_dim_size + num_outputs - 1) / num_outputs); + uint64_t remainder = split_dim_size % chunk_size; + if (remainder) { + // uneven + auto split_sizes = InlinedVector(num_outputs, chunk_size); + split_sizes.back() = remainder; + for (size_t i = 0; i < split_sizes.size(); i++) { + coreml_splitnd->add_splitsizes(split_sizes[i]); + } + } else { + // even + coreml_splitnd->set_numsplits(num_outputs); + } + } + + *layer->mutable_input()->Add() = node.InputDefs()[0]->Name(); + // variadic number of outputs. Calculated based on the length of the given splitSizes if provided. + // Otherwise, uses attribute value 'num_outputs'. + for (uint64_t i = 0; i < num_outputs; i++) { + *layer->mutable_output()->Add() = node.OutputDefs()[i]->Name(); + } + model_builder.AddLayer(std::move(layer)); + + return Status::OK(); +} + +#endif + +// Operator support related + +bool SplitOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& input_params, + const logging::Logger& logger) const { + const auto& input_defs = node.InputDefs(); + const auto& initializers = input_params.graph_viewer.GetAllInitializedTensors(); + + NodeAttrHelper helper(node); + const auto axis = helper.Get("axis", 0); + + std::vector input_shape; + if (!GetShape(*input_defs[0], input_shape, logger)) + return false; + + const auto split_dims_at_axis = input_shape[HandleNegativeAxis(axis, input_shape.size())]; + if (input_defs.size() > 1 && input_defs[1]->Exists()) { + if (!CheckIsConstantInitializer(*input_defs[1], input_params.graph_viewer, logger, "'split'")) { + return false; + } + const auto split_shape = *input_defs[1]->Shape(); + if (split_shape.dim_size() < 2) { + LOGS(logger, VERBOSE) << "CoreML SplitND requires to produce at least 2 outputs."; + return false; + } + const auto& splits_tensor = *initializers.at(input_defs[1]->Name()); + Initializer unpacked_tensor(splits_tensor); + auto splits_span = unpacked_tensor.DataAsSpan(); + int sum_of_splits = std::accumulate(splits_span.begin(), splits_span.end(), 0); + if (sum_of_splits != split_dims_at_axis) { + LOGS(logger, VERBOSE) << "Mismatch between the sum of 'split'. Expected: " + << split_dims_at_axis + << "Actual: " + << sum_of_splits; + return false; + } + auto it = std::find(splits_span.begin(), splits_span.end(), 0); + if (it != splits_span.end()) { + LOGS(logger, VERBOSE) << "Invalid value in 'splits' input."; + return false; + } + if (split_dims_at_axis == -1) { + LOGS(logger, VERBOSE) << "Dim at the splitting axis is not allowed to be dynamic."; + return false; + } + } else { + if (node.SinceVersion() >= 18) { + const auto num_outputs = helper.GetInt("num_outputs"); + if (!num_outputs.has_value()) { + LOGS(logger, VERBOSE) << "No 'num_outputs' provided. For split 18+, num_outputs is a required attribute."; + return false; + } + if (num_outputs.value() < 2) { + LOGS(logger, VERBOSE) << "Invalid num_outputs. The value cannot be lower than 2.\n" + << "CoreML SplitND requires at least 2 outputs. num_outputs: " << num_outputs.value(); + return false; + } + if (num_outputs.value() != static_cast(node.OutputDefs().size()) || num_outputs.value() > split_dims_at_axis) { + LOGS(logger, VERBOSE) << "Invalid num_outputs provided.\n." + << "The value should be smaller or equal to the size of dimension being split. num_outputs: " + << num_outputs.value(); + return false; + } + } + } + return true; +} + +void CreateSplitOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) { + op_registrations.builders.push_back(std::make_unique()); + op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get()); +} + +} // namespace coreml +} // namespace onnxruntime diff --git a/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc b/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc index c1b09cec8a30a..2c06659852134 100644 --- a/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc +++ b/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc @@ -122,6 +122,14 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() { CreateSliceOpBuilder("Slice", op_registrations); } + { // Softmax + CreateSoftmaxOpBuilder("Softmax", op_registrations); + } + + { // Split + CreateSplitOpBuilder("Split", op_registrations); + } + return op_registrations; } diff --git a/onnxruntime/core/providers/coreml/builders/op_builder_factory.h b/onnxruntime/core/providers/coreml/builders/op_builder_factory.h index b2c8dc765d33d..d72420bcfff88 100644 --- a/onnxruntime/core/providers/coreml/builders/op_builder_factory.h +++ b/onnxruntime/core/providers/coreml/builders/op_builder_factory.h @@ -36,6 +36,8 @@ void CreateReshapeOpBuilder(const std::string& op_type, OpBuilderRegistrations& void CreateResizeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); void CreateShapeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); void CreateSliceOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); +void CreateSoftmaxOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); +void CreateSplitOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); void CreateSqueezeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); void CreateTransposeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); void CreateUnaryOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations); diff --git a/onnxruntime/core/providers/shared/utils/utils.cc b/onnxruntime/core/providers/shared/utils/utils.cc index 6b1207d3d16f0..39ea4dd8412bb 100644 --- a/onnxruntime/core/providers/shared/utils/utils.cc +++ b/onnxruntime/core/providers/shared/utils/utils.cc @@ -166,6 +166,12 @@ std::vector NodeAttrHelper::Get(const std::string& key, const std::vector return std::vector{source.cbegin(), source.cend()}; } +std::optional NodeAttrHelper::GetInt(const std::string& key) const { + if (!HasAttr(key)) + return std::nullopt; + return node_attributes_.at(key).i(); +} + bool NodeAttrHelper::HasAttr(const std::string& key) const { return Contains(node_attributes_, key); } diff --git a/onnxruntime/core/providers/shared/utils/utils.h b/onnxruntime/core/providers/shared/utils/utils.h index db07938c1897e..1e93f040711df 100644 --- a/onnxruntime/core/providers/shared/utils/utils.h +++ b/onnxruntime/core/providers/shared/utils/utils.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "core/graph/basic_types.h" @@ -57,6 +58,8 @@ class NodeAttrHelper { uint32_t Get(const std::string& key, uint32_t def_val) const; std::vector Get(const std::string& key, const std::vector& def_val) const; + std::optional GetInt(const std::string& key) const; + bool HasAttr(const std::string& key) const; private: diff --git a/onnxruntime/test/providers/cpu/math/softmax_test.cc b/onnxruntime/test/providers/cpu/math/softmax_test.cc index b94c17c3b0e24..6eb72255bdf9a 100644 --- a/onnxruntime/test/providers/cpu/math/softmax_test.cc +++ b/onnxruntime/test/providers/cpu/math/softmax_test.cc @@ -421,7 +421,7 @@ TEST(SoftmaxOperator, GH15949_regression_test) { {0.00032932f, 0.01798029f, 0.9816904f}); // disable TRT as it does not support axis=0 as used by the model - tester.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider, kCoreMLExecutionProvider}); + tester.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider}); } } // namespace test diff --git a/onnxruntime/test/providers/cpu/tensor/split_op_test.cc b/onnxruntime/test/providers/cpu/tensor/split_op_test.cc index 7712a0a5bf724..70a43d660decb 100644 --- a/onnxruntime/test/providers/cpu/tensor/split_op_test.cc +++ b/onnxruntime/test/providers/cpu/tensor/split_op_test.cc @@ -94,7 +94,7 @@ constexpr T ValueFromIdx(size_t idx) { } template -void SplitTestAxis0EqualSplit(bool use_opset_13 = false) { +void SplitTestAxis0EqualSplit() { SCOPED_TRACE(onnxruntime::MakeString("data type: ", utils::ToTensorProtoElementType())); constexpr int64_t axis = 0; @@ -117,11 +117,20 @@ void SplitTestAxis0EqualSplit(bool use_opset_13 = false) { {V(5), V(6), V(7), V(8)}}); + // BFloat16 added in opset 13 + if constexpr (!std::is_same_v) { + RunTest(axis, {}, input, outputs, + // TensorRT parser: Assertion failed: axis != BATCH_DIM + {kTensorrtExecutionProvider}, // is_tensorrt_supported + false, // expect_failure + false /*split_as_input*/); + } + RunTest(axis, {}, input, outputs, // TensorRT parser: Assertion failed: axis != BATCH_DIM {kTensorrtExecutionProvider}, // is_tensorrt_supported false, // expect_failure - use_opset_13); // split_as_input + true /*split_as_input*/); } } // namespace @@ -130,7 +139,7 @@ TEST(SplitOperatorTest, Axis0EqualSplit) { SplitTestAxis0EqualSplit(); SplitTestAxis0EqualSplit(); SplitTestAxis0EqualSplit(); - SplitTestAxis0EqualSplit(true); // BFloat16 added in opset 13 + SplitTestAxis0EqualSplit(); SplitTestAxis0EqualSplit(); SplitTestAxis0EqualSplit(); SplitTestAxis0EqualSplit(); @@ -162,8 +171,11 @@ TEST(SplitOperatorTest, Axis0UnequalSplitFloat) { {3.f, 4.f, 5.f, 6.f, 7.f, 8.f}}); + // TensorRT parser: Assertion failed: axis != BATCH_DIM RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}); + // CoreML EP, etc. requires split to be an input. Same applies to below sets of tests. + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, false, true); } TEST(SplitOperatorTest, Axis0UnequalSplitString) { @@ -186,6 +198,7 @@ TEST(SplitOperatorTest, Axis0UnequalSplitString) { "e", "f", "g", "h"}}); // TensorRT parser: Assertion failed: axis != BATCH_DIM + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}); } @@ -205,7 +218,7 @@ TEST(SplitOperatorTest, Axis1EqualSplitFloat) { outputs.push_back({{2, 2}, {3.f, 4.f, 7.f, 8.f}}); - + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}); } @@ -226,6 +239,7 @@ TEST(SplitOperatorTest, Axis1EqualSplitString) { {"c", "d", "g", "h"}}); + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}); } @@ -248,6 +262,7 @@ TEST(SplitOperatorTest, Axis1UnequalSplitFloat) { {4.f, 8.f}}); + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}); } @@ -270,6 +285,7 @@ TEST(SplitOperatorTest, Axis1UnequalSplitString) { {"d", "h"}}); + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}); } @@ -312,6 +328,7 @@ TEST(SplitOperatorTest, Axis2EqualSplit) { 17.f, 18.f, 23.f, 24.f}}); + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}); } @@ -344,6 +361,9 @@ TEST(SplitOperatorTest, Axis2UnequalSplit) { 16.f, 17.f, 18.f, 22.f, 23.f, 24.f}}); + // Note: temporarily marked qnn ep as excluded when running tests with split_as_input=true. + // TODO: Need to resolve to see if it's not supported or test case failure. + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider}, false, true); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}); } @@ -353,7 +373,7 @@ TEST(SplitOperatorTest, ZeroSizeInput) { ShapeAndFloatData input = CreateInput({0, 2}); - RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider}); + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider, kCoreMLExecutionProvider}); } // test a split of a dimension that has leading and trailing dimensions @@ -377,6 +397,7 @@ TEST(SplitOperatorTest, Axis1SplitMiddleDimensionEqually) { 25.f, 26.f, 27.f, 28.f, 29.f, 30.f, 31.f, 32.f}}); + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}); } @@ -403,6 +424,7 @@ TEST(SplitOperatorTest, Axis1SplitMiddleDimensionUnequally) { 25.f, 26.f, 27.f, 28.f, 29.f, 30.f, 31.f, 32.f}}); + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}); } @@ -423,6 +445,7 @@ TEST(SplitOperatorTest, NegativeAxis) { {3.f, 4.f, 7.f, 8.f}}); + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true); RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}); } @@ -439,6 +462,7 @@ TEST(SplitOperatorTest, InvalidAxis) { outputs.push_back({{1}, {0.f}}); + RunTest(axis, {}, input, outputs, {}, true, true, -1, true, "Invalid value of attribute 'axis'"); RunTest(axis, {}, input, outputs, {}, true, false, -1, true, "Invalid value of attribute 'axis'"); } @@ -459,6 +483,8 @@ TEST(SplitOperatorTest, SplitAttributeSumTooSmall) { outputs.push_back({{1, 2}, {1.f, 2.f}}); outputs.push_back({{2, 2}, {3.f, 4.f, 5.f, 6.f}}); + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, true, true, -1, true, + "[ShapeInferenceError] Mismatch between the sum of 'split'"); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, true, false, -1, true, "[ShapeInferenceError] Mismatch between the sum of 'split'"); // TensorRT parser: Assertion failed: axis != BATCH_DIM } @@ -478,6 +504,8 @@ TEST(SplitOperatorTest, InvalidValueInSplitAttribute) { outputs.push_back({{1, 2}, {1.f, 2.f}}); outputs.push_back({{3, 2}, {3.f, 4.f, 5.f, 6.f, 7.f, 8.f}}); + RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, true, true, -1, true, + "[ShapeInferenceError] Mismatch between number of splits"); RunTest(axis, splits, input, outputs, {kTensorrtExecutionProvider}, true, false, -1, true, "[ShapeInferenceError] Mismatch between number of splits"); // TensorRT parser: Assertion failed: axis != BATCH_DIM } @@ -654,7 +682,8 @@ TEST(SplitOperatorTest, MissingOptionalInputAdded) { {3.f, 4.f, 7.f, 8.f}}); - RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true, -1, false, {}, false); + // CoreML EP does not support the case when split_is_input==true but missing providing the split as initializer. + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kCoreMLExecutionProvider}, false, true, -1, false, {}, false); } TEST(SplitOperatorTest, Split18_NumOutputs_EvenSplit) { @@ -677,6 +706,9 @@ TEST(SplitOperatorTest, Split18_NumOutputs_EvenSplit) { 7.f, 8.f}}); int64_t num_outputs = 2; +#ifdef USE_COREML + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true, num_outputs, true); +#endif RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true, num_outputs, false); } @@ -703,6 +735,9 @@ TEST(SplitOperatorTest, Split18_NumOutputs_UnevenSplit) { outputs.push_back({{1, 2}, {9.f, 10.f}}); int64_t num_outputs = 3; +#ifdef USE_COREML + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider}, false, true, num_outputs, true); +#endif RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider}, false, true, num_outputs, false); } @@ -728,6 +763,10 @@ TEST(SplitOperatorTest, Split18_InvalidNumOutputs) { }; RunTest(axis, {}, input, outputs, excluded_providers, true, true, num_outputs, false, "Attribute `num_outputs` value cannot be lower than 1"); +#ifdef USE_COREML + RunTest(axis, {}, input, outputs, excluded_providers, true, true, num_outputs, true, + "Attribute `num_outputs` value cannot be lower than 1"); +#endif outputs.clear(); outputs.push_back({{1, 2}, @@ -738,6 +777,10 @@ TEST(SplitOperatorTest, Split18_InvalidNumOutputs) { num_outputs = 3; RunTest(axis, {}, input, outputs, excluded_providers, true, true, num_outputs, false, "Invalid num_outputs value of 3. Size of dimension being split is 2"); +#ifdef USE_COREML + RunTest(axis, {}, input, outputs, excluded_providers, true, true, num_outputs, true, + "Invalid num_outputs value of 3. Size of dimension being split is 2"); +#endif } TEST(SplitOperatorTest, Split18_NumOutputsEvenSplitAxis1) { @@ -755,6 +798,9 @@ TEST(SplitOperatorTest, Split18_NumOutputsEvenSplitAxis1) { int64_t num_outputs = 3; RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true, num_outputs, false); +#ifdef USE_COREML + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider}, false, true, num_outputs); +#endif } TEST(SplitOperatorTest, Split18_NumOutputsUnevenSplitAxis1) { @@ -772,6 +818,9 @@ TEST(SplitOperatorTest, Split18_NumOutputsUnevenSplitAxis1) { outputs.push_back({{2, 1}, {3.f, 6.f}}); int64_t num_outputs = 2; +#ifdef USE_COREML + RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider}, false, true, num_outputs); +#endif RunTest(axis, {}, input, outputs, {kTensorrtExecutionProvider, kQnnExecutionProvider}, false, true, num_outputs, false); } diff --git a/tools/ci_build/github/apple/coreml_supported_ops.md b/tools/ci_build/github/apple/coreml_supported_ops.md index 959177bcb4d7b..e2e43587ab674 100644 --- a/tools/ci_build/github/apple/coreml_supported_ops.md +++ b/tools/ci_build/github/apple/coreml_supported_ops.md @@ -34,6 +34,8 @@ Keep in sync with doco generated from /docs/execution-providers/CoreML-Execution |ai.onnx:Shape|Attribute `start` with non-default value is not supported.
Attribute `end` is not supported.| |ai.onnx:Sigmoid|| |ai.onnx:Slice|Inputs `starts`, `ends`, `axes`, and `steps` should be constant. Empty slice is not supported.| +|ai.onnx:Softmax|| +|ai.onnx:Split|If provided, `splits` should be constant. num of outputs supported is at least 2.| |ai.onnx:Squeeze|| |ai.onnx:Sqrt|| |ai.onnx:Sub||