Skip to content
This repository was archived by the owner on Feb 7, 2023. It is now read-only.

Commit 7287539

Browse files
authored
releasing 1.0b3 (#471)
* Changing disable_coreml_rank5_mapping option to target_ios Introducing SupportedVersion class to control and manager version and feature support log * Updating readme and comments * test cases using SupportedVersion for version and feature checking * Updating comments and adding specification version * changes in README
1 parent 19f658b commit 7287539

12 files changed

+212
-133
lines changed

README.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,23 @@ There's a comprehensive [Tutorial](https://github.com/onnx/tutorials/tree/master
1010
## [New] Beta onnx-coreml converter with Core ML 3
1111

1212
To try out the new beta converter with CoreML 3 (>= iOS 13, >= macOS 15),
13-
install coremltools 3.0b3 and coremltools 1.0b2
13+
install coremltools 3.0b3 and coremltools 1.0b3
1414

1515
```shell
16-
pip install coremltools==3.0b3
17-
pip install onnx-coreml==1.0b2
16+
pip install coremltools==3.0b6
17+
pip install onnx-coreml==1.0b3
1818
```
1919

20-
There is a new flag `disable_coreml_rank5_mapping` which should be set to true to utilize
21-
the Core ML 3 specification.
20+
With beta 3, flag `disable_coreml_rank5_mapping` which should be set to true to utilize
21+
the Core ML 3 specification is removed and replace by target iOS specific flag, taking default value of '12'
2222

23+
target_ios takes a string specifying target deployment iOS version e.g. '11.2', '12' and '13'
2324

2425
For example:
2526
```python
2627
from onnx_coreml import convert
2728

28-
ml_model = convert(model='my_model.onnx', disable_coreml_rank5_mapping=True)
29+
ml_model = convert(model='my_model.onnx', target_ios='13')
2930
```
3031

3132
## Installation
@@ -80,7 +81,7 @@ def convert(model,
8081
predicted_feature_name='classLabel',
8182
add_custom_layers = False,
8283
custom_conversion_functions = {},
83-
disable_coreml_rank5_mapping=False)
84+
target_ios='12')
8485
```
8586

8687
The function returns a coreml model instance that can be saved to a .mlmodel file, e.g.:
@@ -164,13 +165,18 @@ __onnx_coreml_input_shape_map__: dict (str: List[int])
164165
0: Sequence, 1: Batch, 2: channel, 3: height, 4: width.
165166
For example, an input of rank 2 could be mapped as [3,4] (i.e. H,W) or [1,2] (i.e. B,C) etc.
166167

167-
__disable_coreml_rank5_mapping__: bool
168-
If True, then it disables the "RANK5_ARRAY_MAPPING" or enables the "EXACT_ARRAY_MAPPING"
169-
option in CoreML (https://github.com/apple/coremltools/blob/655b3be5cc0d42c3c4fa49f0f0e4a93a26b3e492/mlmodel/format/NeuralNetwork.proto#L67)
170-
Thus, no longer, onnx tensors are forced to map to rank 5 CoreML tensors.
171-
With this flag on, a rank r ONNX tensor, (1<=r<=5), will map to a rank r tensor in CoreML as well.
172-
This flag must be on to utilize any of the new layers added in CoreML 3 (i.e. specification version 4, iOS13)
173-
168+
__target_ios__: str
169+
Target Deployment iOS Version (default: '12')
170+
Supported iOS version options: '11.2', '12', '13'
171+
CoreML model produced by the converter will be compatible with the iOS version specified in this argument.
172+
e.g. if target_ios = '12', the converter would only utilize CoreML features released till iOS12 (equivalently macOS 10.14, watchOS 5 etc).
173+
174+
- (Supported features: https://github.com/apple/coremltools/releases/tag/v0.8)
175+
iOS 12 (CoreML 2.0)
176+
- (Supported features: https://github.com/apple/coremltools/releases/tag/v2.0)
177+
iSO 13 (CoreML 3.0)
178+
- (Supported features: https://github.com/apple/coremltools/releases/tag/3.0-beta6)
179+
174180
### Returns
175181
__model__: A coreml model.
176182

@@ -235,4 +241,4 @@ See the testing script `tests/custom_layers_test.py` on how to produce CoreML mo
235241
## License
236242
Copyright © 2018 by Apple Inc., Facebook Inc., and Prisma Labs Inc.
237243

238-
Use of this source code is governed by the [MIT License](https://opensource.org/licenses/MIT) that can be found in the LICENSE.txt file.
244+
Use of this source code is governed by the [MIT License](https://opensource.org/licenses/MIT) that can be found in the LICENSE.txt file.

onnx_coreml/_backend.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class CoreMLBackend(Backend):
3636
def prepare(cls,
3737
model, # type: ModelProto
3838
device='CPU', # type: Text
39-
disable_rank5_mapping=False, # type: Bool
39+
target_ios='12', # type: str
4040
**kwargs # type: Any
4141
):
4242
# type: (...) -> CoreMLRep
@@ -45,11 +45,11 @@ def prepare(cls,
4545
with open('/tmp/node_model.onnx', 'wb') as f:
4646
s = model.SerializeToString()
4747
f.write(s)
48-
coreml_model = convert(model, disable_coreml_rank5_mapping=disable_rank5_mapping)
48+
coreml_model = convert(model, target_ios=target_ios)
4949
if DEBUG:
5050
coreml_model.save('/tmp/node_model.mlmodel')
5151
onnx_outputs_info = _get_onnx_outputs_info(model)
52-
return CoreMLRep(coreml_model, onnx_outputs_info, device == 'CPU', disable_rank5_mapping=disable_rank5_mapping)
52+
return CoreMLRep(coreml_model, onnx_outputs_info, device == 'CPU', target_ios=target_ios)
5353

5454
@classmethod
5555
def is_compatible(cls,
@@ -114,7 +114,7 @@ class CoreMLBackendND(Backend):
114114
def prepare(cls,
115115
model, # type: ModelProto
116116
device='CPU', # type: Text
117-
disable_rank5_mapping=True, # type: Bool
117+
target_ios='13', # type: str
118118
**kwargs # type: Any
119119
):
120120
# type: (...) -> CoreMLRep
@@ -123,11 +123,11 @@ def prepare(cls,
123123
with open('/tmp/node_model.onnx', 'wb') as f:
124124
s = model.SerializeToString()
125125
f.write(s)
126-
coreml_model = convert(model, disable_coreml_rank5_mapping=disable_rank5_mapping)
126+
coreml_model = convert(model, target_ios=target_ios)
127127
if DEBUG:
128128
coreml_model.save('/tmp/node_model.mlmodel')
129129
onnx_outputs_info = _get_onnx_outputs_info(model)
130-
return CoreMLRep(coreml_model, onnx_outputs_info, device == 'CPU', disable_rank5_mapping=disable_rank5_mapping)
130+
return CoreMLRep(coreml_model, onnx_outputs_info, device == 'CPU', target_ios=target_ios)
131131

132132
@classmethod
133133
def is_compatible(cls,

onnx_coreml/_backend_rep.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from typing import Dict, Any, Text, Tuple
1313
from onnx import TensorProto
1414
from ._graph import EdgeInfo
15+
from .converter import SupportedVersion
1516

1617

1718
def _set_dtypes(input_dict, #type: Dict[Text, np._ArrayLike[Any]]
@@ -34,13 +35,13 @@ def __init__(self,
3435
coreml_model, # type: MLModel
3536
onnx_outputs_info, # type: Dict[Text, EdgeInfo]
3637
useCPUOnly=False, # type: bool
37-
disable_rank5_mapping=False # type: bool
38+
target_ios='12' # type: str
3839
):
3940
# type: (...) -> None
4041
super(CoreMLRep, self).__init__()
4142
self.model = coreml_model
4243
self.useCPUOnly = useCPUOnly
43-
self.disable_rank5_mapping = disable_rank5_mapping
44+
self.target_ios = target_ios
4445

4546
spec = coreml_model.get_spec()
4647
self.input_names = [str(i.name) for i in spec.description.input]
@@ -55,7 +56,7 @@ def run(self,
5556
super(CoreMLRep, self).run(inputs, **kwargs)
5657
inputs_ = inputs
5758
_reshaped = False
58-
if not self.disable_rank5_mapping:
59+
if not SupportedVersion.is_nd_array_supported(self.target_ios):
5960
for i, input_ in enumerate(inputs_):
6061
shape = input_.shape
6162
if len(shape) == 4 or len(shape) == 2:
@@ -79,7 +80,7 @@ def run(self,
7980
prediction = self.model.predict(input_dict, self.useCPUOnly)
8081
output_values = [prediction[name] for name in self.output_names]
8182

82-
if not self.disable_rank5_mapping:
83+
if not SupportedVersion.is_nd_array_supported(self.target_ios):
8384
for i, output_ in enumerate(output_values):
8485
shape = output_.shape
8586
#reshape the CoreML output to match Onnx's output shape

onnx_coreml/_error_utils.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,14 @@ def __init__(self,
1515
add_custom_layers = False, # type: bool
1616
custom_conversion_functions = dict(), # type: Dict[Text, Any]
1717
custom_layer_nodes = [], # type : List[Node]
18-
disable_coreml_rank5_mapping = False
1918
):
2019
# type: (...) -> None
2120
self.add_custom_layers = add_custom_layers
2221
self.custom_conversion_functions = custom_conversion_functions
2322
self.custom_layer_nodes = custom_layer_nodes
24-
self.disable_coreml_rank5_mapping = disable_coreml_rank5_mapping
25-
# TODO: Remove following error message once, disable_coreml_rank5_mapping is default to True
26-
self.coreml_3_rerun_message = ''
27-
if not disable_coreml_rank5_mapping:
28-
self.coreml_3_rerun_message = '\nPlease try converting again with disable_coreml_rank5_mapping=True' \
29-
' and coremltools 3.0 latest beta'
23+
24+
self.rerun_suggestion = '\n Please try converting with higher target_ios.\n' \
25+
'You can also provide custom function/layer to convert the model.'
3026

3127

3228
def unsupported_op(self,
@@ -41,7 +37,7 @@ def unsupported_op(self,
4137
return _convert_custom
4238
else:
4339
raise TypeError(
44-
"ONNX node of type {} is not supported. {}\n".format(node.op_type, self.coreml_3_rerun_message)
40+
"ONNX node of type {} is not supported. {}\n".format(node.op_type, self.rerun_suggestion)
4541
)
4642

4743

@@ -61,7 +57,7 @@ def unsupported_op_configuration(self,
6157
else:
6258
raise TypeError(
6359
"Error while converting op of type: {}. Error message: {} {}\n".format(node.op_type, err_message,
64-
self.coreml_3_rerun_message)
60+
self.rerun_suggestion)
6561
)
6662

6763

@@ -76,7 +72,7 @@ def missing_initializer(self,
7672
raise ValueError(
7773
"Missing initializer error in op of type {}, with input name = {}, "
7874
"output name = {}. Error message: {} {}\n".
79-
format(node.op_type, node.inputs[0], node.outputs[0], err_message, self.coreml_3_rerun_message)
75+
format(node.op_type, node.inputs[0], node.outputs[0], err_message, self.rerun_suggestion)
8076
)
8177

8278
def unsupported_feature_warning(self,
@@ -89,8 +85,8 @@ def unsupported_feature_warning(self,
8985
'''
9086
print(
9187
"Warning: Unsupported Feature in op of type {}, with input name = {}, "
92-
"output name = {}. Warning message: {} {}\n".
93-
format(node.op_type, node.inputs[0], node.outputs[0], err_message, self.coreml_3_rerun_message)
88+
"output name = {}. Warning message: {}\n".
89+
format(node.op_type, node.inputs[0], node.outputs[0], err_message)
9490
)
9591

9692

onnx_coreml/_operators_nd.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1759,7 +1759,6 @@ def add_softmax(output_name, rank=-1, axis=-3):
17591759
transpose_axes = list(range(rank))
17601760
transpose_axes[-3], transpose_axes[axis] = transpose_axes[axis], transpose_axes[-3]
17611761

1762-
print(transpose_axes)
17631762
builder.add_transpose(
17641763
name=node.name + '_transpose',
17651764
axes=transpose_axes,

onnx_coreml/converter.py

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
from coremltools.models.neural_network import NeuralNetworkBuilder #type: ignore
1414
from coremltools.models import datatypes, MLModel #type: ignore
1515
from coremltools.proto import FeatureTypes_pb2 as ft #type: ignore
16+
from coremltools import _MINIMUM_CUSTOM_LAYER_SPEC_VERSION as IOS_11_2_SPEC_VERSION # iOS 11.2
17+
from coremltools import _MINIMUM_CUSTOM_MODEL_SPEC_VERSION as IOS_12_SPEC_VERSION # iOS 12.0
18+
from coremltools import _MINIMUM_NDARRAY_SPEC_VERSION as IOS_13_SPEC_VERSION # iOS 13.0
1619

1720
from typing import Tuple
1821

@@ -34,6 +37,41 @@
3437

3538
DEBUG = False
3639

40+
class SupportedVersion():
41+
# Supported iOS Version
42+
# New OS Version must be added at the end to maintain backward version index
43+
supported_ios_version = ['11.2', '12', '13']
44+
IOS_13_VERSION = supported_ios_version.index('13')
45+
ND_ARRARY_SUPPORT = IOS_13_VERSION
46+
47+
@staticmethod
48+
def ios_support_check(target_ios):
49+
return target_ios in SupportedVersion.supported_ios_version
50+
51+
@staticmethod
52+
def is_nd_array_supported(target_ios):
53+
if not SupportedVersion.ios_support_check(target_ios):
54+
raise TypeError('{} not supported. Please provide one of target iOS: {}', target_ios, SupportedVersion.supported_ios_version)
55+
56+
target_ios_index = SupportedVersion.supported_ios_version.index(target_ios)
57+
return SupportedVersion.ND_ARRARY_SUPPORT <= target_ios_index
58+
59+
@staticmethod
60+
def get_supported_ios():
61+
return SupportedVersion.supported_ios_version
62+
63+
@staticmethod
64+
def get_specification_version(target_ios):
65+
if not SupportedVersion.ios_support_check(target_ios):
66+
raise TypeError('{} not supported. Please provide one of target iOS: {}', target_ios, SupportedVersion.supported_ios_version)
67+
68+
if target_ios == '11.2':
69+
return IOS_11_2_SPEC_VERSION
70+
elif target_ios == '12':
71+
return IOS_12_SPEC_VERSION
72+
else:
73+
return IOS_13_SPEC_VERSION
74+
3775
'''
3876
inputs: list of tuples.
3977
[Tuple]: [(name, type, shape)]
@@ -177,7 +215,7 @@ def _check_unsupported_ops(nodes, disable_coreml_rank5_mapping=False): # type: (
177215

178216
coreml_3_rerun_message = ''
179217
if not disable_coreml_rank5_mapping:
180-
coreml_3_rerun_message = '\nPlease try converting again with disable_coreml_rank5_mapping=True' \
218+
coreml_3_rerun_message = '\nPlease try converting again with target_ios=13' \
181219
' and coremltools 3.0 latest beta'
182220
if len(unsupported_op_types) > 0:
183221
raise NotImplementedError("Unsupported ONNX ops of type: %s %s" % (
@@ -343,7 +381,7 @@ def convert(model, # type: Union[onnx.ModelProto, Text]
343381
add_custom_layers = False, # type: bool
344382
custom_conversion_functions = {}, #type: Dict[Text, Any]
345383
onnx_coreml_input_shape_map = {}, # type: Dict[Text, List[int,...]]
346-
disable_coreml_rank5_mapping = False):
384+
target_ios = '12'):
347385
# type: (...) -> MLModel
348386
"""
349387
Convert ONNX model to CoreML.
@@ -384,13 +422,19 @@ def convert(model, # type: Union[onnx.ModelProto, Text]
384422
how the shape of the input is mapped to CoreML. Convention used for CoreML shapes is
385423
0: Sequence, 1: Batch, 2: channel, 3: height, 4: width.
386424
For example, an input of rank 2 could be mapped as [3,4] (i.e. H,W) or [1,2] (i.e. B,C) etc.
387-
This is ignored if "disable_coreml_rank5_mapping" is set to True.
388-
disable_coreml_rank5_mapping: bool
389-
If True, then it disables the "RANK5_ARRAY_MAPPING" or enables the "EXACT_ARRAY_MAPPING"
390-
option in CoreML (https://github.com/apple/coremltools/blob/655b3be5cc0d42c3c4fa49f0f0e4a93a26b3e492/mlmodel/format/NeuralNetwork.proto#L67)
391-
Thus, no longer, onnx tensors are forced to map to rank 5 CoreML tensors.
392-
With this flag on, a rank r ONNX tensor, (1<=r<=5), will map to a rank r tensor in CoreML as well.
393-
This flag must be on to utilize any of the new layers added in CoreML 3 (i.e. specification version 4, iOS13)
425+
This is ignored if "target_ios" is set to 13.
426+
target_ios: str
427+
Target Deployment iOS Version (default: '12')
428+
Supported iOS version options: '11.2', '12', '13'
429+
CoreML model produced by the converter will be compatible with the iOS version specified in this argument.
430+
e.g. if target_ios = '12', the converter would only utilize CoreML features released till iOS12 (equivalently macOS 10.14, watchOS 5 etc).
431+
432+
iOS 11.2 (CoreML 0.8) does not support resize_bilinear, crop_resize layers
433+
- (Supported features: https://github.com/apple/coremltools/releases/tag/v0.8)
434+
iOS 12 (CoreML 2.0)
435+
- (Supported features: https://github.com/apple/coremltools/releases/tag/v2.0)
436+
iSO 13 (CoreML 3.0)
437+
- (Supported features: https://github.com/apple/coremltools/releases/tag/3.0-beta6)
394438
395439
Returns
396440
-------
@@ -405,13 +449,20 @@ def convert(model, # type: Union[onnx.ModelProto, Text]
405449
"Model must be file path to .onnx file or onnx loaded model"
406450
)
407451

452+
if not SupportedVersion.ios_support_check(target_ios):
453+
raise TypeError('{} not supported. Please provide one of target iOS: {}', target_ios, SupportedVersion.get_supported_ios())
454+
455+
408456
global USE_SHAPE_MAPPING
457+
disable_coreml_rank5_mapping = False
458+
if SupportedVersion.is_nd_array_supported(target_ios):
459+
disable_coreml_rank5_mapping = True
460+
409461
if disable_coreml_rank5_mapping:
410462
USE_SHAPE_MAPPING = False
411463
else:
412464
USE_SHAPE_MAPPING = True
413465

414-
415466
'''
416467
First, apply a few optimizations to the ONNX graph,
417468
in preparation for conversion to CoreML.
@@ -495,6 +546,9 @@ def __call__(self, graph):
495546

496547
builder = NeuralNetworkBuilder(input_features, output_features, mode=mode, disable_rank5_shape_mapping=disable_coreml_rank5_mapping)
497548

549+
# TODO: To be removed once, auto-downgrading of spec version is enabled
550+
builder.spec.specificationVersion = SupportedVersion.get_specification_version(target_ios)
551+
498552
'''
499553
Set CoreML input,output types (float, double, int) same as onnx types, if supported
500554
'''
@@ -559,9 +613,7 @@ def __call__(self, graph):
559613
ErrorHandling is a generic class, useful to store a variety of parameters during the conversion process
560614
'''
561615
err = ErrorHandling(add_custom_layers,
562-
custom_conversion_functions,
563-
disable_coreml_rank5_mapping=disable_coreml_rank5_mapping)
564-
616+
custom_conversion_functions)
565617

566618
for i, node in enumerate(graph.nodes):
567619
print("%d/%d: Converting Node Type %s" %(i+1, len(graph.nodes), node.op_type))
@@ -658,6 +710,13 @@ def _add_informative_description(feature, raise_error=True):
658710
if len(graph.optional_inputs) > 0 or len(graph.optional_outputs):
659711
builder.add_optionals(graph.optional_inputs, graph.optional_outputs)
660712

713+
# Check for specification version and target ios compatibility
714+
if target_ios == '11.2' and builder.spec.WhichOneof('Type') == 'neuralNetwork':
715+
nn_spec = builder.spec.neuralNetwork
716+
for layer in nn_spec.layers:
717+
if layer.WhichOneof('layer') == 'resizeBilinear' or layer.WhichOneof('layer') == 'cropResize':
718+
raise TypeError('{} not supported with target iOS 11.2 please provide higher target iOS'.format(layer.WhichOneof('layer')))
719+
661720
print("Translation to CoreML spec completed. Now compiling the CoreML model.")
662721
try:
663722
if DEBUG:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55

6-
VERSION = '1.0b2'
6+
VERSION = '1.0b3'
77

88
here = path.abspath(path.dirname(__file__))
99

0 commit comments

Comments
 (0)