Skip to content

Commit 998cb89

Browse files
alexeyrAWS Neo
authored andcommitted
[FRONTEND][TENSORFLOW] Support Unstack and Split (apache#2105)
1 parent fffd54e commit 998cb89

2 files changed

Lines changed: 136 additions & 63 deletions

File tree

nnvm/python/nnvm/frontend/tensorflow.py

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def __call__(self, inputs, attrs, *args):
3636
self._ignores.append('_node_name')
3737
self._ignores.append('is_training')
3838
self._ignores.append('_target_layout')
39+
self._ignores.append('_input_0d_mismatch')
3940
# Retain the names
4041
try:
4142
attrs['name'] = attrs['_node_name']
@@ -319,8 +320,7 @@ def _impl(inputs, attr, params):
319320
dim_input = inputs.pop(1)
320321
axis = params[dim_input.list_output_names()[0]]
321322
params.pop(dim_input.list_output_names()[0])
322-
return AttrCvt(op_name="expand_dims", ignores=['Tdim'],
323-
extras={'axis': axis.asnumpy()[0]})(inputs, attr)
323+
return _expand_dims_0d_aware(inputs[0], attr, axis=axis.asnumpy()[0])
324324
return _impl
325325

326326
def _resize_bilinear():
@@ -383,7 +383,7 @@ def _impl(inputs, attr, params):
383383
def _pack():
384384
def _impl(inputs, attr, params):
385385
axis = int(attr["axis"])
386-
inputs_reshaped = [_sym.expand_dims(i, axis=axis, num_newaxis=1) for i in inputs]
386+
inputs_reshaped = [_expand_dims_0d_aware(i, attr, axis=axis, num_newaxis=1) for i in inputs]
387387
return _sym.concatenate(*inputs_reshaped, axis=axis, name=attr["_node_name"])
388388

389389
return _impl
@@ -804,15 +804,64 @@ def _impl(inputs, attr, params):
804804
)(inputs, attr)
805805
return _impl
806806

807-
def _split():
807+
def _split(has_size_vector):
808+
# TF documentation https://www.tensorflow.org/api_docs/python/tf/split
808809
def _impl(inputs, attr, params):
809-
axis = params.pop(inputs[0].list_output_names()[0])
810-
return AttrCvt(
811-
op_name="split", ignores=['T'],
812-
transforms={'num_split': 'indices_or_sections'},
813-
extras={'axis': axis.asnumpy()[0]})(inputs[1], attr)
810+
try:
811+
# order and number of inputs are different:
812+
# if has_size_vector:
813+
# https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/split-v
814+
# else:
815+
# https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/split
816+
817+
# in addition, `axis` and `num_or_size_splits` can be tensors in TensorFlow,
818+
# we can only support constants
819+
if has_size_vector:
820+
input_node_index = 0
821+
input_axis_index = 2
822+
size_splits_input_name = inputs[1].list_output_names()[0]
823+
size_splits = params[size_splits_input_name].asnumpy()
824+
section_beginnings = np.cumsum(size_splits)[:-1]
825+
indices_or_sections = tuple(section_beginnings)
826+
else:
827+
input_node_index = 1
828+
input_axis_index = 0
829+
indices_or_sections = attr['num_split']
830+
input_node = inputs[input_node_index]
831+
axis_input_name = inputs[input_axis_index].list_output_names()[0]
832+
axis_input_value = params[axis_input_name].asnumpy()[0]
833+
except (IndexError, KeyError):
834+
raise TypeError( \
835+
"Unsupported argument for split: `axis` and `num_or_size_splits` " \
836+
"should be constants")
837+
return _sym.split(input_node,
838+
indices_or_sections=indices_or_sections,
839+
axis=axis_input_value)
814840
return _impl
815841

842+
def _unpack():
843+
def _impl(inputs, attr, params):
844+
input_node = inputs[0]
845+
axis = attr['axis']
846+
input_shape = attr['_input_shapes'][input_node][0]
847+
axis_length = input_shape[axis]
848+
if axis_length < 0:
849+
raise TypeError("Unstack with unknown axis length")
850+
splitted = _sym.split(input_node,
851+
indices_or_sections=axis_length,
852+
axis=axis,
853+
name=attr.get('_node_name', 'unstack'))
854+
855+
return _sym.Group([_sym.squeeze(split_item, axis=axis) for split_item in splitted])
856+
return _impl
857+
858+
def _expand_dims_0d_aware(data, attr, axis, num_newaxis=1):
859+
if data in attr['_input_0d_mismatch']:
860+
return data if num_newaxis == 1 else \
861+
_sym.expand_dims(data, axis=axis, num_newaxis=num_newaxis-1)
862+
863+
return _sym.expand_dims(data, axis=axis, num_newaxis=num_newaxis)
864+
816865
# compatible operators that do NOT require any conversion.
817866
_identity_list = []
818867

