Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CoreML] Create EP by AppendExecutionProvider #22675

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,7 @@ IntPtr[] outputValues /* An array of output value pointers. Array must be alloca
/// <summary>
/// Append an execution provider instance to the native OrtSessionOptions instance.
///
/// 'SNPE' and 'XNNPACK' are currently supported as providerName values.
/// 'SNPE', 'XNNPACK' and 'CoreML' are currently supported as providerName values.
///
/// The number of providerOptionsKeys must match the number of providerOptionsValues and equal numKeys.
/// </summary>
Expand Down
8 changes: 1 addition & 7 deletions csharp/src/Microsoft.ML.OnnxRuntime/SessionOptions.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -430,16 +430,10 @@ public IntPtr Appender(IntPtr handle, IntPtr[] optKeys, IntPtr[] optValues, UInt
/// <summary>
/// Append QNN, SNPE or XNNPACK execution provider
/// </summary>
/// <param name="providerName">Execution provider to add. 'QNN', 'SNPE' or 'XNNPACK' are currently supported.</param>
/// <param name="providerName">Execution provider to add. 'QNN', 'SNPE' 'XNNPACK', 'CoreML and 'AZURE are currently supported.</param>
/// <param name="providerOptions">Optional key/value pairs to specify execution provider options.</param>
public void AppendExecutionProvider(string providerName, Dictionary<string, string> providerOptions = null)
{
if (providerName != "SNPE" && providerName != "XNNPACK" && providerName != "QNN" && providerName != "AZURE")
{
throw new NotSupportedException(
"Only QNN, SNPE, XNNPACK and AZURE execution providers can be enabled by this method.");
}

if (providerOptions == null)
{
providerOptions = new Dictionary<string, string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ public void TestSessionOptions()
ex = Assert.Throws<OnnxRuntimeException>(() => { opt.AppendExecutionProvider("QNN"); });
Assert.Contains("QNN execution provider is not supported in this build", ex.Message);
#endif
#if USE_COREML
opt.AppendExecutionProvider("CoreML");
#else
ex = Assert.Throws<OnnxRuntimeException>(() => { opt.AppendExecutionProvider("CoreML"); });
Assert.Contains("CoreML execution provider is not supported in this build", ex.Message);
#endif

opt.AppendExecutionProvider_CPU(1);
}
Expand Down Expand Up @@ -2041,7 +2047,7 @@ public SkipNonPackageTests()
}

// Test hangs on mobile.
#if !(ANDROID || IOS)
#if !(ANDROID || IOS)
[Fact(DisplayName = "TestModelRunAsyncTask")]
private async Task TestModelRunAsyncTask()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ enum COREMLFlags {
COREML_FLAG_LAST = COREML_FLAG_USE_CPU_AND_GPU,
};

// MLComputeUnits can be one of the following values:
// 'MLComputeUnitsCPUAndNeuralEngine|MLComputeUnitsCPUAndGPU|MLComputeUnitsCPUOnly|MLComputeUnitsAll'
// these values are intended to be used with Ort::SessionOptions::AppendExecutionProvider (C++ API)
// and SessionOptionsAppendExecutionProvider (C API). For the old API, use COREMLFlags instead.
static const char* const kCoremlProviderOption_MLComputeUnits = "MLComputeUnits";
static const char* const kCoremlProviderOption_ModelFormat = "ModelFormat";
static const char* const kCoremlProviderOption_RequireStaticInputShapes = "RequireStaticInputShapes";
static const char* const kCoremlProviderOption_EnableOnSubgraphs = "EnableOnSubgraphs";

#ifdef __cplusplus
extern "C" {
#endif
Expand Down
12 changes: 12 additions & 0 deletions java/src/main/java/ai/onnxruntime/OrtSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,18 @@ public void addQnn(Map<String, String> providerOptions) throws OrtException {
addExecutionProvider(qnnProviderName, providerOptions);
}

/**
* Adds CoreML as an execution backend.
*
* @param providerOptions Configuration options for the CoreML backend. Refer to the CoreML
* execution provider's documentation.
* @throws OrtException If there was an error in native code.
*/
public void addCoreML(Map<String, String> providerOptions) throws OrtException {
String CoreMLProviderName = "CoreML";
addExecutionProvider(CoreMLProviderName, providerOptions);
}

