6
6
// Please refer to the license found in the LICENSE file in the root directory of the source tree.
7
7
8
8
#import < Foundation/Foundation.h>
9
-
10
9
#import < chrono>
11
- #import < memory>
12
- #import < numeric>
13
- #import < string>
14
-
10
+ #import < coreml_backend/delegate.h>
15
11
#import < executorch/extension/data_loader/file_data_loader.h>
16
12
#import < executorch/runtime/executor/method.h>
17
13
#import < executorch/runtime/executor/program.h>
18
14
#import < executorch/runtime/platform/log.h>
19
15
#import < executorch/runtime/platform/runtime.h>
20
- #import < executorch/util/util.h>
21
16
#import < executorch/sdk/etdump/etdump_flatcc.h>
22
-
23
- #import < coreml_backend/delegate.h>
17
+ #import < executorch/util/util.h>
18
+ #import < memory>
19
+ #import < numeric>
20
+ #import < string>
24
21
25
22
static inline id check_class (id obj, Class cls) {
26
23
return [obj isKindOfClass: cls] ? obj : nil ;
@@ -44,7 +41,8 @@ static inline id check_class(id obj, Class cls) {
44
41
bool purge_models_cache = false ;
45
42
bool dump_model_outputs = false ;
46
43
bool dump_intermediate_outputs = false ;
47
-
44
+ bool profile_model = false ;
45
+
48
46
Args (NSDictionary <NSString *, NSString *> *params) {
49
47
{
50
48
NSString *value = SAFE_CAST (params[@" --model_path" ], NSString );
@@ -83,15 +81,18 @@ static inline id check_class(id obj, Class cls) {
83
81
}
84
82
}
85
83
{
86
- NSString *value = SAFE_CAST (params[@" --dump_intermediate_outputs" ], NSString );
87
- if (value.length > 0 ) {
88
- dump_intermediate_outputs = value.boolValue ;
84
+ if (params[@" --profile_model" ] != nil ) {
85
+ profile_model = true ;
89
86
}
90
87
}
91
88
{
92
- NSString *value = SAFE_CAST (params[@" --dump_model_outputs" ], NSString );
93
- if (value.length > 0 ) {
94
- dump_model_outputs = value.boolValue ;
89
+ if (params[@" --dump_intermediate_outputs" ] != nil ) {
90
+ dump_intermediate_outputs = true ;
91
+ }
92
+ }
93
+ {
94
+ if (params[@" --dump_model_outputs" ] != nil ) {
95
+ dump_model_outputs = true ;
95
96
}
96
97
}
97
98
}
@@ -102,7 +103,17 @@ static inline id check_class(id obj, Class cls) {
102
103
}
103
104
104
105
NSSet <NSString *> *all_keys () {
105
- return [NSSet setWithObjects: @" --model_path" , @" --iterations" , @" --purge_models_cache" , @" --etdump_path" , @" --debug_buffer_path" , @" --debug_buffer_size" , @" --dump_intermediate_outputs" , @" --dump_model_outputs" , nil ];
106
+ return [NSSet setWithArray: @[
107
+ @" --model_path" ,
108
+ @" --iterations" ,
109
+ @" --purge_models_cache" ,
110
+ @" --etdump_path" ,
111
+ @" --debug_buffer_path" ,
112
+ @" --debug_buffer_size" ,
113
+ @" --dump_intermediate_outputs" ,
114
+ @" --dump_model_outputs" ,
115
+ @" --profile_model"
116
+ ]];
106
117
}
107
118
108
119
Args parse_command_line_args (NSArray <NSString *> *args) {
@@ -127,11 +138,11 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
127
138
key = value;
128
139
values = [NSMutableString string ];
129
140
}
130
-
141
+
131
142
if (key.length > 0 ) {
132
143
params[key] = values.length > 0 ? clean_string (values.copy ) : @" " ;
133
144
}
134
-
145
+
135
146
return Args (params);
136
147
}
137
148
@@ -148,29 +159,28 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
148
159
DataLoaderImpl (const std::string& filePath)
149
160
:data_(read_data(filePath))
150
161
{}
151
-
162
+
152
163
Result<FreeableBuffer> Load (size_t offset, size_t size) override {
153
164
NSData *subdata = [data_ subdataWithRange: NSMakeRange (offset, size)];
154
165
return FreeableBuffer (subdata.bytes , size, nullptr );
155
166
}
156
-
167
+
157
168
Result<size_t > size () const override {
158
169
return data_.length ;
159
170
}
160
-
171
+
161
172
private:
162
- NSData *data_;
173
+ NSData *data_;
163
174
};
164
175
165
176
using Buffer = std::vector<uint8_t >;
166
177
167
- std::unique_ptr<Program> get_program (NSURL *url) {
168
- DataLoaderImpl dataLoader (url.path .UTF8String );
169
- auto program = Program::load (&dataLoader);
178
+ std::unique_ptr<Program> make_program (DataLoader *data_loader) {
179
+ auto program = Program::load (data_loader);
170
180
if (!program.ok ()) {
171
181
return nullptr ;
172
182
}
173
-
183
+
174
184
return std::make_unique<Program>(std::move (program.get ()));
175
185
}
176
186
@@ -179,7 +189,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
179
189
if (!methodName.ok ()) {
180
190
return Error::InvalidProgram;
181
191
}
182
-
192
+
183
193
return std::string (methodName.get ());
184
194
}
185
195
@@ -189,15 +199,15 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
189
199
if (!method_meta.ok ()) {
190
200
return Error::InvalidProgram;
191
201
}
192
-
202
+
193
203
std::vector<std::vector<uint8_t >> buffers;
194
204
buffers.reserve (method_meta->num_memory_planned_buffers ());
195
205
for (size_t bufferID = 0 ; bufferID < method_meta->num_memory_planned_buffers (); ++bufferID) {
196
206
auto buffer_size = method_meta->memory_planned_buffer_size (bufferID);
197
207
std::vector<uint8_t > data (buffer_size.get (), 0 );
198
208
buffers.emplace_back (std::move (data));
199
209
}
200
-
210
+
201
211
return buffers;
202
212
}
203
213
@@ -207,7 +217,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
207
217
for (auto & buffer : buffers) {
208
218
result.emplace_back (buffer.data (), buffer.size ());
209
219
}
210
-
220
+
211
221
return result;
212
222
}
213
223
@@ -221,7 +231,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
221
231
ET_LOG (Info, " Skipping non-tensor input %zu" , i);
222
232
continue ;
223
233
}
224
- Buffer buffer (tensor_meta->nbytes (), 1 );
234
+ Buffer buffer (tensor_meta->nbytes (), 0 );
225
235
auto sizes = tensor_meta->sizes ();
226
236
exec_aten::TensorImpl tensor_impl (tensor_meta->scalar_type (), std::size (sizes), const_cast <int *>(sizes.data ()), buffer.data ());
227
237
exec_aten::Tensor tensor (&tensor_impl);
@@ -241,7 +251,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
241
251
if (durations.size () == 0 ) {
242
252
return 0.0 ;
243
253
}
244
-
254
+
245
255
return std::accumulate (durations.begin (), durations.end (), 0.0 )/durations.size ();
246
256
}
247
257
@@ -258,96 +268,115 @@ Error execute_method(Method *method, size_t n, std::vector<double>& durations) {
258
268
auto diff = current_time - start_time;
259
269
durations.emplace_back (std::chrono::duration<double , std::milli>(diff).count ());
260
270
}
261
-
271
+
262
272
return status;
263
273
}
274
+
275
+ bool is_model_analysis_enabled (const Args& args) {
276
+ return args.profile_model || args.dump_model_outputs || args.dump_intermediate_outputs ;
277
+ }
278
+
279
+ std::unique_ptr<ETDumpGen> make_etdump_gen (Buffer& debug_buffer, const Args& args) {
280
+ if (!is_model_analysis_enabled (args)) {
281
+ return nullptr ;
282
+ }
283
+
284
+ auto etdump_gen = std::make_unique<ETDumpGen>();
285
+ debug_buffer.resize (args.debug_buffer_size );
286
+ if (args.dump_intermediate_outputs || args.dump_model_outputs ) {
287
+ debug_buffer.resize (args.debug_buffer_size );
288
+ ET_LOG (Info, args.dump_model_outputs ? " Logging model outputs." : " Logging intermediate outputs." );
289
+ Span<uint8_t > debug_buffer_span (debug_buffer.data (), debug_buffer.size ());
290
+ etdump_gen->set_debug_buffer (debug_buffer_span);
291
+ etdump_gen->set_event_tracer_debug_level (args.dump_model_outputs ? EventTracerDebugLogLevel::kProgramOutputs : EventTracerDebugLogLevel::kIntermediateOutputs );
292
+ }
293
+
294
+ return etdump_gen;
295
+ }
296
+
297
+ void dump_etdump_gen (ETDumpGen *etdump_gen, const Buffer& debug_buffer, const Args& args) {
298
+ etdump_result result = (etdump_gen != nullptr ) ? etdump_gen->get_etdump_data () : etdump_result{.buf = nullptr , .size = 0 };
299
+ if (result.size == 0 ) {
300
+ return ;
301
+ }
302
+
303
+ FILE *ptr = fopen (args.etdump_path .c_str (), " wb" );
304
+ fwrite (result.buf , 1 , result.size , ptr);
305
+ fclose (ptr);
306
+ ET_LOG (Info, " Profiling result saved at path = %s" , args.etdump_path .c_str ());
307
+ if (args.dump_intermediate_outputs || args.dump_model_outputs ) {
308
+ ET_LOG (Info, " Debug buffer size = %zu" , result.size );
309
+ FILE *ptr = fopen (args.debug_buffer_path .c_str (), " wb" );
310
+ fwrite (debug_buffer.data (), 1 , debug_buffer.size (), ptr);
311
+ fclose (ptr);
312
+ ET_LOG (Info, " Debug result saved at path = %s" , args.etdump_path .c_str ());
313
+ }
314
+ }
315
+
264
316
}
265
317
266
318
int main (int argc, char * argv[]) {
267
319
@autoreleasepool {
268
320
runtime_init ();
269
-
321
+
270
322
auto args = parse_command_line_args ([[NSProcessInfo processInfo ] arguments ]);
271
323
if (args.purge_models_cache ) {
272
324
ET_LOG (Info, " Purging models cache" );
273
325
auto delegate = CoreMLBackendDelegate::get_registered_delegate ();
274
326
delegate->purge_models_cache ();
275
327
}
276
-
328
+
277
329
if (args.model_path .empty ()) {
278
330
ET_LOG (Error, " Model path is empty." );
279
331
return EXIT_FAILURE;
280
332
}
281
-
333
+
282
334
NSURL *model_url = [NSURL fileURLWithPath: @(args.model_path.c_str ())];
283
335
ET_CHECK_MSG (model_url != nil , " Model path=%s is invalid" , args.model_path .c_str ());
284
-
285
- auto program = get_program (model_url);
336
+
337
+ auto data_loader = std::make_unique<DataLoaderImpl>(model_url.path .UTF8String );
338
+ auto program = ::make_program (data_loader.get ());
286
339
ET_CHECK_MSG (program != nil , " Failed to load program from path=%s" , args.model_path .c_str ());
287
-
340
+
288
341
auto method_name = get_method_name (program.get ());
289
342
ET_CHECK_MSG (method_name.ok (), " Failed to get method name from program=%p" , program.get ());
290
-
291
- auto plannedBuffers = get_planned_buffers (method_name.get (), program.get ());
343
+
344
+ auto planned_buffers = get_planned_buffers (method_name.get (), program.get ());
292
345
Buffer method_buffer (kRuntimeMemorySize , 0 );
293
346
MemoryAllocator method_allocator (static_cast <int32_t >(method_buffer.size ()), method_buffer.data ());
294
- auto spans = to_spans (plannedBuffers .get ());
347
+ auto spans = to_spans (planned_buffers .get ());
295
348
HierarchicalAllocator planned_allocator (Span<Span<uint8_t >>(reinterpret_cast <Span<uint8_t > *>(spans.data ()), spans.size ()));
296
349
MemoryManager memory_manager (&method_allocator, &planned_allocator);
297
-
298
- ETDumpGen *etdump_gen = new ETDumpGen ();
299
- Buffer debug_buffer (args.debug_buffer_size , 0 );
300
- if (args.dump_intermediate_outputs ) {
301
- ET_LOG (Info, " Dumping intermediate outputs" );
302
- Span<uint8_t > buffer (debug_buffer.data (), debug_buffer.size ());
303
- etdump_gen->set_debug_buffer (buffer);
304
- etdump_gen->set_event_tracer_debug_level (
305
- EventTracerDebugLogLevel::kIntermediateOutputs );
306
- } else if (args.dump_model_outputs ) {
307
- ET_LOG (Info, " Dumping model outputs" );
308
- Span<uint8_t > buffer (debug_buffer.data (), debug_buffer.size ());
309
- etdump_gen->set_debug_buffer (buffer);
310
- etdump_gen->set_event_tracer_debug_level (
311
- EventTracerDebugLogLevel::kProgramOutputs );
312
- }
313
-
350
+
351
+ Buffer debug_buffer;
352
+ auto etdump_gen = ::make_etdump_gen (debug_buffer, args);
353
+
314
354
auto load_start_time = std::chrono::steady_clock::now ();
315
- auto method = program->load_method (method_name.get ().c_str (), &memory_manager, (EventTracer *)etdump_gen);
355
+ auto method = program->load_method (method_name.get ().c_str (), &memory_manager, (EventTracer *)etdump_gen. get () );
316
356
auto load_duration = std::chrono::steady_clock::now () - load_start_time;
317
357
ET_LOG (Info, " Load duration = %f" ,std::chrono::duration<double , std::milli>(load_duration).count ());
318
-
358
+
319
359
ET_CHECK_MSG (method_name.ok (), " Failed to load method with name=%s from program=%p" , method_name.get ().c_str (), program.get ());
320
360
ET_LOG (Info, " Running method = %s" , method_name.get ().c_str ());
321
-
361
+
322
362
auto inputs = ::prepare_input_tensors (*method);
323
363
ET_LOG (Info, " Inputs prepared." );
324
-
364
+
325
365
// Run the model.
326
366
std::vector<double > durations;
327
- Error status = execute_method (&method.get (), args.iterations , durations);
367
+ Error status = :: execute_method (&method.get (), args.iterations , durations);
328
368
ET_CHECK_MSG (status == Error::Ok, " Execution of method %s failed with status 0x%" PRIx32, method_name.get ().c_str (), status);
329
369
ET_LOG (Info, " Model executed successfully." );
330
-
331
- double mean = calculate_mean (durations);
370
+
371
+ double mean = :: calculate_mean (durations);
332
372
ET_LOG (Info, " Inference latency=%.2fms." , mean);
333
-
373
+
334
374
auto outputs = method_allocator.allocateList <EValue>(method->outputs_size ());
335
375
status = method->get_outputs (outputs, method->outputs_size ());
336
376
ET_CHECK (status == Error::Ok);
337
-
338
- etdump_result result = etdump_gen->get_etdump_data ();
339
- if (result.size != 0 ) {
340
- ET_LOG (Info, " Size = %zu" , result.size );
341
- FILE *ptr = fopen (args.etdump_path .c_str (), " wb" );
342
- fwrite (result.buf , 1 , result.size , ptr);
343
- fclose (ptr);
344
- if (args.dump_intermediate_outputs || args.dump_model_outputs ) {
345
- FILE *ptr = fopen (args.debug_buffer_path .c_str (), " wb" );
346
- fwrite (debug_buffer.data (), 1 , debug_buffer.size (), ptr);
347
- fclose (ptr);
348
- }
349
- }
350
-
377
+
378
+ dump_etdump_gen (etdump_gen.get (), debug_buffer, args);
379
+
351
380
return EXIT_SUCCESS;
352
381
}
353
382
}
0 commit comments