@@ -880,7 +929,9 @@ def _impl(inputs, attr, params):
880929
'GreaterEqual' : _broadcast('greater_equal'),
881930
'Equal' : _broadcast('equal'),
882931
'NotEqual' : _broadcast('not_equal'),
883-
'Split' : _split(),
932+
'Split' : _split(False),
933+
'SplitV' : _split(True),
934+
'Unpack' : _unpack(),
884935
}
885936

886937
# _convert_map_rnn defines maps of rnn operator name to
@@ -1076,6 +1127,7 @@ def __init__(self):
10761127
self._output_shapes = {}
10771128
self._num_param = 0
10781129
self._num_rnn_layer = False
1130+
self._outputs_are_0d = {}
10791131

10801132
def from_tensorflow(self, graph, layout="NHWC", shape=None, outputs=None):
10811133
"""Construct nnvm nodes from tensorflow graph definition - GraphDef.
@@ -1131,6 +1183,7 @@ def from_tensorflow(self, graph, layout="NHWC", shape=None, outputs=None):
11311183
# Operator name 'Const' is treated as a parameter to build NNVM params dict.
11321184

11331185
input_shapes = {}
1186+
input_0d_mismatch = set()
11341187
attr = self._parse_attr(node.attr)
11351188

11361189
#Variable converted to Const will not have only value attr
@@ -1150,6 +1203,9 @@ def from_tensorflow(self, graph, layout="NHWC", shape=None, outputs=None):
11501203
else:
11511204
raise NotImplementedError( \
11521205
"Please freeze the graph with add_shapes=True")
1206+
self._outputs_are_0d[node.name] = [ \
1207+
not shape if isinstance(shape, list) else False \
1208+
for shape in self._output_shapes[node.name]]
11531209

11541210
if node.op == "Placeholder":
11551211
self._nodes[node.name] = _sym.Variable(name=node.name,
@@ -1179,24 +1235,32 @@ def from_tensorflow(self, graph, layout="NHWC", shape=None, outputs=None):
11791235
# Fill shapes for all inputs in a list
11801236
inputs = []
11811237
for i in node.input:
1182-
#ToDo: Some of the tensorflow operators internaly maintain
1183-
#execution layers and its output name will the layer number along with
1184-
#graph node name.eg: Node name:- 'Model/RNN/cell_0/RnnCell', but the
1185-
#output name will be 'Model/RNN/cell_0/RnnCell:0'. In this case,
1186-
#the digit has to be ignored.
1238+
# Some TensorFlow operators internally maintain execution layers
1239+
# and their output name includes the layer number along with
1240+
# graph node name. E.g. the node name is 'Model/RNN/cell_0/RnnCell', but the
1241+
# output tensor name is 'Model/RNN/cell_0/RnnCell:0'. In this case,
1242+
# the number has to be ignored for single-output nodes.
1243+
# On the other hand, for multi-output nodes the number is the output index,
1244+
# and the lack of the number implies 0.
11871245
tensor_name = i.split(':')
11881246
node_name = tensor_name[0]
11891247
if node_name in self._nodes:
11901248
in_sym = self._nodes[node_name]
11911249
if len(in_sym.list_output_names()) > 1:
11921250
tensor_slot = int(tensor_name[1]) if len(tensor_name) > 1 else 0
11931251
in_sym = in_sym[tensor_slot]
1194-
input_shape = (self._output_shapes[node_name])[tensor_slot]
1252+
input_shape = self._output_shapes[node_name][tensor_slot]
11951253
else:
1254+
tensor_slot = 0
11961255
input_shape = self._output_shapes[node_name][0]
11971256
inputs.append(in_sym)
11981257
input_shapes[in_sym] = [input_shape]
1258+
# This means the node is 1d in NNVM and 0d in TF.
1259+
# See `_expand_dims_0d_aware`.
1260+
if self._outputs_are_0d[node_name][tensor_slot] and input_shape:
1261+
input_0d_mismatch.add(in_sym)
11991262
attr['_input_shapes'] = input_shapes
1263+
attr['_input_0d_mismatch'] = input_0d_mismatch
12001264

12011265
inputs = self._fix_extranodes(node.op, attr, inputs)
12021266
op = self._convert_operator(node.op, inputs, attr, graph)
@@ -1224,15 +1288,21 @@ def from_tensorflow(self, graph, layout="NHWC", shape=None, outputs=None):
12241288
if outputs is None:
12251289
out.append(final_op)
12261290
else:
1227-
out = [self._nodes[out_name] for out_name in outputs]
1291+
for out_name in outputs:
1292+
if ":" in out_name:
1293+
out_name, out_num = out_name.split(":")
1294+
out_num = int(out_num)
1295+
out.append(self._nodes[out_name][out_num])
1296+
else:
1297+
out.append(self._nodes[out_name])
12281298

12291299
#Add the RNN outputs also with 'head' nodes of the nnvm graph
12301300
if self._num_rnn_layer:
12311301
out_rnn = _sym.concatenate(*self._out_rnn, axis=0)
12321302
out.append(out_rnn)
12331303

12341304
if isinstance(out, list):
1235-
out = _sym.Group(out)
1305+
out = _sym.Group(out) if len(out) > 1 else out[0]
12361306

12371307
return out, self._params
12381308

nnvm/tests/python/frontend/tensorflow/test_forward.py

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ def compare_tf_with_tvm(in_data, in_name, out_name, init_global_variables=False,
124124
if no_gpu and device == 'cuda':
125125
continue
126126

127-
tvm_output = run_tvm_graph(final_graph_def, in_data, in_node, target=device)
127+
tvm_output = run_tvm_graph(final_graph_def, in_data, in_node,
128+
num_output=len(out_node), target=device, out_names=out_name)
128129
# since the names from tensorflow and nnvm runs are not exactly same,
129130
# first len(tf_output) will be compared
130131
for i in range(len(tf_output)):
@@ -506,14 +507,24 @@ def test_forward_gather():
506507
# Split
507508
# -----
508509

509-
def _test_split(in_shape, axis, num_split, dtype):
510+
def _test_split(in_shape, axis, num_or_size_splits, dtype):
511+
np_data = np.random.uniform(-5, 5, size=in_shape).astype(dtype)
512+
510513
""" One iteration of a Split """
514+
tf.reset_default_graph()
515+
in_data = tf.placeholder(dtype, in_shape, name="in_data")
516+
num_split = len(num_or_size_splits) if isinstance(num_or_size_splits, list) else num_or_size_splits
517+
tf.split(in_data, num_or_size_splits, axis=axis)
511518

512-
with tf.Graph().as_default():
513-
in_data = tf.placeholder(dtype, in_shape, name="in_data")
514-
tf.split(in_data, num_split, axis)
515-
np_data = np.random.uniform(size=in_shape).astype(dtype)
516-
compare_tf_with_tvm(np_data, 'in_data:0', 'split:0')
519+
compare_tf_with_tvm([np_data], ['in_data:0'], [f'split:{n}' for n in range(num_split)])
520+
521+
# and now test together with concat
522+
tf.reset_default_graph()
523+
in_data = tf.placeholder(dtype, in_shape, name="in_data")
524+
splitted = tf.split(in_data, num_or_size_splits, axis=axis)
525+
tf.concat(splitted, axis)
526+
527+
compare_tf_with_tvm([np_data], 'in_data:0', 'concat:0')
517528

518529
def test_forward_split():
519530
'''test split layer'''
@@ -523,11 +534,11 @@ def test_forward_split():
523534
_test_split((6,), 0, 3, 'float32')
524535
# rank 2
525536
_test_split((6, 2), 0, 3, 'float32')
526-
_test_split((2, 6), 1, 3, 'float32')
537+
_test_split((2, 6), 1, 6, 'float32')
527538
# rank 3
528-
_test_split((6, 2, 4), 0, 3, 'float32')
539+
_test_split((6, 2, 4), 0, 2, 'int32')
529540
_test_split((2, 6, 4), 1, 3, 'float32')
530-
_test_split((2, 4, 6), 2, 3, 'float32')
541+
_test_split((2, 4, 6), 2, 1, 'float32')
531542
# rank 4
532543
_test_split((6, 1, 3, 5), 0, 3, 'float32')
533544
_test_split((1, 6, 3, 5), 1, 3, 'float32')
@@ -538,45 +549,37 @@ def test_forward_split():
538549
_test_split((1, 6, 3, 5), -3, 3, 'float32')
539550
_test_split((1, 3, 6, 5), -2, 3, 'float32')
540551
_test_split((1, 3, 5, 6), -1, 3, 'float32')
552+
# size_splits list
553+
_test_split((6,), 0, [1, 2, 3], 'int32')
554+
_test_split((3, 6, 4), -2, [1, 4, 1], 'float32')
541555

542556

543557
#######################################################################
544-
# Split followed by concat
545-
# ------------------------
558+
# Unstack
559+
# -------
546560

547-
def _test_split_concat(in_shape, axis, num_split, dtype):
548-
""" One iteration of a split_concat pair"""
561+
def _test_unstack(ip_shape, axis, dtype):
562+
np_data = np.random.uniform(-5, 5, size=ip_shape).astype(dtype)
549563

550-
with tf.Graph().as_default():
551-
in_data = tf.placeholder(dtype, in_shape, name="in_data")
552-
splitted = tf.split(in_data, num_split, axis)
553-
tf.concat(splitted, axis)
554-
np_data = np.random.uniform(size=in_shape).astype(dtype)
555-
compare_tf_with_tvm(np_data, 'in_data:0', 'concat:0')
556-
557-
def test_forward_split_concat():
558-
'''test split followed by concat layers'''
559-
# rank 1
560-
_test_split_concat((3,), 0, 1, 'float32')
561-
_test_split_concat((3,), 0, 3, 'float32')
562-
_test_split_concat((6,), 0, 3, 'float32')
563-
# rank 2
564-
_test_split_concat((6, 2), 0, 3, 'float32')
565-
_test_split_concat((2, 6), 1, 3, 'float32')
566-
# rank 3
567-
_test_split_concat((6, 2, 4), 0, 3, 'float32')
568-
_test_split_concat((2, 6, 4), 1, 3, 'float32')
569-
_test_split_concat((2, 4, 6), 2, 3, 'float32')
570-
# rank 4
571-
_test_split((6, 1, 3, 5), 0, 3, 'float32')
572-
_test_split((1, 6, 3, 5), 1, 3, 'float32')
573-
_test_split((1, 3, 6, 5), 2, 3, 'float32')
574-
_test_split((1, 3, 5, 6), 3, 3, 'float32')
575-
# split along negative axis
576-
_test_split((6, 1, 3, 5), -4, 3, 'float32')
577-
_test_split((1, 6, 3, 5), -3, 3, 'float32')
578-
_test_split((1, 3, 6, 5), -2, 3, 'float32')
579-
_test_split((1, 3, 5, 6), -1, 3, 'float32')
564+
tf.reset_default_graph()
565+
in_data = tf.placeholder(dtype, ip_shape, name="in_data")
566+
tf.unstack(in_data, axis=axis)
567+
568+
compare_tf_with_tvm([np_data], ['in_data:0'], [f'unstack:{n}' for n in range(ip_shape[axis])])
569+
570+
tf.reset_default_graph()
571+
in_data = tf.placeholder(dtype, ip_shape, name="in_data")
572+
tf.stack(tf.unstack(in_data, axis=axis), axis=axis)
573+
574+
compare_tf_with_tvm([np_data], ['in_data:0'], 'stack:0')
575+
576+
def test_forward_unstack():
577+
'''test unstack layer'''
578+
_test_unstack((6,), 0, 'int32')
579+
_test_unstack((2,6), 1, 'float64')
580+
# negative axis
581+
_test_unstack((1,4), -1, 'int32')
582+
_test_unstack((3,6,4), -2, 'float32')
580583

581584

582585
#######################################################################
@@ -1139,7 +1142,7 @@ def test_forward_rel_ops():
11391142
test_forward_gather()
11401143
test_forward_stridedslice()
11411144
test_forward_split()
1142-
test_forward_split_concat()
1145+
test_forward_unstack()
11431146

11441147
# Activations
11451148
test_forward_sigmoid()

0 commit comments

Comments
 (0)