private native void setExecutionMode(long apiHandle, long nativeHandle, int mode)
throws OrtException;

Expand Down
17 changes: 16 additions & 1 deletion objectivec/include/ort_coreml_execution_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,22 @@
*/
- (BOOL)appendCoreMLExecutionProviderWithOptions:(ORTCoreMLExecutionProviderOptions*)options
error:(NSError**)error;

/**
* Enables the CoreML execution provider in the session configuration options.
* It is appended to the execution provider list which is ordered by
* decreasing priority.
*
* @param provider_options The CoreML execution provider options in dict.
* available keys-values: more detail in core/providers/coreml/coreml_execution_provider.h
* kCoremlProviderOption_MLComputeUnits: one of "CPUAndNeuralEngine", "CPUAndGPU", "CPUOnly", "All"
* kCoremlProviderOption_ModelFormat: one of "MLProgram", "NeuralNetwork"
* kCoremlProviderOption_RequireStaticInputShapes: "1" or "0"
* kCoremlProviderOption_EnableOnSubgraphs: "1" or "0"
* @param error Optional error information set if an error occurs.
* @return Whether the provider was enabled successfully.
*/
- (BOOL)appendCoreMLExecutionProviderWithOptionsV2:(NSDictionary*)provider_options

Check warning on line 87 in objectivec/include/ort_coreml_execution_provider.h

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Using C-style cast. Use reinterpret_cast<NSDictionary*>(...) instead [readability/casting] [4] Raw Output: objectivec/include/ort_coreml_execution_provider.h:87: Using C-style cast. Use reinterpret_cast<NSDictionary*>(...) instead [readability/casting] [4]
error:(NSError**)error;

Check warning on line 88 in objectivec/include/ort_coreml_execution_provider.h

View workflow job for this annotation

GitHub Actions / Optional Lint C++

[cpplint] reported by reviewdog 🐶 Using C-style cast. Use reinterpret_cast<NSError**>(...) instead [readability/casting] [4] Raw Output: objectivec/include/ort_coreml_execution_provider.h:88: Using C-style cast. Use reinterpret_cast<NSError**>(...) instead [readability/casting] [4]
@end

NS_ASSUME_NONNULL_END
15 changes: 15 additions & 0 deletions objectivec/ort_coreml_execution_provider.mm
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ - (BOOL)appendCoreMLExecutionProviderWithOptions:(ORTCoreMLExecutionProviderOpti
#endif
}

- (BOOL)appendCoreMLExecutionProviderWithOptionsV2:(NSDictionary*)provider_options
error:(NSError**)error {
#if ORT_OBJC_API_COREML_EP_AVAILABLE
try {
return [self appendExecutionProvider:@"CoreML" providerOptions:provider_options error:error];
}
ORT_OBJC_API_IMPL_CATCH_RETURNING_BOOL(error);

#else // !ORT_OBJC_API_COREML_EP_AVAILABLE
static_cast<void>(provider_options);
ORTSaveCodeAndDescriptionToError(ORT_FAIL, "CoreML execution provider is not enabled.", error);
return NO;
#endif
}

@end

NS_ASSUME_NONNULL_END
22 changes: 22 additions & 0 deletions objectivec/test/ort_session_test.mm
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,28 @@ - (void)testAppendCoreMLEP {
ORTAssertNullableResultSuccessful(session, err);
}

- (void)testAppendCoreMLEP_v2 {
NSError* err = nil;
ORTSessionOptions* sessionOptions = [ORTSessionTest makeSessionOptions];
NSDictionary* provider_options = @{@"EnableOnSubgraphs" : @"1"}; // set an arbitrary option

BOOL appendResult = [sessionOptions appendCoreMLExecutionProviderWithOptionsV2:provider_options
error:&err];

if (!ORTIsCoreMLExecutionProviderAvailable()) {
ORTAssertBoolResultUnsuccessful(appendResult, err);
return;
}

ORTAssertBoolResultSuccessful(appendResult, err);

ORTSession* session = [[ORTSession alloc] initWithEnv:self.ortEnv
modelPath:[ORTSessionTest getAddModelPath]
sessionOptions:sessionOptions
error:&err];
ORTAssertNullableResultSuccessful(session, err);
}

