Skip to content

Commit 22f23bc

Browse files
committed
[CoreML Backend] Handle missing data types.
1 parent d761f99 commit 22f23bc

21 files changed

+1231
-659
lines changed

backends/apple/coreml/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ if(NOT EXECUTORCH_ROOT)
1313
set(EXECUTORCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
1414
endif()
1515

16+
option(COREML_BUILD_EXECUTOR_RUNNER "Build CoreML executor runner." OFF)
17+
1618
# inmemoryfs sources
1719
set(INMEMORYFS_SOURCES
1820
runtime/inmemoryfs/inmemory_filesystem.cpp
@@ -181,6 +183,14 @@ target_link_libraries(coremldelegate
181183
${SQLITE_LIBRARY}
182184
)
183185

186+
if(COREML_BUILD_EXECUTOR_RUNNER)
187+
target_link_libraries(coremldelegate
188+
PRIVATE
189+
portable_ops_lib
190+
portable_kernels
191+
)
192+
endif()
193+
184194
target_compile_options(coremldelegate PRIVATE "-fobjc-arc")
185195
target_compile_options(coremldelegate PRIVATE "-fno-exceptions")
186196

backends/apple/coreml/runtime/delegate/ETCoreMLLogging.h

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#import <Foundation/Foundation.h>
99

10+
#import <executorch/runtime/platform/log.h>
1011
#import <os/log.h>
1112

1213
NS_ASSUME_NONNULL_BEGIN
@@ -48,7 +49,11 @@ typedef NS_ERROR_ENUM(ETCoreMLErrorDomain, ETCoreMLError) {
4849

4950
/// Record the error with `os_log_error` and fills `*errorOut` with `NSError`.
5051
#define ETCoreMLLogErrorAndSetNSError(errorOut, errorCode, formatString, ...) \
51-
os_log_error(ETCoreMLErrorUtils.loggingChannel, formatString, ##__VA_ARGS__); \
52+
if (ET_LOG_ENABLED) { \
53+
ET_LOG(Error, "%s", [NSString stringWithFormat:@formatString, ##__VA_ARGS__].UTF8String); \
54+
} else { \
55+
os_log_error(ETCoreMLErrorUtils.loggingChannel, formatString, ##__VA_ARGS__); \
56+
} \
5257
if (errorOut) { \
5358
*errorOut = \
5459
[NSError errorWithDomain:ETCoreMLErrorDomain \
@@ -58,24 +63,31 @@ typedef NS_ERROR_ENUM(ETCoreMLErrorDomain, ETCoreMLError) {
5863
}]; \
5964
}
6065

61-
/// Record the error and its underlying error with `os_log_error` and fills
62-
/// `*errorOut` with NSError.
66+
/// Record the error and its underlying error with `os_log_error` and fills `*errorOut` with `NSError`.
6367
#define ETCoreMLLogUnderlyingErrorAndSetNSError(errorOut, errorCode, underlyingNSError, formatString, ...) \
64-
os_log_error(ETCoreMLErrorUtils.loggingChannel, \
65-
formatString ", with underlying error= %@.", \
66-
##__VA_ARGS__, \
67-
(underlyingNSError).localizedDescription); \
68+
if (ET_LOG_ENABLED) { \
69+
ET_LOG(Error, "%s", [NSString stringWithFormat:@formatString, ##__VA_ARGS__].UTF8String); \
70+
} else { \
71+
os_log_error(ETCoreMLErrorUtils.loggingChannel, \
72+
formatString ", with underlying error= %@.", \
73+
##__VA_ARGS__, \
74+
(underlyingNSError).localizedDescription); \
75+
} \
6876
if (errorOut) { \
6977
*errorOut = [ETCoreMLErrorUtils errorWithCode:errorCode \
7078
underlyingError:underlyingNSError \
7179
format:@formatString, ##__VA_ARGS__]; \
7280
}
7381

74-
#define ETCoreMLLogError(error, formatString, ...) \
75-
os_log_error(ETCoreMLErrorUtils.loggingChannel, \
76-
formatString ", with error= %@.", \
77-
##__VA_ARGS__, \
78-
(error).localizedDescription);
82+
#define ETCoreMLLogError(error, formatString, ...) \
83+
if (ET_LOG_ENABLED) { \
84+
ET_LOG(Error, "%s", [NSString stringWithFormat:@formatString, ##__VA_ARGS__].UTF8String); \
85+
} else { \
86+
os_log_error(ETCoreMLErrorUtils.loggingChannel, \
87+
formatString ", with error= %@.", \
88+
##__VA_ARGS__, \
89+
(error).localizedDescription); \
90+
}
7991

8092

8193
#pragma clang diagnostic pop

backends/apple/coreml/runtime/delegate/ETCoreMLModel.h

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

88
#import <CoreML/CoreML.h>
9+
#import <vector>
910

1011
NS_ASSUME_NONNULL_BEGIN
1112

1213
@class ETCoreMLAsset;
1314

15+
namespace executorchcoreml {
16+
class MultiArray;
17+
}
18+
1419
/// Represents a ML model, the class is a thin wrapper over `MLModel` with additional properties.
15-
@interface ETCoreMLModel : NSObject
20+
__attribute__((objc_subclassing_restricted)) @interface ETCoreMLModel : NSObject
1621

1722
- (instancetype)init NS_UNAVAILABLE;
1823

@@ -31,6 +36,12 @@ NS_ASSUME_NONNULL_BEGIN
3136
orderedOutputNames:(NSOrderedSet<NSString*>*)orderedOutputNames
3237
error:(NSError* __autoreleasing*)error NS_DESIGNATED_INITIALIZER;
3338

39+
- (nullable NSArray<MLMultiArray*>*)prepareInputs:(const std::vector<executorchcoreml::MultiArray>&)inputs
40+
error:(NSError* __autoreleasing*)error;
41+
42+
- (nullable NSArray<MLMultiArray*>*)prepareOutputBackings:(const std::vector<executorchcoreml::MultiArray>&)outputs
43+
error:(NSError* __autoreleasing*)error;
44+
3445
/// The underlying MLModel.
3546
@property (strong, readonly, nonatomic) MLModel* mlModel;
3647

backends/apple/coreml/runtime/delegate/ETCoreMLModel.mm

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,164 @@
88
#import <ETCoreMLModel.h>
99

1010
#import <ETCoreMLAsset.h>
11+
#import <functional>
12+
#import <objc_array_util.h>
13+
#import <multiarray.h>
14+
#import <numeric>
15+
16+
#pragma mark - ETCoreMLMultiArrayDescriptor
17+
__attribute__((objc_subclassing_restricted))
18+
@interface ETCoreMLMultiArrayDescriptor: NSObject <NSCopying>
19+
20+
- (instancetype)init NS_UNAVAILABLE;
21+
22+
+ (instancetype)new NS_UNAVAILABLE;
23+
24+
- (instancetype)initWithShape:(NSArray<NSNumber *> *)shape
25+
dataType:(MLMultiArrayDataType)dataType NS_DESIGNATED_INITIALIZER;
26+
27+
@property (copy, readonly, nonatomic) NSArray<NSNumber *> *shape;
28+
29+
@property (assign, readonly, nonatomic) MLMultiArrayDataType dataType;
30+
31+
@end
32+
33+
@implementation ETCoreMLMultiArrayDescriptor
34+
35+
- (instancetype)initWithShape:(NSArray<NSNumber *> *)shape
36+
dataType:(MLMultiArrayDataType)dataType {
37+
self = [super init];
38+
if (self) {
39+
_shape = shape;
40+
_dataType = dataType;
41+
}
42+
43+
return self;
44+
}
45+
46+
- (BOOL)isEqual:(id)object {
47+
if (object == self) {
48+
return YES;
49+
}
50+
51+
if (![object isKindOfClass:self.class]) {
52+
return NO;
53+
}
54+
55+
ETCoreMLMultiArrayDescriptor *other = (ETCoreMLMultiArrayDescriptor *)object;
56+
return [self.shape isEqualToArray:other.shape] && self.dataType == other.dataType;
57+
}
58+
59+
- (NSUInteger)hash {
60+
return [self.shape hash] ^ (NSUInteger)self.dataType;
61+
}
62+
63+
- (instancetype)copyWithZone:(NSZone *)zone {
64+
return [[ETCoreMLMultiArrayDescriptor allocWithZone:zone] initWithShape:self.shape
65+
dataType:self.dataType];
66+
}
67+
68+
@end
69+
70+
namespace {
71+
72+
using namespace executorchcoreml;
73+
74+
size_t get_number_of_bytes(MLMultiArrayDataType data_type) {
75+
switch (data_type) {
76+
case MLMultiArrayDataTypeFloat16: {
77+
return 2;
78+
}
79+
case MLMultiArrayDataTypeFloat32: {
80+
return 4;
81+
}
82+
case MLMultiArrayDataTypeInt32: {
83+
return 4;
84+
}
85+
case MLMultiArrayDataTypeFloat64: {
86+
return 8;
87+
}
88+
default: {
89+
return 0;
90+
}
91+
}
92+
}
93+
94+
std::vector<size_t> calculate_strides(const std::vector<size_t>& shape) {
95+
if (shape.size() == 0) {
96+
return {};
97+
}
98+
99+
if (shape.size() == 1) {
100+
return {1};
101+
}
102+
103+
std::vector<size_t> strides(shape.size(), 1);
104+
size_t product = 1;
105+
for (size_t i = shape.size(); i > 0; i--) {
106+
strides[i - 1] = product;
107+
product *= shape[i - 1];
108+
}
109+
110+
return strides;
111+
}
112+
113+
MLMultiArray * _Nullable make_ml_multi_array(const std::vector<size_t>& shape,
114+
MLMultiArrayDataType dataType,
115+
NSCache<ETCoreMLMultiArrayDescriptor *, NSMutableData *> *cache,
116+
NSError * __autoreleasing *error) {
117+
ETCoreMLMultiArrayDescriptor *descriptor = [[ETCoreMLMultiArrayDescriptor alloc] initWithShape:to_array(shape)
118+
dataType:dataType];
119+
// Check the cache first otherwise allocate a new backing storage.
120+
NSMutableData *backing_storage = [cache objectForKey:descriptor];
121+
if (backing_storage) {
122+
[cache removeObjectForKey:descriptor];
123+
} else {
124+
size_t n = std::accumulate(shape.cbegin(), shape.cend(), 1, std::multiplies<size_t>{});
125+
backing_storage = [[NSMutableData alloc] initWithLength:n * get_number_of_bytes(dataType)];
126+
}
127+
128+
__weak NSCache<ETCoreMLMultiArrayDescriptor *, NSMutableData *> *weakCache = cache;
129+
// Add the storage back to the cache when it gets deallocated, the next prediction would use the same storage.
130+
MLMultiArray *result = [[MLMultiArray alloc] initWithDataPointer:backing_storage.mutableBytes
131+
shape:descriptor.shape
132+
dataType:descriptor.dataType
133+
strides:to_array(calculate_strides(shape))
134+
deallocator:^(void * _Nonnull bytes) {[weakCache setObject:backing_storage forKey:descriptor];}
135+
error:error];
136+
137+
return result;
138+
}
139+
140+
NSDictionary<NSString *, MLMultiArrayConstraint *> *
141+
get_multi_array_constraints_by_name(NSDictionary<NSString *, MLFeatureDescription *> *feature_descriptions) {
142+
NSMutableDictionary<NSString *, MLMultiArrayConstraint *> *result = [NSMutableDictionary dictionaryWithCapacity:feature_descriptions.count];
143+
[feature_descriptions enumerateKeysAndObjectsUsingBlock:^(NSString *key, MLFeatureDescription *description, BOOL * _Nonnull stop) {
144+
result[key] = description.multiArrayConstraint;
145+
}];
146+
147+
return result;
148+
}
149+
150+
NSDictionary<NSString *, MLMultiArrayConstraint *> *get_multi_array_input_constraints_by_name(MLModelDescription *description) {
151+
return get_multi_array_constraints_by_name(description.inputDescriptionsByName);
152+
}
153+
154+
NSDictionary<NSString *, MLMultiArrayConstraint *> *get_multi_array_output_constraints_by_name(MLModelDescription *description) {
155+
return get_multi_array_constraints_by_name(description.outputDescriptionsByName);
156+
}
157+
158+
}
159+
160+
#pragma mark - ETCoreMLModel
161+
@interface ETCoreMLModel ()
162+
163+
@property (strong, readonly, nonatomic) NSCache<ETCoreMLMultiArrayDescriptor *, NSMutableData *> *cache;
164+
@property (copy, readonly, nonatomic) NSDictionary<NSString *, MLMultiArrayConstraint *> *inputConstraintsByName;
165+
@property (copy, readonly, nonatomic) NSDictionary<NSString *, MLMultiArrayConstraint *> *outputConstraintsByName;
166+
167+
@end
168+
11169

12170
@implementation ETCoreMLModel
13171

@@ -33,13 +191,84 @@ - (nullable instancetype)initWithAsset:(ETCoreMLAsset *)asset
33191
_asset = asset;
34192
_orderedInputNames = [orderedInputNames copy];
35193
_orderedOutputNames = [orderedOutputNames copy];
194+
_cache = [[NSCache alloc] init];
195+
_inputConstraintsByName = get_multi_array_input_constraints_by_name(mlModel.modelDescription);
196+
_outputConstraintsByName = get_multi_array_output_constraints_by_name(mlModel.modelDescription);
36197
}
37-
198+
38199
return self;
39200
}
40201

41202
- (NSString *)identifier {
42203
return self.asset.identifier;
43204
}
44205

206+
- (nullable NSArray<MLMultiArray *> *)prepareArgs:(const std::vector<executorchcoreml::MultiArray>&)args
207+
argNames:(NSOrderedSet<NSString *> *)argNames
208+
argConstraintsByName:(NSDictionary<NSString *, MLMultiArrayConstraint *> *)argConstraintsByName
209+
copyData:(BOOL)copyData
210+
error:(NSError * __autoreleasing *)error {
211+
NSEnumerator *nameEnumerator = [argNames objectEnumerator];
212+
NSMutableArray<MLMultiArray *> *result = [NSMutableArray arrayWithCapacity:args.size()];
213+
for (const auto& arg : args) {
214+
NSString *argName = [nameEnumerator nextObject];
215+
MLMultiArrayConstraint *constraint = argConstraintsByName[argName];
216+
const auto& layout = arg.layout();
217+
auto dataType = to_ml_multiarray_data_type(layout.dataType());
218+
MLMultiArray *multiArrayArg = nil;
219+
if (dataType == constraint.dataType) {
220+
// We can use the same data storage.
221+
multiArrayArg = [[MLMultiArray alloc] initWithDataPointer:arg.data()
222+
shape:to_array(layout.shape())
223+
dataType:constraint.dataType
224+
strides:to_array(layout.strides())
225+
deallocator:^(void * _Nonnull bytes) {}
226+
error:error];
227+
copyData = NO;
228+
} else {
229+
// We can' use the same data storage, data types are not the same.
230+
multiArrayArg = ::make_ml_multi_array(layout.shape(), constraint.dataType, self.cache, error);
231+
}
232+
233+
if (!multiArrayArg) {
234+
return nil;
235+
}
236+
237+
if (multiArrayArg && copyData) {
238+
[multiArrayArg getMutableBytesWithHandler:^(void *_Nonnull mutableBytes,
239+
NSInteger __unused size,
240+
NSArray<NSNumber *> *strides) {
241+
MultiArray buffer(mutableBytes, MultiArray::MemoryLayout(to_multiarray_data_type(constraint.dataType).value(),
242+
layout.shape(),
243+
to_vector<ssize_t>(strides)));
244+
arg.copy(buffer);
245+
}];
246+
}
247+
248+
[result addObject:multiArrayArg];
249+
}
250+
251+
return result;
252+
}
253+
254+
- (nullable NSArray<MLMultiArray *> *)prepareInputs:(const std::vector<executorchcoreml::MultiArray>&)inputs
255+
error:(NSError * __autoreleasing *)error {
256+
return [self prepareArgs:inputs
257+
argNames:self.orderedInputNames
258+
argConstraintsByName:self.inputConstraintsByName
259+
copyData:YES
260+
error:error];
261+
262+
}
263+
264+
- (nullable NSArray<MLMultiArray *> *)prepareOutputBackings:(const std::vector<executorchcoreml::MultiArray>&)outputs
265+
error:(NSError * __autoreleasing *)error {
266+
return [self prepareArgs:outputs
267+
argNames:self.orderedOutputNames
268+
argConstraintsByName:self.outputConstraintsByName
269+
copyData:NO
270+
error:error];
271+
272+
}
273+
45274
@end

0 commit comments

Comments
 (0)