Skip to content

Commit 8487a33

Browse files
committed
[CoreML Backend] Update coreml runner to only profile model when profile_model option is set
1 parent 3152d7f commit 8487a33

File tree

2 files changed

+120
-87
lines changed

2 files changed

+120
-87
lines changed

backends/apple/coreml/runtime/sdk/ETCoreMLModelAnalyzer.mm

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,6 @@ - (nullable instancetype)initWithCompiledModelAsset:(ETCoreMLAsset *)compiledMod
123123
modelAsset.contentURL.path);
124124
}
125125

126-
ETCoreMLModelProfiler *profiler = [[ETCoreMLModelProfiler alloc] initWithCompiledModelAsset:model.asset
127-
outputNames:model.orderedOutputNames
128-
configuration:configuration
129-
error:error];
130-
131126
self = [super init];
132127
if (self) {
133128
_model = model;
@@ -136,7 +131,6 @@ - (nullable instancetype)initWithCompiledModelAsset:(ETCoreMLAsset *)compiledMod
136131
_configuration = configuration;
137132
_pathToSymbolNameMap = pathToSymbolNameMap;
138133
_executor = [[ETCoreMLDefaultModelExecutor alloc] initWithModel:model];
139-
_profiler = profiler;
140134
}
141135

142136
return self;
@@ -147,12 +141,22 @@ - (nullable instancetype)initWithCompiledModelAsset:(ETCoreMLAsset *)compiledMod
147141
eventLogger:(const executorchcoreml::ModelEventLogger *)eventLogger
148142
error:(NSError * __autoreleasing *)error {
149143
if (self.profiler == nil) {
144+
ETCoreMLModelProfiler *profiler = [[ETCoreMLModelProfiler alloc] initWithCompiledModelAsset:self.model.asset
145+
outputNames:self.model.orderedOutputNames
146+
configuration:self.configuration
147+
error:error];
148+
self.profiler = profiler;
149+
}
150+
151+
152+
if (!self.profiler) {
150153
ETCoreMLLogErrorAndSetNSError(error,
151154
ETCoreMLErrorModelProfilingNotSupported,
152155
"%@: Model profiling is only available for macOS >= 14.4, iOS >= 17.4, tvOS >= 17.4 and watchOS >= 10.4.",
153156
NSStringFromClass(ETCoreMLModelAnalyzer.class));
154157
return nil;
155158
}
159+
156160
NSArray<MLMultiArray *> *modelOutputs = nil;
157161
NSArray<ETCoreMLModelStructurePath *> *operationPaths = self.profiler.operationPaths;
158162
ETCoreMLModelProfilingResult *profilingInfos = [self.profiler profilingInfoForOperationsAtPaths:operationPaths

examples/apple/coreml/executor_runner/main.mm

Lines changed: 110 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,18 @@
66
// Please refer to the license found in the LICENSE file in the root directory of the source tree.
77

88
#import <Foundation/Foundation.h>
9-
109
#import <chrono>
11-
#import <memory>
12-
#import <numeric>
13-
#import <string>
14-
10+
#import <coreml_backend/delegate.h>
1511
#import <executorch/extension/data_loader/file_data_loader.h>
1612
#import <executorch/runtime/executor/method.h>
1713
#import <executorch/runtime/executor/program.h>
1814
#import <executorch/runtime/platform/log.h>
1915
#import <executorch/runtime/platform/runtime.h>
20-
#import <executorch/util/util.h>
2116
#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>
2421

2522
static inline id check_class(id obj, Class cls) {
2623
return [obj isKindOfClass:cls] ? obj : nil;
@@ -44,7 +41,8 @@ static inline id check_class(id obj, Class cls) {
4441
bool purge_models_cache = false;
4542
bool dump_model_outputs = false;
4643
bool dump_intermediate_outputs = false;
47-
44+
bool profile_model = false;
45+
4846
Args(NSDictionary<NSString *, NSString *> *params) {
4947
{
5048
NSString *value = SAFE_CAST(params[@"--model_path"], NSString);
@@ -83,15 +81,18 @@ static inline id check_class(id obj, Class cls) {
8381
}
8482
}
8583
{
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;
8986
}
9087
}
9188
{
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;
9596
}
9697
}
9798
}
@@ -102,7 +103,17 @@ static inline id check_class(id obj, Class cls) {
102103
}
103104

104105
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+
]];
106117
}
107118

108119
Args parse_command_line_args(NSArray<NSString *> *args) {
@@ -127,11 +138,11 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
127138
key = value;
128139
values = [NSMutableString string];
129140
}
130-
141+
131142
if (key.length > 0) {
132143
params[key] = values.length > 0 ? clean_string(values.copy) : @"";
133144
}
134-
145+
135146
return Args(params);
136147
}
137148

@@ -148,29 +159,28 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
148159
DataLoaderImpl(const std::string& filePath)
149160
:data_(read_data(filePath))
150161
{}
151-
162+
152163
Result<FreeableBuffer> Load(size_t offset, size_t size) override {
153164
NSData *subdata = [data_ subdataWithRange:NSMakeRange(offset, size)];
154165
return FreeableBuffer(subdata.bytes, size, nullptr);
155166
}
156-
167+
157168
Result<size_t> size() const override {
158169
return data_.length;
159170
}
160-
171+
161172
private:
162-
NSData *data_;
173+
NSData *data_;
163174
};
164175

