1// 2// backend_delegate.mm 3// 4// Copyright © 2024 Apple Inc. All rights reserved. 5// 6// Please refer to the license found in the LICENSE file in the root directory of the source tree. 7 8 9#import <ETCoreMLAssetManager.h> 10#import <ETCoreMLModel.h> 11#import <ETCoreMLModelManager.h> 12#import <ETCoreMLStrings.h> 13#import <backend_delegate.h> 14#import <model_event_logger.h> 15#import <multiarray.h> 16 17namespace { 18using namespace executorchcoreml; 19 20MLComputeUnits get_compute_units(const Buffer& buffer) { 21 std::string value(reinterpret_cast<const char *>(buffer.data()), buffer.size()); 22 if (value == std::string(ETCoreMLStrings.cpuComputeUnitName.UTF8String)) { 23 return MLComputeUnitsCPUOnly; 24 } else if (value == std::string(ETCoreMLStrings.cpuAndGpuComputeUnitsName.UTF8String)) { 25 return MLComputeUnitsCPUAndGPU; 26 } else if (value == std::string(ETCoreMLStrings.cpuAndNeuralEngineComputeUnitsName.UTF8String)) { 27 return MLComputeUnitsCPUAndNeuralEngine; 28 } else { 29 return MLComputeUnitsAll; 30 } 31} 32 33MLModelConfiguration *get_model_configuration(const std::unordered_map<std::string, Buffer>& specs) { 34 std::string key_name(ETCoreMLStrings.computeUnitsKeyName.UTF8String); 35 MLModelConfiguration *configuration = [[MLModelConfiguration alloc] init]; 36 for (const auto& [key, buffer] : specs) { 37 if (key == key_name) { 38 configuration.computeUnits = get_compute_units(buffer); 39 break; 40 } 41 } 42 43 return configuration; 44} 45 46NSURL * _Nullable create_directory_if_needed(NSURL *url, 47 NSFileManager *fileManager, 48 NSError * __autoreleasing *error) { 49 if (![fileManager fileExistsAtPath:url.path] && 50 ![fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:@{} error:error]) { 51 return nil; 52 } 53 54 return url; 55} 56 57ETCoreMLAssetManager * _Nullable create_asset_manager(NSString *assets_directory_path, 58 NSString *trash_directory_path, 59 NSString *database_directory_path, 60 NSString *database_name, 61 NSInteger max_assets_size_in_bytes, 62 NSError * __autoreleasing *error) { 63 NSFileManager *fm = [[NSFileManager alloc] init]; 64 65 NSURL *assets_directory_url = [NSURL fileURLWithPath:assets_directory_path]; 66 if (!create_directory_if_needed(assets_directory_url, fm, error)) { 67 return nil; 68 } 69 70 NSURL *trash_directory_url = [NSURL fileURLWithPath:trash_directory_path]; 71 if (!create_directory_if_needed(trash_directory_url, fm, error)) { 72 return nil; 73 } 74 75 NSURL *database_directory_url = [NSURL fileURLWithPath:database_directory_path]; 76 if (!create_directory_if_needed(database_directory_url, fm, error)) { 77 return nil; 78 } 79 80 NSURL *database_url = [database_directory_url URLByAppendingPathComponent:database_name]; 81 ETCoreMLAssetManager *manager = [[ETCoreMLAssetManager alloc] initWithDatabaseURL:database_url 82 assetsDirectoryURL:assets_directory_url 83 trashDirectoryURL:trash_directory_url 84 maxAssetsSizeInBytes:max_assets_size_in_bytes 85 error:error]; 86 return manager; 87} 88} //namespace 89 90@interface ETCoreMLModelManagerDelegate : NSObject 91 92- (instancetype)init NS_UNAVAILABLE; 93 94+ (instancetype)new NS_UNAVAILABLE; 95 96- (instancetype)initWithConfig:(BackendDelegate::Config)config NS_DESIGNATED_INITIALIZER; 97 98- (BOOL)loadAndReturnError:(NSError * _Nullable __autoreleasing *)error; 99 100- (void)loadAsynchronously; 101 102- (ModelHandle*)loadModelFromAOTData:(NSData*)data 103 configuration:(MLModelConfiguration*)configuration 104 error:(NSError* __autoreleasing*)error; 105 106- (BOOL)executeModelWithHandle:(ModelHandle*)handle 107 argsVec:(const std::vector<executorchcoreml::MultiArray>&)argsVec 108 loggingOptions:(const executorchcoreml::ModelLoggingOptions&)loggingOptions 109 eventLogger:(const executorchcoreml::ModelEventLogger* _Nullable)eventLogger 110 error:(NSError* __autoreleasing*)error; 111 112- (BOOL)unloadModelWithHandle:(ModelHandle*)handle; 113 114- (BOOL)purgeModelsCacheAndReturnError:(NSError * _Nullable __autoreleasing *)error; 115 116@property (assign, readonly, nonatomic) BackendDelegate::Config config; 117@property (strong, readonly, nonatomic) dispatch_queue_t syncQueue; 118@property (strong, nonatomic, nullable) ETCoreMLModelManager *impl; 119@property (assign, readonly, nonatomic) BOOL isAvailable; 120 121@end 122 123@implementation ETCoreMLModelManagerDelegate 124 125- (instancetype)initWithConfig:(BackendDelegate::Config)config { 126 self = [super init]; 127 if (self) { 128 _config = std::move(config); 129 _syncQueue = dispatch_queue_create("com.executorchcoreml.modelmanagerdelegate.sync", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); 130 } 131 132 return self; 133} 134 135- (BOOL)_loadAndReturnError:(NSError * _Nullable __autoreleasing *)error { 136 if (self.impl != nil) { 137 return YES; 138 } 139 140 ETCoreMLAssetManager *assetManager = create_asset_manager(ETCoreMLStrings.assetsDirectoryPath, 141 ETCoreMLStrings.trashDirectoryPath, 142 ETCoreMLStrings.databaseDirectoryPath, 143 ETCoreMLStrings.databaseName, 144 self.config.max_models_cache_size, 145 error); 146 if (!assetManager) { 147 return NO; 148 } 149 150 ETCoreMLModelManager *modelManager = [[ETCoreMLModelManager alloc] initWithAssetManager:assetManager]; 151 if (!modelManager) { 152 return NO; 153 } 154 155 self.impl = modelManager; 156 157 if (self.config.should_prewarm_asset) { 158 [modelManager prewarmRecentlyUsedAssetsWithMaxCount:1]; 159 } 160 161 return YES; 162} 163 164- (BOOL)loadAndReturnError:(NSError * _Nullable __autoreleasing *)error { 165 __block NSError *localError = nil; 166 __block BOOL result = NO; 167 dispatch_sync(self.syncQueue, ^{ 168 result = [self _loadAndReturnError:&localError]; 169 }); 170 171 if (error) { 172 *error = localError; 173 } 174 175 return result; 176} 177 178- (void)loadAsynchronously { 179 dispatch_async(self.syncQueue, ^{ 180 (void)[self _loadAndReturnError:nil]; 181 }); 182} 183 184- (ModelHandle*)loadModelFromAOTData:(NSData*)data 185 configuration:(MLModelConfiguration*)configuration 186 error:(NSError* __autoreleasing*)error { 187 if (![self loadAndReturnError:error]) { 188 return nil; 189 } 190 191 auto handle = [self.impl loadModelFromAOTData:data 192 configuration:configuration 193 error:error]; 194 if ((handle != NULL) && self.config.should_prewarm_model) { 195 [self.impl prewarmModelWithHandle:handle error:nil]; 196 } 197 198 return handle; 199} 200 201- (BOOL)executeModelWithHandle:(ModelHandle*)handle 202 argsVec:(const std::vector<executorchcoreml::MultiArray>&)argsVec 203 loggingOptions:(const executorchcoreml::ModelLoggingOptions&)loggingOptions 204 eventLogger:(const executorchcoreml::ModelEventLogger* _Nullable)eventLogger 205 error:(NSError* __autoreleasing*)error { 206 assert(self.impl != nil && "Impl must not be nil"); 207 return [self.impl executeModelWithHandle:handle 208 argsVec:argsVec 209 loggingOptions:loggingOptions 210 eventLogger:eventLogger 211 error:error]; 212} 213 214- (nullable ETCoreMLModel*)modelWithHandle:(ModelHandle*)handle { 215 assert(self.impl != nil && "Impl must not be nil"); 216 return [self.impl modelWithHandle:handle]; 217} 218 219- (BOOL)unloadModelWithHandle:(ModelHandle*)handle { 220 assert(self.impl != nil && "Impl must not be nil"); 221 return [self.impl unloadModelWithHandle:handle]; 222} 223 224- (BOOL)purgeModelsCacheAndReturnError:(NSError * _Nullable __autoreleasing *)error { 225 if (![self loadAndReturnError:error]) { 226 return NO; 227 } 228 229 return [self.impl purgeModelsCacheAndReturnError:error];; 230} 231 232- (BOOL)isAvailable { 233 if (![self loadAndReturnError:nil]) { 234 return NO; 235 } 236 237 return YES; 238} 239 240@end 241 242namespace executorchcoreml { 243 244std::string BackendDelegate::ErrorCategory::message(int code) const { 245 switch (static_cast<ErrorCode>(code)) { 246 case ErrorCode::CorruptedData: 247 return "AOT blob can't be parsed"; 248 case ErrorCode::CorruptedMetadata: 249 return "AOT blob has incorrect or missing metadata."; 250 case ErrorCode::CorruptedModel: 251 return "AOT blob has incorrect or missing CoreML model."; 252 case ErrorCode::BrokenModel: 253 return "CoreML model doesn't match the input and output specifications."; 254 case ErrorCode::CompilationFailed: 255 return "Failed to compile CoreML model."; 256 case ErrorCode::ModelSaveFailed: 257 return "Failed to write CoreML model to disk."; 258 case ErrorCode::ModelCacheCreationFailed: 259 return "Failed to create model cache."; 260 default: 261 return "Unexpected error."; 262 } 263} 264 265class BackendDelegateImpl: public BackendDelegate { 266public: 267 explicit BackendDelegateImpl(const Config& config) noexcept 268 :BackendDelegate(), model_manager_([[ETCoreMLModelManagerDelegate alloc] initWithConfig:config]) 269 { 270 [model_manager_ loadAsynchronously]; 271 } 272 273 BackendDelegateImpl(BackendDelegateImpl const&) = delete; 274 BackendDelegateImpl& operator=(BackendDelegateImpl const&) = delete; 275 276 Handle *init(Buffer processed,const std::unordered_map<std::string, Buffer>& specs) const noexcept override { 277 NSError *localError = nil; 278 MLModelConfiguration *configuration = get_model_configuration(specs); 279 NSData *data = [NSData dataWithBytesNoCopy:const_cast<void *>(processed.data()) 280 length:processed.size() 281 freeWhenDone:NO]; 282 ModelHandle *modelHandle = [model_manager_ loadModelFromAOTData:data 283 configuration:configuration 284 error:&localError]; 285 return modelHandle; 286 } 287 288 bool execute(Handle* handle, 289 const std::vector<MultiArray>& args, 290 const ModelLoggingOptions& logging_options, 291 ModelEventLogger *event_logger, 292 std::error_code& ec) const noexcept override { 293 NSError *error = nil; 294 if (![model_manager_ executeModelWithHandle:handle 295 argsVec:args 296 loggingOptions:logging_options 297 eventLogger:event_logger 298 error:&error]) { 299 ec = static_cast<ErrorCode>(error.code); 300 return false; 301 } 302 303 return true; 304 } 305 306 bool is_valid_handle(Handle* handle) const noexcept override { 307 return [model_manager_ modelWithHandle:handle] != nil; 308 } 309 310 bool is_available() const noexcept override { 311 return static_cast<bool>(model_manager_.isAvailable); 312 } 313 314 std::pair<size_t, size_t> get_num_arguments(Handle* handle) const noexcept override { 315 ETCoreMLModel *model = [model_manager_ modelWithHandle:handle]; 316 return {model.orderedInputNames.count, model.orderedOutputNames.count}; 317 } 318 319 void destroy(Handle* handle) const noexcept override { 320 [model_manager_ unloadModelWithHandle:handle]; 321 } 322 323 bool purge_models_cache() const noexcept override { 324 NSError *localError = nil; 325 bool result = static_cast<bool>([model_manager_ purgeModelsCacheAndReturnError:&localError]); 326 return result; 327 } 328 329 ETCoreMLModelManagerDelegate *model_manager_; 330 Config config_; 331}; 332 333std::shared_ptr<BackendDelegate> BackendDelegate::make(const Config& config) { 334 return std::make_shared<BackendDelegateImpl>(config); 335} 336} //namespace executorchcoreml 337