You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

util.py 9.1 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. from __future__ import division
  2. from __future__ import print_function
  3. from __future__ import unicode_literals
  4. from __future__ import absolute_import
  5. import numpy as np
  6. import os
  7. import onnx
  8. from onnx import onnx_pb, helper, defs, numpy_helper, TensorProto, OperatorSetIdProto, shape_inference
  9. from hetu.onnx import constants, util
  10. from collections import defaultdict, OrderedDict
  11. #
  12. # mapping dtypes from hetu to onnx
  13. #
  14. # fixme:bug,(int64) type is error
  15. # fixme:unused now
  16. HETU_TO_ONNX_DTYPES = {
  17. np.float: onnx_pb.TensorProto.FLOAT,
  18. }
  19. #
  20. # mapping dtypes from onnx to numpy
  21. #
  22. ONNX_TO_NUMPY_DTYPE = {
  23. onnx_pb.TensorProto.FLOAT: np.float32,
  24. onnx_pb.TensorProto.FLOAT16: np.float16,
  25. onnx_pb.TensorProto.DOUBLE: np.float64,
  26. onnx_pb.TensorProto.INT32: np.int32,
  27. onnx_pb.TensorProto.INT16: np.int16,
  28. onnx_pb.TensorProto.INT8: np.int8,
  29. onnx_pb.TensorProto.UINT8: np.uint8,
  30. onnx_pb.TensorProto.UINT16: np.uint16,
  31. onnx_pb.TensorProto.INT64: np.int64,
  32. onnx_pb.TensorProto.UINT64: np.uint64,
  33. onnx_pb.TensorProto.BOOL: np.bool,
  34. }
  35. def numpy_to_onnx_dtype(np_dtype):
  36. for onnx_dtype, numpy_dtype in ONNX_TO_NUMPY_DTYPE.items():
  37. if numpy_dtype == np_dtype:
  38. return onnx_dtype
  39. raise ValueError("unsupported dtype "+np_dtype+" for mapping")
  40. def onnx_to_numpy_dtype(onnx_dtype):
  41. return ONNX_TO_NUMPY_DTYPE[onnx_dtype]
  42. def map_hetu_dtype(dtype):
  43. if dtype:
  44. dtype = HETU_TO_ONNX_DTYPES[dtype]
  45. return dtype
  46. ONNX_UNKNOWN_DIMENSION = -1
  47. INSERT_NAME_ID = 1
  48. def make_name(name):
  49. global INSERT_NAME_ID
  50. INSERT_NAME_ID += 1
  51. return "{}_{}".format(name, INSERT_NAME_ID)
  52. def FindOpset(opset):
  53. if opset is None or opset == 0:
  54. opset = defs.onnx_opset_version()
  55. return opset
  56. def get_onnx_version():
  57. return onnx.__version__
  58. def MakeOnnxInputsOutputs(name, elem_type, shape, **kwargs):
  59. if elem_type is None:
  60. elem_type = onnx_pb.TensorProto.UNDEFINED
  61. return helper.make_tensor_value_info(
  62. name, elem_type, shape)
  63. def GenerateValidFilename(s):
  64. return "".join([c if c.isalpha() or c.isdigit() else "_" for c in s])
  65. def TensorProtoFromNumpy(
  66. arr: np.ndarray, name=None, external_data=False, export_path=None
  67. ):
  68. if name is None:
  69. name = make_name("tensor_")
  70. tp = numpy_helper.from_array(arr, name)
  71. # value with size < 1024 bytes will remain in .onnx file
  72. # (like what pytorch does)
  73. if (not external_data) or arr.nbytes < 1024:
  74. return tp
  75. assert tp.HasField("raw_data")
  76. tp.ClearField("raw_data")
  77. export_dir = os.path.dirname(export_path)
  78. filename = GenerateValidFilename(name)
  79. with open(os.path.join(export_dir, filename), "wb") as f:
  80. arr.tofile(f)
  81. tp.data_location = onnx_pb.TensorProto.EXTERNAL
  82. external_data = tp.external_data.add()
  83. external_data.key = "location"
  84. external_data.value = filename
  85. return tp
  86. class OnnxOpSchema(object):
  87. def __init__(self, name, domain, since_version, attributes):
  88. self._name = name
  89. self._domain = domain
  90. self._attributes = attributes
  91. self._since_version = since_version
  92. @property
  93. def attributes(self):
  94. return self._attributes
  95. @property
  96. def domain(self):
  97. return self._domain
  98. @property
  99. def name(self):
  100. return self._name
  101. @property
  102. def since_version(self):
  103. return self._since_version
  104. @staticmethod
  105. def FromOnnxSchema(onnx_schema):
  106. name = onnx_schema.name
  107. domain = onnx_schema.domain
  108. since_version = int(onnx_schema.since_version)
  109. attributes = onnx_schema.attributes
  110. return OnnxOpSchema(name, domain, since_version, attributes)
  111. def has_attribute(self, attr):
  112. return attr in self.attributes
  113. def _RegisterAllSchemasWithHistory():
  114. """Register all schemas with history"""
  115. onnx_schemas = defs.get_all_schemas_with_history()
  116. name_domain_version_schema_map = defaultdict(lambda: defaultdict(dict))
  117. for s in onnx_schemas:
  118. schema = OnnxOpSchema.FromOnnxSchema(s)
  119. name_domain_version_schema_map[schema.name][schema.domain][
  120. schema.since_version
  121. ] = schema
  122. ordered_map = defaultdict(lambda: defaultdict(OrderedDict))
  123. for name, domain_version_schema_map in name_domain_version_schema_map.items():
  124. for domain, version_schema_map in domain_version_schema_map.items():
  125. ordered_map[name][domain] = OrderedDict(
  126. sorted(version_schema_map.items(), key=lambda x: -x[0])
  127. )
  128. return ordered_map
  129. def _ParseDomainOpsetVersions(schemas):
  130. """ Get max opset version among all schemas within each domain. """
  131. domain_opset_versions = dict()
  132. for domain_version_schema_map in schemas.values():
  133. for domain, version_schema_map in domain_version_schema_map.items():
  134. # version_schema_map is sorted by since_version in descend order
  135. max_version = next(iter(version_schema_map))
  136. if domain not in domain_opset_versions:
  137. domain_opset_versions[domain] = int(max_version)
  138. else:
  139. domain_opset_versions[domain] = max(
  140. domain_opset_versions[domain], int(max_version)
  141. )
  142. return domain_opset_versions
  143. _schemas = _RegisterAllSchemasWithHistory()
  144. _domain_opset_versions = _ParseDomainOpsetVersions(_schemas)
  145. def get_schema(name, max_inclusive_opset_version, domain=None):
  146. """Get schema by name within specific version."""
  147. domain = domain or constants.ONNX_DOMAIN
  148. domain_version_schema_map = _schemas[name]
  149. version_schema_map = domain_version_schema_map[domain]
  150. for version, schema in version_schema_map.items():
  151. if version <= max_inclusive_opset_version:
  152. return schema
  153. return None
  154. def get_max_supported_opset_version(domain=None):
  155. """Get max supported opset version by current onnx package given a domain."""
  156. domain = domain or constants.ONNX_DOMAIN
  157. return _domain_opset_versions.get(domain, None)
  158. def InferOnnxShapeDtype(
  159. node, opset_version, input_shapes, input_dtypes, initializers=None
  160. ):
  161. """
  162. Infer shapes and dtypes for outputs of the node.
  163. Sometimes, shape inference needs the values of node's inputs, so initializers are used.
  164. """
  165. def BuildOnnxOp(node):
  166. """Build onnx op"""
  167. onnx_node = helper.make_node(
  168. node.op_type,
  169. node.input_tensor_names,
  170. node.output_tensor_names,
  171. name=node.name,
  172. )
  173. # # deal with attributes
  174. # attr = []
  175. # attr_graphs = node.get_body_graphs()
  176. # if attr_graphs:
  177. # for attr_name, sub_graph in attr_graphs.items():
  178. # copied_sub_graph = copy.deepcopy(sub_graph)
  179. # graph_proto = copied_sub_graph.MakeGraph(
  180. # "graph for " + node.name + " " + attr_name
  181. # )
  182. # attr.append(helper.make_attribute(attr_name, graph_proto))
  183. # attr.extend(node.attrs_onnx.values())
  184. # if attr:
  185. # onnx_node.attribute.extend(attr)
  186. return onnx_node
  187. inputs = []
  188. outputs = []
  189. for inp, shape, dtype in zip(node.input_tensor_names, input_shapes, input_dtypes):
  190. inputs.append(util.MakeOnnxInputsOutputs(inp, dtype, shape))
  191. for output in node.output_tensor_names:
  192. outputs.append(util.MakeOnnxInputsOutputs(
  193. output, TensorProto.UNDEFINED, None))
  194. graph_proto = helper.make_graph(
  195. [BuildOnnxOp(node)], "infer-graph", inputs, outputs, initializer=initializers
  196. )
  197. imp = OperatorSetIdProto()
  198. imp.version = opset_version
  199. model_proto = helper.make_model(graph_proto, opset_imports=[imp])
  200. inferred_model = None
  201. try:
  202. inferred_model = shape_inference.infer_shapes(model_proto)
  203. except Exception:
  204. print('error')
  205. return None, None
  206. shapes = {}
  207. dtypes = {}
  208. for output in inferred_model.graph.output:
  209. tensor_type = output.type.tensor_type
  210. if tensor_type.HasField("elem_type"):
  211. dtypes[output.name] = tensor_type.elem_type
  212. else:
  213. dtypes[output.name] = TensorProto.UNDEFINED
  214. # 0 in shapes of onnx means unknown which is -1 in our convertor
  215. # fixme:how to do if the dim is -1 originally
  216. if tensor_type.HasField("shape"):
  217. shapes[output.name] = [
  218. dim.dim_value if dim.dim_value != 0 else util.ONNX_UNKNOWN_DIMENSION
  219. for dim in tensor_type.shape.dim
  220. ]
  221. else:
  222. shapes[output.name] = None
  223. output_shapes = []
  224. output_dtypes = []
  225. for output in node.output_tensor_names:
  226. if output in shapes:
  227. output_shapes.append(shapes[output])
  228. else:
  229. output_shapes.append(None)
  230. if output in dtypes:
  231. output_dtypes.append(dtypes[output])
  232. else:
  233. output_dtypes.append(TensorProto.UNDEFINED)
  234. return output_shapes, output_dtypes