- (void)testAppendXnnpackEP {
NSError* err = nil;
ORTSessionOptions* sessionOptions = [ORTSessionTest makeSessionOptions];
Expand Down
13 changes: 7 additions & 6 deletions onnxruntime/core/providers/coreml/builders/helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ namespace coreml {

OpBuilderInputParams MakeOpBuilderParams(const GraphViewer& graph_viewer,
int32_t coreml_version,
uint32_t coreml_flags) {
bool only_allow_static_input_shapes,
bool create_mlprogram) {
return OpBuilderInputParams{graph_viewer,
coreml_version,
(coreml_flags & COREML_FLAG_ONLY_ALLOW_STATIC_INPUT_SHAPES) != 0,
(coreml_flags & COREML_FLAG_CREATE_MLPROGRAM) != 0};
only_allow_static_input_shapes,
create_mlprogram};
}

const IOpBuilder* GetOpBuilder(const Node& node) {
Expand Down Expand Up @@ -133,13 +134,13 @@ bool CheckIsConstantInitializer(const NodeArg& node_arg, const GraphViewer& grap
return true;
}

bool HasNeuralEngine(const logging::Logger& logger) {
bool HasNeuralEngine() {
bool has_neural_engine = false;

#ifdef __APPLE__
struct utsname system_info;
uname(&system_info);
LOGS(logger, VERBOSE) << "Current Apple hardware info: " << system_info.machine;
LOGS_DEFAULT(VERBOSE) << "Current Apple hardware info: " << system_info.machine;

#if TARGET_OS_IPHONE
// utsname.machine has device identifier. For example, identifier for iPhone Xs is "iPhone11,2".
Expand All @@ -163,7 +164,7 @@ bool HasNeuralEngine(const logging::Logger& logger) {
#else
// In this case, we are running the EP on non-apple platform, which means we are running the model
// conversion with CoreML EP enabled, for this we always assume the target system has Neural Engine
LOGS(logger, INFO) << "HasNeuralEngine running on non-Apple hardware. "
LOGS_DEFAULT(INFO) << "HasNeuralEngine running on non-Apple hardware. "
"Returning true to enable model conversion and local testing of CoreML EP implementation. "
"No CoreML model will be compiled or run.";
has_neural_engine = true;
Expand Down
5 changes: 3 additions & 2 deletions onnxruntime/core/providers/coreml/builders/helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace coreml {

OpBuilderInputParams MakeOpBuilderParams(const GraphViewer& graph_viewer,
int32_t coreml_version,
uint32_t coreml_flags);
bool only_allow_static_input_shapes,
bool create_mlprogram);

const IOpBuilder* GetOpBuilder(const Node& node);

Expand All @@ -45,7 +46,7 @@ bool CheckIsConstantInitializer(const NodeArg& node_arg, const GraphViewer& grap

// CoreML is more efficient running using Apple Neural Engine
// This is to detect if the current system has Apple Neural Engine
bool HasNeuralEngine(const logging::Logger& logger);
bool HasNeuralEngine();

} // namespace coreml
} // namespace onnxruntime
15 changes: 8 additions & 7 deletions onnxruntime/core/providers/coreml/builders/model_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "core/platform/env.h"
#include "core/providers/common.h"
#include "core/providers/coreml/builders/model_builder.h"
#include "core/providers/coreml/coreml_execution_provider.h"
#include "core/providers/coreml/builders/helper.h"
#include "core/providers/coreml/builders/op_builder_factory.h"
#include "core/providers/coreml/builders/impl/builder_utils.h"
Expand Down Expand Up @@ -401,14 +402,14 @@ std::string GetModelOutputPath(bool create_ml_program) {
} // namespace

ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logger& logger,
int32_t coreml_version, uint32_t coreml_flags,
int32_t coreml_version, const CoreMLOptions& coreml_options,
std::vector<std::string>&& onnx_input_names,
std::vector<std::string>&& onnx_output_names)
: graph_viewer_(graph_viewer),
logger_(logger),
coreml_version_(coreml_version),
coreml_flags_(coreml_flags),
create_ml_program_((coreml_flags_ & COREML_FLAG_CREATE_MLPROGRAM) != 0),
coreml_compute_unit_(coreml_options.ComputeUnits()),
create_ml_program_(coreml_options.CreateMLProgram()),
model_output_path_(GetModelOutputPath(create_ml_program_)),
onnx_input_names_(std::move(onnx_input_names)),
onnx_output_names_(std::move(onnx_output_names)),
Expand Down Expand Up @@ -988,7 +989,7 @@ Status ModelBuilder::LoadModel(std::unique_ptr<Model>& model) {
get_sanitized_io_info(std::move(input_output_info_)),
std::move(scalar_outputs_),
std::move(int64_outputs_),
logger_, coreml_flags_);
logger_, coreml_compute_unit_);
} else
#endif
{
Expand All @@ -998,7 +999,7 @@ Status ModelBuilder::LoadModel(std::unique_ptr<Model>& model) {
std::move(input_output_info_),
std::move(scalar_outputs_),
std::move(int64_outputs_),
logger_, coreml_flags_);
logger_, coreml_compute_unit_);
}

