xref: /aosp_15_r20/external/executorch/backends/apple/coreml/runtime/sdk/ETCoreMLModelAnalyzer.mm (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
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