diff --git a/onnxruntime/core/providers/coreml/builders/impl/unary_op_builder.cc b/onnxruntime/core/providers/coreml/builders/impl/unary_op_builder.cc index 0ae2a18e94e29..86a3d8f793b48 100644 --- a/onnxruntime/core/providers/coreml/builders/impl/unary_op_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/impl/unary_op_builder.cc @@ -44,6 +44,10 @@ Status UnaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const coreml_op_type = "round"; } else if (op_type == "Exp") { coreml_op_type = "exp"; + } else if (op_type == "Sin") { + coreml_op_type = "sin"; + } else if (op_type == "Cos") { + coreml_op_type = "cos"; } else if (op_type == "Ceil") { coreml_op_type = "ceil"; } else { @@ -89,8 +93,11 @@ Status UnaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const bool UnaryOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& input_params, const logging::Logger& /*logger*/) const { if (!input_params.create_mlprogram) { - if (node.OpType() == "Erf" || node.OpType() == "Round" || node.OpType() == "Exp" || - node.OpType() == "Ceil") { + // These ops only have an ML Program lowering; the NeuralNetwork + // UnaryFunctionLayerParams has no equivalent. + const auto& op_type = node.OpType(); + if (op_type == "Erf" || op_type == "Round" || op_type == "Exp" || + op_type == "Sin" || op_type == "Cos" || op_type == "Ceil") { return false; } } diff --git a/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc b/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc index fd0e19dbd055a..7ba8a9fe5f09c 100644 --- a/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc +++ b/onnxruntime/core/providers/coreml/builders/op_builder_factory.cc @@ -38,6 +38,8 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() { CreateUnaryOpBuilder("Round", op_registrations); CreateUnaryOpBuilder("Sqrt", op_registrations); CreateUnaryOpBuilder("Exp", op_registrations); + CreateUnaryOpBuilder("Sin", op_registrations); + CreateUnaryOpBuilder("Cos", op_registrations); CreateUnaryOpBuilder("Ceil", op_registrations); // Binary elementwise ops diff --git a/onnxruntime/test/providers/coreml/coreml_basic_test.cc b/onnxruntime/test/providers/coreml/coreml_basic_test.cc index 13b75f3c6e4fa..b67a9bde5c63b 100644 --- a/onnxruntime/test/providers/coreml/coreml_basic_test.cc +++ b/onnxruntime/test/providers/coreml/coreml_basic_test.cc @@ -2360,6 +2360,55 @@ TEST(CoreMLExecutionProviderTest, Split11SingleOutputNotSupported) { TestModelLoad(model_span, MakeCoreMLExecutionProvider("MLProgram"), ExpectedEPNodeAssignment::None); } +namespace { +// Single-input model with both Sin and Cos consuming `X`, used by the +// Sin/Cos tests below. +std::string MakeSinCosModelData() { + onnxruntime::Model model("sin_cos_test", false, DefaultLoggingManager().DefaultLogger()); + auto& graph = model.MainGraph(); + + ONNX_NAMESPACE::TypeProto float_tensor; + float_tensor.mutable_tensor_type()->set_elem_type(ONNX_NAMESPACE::TensorProto_DataType_FLOAT); + auto* shape = float_tensor.mutable_tensor_type()->mutable_shape(); + shape->add_dim()->set_dim_value(1); + shape->add_dim()->set_dim_value(6); + + auto& x = graph.GetOrCreateNodeArg("X", &float_tensor); + auto& sin_out = graph.GetOrCreateNodeArg("Sin_out", &float_tensor); + auto& cos_out = graph.GetOrCreateNodeArg("Cos_out", &float_tensor); + graph.AddNode("sin", "Sin", "sin node", {&x}, {&sin_out}); + graph.AddNode("cos", "Cos", "cos node", {&x}, {&cos_out}); + + ORT_THROW_IF_ERROR(graph.Resolve()); + std::string model_data; + model.ToProto().SerializeToString(&model_data); + return model_data; +} +} // namespace + +// Sin and Cos are lowered to the ML Program 'sin' / 'cos' ops. +TEST(CoreMLExecutionProviderTest, SinCos_MLProgram) { + const std::string model_data = MakeSinCosModelData(); + gsl::span model_span{reinterpret_cast(model_data.data()), + model_data.size()}; + +#if defined(__APPLE__) + std::vector dims = {1, 6}; + std::vector values = {-2.0f, -0.5f, 0.0f, 0.5f, 1.0f, 2.0f}; + OrtValue x_val; + CreateMLValue(CPUAllocator::DefaultInstance(), dims, values, &x_val); + NameMLValMap feeds; + feeds.insert(std::make_pair("X", x_val)); + + EPVerificationParams params{}; + params.ep_node_assignment = ExpectedEPNodeAssignment::All; + RunAndVerifyOutputsWithEP(model_span, CurrentTestName(), + MakeCoreMLExecutionProvider("MLProgram"), feeds, params); +#else + TestModelLoad(model_span, MakeCoreMLExecutionProvider("MLProgram"), ExpectedEPNodeAssignment::All); +#endif +} + TEST(CoreMLExecutionProviderTest, GatherScalarIndicesAxis1) { // ai.onnx:Gather with rank-0 (scalar) 'indices'. ONNX output rank = // data_rank + indices_rank - 1 = 2. The CoreML builder internally promotes @@ -2437,6 +2486,16 @@ TEST(CoreMLExecutionProviderTest, GatherScalarIndicesAxis1) { #endif } +// Sin/Cos only have an ML Program lowering (the NeuralNetwork +// UnaryFunctionLayerParams has no sin/cos), so on the NeuralNetwork format +// they must fall back to CPU rather than be claimed. +TEST(CoreMLExecutionProviderTest, SinCosNeuralNetworkNotSupported) { + const std::string model_data = MakeSinCosModelData(); + gsl::span model_span{reinterpret_cast(model_data.data()), + model_data.size()}; + TestModelLoad(model_span, MakeCoreMLExecutionProvider(), ExpectedEPNodeAssignment::None); +} + TEST(CoreMLExecutionProviderTest, GatherScalarIndicesAxis0) { // Scalar Gather along axis 0 — squeeze axis is 0; covers a different // squeeze position than the axis=1 test. diff --git a/tools/ci_build/github/apple/coreml_supported_mlprogram_ops.md b/tools/ci_build/github/apple/coreml_supported_mlprogram_ops.md index 106280d258ecb..0365da55bd48a 100644 --- a/tools/ci_build/github/apple/coreml_supported_mlprogram_ops.md +++ b/tools/ci_build/github/apple/coreml_supported_mlprogram_ops.md @@ -11,6 +11,7 @@ Keep in sync with doco generated from /docs/execution-providers/CoreML-Execution |ai.onnx:Concat|| |ai.onnx:Conv|Only 1D/2D Conv is supported.
Bias if provided must be constant.| |ai.onnx:ConvTranspose|Weight and bias must be constant.
padding_type of SAME_UPPER/SAME_LOWER is not supported.
kernel_shape must have default values.
output_shape is not supported.
output_padding must have default values.| +|ai.onnx:Cos|| |ai.onnx:DepthToSpace|If 'mode' is 'CRD' the input must have a fixed shape.| |ai.onnx:Ceil|| |ai.onnx:Div|| @@ -43,6 +44,7 @@ Keep in sync with doco generated from /docs/execution-providers/CoreML-Execution |ai.onnx:Resize|See [resize_op_builder.cc](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/providers/coreml/builders/impl/resize_op_builder.cc) implementation. There are too many permutations to describe the valid combinations.| |ai.onnx:Round|| |ai.onnx:Shape|| +|ai.onnx:Sin|| |ai.onnx:Slice|starts/ends/axes/steps must be constant initializers.| |ai.onnx:Softplus|| |ai.onnx:Split|If provided, `splits` must be constant.|