return model->LoadModel(); // load using CoreML API, including compilation
Expand Down Expand Up @@ -1048,11 +1049,11 @@ std::string_view ModelBuilder::AddConstant(std::string_view op_type, std::string
#endif
// static
Status ModelBuilder::Build(const GraphViewer& graph_viewer, const logging::Logger& logger,
int32_t coreml_version, uint32_t coreml_flags,
int32_t coreml_version, const CoreMLOptions& coreml_options,
std::vector<std::string>&& onnx_input_names,
std::vector<std::string>&& onnx_output_names,
std::unique_ptr<Model>& model) {
ModelBuilder builder(graph_viewer, logger, coreml_version, coreml_flags,
ModelBuilder builder(graph_viewer, logger, coreml_version, coreml_options,
std::move(onnx_input_names), std::move(onnx_output_names));

ORT_RETURN_IF_ERROR(builder.CreateModel());
Expand Down
8 changes: 5 additions & 3 deletions onnxruntime/core/providers/coreml/builders/model_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@ class StorageWriter;
#endif

namespace onnxruntime {
class CoreMLOptions;

namespace coreml {

class IOpBuilder;

class ModelBuilder {
private:
ModelBuilder(const GraphViewer& graph_viewer, const logging::Logger& logger,
int32_t coreml_version, uint32_t coreml_flags,
int32_t coreml_version, const CoreMLOptions& coreml_options,
std::vector<std::string>&& onnx_input_names,
std::vector<std::string>&& onnx_output_names);

public:
// Create the CoreML model, serialize to disk, load and compile using the CoreML API and return in `model`
static Status Build(const GraphViewer& graph_viewer, const logging::Logger& logger,
int32_t coreml_version, uint32_t coreml_flags,
int32_t coreml_version, const CoreMLOptions& coreml_options,
std::vector<std::string>&& onnx_input_names,
std::vector<std::string>&& onnx_output_names,
std::unique_ptr<Model>& model);
Expand Down Expand Up @@ -216,7 +218,7 @@ class ModelBuilder {
const GraphViewer& graph_viewer_;
const logging::Logger& logger_;
const int32_t coreml_version_;
const uint32_t coreml_flags_;
const uint32_t coreml_compute_unit_;
const bool create_ml_program_; // ML Program (CoreML5, iOS 15+, macOS 12+) or NeuralNetwork (old)
const std::string model_output_path_; // create_ml_program_ ? dir for mlpackage : filename for mlmodel

Expand Down
Loading
Loading