From 1d1c874b16447e5921c8aa7b3796510596f90bc1 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 10 Dec 2024 18:22:27 +0800 Subject: [PATCH 01/35] support coreml model cache --- .../coreml/coreml_provider_factory.h | 12 + onnxruntime/core/platform/env.h | 2 + onnxruntime/core/platform/posix/env.cc | 8 + onnxruntime/core/platform/windows/env.cc | 10 + onnxruntime/core/platform/windows/env.h | 2 + .../coreml/builders/model_builder.cc | 42 +++- .../providers/coreml/builders/model_builder.h | 6 +- .../core/providers/coreml/coreml_options.cc | 20 ++ .../core/providers/coreml/coreml_options.h | 4 + .../core/providers/coreml/model/model.mm | 225 ++++++++++++------ onnxruntime/test/perftest/ort_test_session.cc | 4 +- 11 files changed, 258 insertions(+), 77 deletions(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index d035fd34bd072..12bdcddb5ae2a 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -61,6 +61,18 @@ static const char* const kCoremlProviderOption_SpecializationStrategy = "Special static const char* const kCoremlProviderOption_ProfileComputePlan = "ProfileComputePlan"; // please refer to https://developer.apple.com/documentation/coreml/mlmodelconfiguration/allowlowprecisionaccumulationongpu static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU = "AllowLowPrecisionAccumulationOnGPU"; +// Specify the path to cache the model. +// CoreML EP will convert onnx subgraph to CoreML model and save to disk. +// If this path is not specified, the model will be saved to a temp directory and deleted after the session is closed. +// otherwise, the model will be saved to the specified path and User should manage to delete the model. +// The basic logic is: +// if (ModelCachePath != nullptr && ModelCachePath/cache_coreml.exists()) { +// // load from cache_coreml +// } else { +// // save to ModelCachePath +// } +// we wound not detect if the cached model match the onnx subgraph, so User should carefully manage the cache for a new model. +static const char* const kCoremlProviderOption_ModelCachePath = "ModelCachePath"; #ifdef __cplusplus extern "C" { diff --git a/onnxruntime/core/platform/env.h b/onnxruntime/core/platform/env.h index c42b31e64d129..7dbc3fe82db47 100644 --- a/onnxruntime/core/platform/env.h +++ b/onnxruntime/core/platform/env.h @@ -197,6 +197,7 @@ class Env { #ifdef _WIN32 /// \brief Returns true if the directory exists. virtual bool FolderExists(const std::wstring& path) const = 0; + virtual bool FileExists(const std::wstring& path) const = 0; /// \brief Recursively creates the directory, if it doesn't exist. virtual common::Status CreateFolder(const std::wstring& path) const = 0; // Mainly for use with protobuf library @@ -206,6 +207,7 @@ class Env { #endif /// \brief Returns true if the directory exists. virtual bool FolderExists(const std::string& path) const = 0; + virtual bool FileExists(const std::string& path) const = 0; /// \brief Recursively creates the directory, if it doesn't exist. virtual common::Status CreateFolder(const std::string& path) const = 0; // Recursively deletes the directory and its contents. diff --git a/onnxruntime/core/platform/posix/env.cc b/onnxruntime/core/platform/posix/env.cc index 04cf5ff6a3329..94aadf3df4d7e 100644 --- a/onnxruntime/core/platform/posix/env.cc +++ b/onnxruntime/core/platform/posix/env.cc @@ -471,6 +471,14 @@ class PosixEnv : public Env { return S_ISDIR(sb.st_mode); } + bool FileExists(const std::string& path) const override { + struct stat sb; + if (stat(path.c_str(), &sb)) { + return false; + } + return S_ISREG(sb.st_mode); + } + common::Status CreateFolder(const std::string& path) const override { size_t pos = 0; do { diff --git a/onnxruntime/core/platform/windows/env.cc b/onnxruntime/core/platform/windows/env.cc index 73319cd9c9b1c..4fccad6dfeb37 100644 --- a/onnxruntime/core/platform/windows/env.cc +++ b/onnxruntime/core/platform/windows/env.cc @@ -483,6 +483,16 @@ bool WindowsEnv::FolderExists(const std::string& path) const { return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY); } +bool WindowsEnv::FileExists(const std::wstring& path) const { + DWORD attributes = GetFileAttributesW(path.c_str()); + return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_NORMAL); +} + +bool WindowsEnv::FileExists(const std::string& path) const { + DWORD attributes = GetFileAttributesA(path.c_str()); + return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_NORMAL); +} + common::Status WindowsEnv::CreateFolder(const std::wstring& path) const { size_t pos = 0; do { diff --git a/onnxruntime/core/platform/windows/env.h b/onnxruntime/core/platform/windows/env.h index 395aface1d809..05b92bb6a21eb 100644 --- a/onnxruntime/core/platform/windows/env.h +++ b/onnxruntime/core/platform/windows/env.h @@ -68,6 +68,8 @@ class WindowsEnv : public Env { MappedMemoryPtr& mapped_memory) const override; bool FolderExists(const std::wstring& path) const override; bool FolderExists(const std::string& path) const override; + bool FileExists(const std::wstring& path) const override; + bool FileExists(const std::string& path) const override; common::Status CreateFolder(const std::wstring& path) const override; common::Status CreateFolder(const std::string& path) const override; common::Status DeleteFolder(const PathString& path) const override; diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 6486942199df7..8c98ee0c4097e 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -410,10 +410,37 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logge coreml_version_(coreml_version), coreml_options_(coreml_options), 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)), coreml_model_(std::make_unique()) { + if (coreml_options.ModelCachePath().empty()) { + model_output_path_ = GetModelOutputPath(create_ml_program_); + } else { + // input names in onnx are unique. so we can use them as the key in the cache. + std::string inputs_collections = std::accumulate( + onnx_input_names_.begin(), onnx_input_names_.end(), std::string(), + [](const std::string& a, const std::string& b) { return a + "," + b; }); + std::hash hasher; + // different subgraph has different folders. so we need to hash the inputs. + model_output_path_ = std::string(coreml_options.ModelCachePath()) + + "/" + std::to_string(hasher(inputs_collections)); + if (!coreml_options_.CreateMLProgram()) { + ORT_THROW_IF_ERROR(Env::Default().CreateFolder(model_output_path_)); + model_output_path_ += "/mlmodel"; + } + } + + // GetModelOutputPath(create_ml_program_) always produce a unique path for the model and this is not existed + // Mlprogram will create a folder while NN create a file + if (Env::Default().FolderExists(ToPathString(model_output_path_)) || + Env::Default().FileExists(ToPathString(model_output_path_))) { + is_model_cached_ = true; + LOGS(logger, WARNING) << "Model is already cached in " << model_output_path_ + << " and will be reused. If you want to update the model or hit other issues, " + << "please consider to clear the cache and retry."; + return; + } + if (create_ml_program_) { #if defined(COREML_ENABLE_MLPROGRAM) coreml_model_->set_specificationversion(CoreMLSpecVersion()); @@ -847,6 +874,10 @@ Status ModelBuilder::RegisterModelInputOutput(const NodeArg& node_arg, bool is_i input_output_info_.emplace(name, OnnxTensorInfo{data_type, shape}); + if (is_model_cached_) { + return Status::OK(); + } + #if defined(COREML_ENABLE_MLPROGRAM) if (create_ml_program_) { if (is_input) { @@ -1056,8 +1087,13 @@ Status ModelBuilder::Build(const GraphViewer& graph_viewer, const logging::Logge 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()); - ORT_RETURN_IF_ERROR(builder.SaveModel()); + if (!builder.IsModelCached()) { + ORT_RETURN_IF_ERROR(builder.CreateModel()); + ORT_RETURN_IF_ERROR(builder.SaveModel()); + } else { + ORT_RETURN_IF_ERROR(builder.RegisterModelInputs()); + ORT_RETURN_IF_ERROR(builder.RegisterModelOutputs()); + } return builder.LoadModel(model); } diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.h b/onnxruntime/core/providers/coreml/builders/model_builder.h index e19597cf0dc2e..28c7dc42da581 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.h +++ b/onnxruntime/core/providers/coreml/builders/model_builder.h @@ -54,6 +54,7 @@ class ModelBuilder { // We only support CoreML 3 and later so the spec version is always version + 1. int32_t CoreMLVersion() const { return coreml_version_; } int32_t CoreMLSpecVersion() const { return coreml_version_ + 1; } + bool IsModelCached() const { return is_model_cached_; } // Returns true if we are creating an ML Program bool CreateMLProgram() const { @@ -218,8 +219,9 @@ class ModelBuilder { const logging::Logger& logger_; const int32_t coreml_version_; CoreMLOptions coreml_options_; - 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 + const bool create_ml_program_; // ML Program (CoreML5, iOS 15+, macOS 12+) or NeuralNetwork (old) + std::string model_output_path_; // create_ml_program_ ? dir for mlpackage : filename for mlmodel + bool is_model_cached_{false}; std::vector onnx_input_names_; std::vector onnx_output_names_; diff --git a/onnxruntime/core/providers/coreml/coreml_options.cc b/onnxruntime/core/providers/coreml/coreml_options.cc index 4ec780208e528..5babd7633cd88 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.cc +++ b/onnxruntime/core/providers/coreml/coreml_options.cc @@ -5,6 +5,7 @@ #include "core/providers/coreml/coreml_provider_factory.h" // defines flags #include "core/providers/coreml/model/host_utils.h" #include "core/providers/coreml/builders/helper.h" +#include "core/platform/env.h" namespace onnxruntime { @@ -71,6 +72,7 @@ void CoreMLOptions::ValidateAndParseProviderOption(const ProviderOptions& option kCoremlProviderOption_SpecializationStrategy, kCoremlProviderOption_ProfileComputePlan, kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU, + kCoremlProviderOption_ModelCachePath, }; // Validate the options for (const auto& option : options) { @@ -103,7 +105,25 @@ void CoreMLOptions::ValidateAndParseProviderOption(const ProviderOptions& option profile_compute_plan_ = option.second == "1"; } else if (kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU == option.first) { allow_low_precision_accumulation_on_gpu_ = option.second == "1"; + } else if (kCoremlProviderOption_ModelCachePath == option.first) { + model_cache_path_ = option.second; } } + + // Set the model cache path with equireStaticShape and ModelFormat + if (model_cache_path_.size()) { + if (require_static_shape_) { + model_cache_path_ += "/static_shape"; + } else { + model_cache_path_ += "/dynamic_shape"; + } + + if (create_mlprogram_) { + model_cache_path_ += "/mlpackage"; + } else { + model_cache_path_ += "/mlnnmodel"; + } + ORT_THROW_IF_ERROR(Env::Default().CreateFolder(model_cache_path_)); + } } } // namespace onnxruntime diff --git a/onnxruntime/core/providers/coreml/coreml_options.h b/onnxruntime/core/providers/coreml/coreml_options.h index fd05c96927bd1..1ec4294492552 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.h +++ b/onnxruntime/core/providers/coreml/coreml_options.h @@ -17,6 +17,8 @@ class CoreMLOptions { std::string strategy_; bool profile_compute_plan_{false}; bool allow_low_precision_accumulation_on_gpu_{false}; + // path to store the converted coreml model + std::string model_cache_path_; public: explicit CoreMLOptions(uint32_t coreml_flags); @@ -32,6 +34,8 @@ class CoreMLOptions { bool UseStrategy(std::string_view strategy) const { return strategy_ == strategy; } bool ProfileComputePlan() const { return profile_compute_plan_ && create_mlprogram_; } + std::string_view ModelCachePath() const { return model_cache_path_; } + private: void ValidateAndParseProviderOption(const ProviderOptions& options); }; diff --git a/onnxruntime/core/providers/coreml/model/model.mm b/onnxruntime/core/providers/coreml/model/model.mm index 755dbfbd6e68c..e0f70e369a1a2 100644 --- a/onnxruntime/core/providers/coreml/model/model.mm +++ b/onnxruntime/core/providers/coreml/model/model.mm @@ -301,53 +301,144 @@ Status GetMLMultiArrayCopyInfo(const MLMultiArray* _Nonnull array, return Status::OK(); } -// since __clang_major__ >= 15, MLComputePlan is introduced in -// We are actually ensure the MacOS/IOS version and Xcode version is greater than `macOS 14.4, iOS 17.4`. -// The macro API_AVAILABLE should also be fine. +// since macos(14.4), ios(17.4), MLComputePlan is introduced in // Otherwise, the compiler will complain `MLComputePlan` is not defined. -// we define __clang_analyzer__ here is for bypass static analysis +#if __has_include() +#define HAS_COREMLPLAN 1 +#else +#define HAS_COREMLPLAN 0 +#endif + +#if HAS_COREMLPLAN +API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) +void ProfileBlock(MLComputePlan* _Nullable computePlan, MLModelStructureProgramBlock* block) { + for (MLModelStructureProgramOperation* operation in block.operations) { + for (size_t i = 0; i < operation.blocks.count; ++i) { + ProfileBlock(computePlan, operation.blocks[i]); + } + // Get the compute device usage for the operation. + MLComputePlanDeviceUsage* computeDeviceUsage = [computePlan computeDeviceUsageForMLProgramOperation:operation]; + id preferredDevice = computeDeviceUsage.preferredComputeDevice; + // Get the estimated cost of executing the operation. + MLComputePlanCost* estimatedCost = [computePlan estimatedCostOfMLProgramOperation:operation]; + if (![operation.operatorName isEqualToString:@"const"]) { + NSLog(@"Operation: %@, Device Usage: %@, Estimated Cost: %f", operation.operatorName, preferredDevice, estimatedCost.weight); + } + } +} +#endif + +// since macos(14.4), ios(17.4), MLComputePlan is introduced in +// Otherwise, the compiler will complain `MLComputePlan` is not defined. +API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) void ProfileComputePlan(NSURL* compileUrl, MLModelConfiguration* config) { -#if defined(__APPLE__) && defined(__clang__) && __clang_major__ >= 15 && !defined(__clang_analyzer__) - if (@available(macOS 14.4, iOS 17.4, *)) { - [MLComputePlan loadContentsOfURL:compileUrl - configuration:config - completionHandler:^(MLComputePlan* _Nullable computePlan, NSError* _Nullable error) { - if (!computePlan) { - NSLog(@"Error loading compute plan: %@", error); - // Handle error. - return; - } - MLModelStructureProgram* program = computePlan.modelStructure.program; - if (!program) { - NSLog(@"Error loading program from compute plan., this is not a mlprogram model"); - return; - } - - MLModelStructureProgramFunction* mainFunction = program.functions[@"main"]; - if (!mainFunction) { - NSLog(@"Error loading main function from program"); - return; - } - - NSArray* operations = mainFunction.block.operations; - NSLog(@"Number of operations, 'const' node is included. : %lu", operations.count); - for (MLModelStructureProgramOperation* operation in operations) { - // Get the compute device usage for the operation. - MLComputePlanDeviceUsage* computeDeviceUsage = [computePlan computeDeviceUsageForMLProgramOperation:operation]; - id preferredDevice = computeDeviceUsage.preferredComputeDevice; - // Get the estimated cost of executing the operation. - MLComputePlanCost* estimatedCost = [computePlan estimatedCostOfMLProgramOperation:operation]; - if (![operation.operatorName isEqualToString:@"const"]) { - NSLog(@"Operation: %@, Device Usage: %@, Estimated Cost: %f", operation.operatorName, preferredDevice, estimatedCost.weight); - } - } +#if HAS_COREMLPLAN + dispatch_semaphore_t fd_sema = dispatch_semaphore_create(0); + [MLComputePlan loadContentsOfURL:compileUrl + configuration:config + completionHandler:^(MLComputePlan* _Nullable computePlan, NSError* _Nullable error) { + if (!computePlan) { + NSLog(@"Error loading compute plan: %@", error); + // Handle error. + return; + } + MLModelStructureProgram* program = computePlan.modelStructure.program; + if (!program) { + NSLog(@"Error loading program from compute plan., this is not a mlprogram model"); + return; + } + + [computePlan.modelStructure.program.functions enumerateKeysAndObjectsUsingBlock:^(NSString* function_name, + MLModelStructureProgramFunction* function, + BOOL* _Nonnull __unused stop) { + NSLog(@"profile function : %@", function_name); + ProfileBlock(computePlan, function.block); + dispatch_semaphore_signal(fd_sema); }]; + }]; + long status = dispatch_semaphore_wait(fd_sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * 60 * NSEC_PER_SEC))); + if (status != 0) { + NSLog(@"profile function : timeout"); + } +#endif +} + +#if __has_include() +#define HAS_COREMLOPTIMIZATIONHINT 1 +#else +#define HAS_COREMLOPTIMIZATIONHINT 0 +#endif + +API_AVAILABLE_COREML8 +void ConfigureOptimizationHints(MLModelConfiguration* config, const CoreMLOptions& coreml_options) { +#if HAS_COREMLOPTIMIZATIONHINT + MLOptimizationHints* optimizationHints = [[MLOptimizationHints alloc] init]; + if (coreml_options.UseStrategy("FastPrediction")) { + optimizationHints.specializationStrategy = MLSpecializationStrategyFastPrediction; + config.optimizationHints = optimizationHints; + } else if (coreml_options.UseStrategy("Default")) { + optimizationHints.specializationStrategy = MLSpecializationStrategyDefault; + config.optimizationHints = optimizationHints; } else { - NSLog(@"iOS 17.4+/macOS 14.4+ or later is required to use the compute plan API"); + // not set } #endif } +Status CompileOrReadCachedModel(NSURL* modelUrl, const CoreMLOptions& coreml_options, + NSMutableString* compiled_model_path) { + NSURL* cached_model_base_url = modelUrl; + if (!coreml_options.CreateMLProgram()) { + cached_model_base_url = [cached_model_base_url URLByDeletingLastPathComponent]; + } + + NSURL* cached_model_url = [cached_model_base_url URLByAppendingPathComponent:@"compiled_model.mlmodelc"]; + // if cached_model_url is existed, just return + NSError* error = nil; + NSString* cached_model_path = [cached_model_url path]; + // to pass clang-tidy static analyzer + if (compiled_model_path == nil || cached_model_path == nil) { + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Error creating cached model URL"); + } + if ([[NSFileManager defaultManager] fileExistsAtPath:cached_model_path]) { + [compiled_model_path appendString:cached_model_path]; + return Status::OK(); + } + + // TODO: Update this to version with callback handler as the API used here is deprecated. + // https://developer.apple.com/documentation/coreml/mlmodel/3929553-compilemodelaturl + // As we call loadModel during EP Compile there shouldn't be an issue letting the actual compile run in the + // background. We will have to check for completion in `predict` and block until it is done. + NSURL* compiled_model_url = [MLModel compileModelAtURL:modelUrl error:&error]; + if (error != nil) { + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Error compiling model: ", + [[error localizedDescription] UTF8String]); + } + + // to pass clang-tidy static analyzer + NSString* compiled_model_path_from_url = [compiled_model_url path]; + if (compiled_model_url == nil || cached_model_url == nil || compiled_model_path_from_url == nil) { + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, " compiled_model_url is nil or cached_model_url is nil"); + } + if (coreml_options.ModelCachePath().empty()) { + [compiled_model_path appendString:compiled_model_path_from_url]; + return Status::OK(); + } + + // save the compiled model if user has set a cache path + if (![[NSFileManager defaultManager] moveItemAtURL:compiled_model_url toURL:cached_model_url error:&error]) { + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Error copying compiled model to cache path: ", + [[cached_model_url path] UTF8String], ", reason: ", [[error localizedDescription] UTF8String]); + } + // clang-tidy + NSString* cached_model_path_from_url = [cached_model_url path]; + if (cached_model_path_from_url == nil) { + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "cached_model_path_from_url is nil"); + } + [compiled_model_path appendString:cached_model_path_from_url]; + return Status::OK(); +} + // Internal Execution class // This class is part of the model class and handles the calls into CoreML. Specifically, it performs // 1. Compile the model by given path for execution @@ -366,7 +457,7 @@ Status Predict(const std::unordered_map& inputs, private: void cleanup(); NSString* coreml_model_path_{nil}; - NSString* compiled_model_path_{nil}; + NSURL* compiled_model_url_{nil}; const logging::Logger& logger_; CoreMLOptions coreml_options_; MLModel* model_{nil}; @@ -387,14 +478,18 @@ Status Predict(const std::unordered_map& inputs, } void Execution::cleanup() { + // we keep the compiled model if the user has set a cache path + if (coreml_options_.ModelCachePath().size()) { + return; + } + NSString* compiled_model_path = [compiled_model_url_ path]; NSError* error = nil; - if (compiled_model_path_ != nil) { - [[NSFileManager defaultManager] removeItemAtPath:compiled_model_path_ error:&error]; + if (compiled_model_path != nil) { + [[NSFileManager defaultManager] removeItemAtPath:compiled_model_path error:&error]; if (error != nil) { - LOGS(logger_, ERROR) << "Failed cleaning up the compiled model: " << [compiled_model_path_ UTF8String] + LOGS(logger_, ERROR) << "Failed cleaning up the compiled model: " << [compiled_model_path UTF8String] << ", error message: " << [[error localizedDescription] UTF8String]; } - compiled_model_path_ = nil; } #if !defined(NDEBUG) @@ -430,17 +525,10 @@ Status Predict(const std::unordered_map& inputs, return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Failed to create model URL from path"); } - // TODO: Update this to version with callback handler as the API used here is deprecated. - // https://developer.apple.com/documentation/coreml/mlmodel/3929553-compilemodelaturl - // As we call loadModel during EP Compile there shouldn't be an issue letting the actual compile run in the - // background. We will have to check for completion in `predict` and block until it is done. - NSURL* compileUrl = [MLModel compileModelAtURL:modelUrl error:&error]; - if (error != nil) { - return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Error compiling model: ", - [[error localizedDescription] UTF8String]); - } - - compiled_model_path_ = [compileUrl path]; + NSMutableString* compiled_model_path = [[NSMutableString alloc] init]; + ORT_RETURN_IF_ERROR(CompileOrReadCachedModel( + [NSURL fileURLWithPath:coreml_model_path_], coreml_options_, compiled_model_path)); + compiled_model_url_ = [NSURL fileURLWithPath:compiled_model_path]; MLModelConfiguration* config = [[MLModelConfiguration alloc] init]; uint32_t coreml_compute_unit = coreml_options_.ComputeUnits(); @@ -458,27 +546,22 @@ Status Predict(const std::unordered_map& inputs, config.allowLowPrecisionAccumulationOnGPU = YES; } -// Set the specialization strategy to FastPrediction for macOS 10.15+ -// since __clang_major__ >= 15, optimizationHints is introduced in -// Same as above comments for why we are checking __clang_major__. -// we define __clang_analyzer__ here is for bypass static analysis -#if defined(__APPLE__) && defined(__clang__) && __clang_major__ >= 15 && !defined(__clang_analyzer__) + // Set the specialization strategy to FastPrediction for macOS 10.15+ if (HAS_COREML8_OR_LATER) { - MLOptimizationHints* optimizationHints = [[MLOptimizationHints alloc] init]; - if (coreml_options_.UseStrategy("FastPrediction")) { - optimizationHints.specializationStrategy = MLSpecializationStrategyFastPrediction; - config.optimizationHints = optimizationHints; - } else if (coreml_options_.UseStrategy("Default")) { - optimizationHints.specializationStrategy = MLSpecializationStrategyDefault; - config.optimizationHints = optimizationHints; - } + ConfigureOptimizationHints(config, coreml_options_); + } else { + LOGS(logger_, WARNING) << "iOS 17.4+/macOS 14.4+ or later is required to ConfigureOptimizationHints"; } -#endif + if (coreml_options_.ProfileComputePlan()) { - ProfileComputePlan(compileUrl, config); + if (@available(macOS 14.4, iOS 17.4, *)) { + ProfileComputePlan(compiled_model_url_, config); + } else { + LOGS(logger_, WARNING) << "iOS 17.4+/macOS 14.4+ or later is required to use the compute plan API"; + } } - model_ = [MLModel modelWithContentsOfURL:compileUrl configuration:config error:&error]; + model_ = [MLModel modelWithContentsOfURL:compiled_model_url_ configuration:config error:&error]; if (error != nil || model_ == nil) { return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Failed to create MLModel", diff --git a/onnxruntime/test/perftest/ort_test_session.cc b/onnxruntime/test/perftest/ort_test_session.cc index a96028ed3903e..0a09595d67252 100644 --- a/onnxruntime/test/perftest/ort_test_session.cc +++ b/onnxruntime/test/perftest/ort_test_session.cc @@ -349,7 +349,8 @@ select from 'TF8', 'TF16', 'UINT8', 'FLOAT', 'ITENSOR'. \n)"); kCoremlProviderOption_EnableOnSubgraphs, kCoremlProviderOption_SpecializationStrategy, kCoremlProviderOption_ProfileComputePlan, - kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU}; + kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU, + kCoremlProviderOption_ModelCachePath}; ParseSessionConfigs(ov_string, provider_options, available_keys); std::unordered_map available_options = { @@ -373,6 +374,7 @@ select from 'TF8', 'TF16', 'UINT8', 'FLOAT', 'ITENSOR'. \n)"); (provider_option.second == "0" || provider_option.second == "1")) { } else if (provider_option.first == kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU && (provider_option.second == "0" || provider_option.second == "1")) { + } else if (provider_option.first == kCoremlProviderOption_ModelCachePath) { } else { ORT_THROW("Invalid value for option ", provider_option.first, ": ", provider_option.second); } From b7888c41ee1b4f2b2d71714cde15e2fb239d6bbf Mon Sep 17 00:00:00 2001 From: wejoncy Date: Wed, 11 Dec 2024 11:37:08 +0800 Subject: [PATCH 02/35] improve --- .../coreml/builders/model_builder.cc | 50 ++++++++++--------- .../test/perftest/command_args_parser.cc | 1 + 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 8c98ee0c4097e..b4ce99b93fd3c 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -390,11 +390,28 @@ void CreateEmptyFile(const std::string& filename) { #endif // defined(COREML_ENABLE_MLPROGRAM) -std::string GetModelOutputPath(bool create_ml_program) { - // path is used to create the ML Package directory for ML Program, and for the model directly otherwise. - auto path = util::GetTemporaryFilePath(); - if (!create_ml_program) { - path += ".model.mlmodel"; +std::string GetModelOutputPath(const CoreMLOptions& coreml_options, + const std::vector& onnx_input_names) { + std::string path; + if (coreml_options.ModelCachePath().empty()) { + // path is used to create the ML Package directory for ML Program, and for the model directly otherwise. + path = util::GetTemporaryFilePath(); + if (!coreml_options.CreateMLProgram()) { + path += ".model.mlmodel"; + } + } else { + // input names in onnx are unique. so we can use them as the key in the cache. + std::string inputs_collections = std::accumulate( + onnx_input_names.begin(), onnx_input_names.end(), std::string(), + [](const std::string& a, const std::string& b) { return a + "," + b; }); + std::hash hasher; + // different subgraph has different folders. so we need to hash the inputs. + path = std::string(coreml_options.ModelCachePath()) + + "/" + std::to_string(hasher(inputs_collections)); + if (!coreml_options_.CreateMLProgram()) { + ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); + path += "/mlmodel"; + } } return path; @@ -410,27 +427,11 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logge coreml_version_(coreml_version), coreml_options_(coreml_options), create_ml_program_(coreml_options.CreateMLProgram()), + model_output_path_(GetModelOutputPath(coreml_options, onnx_input_names)), onnx_input_names_(std::move(onnx_input_names)), onnx_output_names_(std::move(onnx_output_names)), coreml_model_(std::make_unique()) { - if (coreml_options.ModelCachePath().empty()) { - model_output_path_ = GetModelOutputPath(create_ml_program_); - } else { - // input names in onnx are unique. so we can use them as the key in the cache. - std::string inputs_collections = std::accumulate( - onnx_input_names_.begin(), onnx_input_names_.end(), std::string(), - [](const std::string& a, const std::string& b) { return a + "," + b; }); - std::hash hasher; - // different subgraph has different folders. so we need to hash the inputs. - model_output_path_ = std::string(coreml_options.ModelCachePath()) + - "/" + std::to_string(hasher(inputs_collections)); - if (!coreml_options_.CreateMLProgram()) { - ORT_THROW_IF_ERROR(Env::Default().CreateFolder(model_output_path_)); - model_output_path_ += "/mlmodel"; - } - } - - // GetModelOutputPath(create_ml_program_) always produce a unique path for the model and this is not existed + // GetTemporaryFilePath() always produce a unique path for the model and this is not existed // Mlprogram will create a folder while NN create a file if (Env::Default().FolderExists(ToPathString(model_output_path_)) || Env::Default().FileExists(ToPathString(model_output_path_))) { @@ -874,7 +875,7 @@ Status ModelBuilder::RegisterModelInputOutput(const NodeArg& node_arg, bool is_i input_output_info_.emplace(name, OnnxTensorInfo{data_type, shape}); - if (is_model_cached_) { + if (IsModelCached()) { return Status::OK(); } @@ -1091,6 +1092,7 @@ Status ModelBuilder::Build(const GraphViewer& graph_viewer, const logging::Logge ORT_RETURN_IF_ERROR(builder.CreateModel()); ORT_RETURN_IF_ERROR(builder.SaveModel()); } else { + // runtime requires the input/output names to be passed ORT_RETURN_IF_ERROR(builder.RegisterModelInputs()); ORT_RETURN_IF_ERROR(builder.RegisterModelOutputs()); } diff --git a/onnxruntime/test/perftest/command_args_parser.cc b/onnxruntime/test/perftest/command_args_parser.cc index 23c3812ebd025..9553889a04c4f 100644 --- a/onnxruntime/test/perftest/command_args_parser.cc +++ b/onnxruntime/test/perftest/command_args_parser.cc @@ -138,6 +138,7 @@ namespace perftest { "\t [CoreML only] [SpecializationStrategy]:[Default FastPrediction].\n" "\t [CoreML only] [ProfileComputePlan]:[0 1].\n" "\t [CoreML only] [AllowLowPrecisionAccumulationOnGPU]:[0 1].\n" + "\t [CoreML only] [ModelCachePath]:[path../a/b/c].\n" "\t [Example] [For CoreML EP] -e coreml -i \"ModelFormat|MLProgram MLComputeUnits|CPUAndGPU\"\n" "\n" "\t [SNPE only] [runtime]: SNPE runtime, options: 'CPU', 'GPU', 'GPU_FLOAT16', 'DSP', 'AIP_FIXED_TF'. \n" From f492fee9dc7fe0a805df870c01f0a89eee21df4d Mon Sep 17 00:00:00 2001 From: wejoncy Date: Wed, 11 Dec 2024 12:17:38 +0800 Subject: [PATCH 03/35] fix --- onnxruntime/core/providers/coreml/builders/model_builder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index b4ce99b93fd3c..92d577770804a 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -408,7 +408,7 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, // different subgraph has different folders. so we need to hash the inputs. path = std::string(coreml_options.ModelCachePath()) + "/" + std::to_string(hasher(inputs_collections)); - if (!coreml_options_.CreateMLProgram()) { + if (!coreml_options.CreateMLProgram()) { ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); path += "/mlmodel"; } From 7b1184890ff5347810e609b603ff66919cff75fd Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 14:10:29 +0800 Subject: [PATCH 04/35] better hash --- .../providers/coreml/builders/model_builder.cc | 17 +++++++---------- .../coreml/coreml_execution_provider.cc | 7 ++++++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 92d577770804a..9c770fe05185f 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -391,7 +391,7 @@ void CreateEmptyFile(const std::string& filename) { #endif // defined(COREML_ENABLE_MLPROGRAM) std::string GetModelOutputPath(const CoreMLOptions& coreml_options, - const std::vector& onnx_input_names) { + const std::string& graph_name) { std::string path; if (coreml_options.ModelCachePath().empty()) { // path is used to create the ML Package directory for ML Program, and for the model directly otherwise. @@ -400,14 +400,11 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, path += ".model.mlmodel"; } } else { - // input names in onnx are unique. so we can use them as the key in the cache. - std::string inputs_collections = std::accumulate( - onnx_input_names.begin(), onnx_input_names.end(), std::string(), - [](const std::string& a, const std::string& b) { return a + "," + b; }); - std::hash hasher; - // different subgraph has different folders. so we need to hash the inputs. - path = std::string(coreml_options.ModelCachePath()) + - "/" + std::to_string(hasher(inputs_collections)); + // graph_name is uniquely generated by + // onnxruntime/core/providers/coreml/coreml_execution_provider.cc::gen_metadef_name + // int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); + // MakeString(COREML, "_", model_hash, "_", metadef_id);. + path = std::string(coreml_options.ModelCachePath()) + "/" + graph_name; if (!coreml_options.CreateMLProgram()) { ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); path += "/mlmodel"; @@ -427,7 +424,7 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logge coreml_version_(coreml_version), coreml_options_(coreml_options), create_ml_program_(coreml_options.CreateMLProgram()), - model_output_path_(GetModelOutputPath(coreml_options, onnx_input_names)), + model_output_path_(GetModelOutputPath(coreml_options, graph_viewer.Name())), onnx_input_names_(std::move(onnx_input_names)), onnx_output_names_(std::move(onnx_output_names)), coreml_model_(std::make_unique()) { diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 5a2867e5524e4..835a70e8ef1e3 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -18,6 +18,7 @@ #include "core/providers/coreml/model/host_utils.h" #include "core/providers/coreml/model/model.h" #include "core/providers/coreml/shape_utils.h" +#include "core/graph/model.h" namespace onnxruntime { @@ -57,7 +58,11 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie [&]() { HashValue model_hash; int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); - return MakeString(COREML, "_", model_hash, "_", metadef_id); + std::string user_provide_hash; + if (graph_viewer.GetGraph().GetModel().MetaData().count("CACHE_KEY") > 0) { + user_provide_hash = graph_viewer.GetGraph().GetModel().MetaData().at("CACHE_KEY"); + } + return MakeString(user_provide_hash, "_", COREML, "_", model_hash, "_", metadef_id); }; result = utils::CreateSupportedPartitions(graph_viewer, supported_nodes, {}, From 4a5772f3a931356e37b6ed24acbcc24a597abb13 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 16:12:51 +0800 Subject: [PATCH 05/35] refactor output -path --- .../coreml/builders/model_builder.cc | 44 +++++++++++++++---- .../coreml/coreml_execution_provider.cc | 15 +++++-- .../core/providers/coreml/coreml_options.cc | 16 ------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 9c770fe05185f..6cc242c017d62 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -391,7 +391,8 @@ void CreateEmptyFile(const std::string& filename) { #endif // defined(COREML_ENABLE_MLPROGRAM) std::string GetModelOutputPath(const CoreMLOptions& coreml_options, - const std::string& graph_name) { + const GraphViewer& graph_viewer) { + const std::string& subgraph_name = graph_viewer.Name(); std::string path; if (coreml_options.ModelCachePath().empty()) { // path is used to create the ML Package directory for ML Program, and for the model directly otherwise. @@ -400,17 +401,42 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, path += ".model.mlmodel"; } } else { - // graph_name is uniquely generated by + // subgraph_name is uniquely generated by // onnxruntime/core/providers/coreml/coreml_execution_provider.cc::gen_metadef_name // int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); - // MakeString(COREML, "_", model_hash, "_", metadef_id);. - path = std::string(coreml_options.ModelCachePath()) + "/" + graph_name; - if (!coreml_options.CreateMLProgram()) { - ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); - path += "/mlmodel"; + // MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); + ORT_ENFORCE(std::count(subgraph_name.begin(), subgraph_name.end(), '_') == 3, + "Unexpected graph name format: ", subgraph_name); + std::string_view cache_key = std::string_view(subgraph_name).substr(0, subgraph_name.find_first_of("_")); + path = MakeString(std::string(coreml_options.ModelCachePath()), "/", cache_key); + ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); + if (!Env::Default().FileExists(ToPathString(path + "/model.txt"))) { + const Graph* main_graph = &graph_viewer.GetGraph(); + while (main_graph->IsSubgraph()) { + main_graph = main_graph->ParentGraph(); + } + std::ofstream file(path + "/model.txt"); + ORT_ENFORCE(file.is_open(), "Failed to open file ", path + "/model.txt"); + file << main_graph->ModelPath().string(); + file.close(); } - } + path = MakeString(path, "/", subgraph_name); + // Set the model cache path with equireStaticShape and ModelFormat + if (coreml_options.RequireStaticShape()) { + path += "/static_shape"; + } else { + path += "/dynamic_shape"; + } + + if (coreml_options.CreateMLProgram()) { + path += ".mlpackage"; + } else { + path += ".mlnnmodel"; + } + ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); + path += "/mlmodel"; + } return path; } } // namespace @@ -424,7 +450,7 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logge coreml_version_(coreml_version), coreml_options_(coreml_options), create_ml_program_(coreml_options.CreateMLProgram()), - model_output_path_(GetModelOutputPath(coreml_options, graph_viewer.Name())), + model_output_path_(GetModelOutputPath(coreml_options, graph_viewer)), onnx_input_names_(std::move(onnx_input_names)), onnx_output_names_(std::move(onnx_output_names)), coreml_model_(std::make_unique()) { diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 835a70e8ef1e3..9ea111a105cdb 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -58,11 +58,18 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie [&]() { HashValue model_hash; int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); - std::string user_provide_hash; - if (graph_viewer.GetGraph().GetModel().MetaData().count("CACHE_KEY") > 0) { - user_provide_hash = graph_viewer.GetGraph().GetModel().MetaData().at("CACHE_KEY"); + std::string user_provide_key; + const Graph* main_graph = &graph_viewer.GetGraph(); + while (main_graph->IsSubgraph()) { + main_graph = main_graph->ParentGraph(); } - return MakeString(user_provide_hash, "_", COREML, "_", model_hash, "_", metadef_id); + if (main_graph->GetModel().MetaData().count("CACHE_KEY") > 0) { + user_provide_key = graph_viewer.GetGraph().GetModel().MetaData().at("CACHE_KEY"); + } else { + // model_hash is a 64-bit hash value of model_path + user_provide_key = std::to_string(model_hash); + } + return MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); }; result = utils::CreateSupportedPartitions(graph_viewer, supported_nodes, {}, diff --git a/onnxruntime/core/providers/coreml/coreml_options.cc b/onnxruntime/core/providers/coreml/coreml_options.cc index 5babd7633cd88..5b141f14cb37e 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.cc +++ b/onnxruntime/core/providers/coreml/coreml_options.cc @@ -109,21 +109,5 @@ void CoreMLOptions::ValidateAndParseProviderOption(const ProviderOptions& option model_cache_path_ = option.second; } } - - // Set the model cache path with equireStaticShape and ModelFormat - if (model_cache_path_.size()) { - if (require_static_shape_) { - model_cache_path_ += "/static_shape"; - } else { - model_cache_path_ += "/dynamic_shape"; - } - - if (create_mlprogram_) { - model_cache_path_ += "/mlpackage"; - } else { - model_cache_path_ += "/mlnnmodel"; - } - ORT_THROW_IF_ERROR(Env::Default().CreateFolder(model_cache_path_)); - } } } // namespace onnxruntime From b57aa2853cf77d67ae5776611c992bc04e1c8507 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 17:26:10 +0800 Subject: [PATCH 06/35] address comments --- .../providers/coreml/builders/model_builder.cc | 4 +++- .../providers/coreml/coreml_execution_provider.cc | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 6cc242c017d62..db2af3d4dddc1 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -410,6 +410,8 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, std::string_view cache_key = std::string_view(subgraph_name).substr(0, subgraph_name.find_first_of("_")); path = MakeString(std::string(coreml_options.ModelCachePath()), "/", cache_key); ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); + // Write the model path to a file in the cache directory. + // This is for developers to know what the cached model is as we used a hash for the directory name. if (!Env::Default().FileExists(ToPathString(path + "/model.txt"))) { const Graph* main_graph = &graph_viewer.GetGraph(); while (main_graph->IsSubgraph()) { @@ -422,7 +424,7 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, } path = MakeString(path, "/", subgraph_name); - // Set the model cache path with equireStaticShape and ModelFormat + // Set the model cache path with setting of RequireStaticShape and ModelFormat if (coreml_options.RequireStaticShape()) { path += "/static_shape"; } else { diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 9ea111a105cdb..fa725d5b2bd46 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -58,18 +58,22 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie [&]() { HashValue model_hash; int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); - std::string user_provide_key; + std::string user_provided_key; const Graph* main_graph = &graph_viewer.GetGraph(); while (main_graph->IsSubgraph()) { main_graph = main_graph->ParentGraph(); } if (main_graph->GetModel().MetaData().count("CACHE_KEY") > 0) { - user_provide_key = graph_viewer.GetGraph().GetModel().MetaData().at("CACHE_KEY"); + user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at("CACHE_KEY"); } else { - // model_hash is a 64-bit hash value of model_path - user_provide_key = std::to_string(model_hash); + // model_hash is a 64-bit hash value of model_path if model_path is not empty, + // otherwise it hashes the graph input names and all the node output names. + // it can't guarantee the uniqueness of the key, so user should manager the key by themselves for the best. + user_provided_key = std::to_string(model_hash); } - return MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); + // The string format is used by onnxruntime/core/providers/coreml/builders/model_builder.cc::GetModelOutputPath + // If the format changes, the function should be updated accordingly. + return MakeString(user_provided_key, "_", COREML, "_", model_hash, "_", metadef_id); }; result = utils::CreateSupportedPartitions(graph_viewer, supported_nodes, {}, From 723b2dd5015c254bb768af00d3effcf4a6058343 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 17:28:58 +0800 Subject: [PATCH 07/35] remove extra check --- onnxruntime/core/providers/coreml/builders/model_builder.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index db2af3d4dddc1..24487d46bdc04 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -405,8 +405,6 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, // onnxruntime/core/providers/coreml/coreml_execution_provider.cc::gen_metadef_name // int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); // MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); - ORT_ENFORCE(std::count(subgraph_name.begin(), subgraph_name.end(), '_') == 3, - "Unexpected graph name format: ", subgraph_name); std::string_view cache_key = std::string_view(subgraph_name).substr(0, subgraph_name.find_first_of("_")); path = MakeString(std::string(coreml_options.ModelCachePath()), "/", cache_key); ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); From 4f0ac2a408e5e337201e371db23a00bb5c8e6d45 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 18:20:11 +0800 Subject: [PATCH 08/35] Apply suggestions from code review Co-authored-by: Scott McKay --- .../onnxruntime/core/providers/coreml/coreml_provider_factory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index 12bdcddb5ae2a..6c6d5c8d601ef 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -61,7 +61,7 @@ static const char* const kCoremlProviderOption_SpecializationStrategy = "Special static const char* const kCoremlProviderOption_ProfileComputePlan = "ProfileComputePlan"; // please refer to https://developer.apple.com/documentation/coreml/mlmodelconfiguration/allowlowprecisionaccumulationongpu static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU = "AllowLowPrecisionAccumulationOnGPU"; -// Specify the path to cache the model. +// Specify the directory to cache any CoreML models created from the ONNX model in. // CoreML EP will convert onnx subgraph to CoreML model and save to disk. // If this path is not specified, the model will be saved to a temp directory and deleted after the session is closed. // otherwise, the model will be saved to the specified path and User should manage to delete the model. From 781e42ee8caefe0d7527b6c86eb25cabf1c393db Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 18:47:00 +0800 Subject: [PATCH 09/35] improve doc --- .../providers/coreml/coreml_provider_factory.h | 18 +++++++++++------- .../providers/coreml/builders/model_builder.cc | 6 +++--- .../coreml/coreml_execution_provider.cc | 6 +++--- .../core/providers/coreml/coreml_options.cc | 4 ++-- onnxruntime/test/perftest/ort_test_session.cc | 4 ++-- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index 6c6d5c8d601ef..24caf52f31ae9 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -65,14 +65,18 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP // CoreML EP will convert onnx subgraph to CoreML model and save to disk. // If this path is not specified, the model will be saved to a temp directory and deleted after the session is closed. // otherwise, the model will be saved to the specified path and User should manage to delete the model. -// The basic logic is: -// if (ModelCachePath != nullptr && ModelCachePath/cache_coreml.exists()) { -// // load from cache_coreml -// } else { -// // save to ModelCachePath -// } + // we wound not detect if the cached model match the onnx subgraph, so User should carefully manage the cache for a new model. -static const char* const kCoremlProviderOption_ModelCachePath = "ModelCachePath"; +// The cache key is generated by +// 1. user provided key in metadata_props if exist (prefered) +// 2. Hash of the model url the inference session was created with +// 3. Hash of the input/output names of the model + +// EP wounln't track the model change or responsible for the cache management. +static const char* const kCoremlProviderOption_ModelCacheDirectory = "ModelCachePath"; + +// User provided cache-key in metadata_props. +static const char* const kCOREML_CACHE_KEY = "CACHE_KEY"; #ifdef __cplusplus extern "C" { diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 24487d46bdc04..a04247b0e8936 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -459,9 +459,9 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logge if (Env::Default().FolderExists(ToPathString(model_output_path_)) || Env::Default().FileExists(ToPathString(model_output_path_))) { is_model_cached_ = true; - LOGS(logger, WARNING) << "Model is already cached in " << model_output_path_ - << " and will be reused. If you want to update the model or hit other issues, " - << "please consider to clear the cache and retry."; + LOGS(logger, INFO) << "Model is already cached in " << model_output_path_ + << " and will be reused. If you want to update the model or hit other issues, " + << "please consider to clear the cache and retry."; return; } diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index fa725d5b2bd46..ee1f39e5723d5 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -63,12 +63,12 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie while (main_graph->IsSubgraph()) { main_graph = main_graph->ParentGraph(); } - if (main_graph->GetModel().MetaData().count("CACHE_KEY") > 0) { - user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at("CACHE_KEY"); + if (main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0) { + user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); } else { // model_hash is a 64-bit hash value of model_path if model_path is not empty, // otherwise it hashes the graph input names and all the node output names. - // it can't guarantee the uniqueness of the key, so user should manager the key by themselves for the best. + // it can't guarantee the uniqueness of the key, so user should manager the key for the best. user_provided_key = std::to_string(model_hash); } // The string format is used by onnxruntime/core/providers/coreml/builders/model_builder.cc::GetModelOutputPath diff --git a/onnxruntime/core/providers/coreml/coreml_options.cc b/onnxruntime/core/providers/coreml/coreml_options.cc index 5b141f14cb37e..3def1860f0494 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.cc +++ b/onnxruntime/core/providers/coreml/coreml_options.cc @@ -72,7 +72,7 @@ void CoreMLOptions::ValidateAndParseProviderOption(const ProviderOptions& option kCoremlProviderOption_SpecializationStrategy, kCoremlProviderOption_ProfileComputePlan, kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU, - kCoremlProviderOption_ModelCachePath, + kCoremlProviderOption_ModelCacheDirectory, }; // Validate the options for (const auto& option : options) { @@ -105,7 +105,7 @@ void CoreMLOptions::ValidateAndParseProviderOption(const ProviderOptions& option profile_compute_plan_ = option.second == "1"; } else if (kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU == option.first) { allow_low_precision_accumulation_on_gpu_ = option.second == "1"; - } else if (kCoremlProviderOption_ModelCachePath == option.first) { + } else if (kCoremlProviderOption_ModelCacheDirectory == option.first) { model_cache_path_ = option.second; } } diff --git a/onnxruntime/test/perftest/ort_test_session.cc b/onnxruntime/test/perftest/ort_test_session.cc index 0a09595d67252..08c2cff8058c2 100644 --- a/onnxruntime/test/perftest/ort_test_session.cc +++ b/onnxruntime/test/perftest/ort_test_session.cc @@ -350,7 +350,7 @@ select from 'TF8', 'TF16', 'UINT8', 'FLOAT', 'ITENSOR'. \n)"); kCoremlProviderOption_SpecializationStrategy, kCoremlProviderOption_ProfileComputePlan, kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU, - kCoremlProviderOption_ModelCachePath}; + kCoremlProviderOption_ModelCacheDirectory}; ParseSessionConfigs(ov_string, provider_options, available_keys); std::unordered_map available_options = { @@ -374,7 +374,7 @@ select from 'TF8', 'TF16', 'UINT8', 'FLOAT', 'ITENSOR'. \n)"); (provider_option.second == "0" || provider_option.second == "1")) { } else if (provider_option.first == kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU && (provider_option.second == "0" || provider_option.second == "1")) { - } else if (provider_option.first == kCoremlProviderOption_ModelCachePath) { + } else if (provider_option.first == kCoremlProviderOption_ModelCacheDirectory) { } else { ORT_THROW("Invalid value for option ", provider_option.first, ": ", provider_option.second); } From 26775b4283a0e04a30819fe2cdab6cabec4a39ef Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 18:48:25 +0800 Subject: [PATCH 10/35] typo --- .../onnxruntime/core/providers/coreml/coreml_provider_factory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index 24caf52f31ae9..1913132cac62c 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -66,7 +66,7 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP // If this path is not specified, the model will be saved to a temp directory and deleted after the session is closed. // otherwise, the model will be saved to the specified path and User should manage to delete the model. -// we wound not detect if the cached model match the onnx subgraph, so User should carefully manage the cache for a new model. +// we would not detect if the cached model match the onnx subgraph, so User should carefully manage the cache for a new model. // The cache key is generated by // 1. user provided key in metadata_props if exist (prefered) // 2. Hash of the model url the inference session was created with From 89317c63b2e57868597a09ed440102ea0bb39ad8 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 19:03:32 +0800 Subject: [PATCH 11/35] check cache-key --- .../core/providers/coreml/coreml_execution_provider.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index ee1f39e5723d5..847e6f6fae87b 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -65,6 +65,11 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie } if (main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0) { user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); + if (user_provided_key.size() > 32 || + std::count_if(user_provided_key.begin(), user_provided_key.end(), + [](unsigned char c) { return std::isalnum(c); })) { + user_provided_key = std::to_string(std::hash{}(user_provided_key)); + } } else { // model_hash is a 64-bit hash value of model_path if model_path is not empty, // otherwise it hashes the graph input names and all the node output names. From 773dce061d1977e18a815a4bc068f63d380ede04 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 19:06:37 +0800 Subject: [PATCH 12/35] validate alpha-number --- .../core/providers/coreml/coreml_execution_provider.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 847e6f6fae87b..86222e364666a 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -66,8 +66,8 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie if (main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0) { user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); if (user_provided_key.size() > 32 || - std::count_if(user_provided_key.begin(), user_provided_key.end(), - [](unsigned char c) { return std::isalnum(c); })) { + std::any_if(user_provided_key.begin(), user_provided_key.end(), + [](unsigned char c) { return !std::isalnum(c); })) { user_provided_key = std::to_string(std::hash{}(user_provided_key)); } } else { From e82f3e41c5f10f633eda08dbb9e896d0a248301a Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 19:07:11 +0800 Subject: [PATCH 13/35] fix --- onnxruntime/core/providers/coreml/coreml_execution_provider.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 86222e364666a..a208a44eb39ff 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -66,7 +66,7 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie if (main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0) { user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); if (user_provided_key.size() > 32 || - std::any_if(user_provided_key.begin(), user_provided_key.end(), + std::any_of(user_provided_key.begin(), user_provided_key.end(), [](unsigned char c) { return !std::isalnum(c); })) { user_provided_key = std::to_string(std::hash{}(user_provided_key)); } From d3d25b97754edd3ead6618742cfbdca98f6cae68 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 19:12:15 +0800 Subject: [PATCH 14/35] format --- onnxruntime/core/providers/coreml/coreml_execution_provider.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index a208a44eb39ff..40f5ff09ae20a 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -67,7 +67,7 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); if (user_provided_key.size() > 32 || std::any_of(user_provided_key.begin(), user_provided_key.end(), - [](unsigned char c) { return !std::isalnum(c); })) { + [](unsigned char c) { return !std::isalnum(c); })) { user_provided_key = std::to_string(std::hash{}(user_provided_key)); } } else { From d053dc0548f1cebce3cc1b431a212185e4e761c3 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 19:29:20 +0800 Subject: [PATCH 15/35] fix bug --- .../core/providers/coreml/coreml_execution_provider.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 40f5ff09ae20a..226c844e9491e 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -70,6 +70,10 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie [](unsigned char c) { return !std::isalnum(c); })) { user_provided_key = std::to_string(std::hash{}(user_provided_key)); } + // invalid cache-key + if (user_provided_key.size() == 0){ + user_provided_key = std::to_string(model_hash); + } } else { // model_hash is a 64-bit hash value of model_path if model_path is not empty, // otherwise it hashes the graph input names and all the node output names. From 2779e3d8da06e891f619893ee5f63401eb1f92c5 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 16 Dec 2024 19:53:46 +0800 Subject: [PATCH 16/35] format --- onnxruntime/core/providers/coreml/coreml_execution_provider.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 226c844e9491e..18540c048b5dc 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -71,7 +71,7 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie user_provided_key = std::to_string(std::hash{}(user_provided_key)); } // invalid cache-key - if (user_provided_key.size() == 0){ + if (user_provided_key.size() == 0) { user_provided_key = std::to_string(model_hash); } } else { From c7194ad6213aa5a50192f1ec75025fb12e8fe935 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 17 Dec 2024 12:10:21 +0800 Subject: [PATCH 17/35] renaming --- .../core/providers/coreml/coreml_provider_factory.h | 2 +- onnxruntime/core/providers/coreml/builders/model_builder.cc | 4 ++-- onnxruntime/core/providers/coreml/coreml_options.cc | 2 +- onnxruntime/core/providers/coreml/coreml_options.h | 4 ++-- onnxruntime/core/providers/coreml/model/model.mm | 4 ++-- onnxruntime/test/perftest/command_args_parser.cc | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index 1913132cac62c..ac25e171e7944 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -73,7 +73,7 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP // 3. Hash of the input/output names of the model // EP wounln't track the model change or responsible for the cache management. -static const char* const kCoremlProviderOption_ModelCacheDirectory = "ModelCachePath"; +static const char* const kCoremlProviderOption_ModelCacheDirectory = "ModelCacheDirectory"; // User provided cache-key in metadata_props. static const char* const kCOREML_CACHE_KEY = "CACHE_KEY"; diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index a04247b0e8936..f04086631ef52 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -394,7 +394,7 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, const GraphViewer& graph_viewer) { const std::string& subgraph_name = graph_viewer.Name(); std::string path; - if (coreml_options.ModelCachePath().empty()) { + if (coreml_options.ModelCacheDirectory().empty()) { // path is used to create the ML Package directory for ML Program, and for the model directly otherwise. path = util::GetTemporaryFilePath(); if (!coreml_options.CreateMLProgram()) { @@ -406,7 +406,7 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, // int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); // MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); std::string_view cache_key = std::string_view(subgraph_name).substr(0, subgraph_name.find_first_of("_")); - path = MakeString(std::string(coreml_options.ModelCachePath()), "/", cache_key); + path = MakeString(std::string(coreml_options.ModelCacheDirectory()), "/", cache_key); ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); // Write the model path to a file in the cache directory. // This is for developers to know what the cached model is as we used a hash for the directory name. diff --git a/onnxruntime/core/providers/coreml/coreml_options.cc b/onnxruntime/core/providers/coreml/coreml_options.cc index 3def1860f0494..14ae55de9266b 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.cc +++ b/onnxruntime/core/providers/coreml/coreml_options.cc @@ -106,7 +106,7 @@ void CoreMLOptions::ValidateAndParseProviderOption(const ProviderOptions& option } else if (kCoremlProviderOption_AllowLowPrecisionAccumulationOnGPU == option.first) { allow_low_precision_accumulation_on_gpu_ = option.second == "1"; } else if (kCoremlProviderOption_ModelCacheDirectory == option.first) { - model_cache_path_ = option.second; + model_cache_directory_ = option.second; } } } diff --git a/onnxruntime/core/providers/coreml/coreml_options.h b/onnxruntime/core/providers/coreml/coreml_options.h index 1ec4294492552..87b6a51f5d9fb 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.h +++ b/onnxruntime/core/providers/coreml/coreml_options.h @@ -18,7 +18,7 @@ class CoreMLOptions { bool profile_compute_plan_{false}; bool allow_low_precision_accumulation_on_gpu_{false}; // path to store the converted coreml model - std::string model_cache_path_; + std::string model_cache_directory_; public: explicit CoreMLOptions(uint32_t coreml_flags); @@ -34,7 +34,7 @@ class CoreMLOptions { bool UseStrategy(std::string_view strategy) const { return strategy_ == strategy; } bool ProfileComputePlan() const { return profile_compute_plan_ && create_mlprogram_; } - std::string_view ModelCachePath() const { return model_cache_path_; } + std::string_view ModelCacheDirectory() const { return model_cache_directory_; } private: void ValidateAndParseProviderOption(const ProviderOptions& options); diff --git a/onnxruntime/core/providers/coreml/model/model.mm b/onnxruntime/core/providers/coreml/model/model.mm index e0f70e369a1a2..5211b89ec17c6 100644 --- a/onnxruntime/core/providers/coreml/model/model.mm +++ b/onnxruntime/core/providers/coreml/model/model.mm @@ -420,7 +420,7 @@ Status CompileOrReadCachedModel(NSURL* modelUrl, const CoreMLOptions& coreml_opt if (compiled_model_url == nil || cached_model_url == nil || compiled_model_path_from_url == nil) { return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, " compiled_model_url is nil or cached_model_url is nil"); } - if (coreml_options.ModelCachePath().empty()) { + if (coreml_options.ModelCacheDirectory().empty()) { [compiled_model_path appendString:compiled_model_path_from_url]; return Status::OK(); } @@ -479,7 +479,7 @@ Status Predict(const std::unordered_map& inputs, void Execution::cleanup() { // we keep the compiled model if the user has set a cache path - if (coreml_options_.ModelCachePath().size()) { + if (coreml_options_.ModelCacheDirectory().size()) { return; } NSString* compiled_model_path = [compiled_model_url_ path]; diff --git a/onnxruntime/test/perftest/command_args_parser.cc b/onnxruntime/test/perftest/command_args_parser.cc index 9553889a04c4f..0b1b2bae6c972 100644 --- a/onnxruntime/test/perftest/command_args_parser.cc +++ b/onnxruntime/test/perftest/command_args_parser.cc @@ -138,7 +138,7 @@ namespace perftest { "\t [CoreML only] [SpecializationStrategy]:[Default FastPrediction].\n" "\t [CoreML only] [ProfileComputePlan]:[0 1].\n" "\t [CoreML only] [AllowLowPrecisionAccumulationOnGPU]:[0 1].\n" - "\t [CoreML only] [ModelCachePath]:[path../a/b/c].\n" + "\t [CoreML only] [ModelCacheDirectory]:[path../a/b/c].\n" "\t [Example] [For CoreML EP] -e coreml -i \"ModelFormat|MLProgram MLComputeUnits|CPUAndGPU\"\n" "\n" "\t [SNPE only] [runtime]: SNPE runtime, options: 'CPU', 'GPU', 'GPU_FLOAT16', 'DSP', 'AIP_FIXED_TF'. \n" From 8204e64450e7cd33f6f02a948da522d52cbd4691 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 17 Dec 2024 14:37:02 +0800 Subject: [PATCH 18/35] max 64 chars --- onnxruntime/core/providers/coreml/coreml_execution_provider.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 18540c048b5dc..8367bc6dbbb62 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -65,7 +65,7 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie } if (main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0) { user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); - if (user_provided_key.size() > 32 || + if (user_provided_key.size() > 64 || std::any_of(user_provided_key.begin(), user_provided_key.end(), [](unsigned char c) { return !std::isalnum(c); })) { user_provided_key = std::to_string(std::hash{}(user_provided_key)); From 9c9374cb29e75eac38e22db06012c7564db77f09 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Wed, 18 Dec 2024 11:20:01 +0800 Subject: [PATCH 19/35] polish cache path --- .../providers/coreml/builders/model_builder.cc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index f04086631ef52..206bd119f5637 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -406,6 +406,8 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, // int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); // MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); std::string_view cache_key = std::string_view(subgraph_name).substr(0, subgraph_name.find_first_of("_")); + // subgraph_short_name is metadef_id + std::string_view subgraph_short_name = std::string_view(subgraph_name).substr(subgraph_name.find_last_of("_")); path = MakeString(std::string(coreml_options.ModelCacheDirectory()), "/", cache_key); ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); // Write the model path to a file in the cache directory. @@ -421,21 +423,21 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, file.close(); } - path = MakeString(path, "/", subgraph_name); + path = MakeString(path, "/", subgraph_short_name); // Set the model cache path with setting of RequireStaticShape and ModelFormat if (coreml_options.RequireStaticShape()) { - path += "/static_shape"; + path += "_static"; } else { - path += "/dynamic_shape"; + path += "_dynamic"; } if (coreml_options.CreateMLProgram()) { - path += ".mlpackage"; + path += "_mlprogram"; } else { - path += ".mlnnmodel"; + path += "_nn"; } ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); - path += "/mlmodel"; + path += "/model"; } return path; } From 8faf178eb7a61051919356df15b38d5ce8075465 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Wed, 18 Dec 2024 11:27:38 +0800 Subject: [PATCH 20/35] fix --- onnxruntime/core/providers/coreml/builders/model_builder.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 206bd119f5637..952529a8a918d 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -405,9 +405,11 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, // onnxruntime/core/providers/coreml/coreml_execution_provider.cc::gen_metadef_name // int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); // MakeString(user_provide_key, "_", COREML, "_", model_hash, "_", metadef_id); - std::string_view cache_key = std::string_view(subgraph_name).substr(0, subgraph_name.find_first_of("_")); + std::string_view cache_key = std::string_view(subgraph_name) + .substr(0, subgraph_name.find_first_of("_")); // subgraph_short_name is metadef_id - std::string_view subgraph_short_name = std::string_view(subgraph_name).substr(subgraph_name.find_last_of("_")); + std::string_view subgraph_short_name = std::string_view(subgraph_name) + .substr(subgraph_name.find_last_of("_") + 1); path = MakeString(std::string(coreml_options.ModelCacheDirectory()), "/", cache_key); ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); // Write the model path to a file in the cache directory. From e4e35470ed856fc0b07bb61d46da0431f5077774 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Thu, 19 Dec 2024 14:42:04 +0800 Subject: [PATCH 21/35] Update include/onnxruntime/core/providers/coreml/coreml_provider_factory.h Co-authored-by: Scott McKay --- .../core/providers/coreml/coreml_provider_factory.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index ac25e171e7944..6d7daaf3c87cd 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -66,9 +66,10 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP // If this path is not specified, the model will be saved to a temp directory and deleted after the session is closed. // otherwise, the model will be saved to the specified path and User should manage to delete the model. -// we would not detect if the cached model match the onnx subgraph, so User should carefully manage the cache for a new model. +// we do NOT detect if the onnx model has changed and no longer matches the cached model. +// the user should carefully manage the cache if modifying/replacing a model. // The cache key is generated by -// 1. user provided key in metadata_props if exist (prefered) +// 1. User provided key in metadata_props if found (preferred) // 2. Hash of the model url the inference session was created with // 3. Hash of the input/output names of the model From 728fbee0dba461f0bb967e8ea306f58acba57774 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Thu, 19 Dec 2024 14:42:14 +0800 Subject: [PATCH 22/35] Update include/onnxruntime/core/providers/coreml/coreml_provider_factory.h Co-authored-by: Scott McKay --- .../onnxruntime/core/providers/coreml/coreml_provider_factory.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index 6d7daaf3c87cd..98a9a1907ecf8 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -72,8 +72,6 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP // 1. User provided key in metadata_props if found (preferred) // 2. Hash of the model url the inference session was created with // 3. Hash of the input/output names of the model - -// EP wounln't track the model change or responsible for the cache management. static const char* const kCoremlProviderOption_ModelCacheDirectory = "ModelCacheDirectory"; // User provided cache-key in metadata_props. From e49112ca2ee48889ca318c5933736eedbbdc8b09 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Thu, 19 Dec 2024 14:42:25 +0800 Subject: [PATCH 23/35] Update include/onnxruntime/core/providers/coreml/coreml_provider_factory.h Co-authored-by: Scott McKay --- .../onnxruntime/core/providers/coreml/coreml_provider_factory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index 98a9a1907ecf8..eedf76b5542b6 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -75,7 +75,7 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP static const char* const kCoremlProviderOption_ModelCacheDirectory = "ModelCacheDirectory"; // User provided cache-key in metadata_props. -static const char* const kCOREML_CACHE_KEY = "CACHE_KEY"; +static const char* const kCOREML_CACHE_KEY = "COREML_CACHE_KEY"; #ifdef __cplusplus extern "C" { From d7b867cd233e430490d0029757e300251c978bc4 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Thu, 19 Dec 2024 15:26:27 +0800 Subject: [PATCH 24/35] disable caching in runtime. --- .../providers/coreml/coreml_provider_factory.h | 3 ++- .../providers/coreml/builders/model_builder.cc | 17 +++++++++++++---- .../coreml/coreml_execution_provider.cc | 3 ++- .../core/providers/coreml/coreml_options.h | 5 ++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h index eedf76b5542b6..351eafc2a4675 100644 --- a/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h +++ b/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h @@ -66,12 +66,13 @@ static const char* const kCoremlProviderOption_AllowLowPrecisionAccumulationOnGP // If this path is not specified, the model will be saved to a temp directory and deleted after the session is closed. // otherwise, the model will be saved to the specified path and User should manage to delete the model. -// we do NOT detect if the onnx model has changed and no longer matches the cached model. +// we do NOT detect if the onnx model has changed and no longer matches the cached model. // the user should carefully manage the cache if modifying/replacing a model. // The cache key is generated by // 1. User provided key in metadata_props if found (preferred) // 2. Hash of the model url the inference session was created with // 3. Hash of the input/output names of the model +// Please find out how to set metadata_props in the onnxruntime API documentation. https://onnxruntime.ai/docs/execution-providers/CoreML-ExecutionProvider.html#configuration-options static const char* const kCoremlProviderOption_ModelCacheDirectory = "ModelCacheDirectory"; // User provided cache-key in metadata_props. diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 952529a8a918d..fbd591a0e809a 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -391,7 +391,8 @@ void CreateEmptyFile(const std::string& filename) { #endif // defined(COREML_ENABLE_MLPROGRAM) std::string GetModelOutputPath(const CoreMLOptions& coreml_options, - const GraphViewer& graph_viewer) { + const GraphViewer& graph_viewer, + const logging::Logger& logger) { const std::string& subgraph_name = graph_viewer.Name(); std::string path; if (coreml_options.ModelCacheDirectory().empty()) { @@ -411,7 +412,11 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, std::string_view subgraph_short_name = std::string_view(subgraph_name) .substr(subgraph_name.find_last_of("_") + 1); path = MakeString(std::string(coreml_options.ModelCacheDirectory()), "/", cache_key); - ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); + if (!Env::Default().CreateFolder(path).IsOK()) { + LOGS(logger, WARNING) << "Failed to create cache directory " << path << ". Model caching is disabled."; + coreml_options.DisableModelCache(); + return GetModelOutputPath(coreml_options, graph_viewer, logger); + } // Write the model path to a file in the cache directory. // This is for developers to know what the cached model is as we used a hash for the directory name. if (!Env::Default().FileExists(ToPathString(path + "/model.txt"))) { @@ -438,7 +443,11 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, } else { path += "_nn"; } - ORT_THROW_IF_ERROR(Env::Default().CreateFolder(path)); + if (!Env::Default().CreateFolder(path).IsOK()) { + LOGS(logger, WARNING) << "Failed to create cache directory " << path << ". Model caching is disabled."; + coreml_options.DisableModelCache(); + return GetModelOutputPath(coreml_options, graph_viewer, logger); + } path += "/model"; } return path; @@ -454,7 +463,7 @@ ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logge coreml_version_(coreml_version), coreml_options_(coreml_options), create_ml_program_(coreml_options.CreateMLProgram()), - model_output_path_(GetModelOutputPath(coreml_options, graph_viewer)), + model_output_path_(GetModelOutputPath(coreml_options_, graph_viewer, logger)), // coreml_options_ must be set before this onnx_input_names_(std::move(onnx_input_names)), onnx_output_names_(std::move(onnx_output_names)), coreml_model_(std::make_unique()) { diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 8367bc6dbbb62..096853b9b07ee 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -68,7 +68,8 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie if (user_provided_key.size() > 64 || std::any_of(user_provided_key.begin(), user_provided_key.end(), [](unsigned char c) { return !std::isalnum(c); })) { - user_provided_key = std::to_string(std::hash{}(user_provided_key)); + LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." + << " It should be alphanumeric and less than 64 characters."; } // invalid cache-key if (user_provided_key.size() == 0) { diff --git a/onnxruntime/core/providers/coreml/coreml_options.h b/onnxruntime/core/providers/coreml/coreml_options.h index 87b6a51f5d9fb..b7e584c36fda5 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.h +++ b/onnxruntime/core/providers/coreml/coreml_options.h @@ -18,7 +18,8 @@ class CoreMLOptions { bool profile_compute_plan_{false}; bool allow_low_precision_accumulation_on_gpu_{false}; // path to store the converted coreml model - std::string model_cache_directory_; + // we may run DisableModelCache() to disable model caching + mutable std::string model_cache_directory_; public: explicit CoreMLOptions(uint32_t coreml_flags); @@ -35,6 +36,8 @@ class CoreMLOptions { bool ProfileComputePlan() const { return profile_compute_plan_ && create_mlprogram_; } std::string_view ModelCacheDirectory() const { return model_cache_directory_; } + // mark const as model_cache_directory_ is mutable and we may update it in const functions. + void DisableModelCache() const { model_cache_directory_.clear(); } private: void ValidateAndParseProviderOption(const ProviderOptions& options); From 7c466a1b77523fd554a636af33a0c3989c0c7e64 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Fri, 20 Dec 2024 16:50:19 +0800 Subject: [PATCH 25/35] Apply suggestions from code review Co-authored-by: Scott McKay --- onnxruntime/core/providers/coreml/builders/model_builder.cc | 4 ++-- onnxruntime/core/providers/coreml/coreml_options.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index fbd591a0e809a..34007bac50657 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -413,7 +413,7 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, .substr(subgraph_name.find_last_of("_") + 1); path = MakeString(std::string(coreml_options.ModelCacheDirectory()), "/", cache_key); if (!Env::Default().CreateFolder(path).IsOK()) { - LOGS(logger, WARNING) << "Failed to create cache directory " << path << ". Model caching is disabled."; + LOGS(logger, ERROR) << "Failed to create cache directory " << path << ". Model caching is disabled."; coreml_options.DisableModelCache(); return GetModelOutputPath(coreml_options, graph_viewer, logger); } @@ -444,7 +444,7 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, path += "_nn"; } if (!Env::Default().CreateFolder(path).IsOK()) { - LOGS(logger, WARNING) << "Failed to create cache directory " << path << ". Model caching is disabled."; + LOGS(logger, ERROR) << "Failed to create cache directory " << path << ". Model caching is disabled."; coreml_options.DisableModelCache(); return GetModelOutputPath(coreml_options, graph_viewer, logger); } diff --git a/onnxruntime/core/providers/coreml/coreml_options.h b/onnxruntime/core/providers/coreml/coreml_options.h index b7e584c36fda5..d7ee04b3f8a79 100644 --- a/onnxruntime/core/providers/coreml/coreml_options.h +++ b/onnxruntime/core/providers/coreml/coreml_options.h @@ -36,7 +36,8 @@ class CoreMLOptions { bool ProfileComputePlan() const { return profile_compute_plan_ && create_mlprogram_; } std::string_view ModelCacheDirectory() const { return model_cache_directory_; } - // mark const as model_cache_directory_ is mutable and we may update it in const functions. + // The options specified by the user are const, but if there's an error setting up caching we disable it + // so that the EP can still be used. The error is logged for the user to investigate. void DisableModelCache() const { model_cache_directory_.clear(); } private: From d1e76338f8a2a9b44b02c53efe2923b3205edf05 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Fri, 20 Dec 2024 17:08:59 +0800 Subject: [PATCH 26/35] address comments --- .../coreml/builders/model_builder.cc | 9 ++++--- .../coreml/coreml_execution_provider.cc | 25 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index 34007bac50657..fe401d7f008fe 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -425,9 +425,12 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, main_graph = main_graph->ParentGraph(); } std::ofstream file(path + "/model.txt"); - ORT_ENFORCE(file.is_open(), "Failed to open file ", path + "/model.txt"); - file << main_graph->ModelPath().string(); - file.close(); + if (!file.is_open()) { + LOGS(logger, ERROR) << "Failed to open file " << path + "/model.txt"; + } else { + file << main_graph->ModelPath().string(); + file.close(); + } } path = MakeString(path, "/", subgraph_short_name); diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 096853b9b07ee..281bee3f70d20 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -58,23 +58,22 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie [&]() { HashValue model_hash; int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); - std::string user_provided_key; + const Graph* main_graph = &graph_viewer.GetGraph(); while (main_graph->IsSubgraph()) { main_graph = main_graph->ParentGraph(); } - if (main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0) { - user_provided_key = graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY); - if (user_provided_key.size() > 64 || - std::any_of(user_provided_key.begin(), user_provided_key.end(), - [](unsigned char c) { return !std::isalnum(c); })) { - LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." - << " It should be alphanumeric and less than 64 characters."; - } - // invalid cache-key - if (user_provided_key.size() == 0) { - user_provided_key = std::to_string(model_hash); - } + // use model_hash as the key if user doesn't provide one + std::string user_provided_key = main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0 + ? graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY) + : std::to_string(model_hash); + + if (user_provided_key.size() > 64 || + std::any_of(user_provided_key.begin(), user_provided_key.end(), + [](unsigned char c) { return !std::isalnum(c); })) { + LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." + << " It should be alphanumeric and less than 64 characters."; + } else { // model_hash is a 64-bit hash value of model_path if model_path is not empty, // otherwise it hashes the graph input names and all the node output names. From a5ffe03e200f9c5fb4002c88054da338000e65a7 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Fri, 20 Dec 2024 17:13:15 +0800 Subject: [PATCH 27/35] fix --- .../core/providers/coreml/coreml_execution_provider.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 281bee3f70d20..0999be4392cb8 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -64,6 +64,9 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie main_graph = main_graph->ParentGraph(); } // use model_hash as the key if user doesn't provide one + // model_hash is a 64-bit hash value of model_path if model_path is not empty, + // otherwise it hashes the graph input names and all the node output names. + // it can't guarantee the uniqueness of the key, so user should manager the key for the best. std::string user_provided_key = main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0 ? graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY) : std::to_string(model_hash); @@ -74,10 +77,7 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." << " It should be alphanumeric and less than 64 characters."; - } else { - // model_hash is a 64-bit hash value of model_path if model_path is not empty, - // otherwise it hashes the graph input names and all the node output names. - // it can't guarantee the uniqueness of the key, so user should manager the key for the best. + } else if (user_provided_key.empty()) { // user passed a empty string user_provided_key = std::to_string(model_hash); } // The string format is used by onnxruntime/core/providers/coreml/builders/model_builder.cc::GetModelOutputPath From 5518e38218735312aa782488e63d6efb30fa6762 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Fri, 20 Dec 2024 17:16:46 +0800 Subject: [PATCH 28/35] format --- .../core/providers/coreml/coreml_execution_provider.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 0999be4392cb8..72b5d9cdcaeb0 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -63,12 +63,13 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie while (main_graph->IsSubgraph()) { main_graph = main_graph->ParentGraph(); } + const auto& metadata = main_graph->GetModel().MetaData(); // use model_hash as the key if user doesn't provide one // model_hash is a 64-bit hash value of model_path if model_path is not empty, // otherwise it hashes the graph input names and all the node output names. // it can't guarantee the uniqueness of the key, so user should manager the key for the best. - std::string user_provided_key = main_graph->GetModel().MetaData().count(kCOREML_CACHE_KEY) > 0 - ? graph_viewer.GetGraph().GetModel().MetaData().at(kCOREML_CACHE_KEY) + std::string user_provided_key = metadata.count(kCOREML_CACHE_KEY) > 0 + ? metadata.at(kCOREML_CACHE_KEY) : std::to_string(model_hash); if (user_provided_key.size() > 64 || From 70075e57022abc3f5311f19504249a5f365f1c38 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Fri, 20 Dec 2024 17:20:00 +0800 Subject: [PATCH 29/35] format --- .../coreml/coreml_execution_provider.cc | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 72b5d9cdcaeb0..8a038d668f5e7 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -53,32 +53,31 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie const auto builder_params = coreml::MakeOpBuilderParams(graph_viewer, coreml_version_, coreml_options_.RequireStaticShape(), coreml_options_.CreateMLProgram()); const auto supported_nodes = coreml::GetSupportedNodes(graph_viewer, builder_params, logger); - + const Graph* main_graph = &graph_viewer.GetGraph(); + while (main_graph->IsSubgraph()) { + main_graph = main_graph->ParentGraph(); + } + const auto& metadata = main_graph->GetModel().MetaData(); + + std::string user_provided_key = metadata.count(kCOREML_CACHE_KEY) > 0 + ? metadata.at(kCOREML_CACHE_KEY) + : ""; + if (user_provided_key.size() > 64 || + std::any_of(user_provided_key.begin(), user_provided_key.end(), + [](unsigned char c) { return !std::isalnum(c); })) { + LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." + << " It should be alphanumeric and less than 64 characters."; + } const auto gen_metadef_name = [&]() { HashValue model_hash; int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); - - const Graph* main_graph = &graph_viewer.GetGraph(); - while (main_graph->IsSubgraph()) { - main_graph = main_graph->ParentGraph(); - } - const auto& metadata = main_graph->GetModel().MetaData(); // use model_hash as the key if user doesn't provide one - // model_hash is a 64-bit hash value of model_path if model_path is not empty, - // otherwise it hashes the graph input names and all the node output names. - // it can't guarantee the uniqueness of the key, so user should manager the key for the best. - std::string user_provided_key = metadata.count(kCOREML_CACHE_KEY) > 0 - ? metadata.at(kCOREML_CACHE_KEY) - : std::to_string(model_hash); - - if (user_provided_key.size() > 64 || - std::any_of(user_provided_key.begin(), user_provided_key.end(), - [](unsigned char c) { return !std::isalnum(c); })) { - LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." - << " It should be alphanumeric and less than 64 characters."; - - } else if (user_provided_key.empty()) { // user passed a empty string + if (user_provided_key.empty()) { + // user passed a empty string + // model_hash is a 64-bit hash value of model_path if model_path is not empty, + // otherwise it hashes the graph input names and all the node output names. + // it can't guarantee the uniqueness of the key, so user should manager the key for the best. user_provided_key = std::to_string(model_hash); } // The string format is used by onnxruntime/core/providers/coreml/builders/model_builder.cc::GetModelOutputPath From dc52361ecc5ce5159c35323d00fd2ba2ff3ef300 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 24 Dec 2024 13:38:24 +0800 Subject: [PATCH 30/35] Update onnxruntime/core/providers/coreml/coreml_execution_provider.cc Co-authored-by: Scott McKay --- onnxruntime/core/providers/coreml/coreml_execution_provider.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc index 8a038d668f5e7..b6bb4f2c1d66a 100644 --- a/onnxruntime/core/providers/coreml/coreml_execution_provider.cc +++ b/onnxruntime/core/providers/coreml/coreml_execution_provider.cc @@ -67,6 +67,7 @@ CoreMLExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_vie [](unsigned char c) { return !std::isalnum(c); })) { LOGS(logger, ERROR) << "[" << kCOREML_CACHE_KEY << ":" << user_provided_key << "] is not a valid cache key." << " It should be alphanumeric and less than 64 characters."; + user_provided_key = ""; } const auto gen_metadef_name = [&]() { From 31e6c685fd9840998d993e6043e8ad2e2f27ddfa Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 24 Dec 2024 15:15:14 +0800 Subject: [PATCH 31/35] add test for model cache --- .../coreml/builders/model_builder.cc | 39 ++++---- .../providers/coreml/coreml_basic_test.cc | 97 ++++++++++++++++++- 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/onnxruntime/core/providers/coreml/builders/model_builder.cc b/onnxruntime/core/providers/coreml/builders/model_builder.cc index fe401d7f008fe..f8952301d59a9 100644 --- a/onnxruntime/core/providers/coreml/builders/model_builder.cc +++ b/onnxruntime/core/providers/coreml/builders/model_builder.cc @@ -412,26 +412,8 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, std::string_view subgraph_short_name = std::string_view(subgraph_name) .substr(subgraph_name.find_last_of("_") + 1); path = MakeString(std::string(coreml_options.ModelCacheDirectory()), "/", cache_key); - if (!Env::Default().CreateFolder(path).IsOK()) { - LOGS(logger, ERROR) << "Failed to create cache directory " << path << ". Model caching is disabled."; - coreml_options.DisableModelCache(); - return GetModelOutputPath(coreml_options, graph_viewer, logger); - } - // Write the model path to a file in the cache directory. - // This is for developers to know what the cached model is as we used a hash for the directory name. - if (!Env::Default().FileExists(ToPathString(path + "/model.txt"))) { - const Graph* main_graph = &graph_viewer.GetGraph(); - while (main_graph->IsSubgraph()) { - main_graph = main_graph->ParentGraph(); - } - std::ofstream file(path + "/model.txt"); - if (!file.is_open()) { - LOGS(logger, ERROR) << "Failed to open file " << path + "/model.txt"; - } else { - file << main_graph->ModelPath().string(); - file.close(); - } - } + + std::string model_file_path = path + "/model.txt"; path = MakeString(path, "/", subgraph_short_name); // Set the model cache path with setting of RequireStaticShape and ModelFormat @@ -447,11 +429,26 @@ std::string GetModelOutputPath(const CoreMLOptions& coreml_options, path += "_nn"; } if (!Env::Default().CreateFolder(path).IsOK()) { - LOGS(logger, ERROR) << "Failed to create cache directory " << path << ". Model caching is disabled."; + LOGS(logger, ERROR) << "Failed to create cache directory `" << path << "`. Model caching is disabled."; coreml_options.DisableModelCache(); return GetModelOutputPath(coreml_options, graph_viewer, logger); } path += "/model"; + // Write the model path to a file in the cache directory. + // This is for developers to know what the cached model is as we used a hash for the directory name. + if (!Env::Default().FileExists(ToPathString(model_file_path))) { + const Graph* main_graph = &graph_viewer.GetGraph(); + while (main_graph->IsSubgraph()) { + main_graph = main_graph->ParentGraph(); + } + std::ofstream file(model_file_path); + if (!file.is_open()) { + LOGS(logger, ERROR) << "Failed to open file " << model_file_path; + } else { + file << main_graph->ModelPath().string(); + file.close(); + } + } } return path; } diff --git a/onnxruntime/test/providers/coreml/coreml_basic_test.cc b/onnxruntime/test/providers/coreml/coreml_basic_test.cc index a8480e7416de5..156fb5114cf20 100644 --- a/onnxruntime/test/providers/coreml/coreml_basic_test.cc +++ b/onnxruntime/test/providers/coreml/coreml_basic_test.cc @@ -15,6 +15,7 @@ #include "test/util/include/inference_session_wrapper.h" #include "test/util/include/test_environment.h" #include "test/util/include/test_utils.h" +#include "onnx/onnx_pb.h" #if !defined(ORT_MINIMAL_BUILD) // if this is a full build we need the provider test utils @@ -31,9 +32,10 @@ namespace onnxruntime { namespace test { static std::unique_ptr MakeCoreMLExecutionProvider( - std::string ModelFormat = "NeuralNetwork", std::string ComputeUnits = "CPUOnly") { + std::string ModelFormat = "NeuralNetwork", std::string ComputeUnits = "CPUOnly", std::string ModelCacheDirectory = "") { std::unordered_map provider_options = {{kCoremlProviderOption_MLComputeUnits, ComputeUnits}, - {kCoremlProviderOption_ModelFormat, ModelFormat}}; + {kCoremlProviderOption_ModelFormat, ModelFormat}, + {kCoremlProviderOption_ModelCacheDirectory, ModelCacheDirectory}}; return CoreMLProviderFactoryCreator::Create(provider_options)->CreateProvider(); } @@ -268,5 +270,96 @@ TEST(CoreMLExecutionProviderTest, TestNameSanitization) { } #endif +TEST(CoreMLExecutionProviderTest, TestModelCache) { + const ORTCHAR_T* model_file_name = ORT_TSTR("testdata/coreml_argmax_cast_test.onnx"); + + onnx::ModelProto model; + { + std::ifstream in(model_file_name, std::ios_base::binary); + model.ParseFromIstream(&in); + in.close(); + } + +#if defined(__APPLE__) + std::vector dims_mul_x = {3, 2, 2}; + std::vector values_mul_x = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f}; + OrtValue ml_value_x; + AllocatorPtr allocator = std::make_shared(); + CreateMLValue(allocator, dims_mul_x, values_mul_x, &ml_value_x); + + NameMLValMap feeds; + feeds.insert(std::make_pair("X", ml_value_x)); + std::string subgraph_name; + const std::function graph_verifier = [&subgraph_name](const Graph& graph) { + GraphViewer graph_viewer{graph}; + const auto& node_indices_in_order = graph_viewer.GetNodesInTopologicalOrder(); + const auto* node = graph.GetNode(node_indices_in_order[0]); + auto _first = node->Name().find('_') + 1; + auto _second = node->Name().find('_', _first); + subgraph_name = node->Name().substr(_first, _second - _first); + }; + EPVerificationParams verification_params{.graph_verifier = &graph_verifier}; + + std::string out_string; + auto* metadata_props = model.add_metadata_props(); + metadata_props->set_key(kCOREML_CACHE_KEY); + { // test with valid model cache directory + metadata_props->set_value("legalhash123"); + model.SerializeToString(&out_string); + gsl::span model_data{reinterpret_cast(out_string.data()), out_string.size()}; + RunAndVerifyOutputsWithEP(model_data, CurrentTestName(), + MakeCoreMLExecutionProvider("MLProgram", "CPUOnly", ORT_TSTR("./tmp/")), + feeds, + verification_params); + ASSERT_EQ(std::filesystem::exists("./tmp/legalhash123"), true); + } + { + // test with invalid model cache directory, only alphanumeric characters are allowed + out_string.clear(); + metadata_props->set_key(kCOREML_CACHE_KEY); + metadata_props->set_value("illegalhash__123"); + model.SerializeToString(&out_string); + gsl::span model_data{reinterpret_cast(out_string.data()), out_string.size()}; + RunAndVerifyOutputsWithEP(model_data, CurrentTestName(), + MakeCoreMLExecutionProvider("MLProgram", "CPUOnly", ORT_TSTR("./tmp")), + feeds, + verification_params); + ASSERT_EQ(std::filesystem::exists("./tmp/illegalhash__123"), false); + // the cache folder name should be the first part of the subgraph name + ASSERT_EQ(std::filesystem::exists("./tmp/" + subgraph_name), true); + } + { + // test with invalid model cache directory, more than 64 characters + out_string.clear(); + metadata_props->set_key(kCOREML_CACHE_KEY); + metadata_props->set_value("modelhashwithmorethan64charactersmodelhashwithmorethan64charactersmodelhashwithmorethan64characters"); + model.SerializeToString(&out_string); + gsl::span model_data{reinterpret_cast(out_string.data()), out_string.size()}; + RunAndVerifyOutputsWithEP(model_data, CurrentTestName(), + MakeCoreMLExecutionProvider("MLProgram", "CPUOnly", ORT_TSTR("./tmp")), + feeds, + verification_params); + ASSERT_EQ(std::filesystem::exists("./tmp/modelhashwithmorethan64charactersmodelhashwithmorethan64charactersmodelhashwithmorethan64characters"), false); + // the cache folder name should be the first part of the subgraph name + ASSERT_EQ(std::filesystem::exists("./tmp/" + subgraph_name), true); + } + { + // test with invalid model cache directory, empty + out_string.clear(); + metadata_props->set_key(kCOREML_CACHE_KEY); + metadata_props->set_value(""); + model.SerializeToString(&out_string); + gsl::span model_data{reinterpret_cast(out_string.data()), out_string.size()}; + RunAndVerifyOutputsWithEP(model_data, CurrentTestName(), + MakeCoreMLExecutionProvider("MLProgram", "CPUOnly", ORT_TSTR("./tmp")), + feeds, + verification_params); + // the cache folder name should be the first part of the subgraph name + ASSERT_EQ(std::filesystem::exists("./tmp/" + subgraph_name), true); + } +#else + TestModelLoad(model_data, MakeCoreMLExecutionProvider(), ExpectedEPNodeAssignment::All); +#endif +} } // namespace test } // namespace onnxruntime From 78b2a4b10bf971b78ef60f822502f46121915f47 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 24 Dec 2024 15:18:17 +0800 Subject: [PATCH 32/35] ut --- .../test/providers/coreml/coreml_basic_test.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/onnxruntime/test/providers/coreml/coreml_basic_test.cc b/onnxruntime/test/providers/coreml/coreml_basic_test.cc index 156fb5114cf20..dd89c12389466 100644 --- a/onnxruntime/test/providers/coreml/coreml_basic_test.cc +++ b/onnxruntime/test/providers/coreml/coreml_basic_test.cc @@ -357,6 +357,18 @@ TEST(CoreMLExecutionProviderTest, TestModelCache) { // the cache folder name should be the first part of the subgraph name ASSERT_EQ(std::filesystem::exists("./tmp/" + subgraph_name), true); } + { + // test with invalid model cache directory, caching shall be disabled + out_string.clear(); + metadata_props->set_key(kCOREML_CACHE_KEY); + metadata_props->set_value(""); + model.SerializeToString(&out_string); + gsl::span model_data{reinterpret_cast(out_string.data()), out_string.size()}; + RunAndVerifyOutputsWithEP(model_data, CurrentTestName(), + MakeCoreMLExecutionProvider("MLProgram", "CPUOnly", ORT_TSTR("/")), + feeds, + verification_params); + } #else TestModelLoad(model_data, MakeCoreMLExecutionProvider(), ExpectedEPNodeAssignment::All); #endif From 5f7bddc87368dcb9ab49e3b917e53ce9df3a2965 Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 24 Dec 2024 15:31:13 +0800 Subject: [PATCH 33/35] ut --- onnxruntime/test/providers/coreml/coreml_basic_test.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/onnxruntime/test/providers/coreml/coreml_basic_test.cc b/onnxruntime/test/providers/coreml/coreml_basic_test.cc index dd89c12389466..7b7e6af6ae778 100644 --- a/onnxruntime/test/providers/coreml/coreml_basic_test.cc +++ b/onnxruntime/test/providers/coreml/coreml_basic_test.cc @@ -368,6 +368,9 @@ TEST(CoreMLExecutionProviderTest, TestModelCache) { MakeCoreMLExecutionProvider("MLProgram", "CPUOnly", ORT_TSTR("/")), feeds, verification_params); + // this folder can't be created + ASSERT_EQ(std::filesystem::exists("/" + subgraph_name), false); + } #else TestModelLoad(model_data, MakeCoreMLExecutionProvider(), ExpectedEPNodeAssignment::All); From f9db65f2880674c8f305f0bf4f7ba60f46bca3cc Mon Sep 17 00:00:00 2001 From: wejoncy Date: Tue, 24 Dec 2024 16:15:26 +0800 Subject: [PATCH 34/35] lint --- onnxruntime/test/providers/coreml/coreml_basic_test.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/onnxruntime/test/providers/coreml/coreml_basic_test.cc b/onnxruntime/test/providers/coreml/coreml_basic_test.cc index 7b7e6af6ae778..682cf4591ca6d 100644 --- a/onnxruntime/test/providers/coreml/coreml_basic_test.cc +++ b/onnxruntime/test/providers/coreml/coreml_basic_test.cc @@ -370,7 +370,6 @@ TEST(CoreMLExecutionProviderTest, TestModelCache) { verification_params); // this folder can't be created ASSERT_EQ(std::filesystem::exists("/" + subgraph_name), false); - } #else TestModelLoad(model_data, MakeCoreMLExecutionProvider(), ExpectedEPNodeAssignment::All); From 201368dd353c1a1a9c2fd65fc5a4ea672e92f87a Mon Sep 17 00:00:00 2001 From: wejoncy Date: Mon, 30 Dec 2024 04:07:36 +0000 Subject: [PATCH 35/35] fix test --- onnxruntime/test/providers/coreml/coreml_basic_test.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onnxruntime/test/providers/coreml/coreml_basic_test.cc b/onnxruntime/test/providers/coreml/coreml_basic_test.cc index 682cf4591ca6d..302ad57fb88c5 100644 --- a/onnxruntime/test/providers/coreml/coreml_basic_test.cc +++ b/onnxruntime/test/providers/coreml/coreml_basic_test.cc @@ -280,6 +280,7 @@ TEST(CoreMLExecutionProviderTest, TestModelCache) { in.close(); } + std::string out_string; #if defined(__APPLE__) std::vector dims_mul_x = {3, 2, 2}; std::vector values_mul_x = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f}; @@ -300,7 +301,6 @@ TEST(CoreMLExecutionProviderTest, TestModelCache) { }; EPVerificationParams verification_params{.graph_verifier = &graph_verifier}; - std::string out_string; auto* metadata_props = model.add_metadata_props(); metadata_props->set_key(kCOREML_CACHE_KEY); { // test with valid model cache directory @@ -372,6 +372,8 @@ TEST(CoreMLExecutionProviderTest, TestModelCache) { ASSERT_EQ(std::filesystem::exists("/" + subgraph_name), false); } #else + model.SerializeToString(&out_string); + gsl::span model_data{reinterpret_cast(out_string.data()), out_string.size()}; TestModelLoad(model_data, MakeCoreMLExecutionProvider(), ExpectedEPNodeAssignment::All); #endif }