1// 2// ETCoreMLModelAnalyzer.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#import "ETCoreMLModelAnalyzer.h" 9 10#import "ETCoreMLAsset.h" 11#import "ETCoreMLAssetManager.h" 12#import "ETCoreMLDefaultModelExecutor.h" 13#import "ETCoreMLLogging.h" 14#import "ETCoreMLModel.h" 15#import "ETCoreMLModelLoader.h" 16#import "ETCoreMLModelStructurePath.h" 17#import "ETCoreMLModelDebugInfo.h" 18#import "ETCoreMLModelDebugger.h" 19#import "ETCoreMLModelProfiler.h" 20#import "ETCoreMLStrings.h" 21#import "model_logging_options.h" 22#import "model_event_logger.h" 23#import "model_metadata.h" 24#import "model_package_info.h" 25#import "objc_safe_cast.h" 26 27namespace { 28using namespace executorchcoreml; 29static constexpr NSInteger MAX_MODEL_OUTPUTS_COUNT = 50; 30} //namespace 31 32@interface ETCoreMLModelAnalyzer () 33 34@property (readonly, strong, nonatomic) ETCoreMLAsset *modelAsset; 35@property (readonly, strong, nonatomic) ETCoreMLAssetManager *assetManager; 36@property (strong, nonatomic, nullable) ETCoreMLModelProfiler *profiler; 37@property (strong, nonatomic, nullable) ETCoreMLModelDebugger *debugger; 38@property (strong, nonatomic, nullable) id<ETCoreMLModelExecutor> executor; 39@property (readonly, copy, nonatomic, nullable) ETCoreMLModelDebugInfo *modelDebugInfo; 40@property (readonly, strong, nonatomic) MLModelConfiguration *configuration; 41 42@end 43 44@implementation ETCoreMLModelAnalyzer 45 46- (nullable instancetype)initWithCompiledModelAsset:(ETCoreMLAsset *)compiledModelAsset 47 modelAsset:(nullable ETCoreMLAsset *)modelAsset 48 modelDebugInfo:(nullable ETCoreMLModelDebugInfo *)modelDebugInfo 49 metadata:(const executorchcoreml::ModelMetadata&)metadata 50 configuration:(MLModelConfiguration *)configuration 51 assetManager:(ETCoreMLAssetManager *)assetManager 52 error:(NSError * __autoreleasing *)error { 53 if (modelAsset && ![modelAsset keepAliveAndReturnError:error]) { 54 return nil; 55 } 56 57 if (![compiledModelAsset keepAliveAndReturnError:error]) { 58 return nil; 59 } 60 61 NSError *localError = nil; 62 ETCoreMLModel *model = [ETCoreMLModelLoader loadModelWithContentsOfURL:compiledModelAsset.contentURL 63 configuration:configuration 64 metadata:metadata 65 assetManager:assetManager 66 error:&localError]; 67 if (!model) { 68 ETCoreMLLogError(localError, 69 "%@: Failed to create model profiler.", 70 NSStringFromClass(ETCoreMLAssetManager.class)); 71 } 72 73 self = [super init]; 74 if (self) { 75 _model = model; 76 _modelAsset = modelAsset; 77 _modelDebugInfo = modelDebugInfo; 78 _assetManager = assetManager; 79 _configuration = configuration; 80 _executor = [[ETCoreMLDefaultModelExecutor alloc] initWithModel:model]; 81 } 82 83 return self; 84} 85 86- (nullable NSArray<MLMultiArray *> *)profileModelWithInputs:(id<MLFeatureProvider>)inputs 87 predictionOptions:(MLPredictionOptions *)predictionOptions 88 eventLogger:(const executorchcoreml::ModelEventLogger *)eventLogger 89 error:(NSError * __autoreleasing *)error { 90 if (self.profiler == nil) { 91 ETCoreMLModelProfiler *profiler = [[ETCoreMLModelProfiler alloc] initWithModel:self.model 92 configuration:self.configuration 93 error:error]; 94 self.profiler = profiler; 95 } 96 97 98 if (!self.profiler) { 99 ETCoreMLLogErrorAndSetNSError(error, 100 ETCoreMLErrorModelProfilingNotSupported, 101 "%@: Model profiling is only available for macOS >= 14.4, iOS >= 17.4, tvOS >= 17.4 and watchOS >= 10.4.", 102 NSStringFromClass(ETCoreMLModelAnalyzer.class)); 103 return nil; 104 } 105 106 NSArray<MLMultiArray *> *modelOutputs = nil; 107 NSArray<ETCoreMLModelStructurePath *> *operationPaths = self.profiler.operationPaths; 108 ETCoreMLModelProfilingResult *profilingInfos = [self.profiler profilingInfoForOperationsAtPaths:operationPaths 109 options:predictionOptions 110 inputs:inputs 111 modelOutputs:&modelOutputs 112 error:error]; 113 if (!profilingInfos) { 114 return nil; 115 } 116 117 eventLogger->log_profiling_infos(profilingInfos, self.modelDebugInfo.pathToDebugSymbolMap); 118 return modelOutputs; 119} 120 121- (nullable NSArray<MLMultiArray *> *)debugModelWithInputs:(id<MLFeatureProvider>)inputs 122 predictionOptions:(MLPredictionOptions *)predictionOptions 123 eventLogger:(const executorchcoreml::ModelEventLogger *)eventLogger 124 error:(NSError * __autoreleasing *)error { 125 if (!self.modelAsset) { 126 ETCoreMLLogErrorAndSetNSError(error, 127 ETCoreMLErrorCorruptedData, 128 "%@: There is no mlpackage, mlpackage is required for debugging a model. Please check the export path.", 129 NSStringFromClass(ETCoreMLModelAnalyzer.class)); 130 return nil; 131 } 132 133 if (!self.debugger) { 134 self.debugger = [[ETCoreMLModelDebugger alloc] initWithModelAsset:self.modelAsset 135 modelDebugInfo:self.modelDebugInfo 136 outputNames:self.model.orderedOutputNames 137 configuration:self.configuration 138 assetManager:self.assetManager 139 error:error]; 140 } 141 142 if (!self.debugger) { 143 return nil; 144 } 145 146 NSArray<MLMultiArray *> *modelOutputs = nil; 147 NSArray<ETCoreMLModelStructurePath *> *operationPaths = self.debugger.operationPaths; 148 NSDictionary<ETCoreMLModelStructurePath *, NSString *> *operationPathToDebugSymbolMap = self.debugger.operationPathToDebugSymbolMap; 149 NSInteger n = operationPaths.count/MAX_MODEL_OUTPUTS_COUNT + (operationPaths.count % MAX_MODEL_OUTPUTS_COUNT == 0 ? 0 : 1); 150 for (NSInteger i = 0; i < n; i++) { 151 @autoreleasepool { 152 NSRange range = NSMakeRange(i * MAX_MODEL_OUTPUTS_COUNT, MIN(operationPaths.count - i * MAX_MODEL_OUTPUTS_COUNT, MAX_MODEL_OUTPUTS_COUNT)); 153 ETCoreMLModelOutputs *outputs = [self.debugger outputsOfOperationsAtPaths:[operationPaths subarrayWithRange:range] 154 options:predictionOptions 155 inputs:inputs 156 modelOutputs:&modelOutputs 157 error:error]; 158 if (!outputs) { 159 return nil; 160 } 161 162 if (outputs.count > 0) { 163 eventLogger->log_intermediate_tensors(outputs, operationPathToDebugSymbolMap); 164 } 165 } 166 } 167 168 return modelOutputs; 169} 170 171- (nullable NSArray<MLMultiArray *> *)executeModelWithInputs:(id<MLFeatureProvider>)inputs 172 predictionOptions:(MLPredictionOptions *)predictionOptions 173 loggingOptions:(const executorchcoreml::ModelLoggingOptions&)loggingOptions 174 eventLogger:(const executorchcoreml::ModelEventLogger* _Nullable)eventLogger 175 error:(NSError * __autoreleasing *)error { 176 if (self.ignoreOutputBackings) { 177 predictionOptions.outputBackings = @{}; 178 } 179 180 NSError *localError = nil; 181 NSArray<MLMultiArray *> *outputs = nil; 182 if (loggingOptions.log_profiling_info) { 183 NSAssert(eventLogger != nullptr, @"%@: Event logger is set to nullptr.", NSStringFromClass(ETCoreMLModelAnalyzer.class)); 184 outputs = [self profileModelWithInputs:inputs 185 predictionOptions:predictionOptions 186 eventLogger:eventLogger 187 error:&localError]; 188 } 189 190 if (loggingOptions.log_intermediate_tensors) { 191 NSAssert(eventLogger != nullptr, @"%@: Event logger is set to nullptr.", NSStringFromClass(ETCoreMLModelAnalyzer.class)); 192 outputs = [self debugModelWithInputs:inputs 193 predictionOptions:predictionOptions 194 eventLogger:eventLogger 195 error:&localError]; 196 } 197 198 if (!loggingOptions.log_profiling_info && !loggingOptions.log_intermediate_tensors) { 199 outputs = [self.executor executeModelWithInputs:inputs 200 predictionOptions:predictionOptions 201 loggingOptions:executorchcoreml::ModelLoggingOptions() 202 eventLogger:nullptr 203 error:&localError]; 204 } 205 206 if (error) { 207 *error = localError; 208 } 209 210 return outputs; 211} 212 213@end 214