165176
using Buffer = std::vector<uint8_t>;
166177

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);
170180
if (!program.ok()) {
171181
return nullptr;
172182
}
173-
183+
174184
return std::make_unique<Program>(std::move(program.get()));
175185
}
176186

@@ -179,7 +189,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
179189
if (!methodName.ok()) {
180190
return Error::InvalidProgram;
181191
}
182-
192+
183193
return std::string(methodName.get());
184194
}
185195

@@ -189,15 +199,15 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
189199
if (!method_meta.ok()) {
190200
return Error::InvalidProgram;
191201
}
192-
202+
193203
std::vector<std::vector<uint8_t>> buffers;
194204
buffers.reserve(method_meta->num_memory_planned_buffers());
195205
for (size_t bufferID = 0; bufferID < method_meta->num_memory_planned_buffers(); ++bufferID) {
196206
auto buffer_size = method_meta->memory_planned_buffer_size(bufferID);
197207
std::vector<uint8_t> data(buffer_size.get(), 0);
198208
buffers.emplace_back(std::move(data));
199209
}
200-
210+
201211
return buffers;
202212
}
203213

@@ -207,7 +217,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
207217
for (auto& buffer : buffers) {
208218
result.emplace_back(buffer.data(), buffer.size());
209219
}
210-
220+
211221
return result;
212222
}
213223

@@ -221,7 +231,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
221231
ET_LOG(Info, "Skipping non-tensor input %zu", i);
222232
continue;
223233
}
224-
Buffer buffer(tensor_meta->nbytes(), 1);
234+
Buffer buffer(tensor_meta->nbytes(), 0);
225235
auto sizes = tensor_meta->sizes();
226236
exec_aten::TensorImpl tensor_impl(tensor_meta->scalar_type(), std::size(sizes), const_cast<int *>(sizes.data()), buffer.data());
227237
exec_aten::Tensor tensor(&tensor_impl);
@@ -241,7 +251,7 @@ Args parse_command_line_args(NSArray<NSString *> *args) {
241251
if (durations.size() == 0) {
242252
return 0.0;
243253
}
244-
254+
245255
return std::accumulate(durations.begin(), durations.end(), 0.0)/durations.size();
246256
}
247257

@@ -258,96 +268,115 @@ Error execute_method(Method *method, size_t n, std::vector<double>& durations) {
258268
auto diff = current_time - start_time;
259269
durations.emplace_back(std::chrono::duration<double, std::milli>(diff).count());
260270
}
261-
271+
262272
return status;
263273
}
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+
264316
}
265317

266318
int main(int argc, char * argv[]) {
267319
@autoreleasepool {
268320
runtime_init();
269-
321+
270322
auto args = parse_command_line_args([[NSProcessInfo processInfo] arguments]);
271323
if (args.purge_models_cache) {
272324
ET_LOG(Info, "Purging models cache");
273325
auto delegate = CoreMLBackendDelegate::get_registered_delegate();
274326
delegate->purge_models_cache();
275327
}
276-
328+
277329
if (args.model_path.empty()) {
278330
ET_LOG(Error, "Model path is empty.");
279331
return EXIT_FAILURE;
280332
}
281-
333+
282334
NSURL *model_url = [NSURL fileURLWithPath:@(args.model_path.c_str())];
283335
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());
286339
ET_CHECK_MSG(program != nil, "Failed to load program from path=%s", args.model_path.c_str());
287-
340+
288341
auto method_name = get_method_name(program.get());
289342
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());
292345
Buffer method_buffer(kRuntimeMemorySize, 0);
293346
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());
295348
HierarchicalAllocator planned_allocator(Span<Span<uint8_t>>(reinterpret_cast<Span<uint8_t> *>(spans.data()), spans.size()));
296349
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+
314354
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());
316356
auto load_duration = std::chrono::steady_clock::now() - load_start_time;
317357
ET_LOG(Info, "Load duration = %f",std::chrono::duration<double, std::milli>(load_duration).count());
318-
358+
319359
ET_CHECK_MSG(method_name.ok(), "Failed to load method with name=%s from program=%p", method_name.get().c_str(), program.get());
320360
ET_LOG(Info, "Running method = %s", method_name.get().c_str());
321-
361+
322362
auto inputs = ::prepare_input_tensors(*method);
323363
ET_LOG(Info, "Inputs prepared.");
324-
364+
325365
// Run the model.
326366
std::vector<double> durations;
327-
Error status = execute_method(&method.get(), args.iterations, durations);
367+
Error status = ::execute_method(&method.get(), args.iterations, durations);
328368
ET_CHECK_MSG(status == Error::Ok, "Execution of method %s failed with status 0x%" PRIx32, method_name.get().c_str(), status);
329369
ET_LOG(Info, "Model executed successfully.");
330-
331-
double mean = calculate_mean(durations);
370+
371+
double mean = ::calculate_mean(durations);
332372
ET_LOG(Info, "Inference latency=%.2fms.", mean);
333-
373+
334374
auto outputs = method_allocator.allocateList<EValue>(method->outputs_size());
335375
status = method->get_outputs(outputs, method->outputs_size());
336376
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+
351380
return EXIT_SUCCESS;
352381
}
353382
}

0 commit comments

Comments
 (0)