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.

graph.py 14 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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 collections
  6. import copy
  7. import logging
  8. import six
  9. import numpy as np
  10. from os.path import join as pathjoin
  11. from onnx import (
  12. helper,
  13. numpy_helper,
  14. OperatorSetIdProto,
  15. AttributeProto,
  16. TensorProto,
  17. onnx_pb,
  18. )
  19. from hetu.onnx import util
  20. from hetu.onnx.util import FindOpset
  21. from hetu.onnx import constants
  22. class Node(object):
  23. def __init__(self, node, graph=None):
  24. self._op = node
  25. self._graph = graph
  26. self._inputs = list(node.input)
  27. self._outputs = list(node.output)
  28. self._attrs = {}
  29. if graph is not None:
  30. graph.set_node_by_name(self)
  31. for a in node.attribute:
  32. self._attrs[a.name] = a
  33. @property
  34. def input_tensor_names(self):
  35. return self._inputs
  36. @input_tensor_names.setter
  37. def input_tensor_names(self, val):
  38. self._inputs = copy.deepcopy(val)
  39. @property
  40. def output_tensor_names(self):
  41. return copy.deepcopy(self._outputs)
  42. @property
  43. def name(self):
  44. return self._op.name
  45. @property
  46. def op_type(self):
  47. return self._op.op_type
  48. @op_type.setter
  49. def op_type(self, val):
  50. self._op.op_type = val
  51. @property
  52. def is_graph_input(self):
  53. return self.op_type in ['defined_in']
  54. @property
  55. def is_graph_output(self):
  56. return self.op_type in ['defined_out']
  57. @property
  58. def input_nodes(self):
  59. return [self._graph.get_node_by_outputname(n) for n in self._inputs]
  60. @property
  61. def op(self):
  62. return self._op
  63. def set_attr(self, name, val):
  64. self._attrs[name] = helper.make_attribute(name, val,)
  65. def get_attr(self, name, default=None):
  66. return self._attrs.get(name, default)
  67. def get_attr_value(self, name, default=None):
  68. attr = self.get_attr(name)
  69. if attr:
  70. attr_val = helper.get_attribute_value(attr)
  71. if isinstance(attr_val, bytes):
  72. attr_val = attr_val.decode('utf-8')
  73. return attr_val
  74. return default
  75. @property
  76. def is_const(self):
  77. return self.op_type in ['Const'] or \
  78. (self.op_type in ['PlaceholderOp']
  79. and self._attrs.get('value') is not None)
  80. def get_tensor_value(self, as_list=True):
  81. assert self.is_const, "Failed: Node {} must be Const".format(self.name)
  82. t = self.get_attr('value')
  83. t = numpy_helper.to_array(helper.get_attribute_value(t))
  84. if as_list:
  85. t = t.tolist()
  86. return t
  87. def onnx_attrs(self):
  88. schema = util.get_schema(self.op_type, self._graph._opset)
  89. onnx_attrs = {}
  90. for name, attr in self._attrs.items():
  91. if name == 'value':
  92. onnx_attrs[name] = self._attrs['value']
  93. elif schema is None or schema.has_attribute(name):
  94. onnx_attrs[name] = attr
  95. return onnx_attrs
  96. def update_node_proto(self):
  97. nodes = list(self._op.input)
  98. for node in nodes:
  99. self._op.input.remove(node)
  100. self._op.input.extend(self._inputs)
  101. nodes = list(self._op.output)
  102. for node in nodes:
  103. self._op.output.remove(node)
  104. self._op.output.extend(self._outputs)
  105. del self._op.attribute[:]
  106. attr = list(self.onnx_attrs().values())
  107. if attr:
  108. self._op.attribute.extend(attr)
  109. # add for X2hetu
  110. @property
  111. def domain(self):
  112. """Return Op type."""
  113. return self._op.domain
  114. class Graph(object):
  115. def __init__(self, nodes, shapes=None, dtypes=None, opset=None, output_names=None):
  116. self._nodes = []
  117. self._nodename_to_node = {}
  118. self._outputname_to_nodename = {}
  119. self._dtypes = dtypes
  120. self._shapes = shapes
  121. self._opset = FindOpset(opset)
  122. self._outputs = output_names if output_names is not None else []
  123. ops = [Node(node, self) for node in nodes]
  124. self.update_graph_nodes(ops)
  125. def update_graph_nodes(self, ops):
  126. remained_dtypes = {}
  127. remained_shapes = {}
  128. self._outputname_to_nodename = {}
  129. for op in ops:
  130. for op_output in op.output_tensor_names:
  131. if op_output in self._dtypes:
  132. remained_dtypes[op_output] = self._dtypes[op_output]
  133. if op_output in self._shapes:
  134. remained_shapes[op_output] = self._shapes[op_output]
  135. self._outputname_to_nodename[op_output] = op.name
  136. self._nodes = ops
  137. self._nodename_to_node = {op.name: op for op in ops}
  138. self._dtypes = remained_dtypes
  139. self._shapes = remained_shapes
  140. def set_node_by_name(self, node):
  141. self._nodename_to_node[node.name] = node
  142. for outputname in node._outputs:
  143. self._outputname_to_nodename[outputname] = node.name
  144. def get_node_by_outputname(self, outputname):
  145. nodename = self._outputname_to_nodename.get(outputname)
  146. if nodename:
  147. return self._nodename_to_node.get(nodename)
  148. return None
  149. def get_shape(self, name):
  150. return self._shapes.get(name)
  151. def set_shape(self, name, val):
  152. self._shapes[name] = val
  153. def get_dtype(self, name):
  154. return self._dtypes.get(name)
  155. def set_dtype(self, name, val):
  156. self._dtypes[name] = val
  157. def update_node_shape_dtype(self, node):
  158. if node.is_const or node.is_graph_input:
  159. return
  160. initializers = []
  161. for i, inp in enumerate(node.input_nodes):
  162. if inp.is_const:
  163. tensor = util.TensorProtoFromNumpy(inp.get_tensor_value(as_list=False),
  164. name=inp.output_tensor_names[0])
  165. initializers.append(tensor)
  166. input_shapes = [self.get_shape(i) for i in node.input_tensor_names]
  167. input_dtypes = [self.get_dtype(i) for i in node.input_tensor_names]
  168. shapes, dtypes = util.InferOnnxShapeDtype(
  169. node, self._opset, input_shapes, input_dtypes, initializers)
  170. if not shapes or not dtypes:
  171. return
  172. for output, shape, dtype in zip(node.output_tensor_names, shapes, dtypes):
  173. self.set_dtype(output, dtype)
  174. self.set_shape(output, shape)
  175. def make_const(self, name, np_val, raw=False, is_0D_tensor=False):
  176. shape = [] if is_0D_tensor else np_val.shape
  177. if raw:
  178. onnx_tensor = None
  179. # fixme: Not yet implemented
  180. pass
  181. else:
  182. onnx_tensor = helper.make_tensor(
  183. name,
  184. util.numpy_to_onnx_dtype(np_val.dtype),
  185. shape,
  186. np_val,
  187. raw=False,
  188. )
  189. dtype = onnx_tensor.data_type
  190. node = self.make_node(
  191. "Const",
  192. [],
  193. outputs=[name],
  194. name=name,
  195. attr={"value": onnx_tensor},
  196. dtypes=[dtype],
  197. )
  198. self.set_shape(name, shape)
  199. self.set_dtype(name, dtype)
  200. return node
  201. def make_node(self, op_type, inputs, attr=None, output_count=1, outputs=None,
  202. name=None, shapes=None, dtypes=None):
  203. if attr is None:
  204. attr = {}
  205. if shapes is None:
  206. shapes = []
  207. if dtypes is None:
  208. dtypes = []
  209. if name is None:
  210. name = util.make_name(op_type)
  211. if outputs is None:
  212. outputs = [name+':'+str(i) for i in range(output_count)]
  213. output_count = len(outputs)
  214. onnx_node = helper.make_node(
  215. op_type, inputs, outputs, name=name, **attr)
  216. node = Node(onnx_node, self)
  217. if shapes:
  218. assert len(
  219. shapes) == output_count, "Failed: output shapes count not equal to output count when make_node"
  220. for i in range(output_count):
  221. self.set_shape(node._outputs[i], shapes[i])
  222. if dtypes:
  223. assert len(
  224. dtypes) == output_count, "Failed: output dtypes count not equal to output count when make_node"
  225. for i in range(output_count):
  226. self.set_dtype(node._outputs[i], dtypes[i])
  227. if not shapes or not dtypes:
  228. self.update_node_shape_dtype(node)
  229. self._nodes.append(node)
  230. return node
  231. def insert_new_node_on_input(self, node, op_type, input_name, name=None, **kwargs):
  232. if name is None:
  233. name = util.make_name(node.name)
  234. new_output = util.make_name(name)
  235. if not isinstance(input_name, list):
  236. input_name = [input_name]
  237. new_node = self.make_node(
  238. op_type,
  239. input_name,
  240. attr=kwargs,
  241. outputs=[new_output],
  242. name=name,
  243. )
  244. for i, n in enumerate(node.input_tensor_names):
  245. if n == input_name[0]:
  246. node.input_tensor_names[i] = new_output
  247. break
  248. return new_node
  249. def insert_new_node_on_output(self, op_type, output_name, name, **kwargs):
  250. new_output = util.make_name(name)
  251. new_node = self.make_node(
  252. op_type,
  253. [output_name],
  254. attr=kwargs,
  255. outputs=[new_output],
  256. name=name,
  257. )
  258. for node in self._nodes:
  259. if node == new_node:
  260. continue
  261. for i, input_name in enumerate(node.input_tensor_names):
  262. if input_name == output_name:
  263. node.input_tensor_names[i] = new_output
  264. return new_node
  265. def replace_input(self, node, old_input, new_input, input_index=None):
  266. if input_index is None:
  267. for i, input_name in enumerate(node._inputs):
  268. if input_name == old_input:
  269. node._inputs[i] = new_input
  270. elif node._inputs[input_index] == old_input:
  271. node._inputs[input_index] = new_input
  272. else:
  273. raise RuntimeError("Failed:Unable to replace input %r into %r for node %r." % (
  274. old_input, new_input, node.name))
  275. def topology_sort(self, ops):
  276. def _push_stack(stack, node, in_stack):
  277. stack.append(node)
  278. if node in in_stack:
  279. raise ValueError("Graph has cycles.")
  280. in_stack[node] = True
  281. def _get_unvisited_child(g, node, not_visited):
  282. for child in g[node]:
  283. if child in not_visited:
  284. return child
  285. return -1
  286. ops.sort(key=lambda op: op.name)
  287. n = len(ops)
  288. g = [[] for _ in range(n)]
  289. op_name_to_index = {}
  290. for i, op in enumerate(ops):
  291. op_name_to_index[op.name] = i
  292. for i, op in enumerate(ops):
  293. all_input = list(op.input_tensor_names)
  294. for inp in sorted(all_input):
  295. j = self.get_node_by_outputname(inp)
  296. g[op_name_to_index[j.name]].append(i)
  297. label = [-1 for _ in range(n)]
  298. stack = []
  299. in_stack = dict()
  300. not_visited = dict.fromkeys(range(n))
  301. label_counter = n-1
  302. while not_visited:
  303. node = list(not_visited.keys())[0]
  304. _push_stack(stack, node, in_stack)
  305. while stack:
  306. node = _get_unvisited_child(g, stack[-1], not_visited)
  307. if node != -1:
  308. _push_stack(stack, node, in_stack)
  309. else:
  310. node = stack.pop()
  311. in_stack.pop(node)
  312. not_visited.pop(node)
  313. label[node] = label_counter
  314. label_counter -= 1
  315. ret = [x for _, x in sorted(zip(label, ops))]
  316. self.update_graph_nodes(ret)
  317. def make_model(self, graph_doc, onnx_filename, graph_name='hetu.python.onnx'):
  318. graph = self.make_graph(
  319. graph_doc, onnx_filename, graph_name=graph_name,
  320. )
  321. model_proto = helper.make_model(graph)
  322. return model_proto
  323. def make_graph(self, doc, onnx_filename, graph_name='hetu.python.onnx'):
  324. for node in self._nodes:
  325. node.update_node_proto()
  326. ops = []
  327. const_ops = []
  328. input_ops = []
  329. for op in self._nodes:
  330. if op.is_const:
  331. const_ops.append(op)
  332. continue
  333. if op.is_graph_input:
  334. input_ops.append(op)
  335. continue
  336. ops.append(op)
  337. initializers = []
  338. for op in const_ops:
  339. tensor_name = op.output_tensor_names[0]
  340. tensor = util.TensorProtoFromNumpy(
  341. op.get_tensor_value(as_list=False),
  342. tensor_name,
  343. export_path=onnx_filename,
  344. )
  345. initializers.append(tensor)
  346. # sorted inputs by input id. input_tensor_name like this: A:0,B:1
  347. # fixme:mybe outputs should be sort also
  348. input_ids = [op.output_tensor_names[0] for op in input_ops]
  349. input_ids = sorted(input_ids, key=lambda x: int(x.split('-')[-1]))
  350. if self._opset < 9:
  351. input_ids += [op.output_tensor_names[0] for op in const_ops]
  352. input_tensor_values = self.MakeOnnxGraphIO(input_ids)
  353. output_tensor_values = self.MakeOnnxGraphIO(self._outputs)
  354. graph = helper.make_graph(
  355. [op.op for op in ops],
  356. graph_name,
  357. input_tensor_values,
  358. output_tensor_values,
  359. initializer=initializers,
  360. doc_string=doc,
  361. )
  362. return graph
  363. def MakeOnnxGraphIO(self, ids):
  364. tensor_value_infos = []
  365. for name in ids:
  366. dtype = self.get_dtype(name)
  367. shape = self.get_shape(name)
  368. v = util.MakeOnnxInputsOutputs(name, dtype, shape)
  369. tensor_value_infos.append(v)
  370. return tensor_value_infos
  371. def copy_shape(self, input_name, output_name):
  372. shape = self.get_shape(input_name)
  373. if shape is not None:
  374. self.set_shape(output_name, shape)