[MO|nGraph]GatherND_8 (#7743)

* Add GatherND_8 operation

* Update shape infer function and tests

* Initial commit for nGraph GatherND_8 operation

* Add GatherNDBase class implementation

* Fix base class errors

* Add missrd header

* Update base class

* Update GatherND_8 implementation

* Fix codestyle

* Fix wrong rank

* Implement tests for gatherND_8 shape inference function

* fix codestyle

* Add limitation to doc

* Siplyfy check in shape inference

* Add more test cases

* Update shape inference function

* Add more test cases to cover all case with dynamic input shapes

* Update shape inference function

* Refactor tests

* Add visitor tests for gatherND_8 operation

* Correct comment

* Add additional check is shape inference function

* Update shape inference implementation for gathernd operartion

* Fix codestyle

* Remove restriction for data is fully defined

* Update shape inference functon

* Fix missed check for nonetype

* Remove redundant checks for batch_dims

* Fix codestyle
This commit is contained in:
Anton Chetverikov authored and GitHub committed 2021-11-10 11:54:52 +03:00
1 parent 76994c6ec9
commit c8e1c8e3eb
12 files changed
+644 -139

No files matched your search

+46 -32
View File
@@ -16,7 +16,7 @@ class GatherND(Op):
mandatory_props = {
'type': self.op,
'op': self.op,
'version': 'opset5',
'version': 'opset8',
'infer': self.infer,
'in_ports_count': 2,
'out_ports_count': 1,
@@ -56,41 +56,55 @@ class GatherND(Op):
assert len(indices_shape) > 0, "Indices must not be a scalar"
assert (batch_dims + indices_shape[-1]) <= len(data_shape), \
"Length of a tuple with indices must not exceed a rank of data tensor excluding batch dimensions"
assert node['version'] in ['opset5', 'opset8'], 'Unsupported version of GatherND operation: {}, operation ' \
'name : {}'.format(node['version'], node.soft_get('name'))
# compute output shape
batch = []
if batch_dims > 0:
if is_fully_defined(data_shape[:batch_dims]):
batch = [np.prod(data_shape[:batch_dims]).tolist()]
else:
batch = [dynamic_dimension_value]
else:
batch = []
if node['version'] == 'opset5': # Support old version of gatherND shape inference
if is_fully_defined(data_shape[:batch_dims]):
batch = [np.prod(data_shape[:batch_dims]).tolist()]
else:
batch = [dynamic_dimension_value]
elif node['version'] == 'opset8':
for dim in range(batch_dims):
assert compatible_dims(indices_shape[dim], data_shape[dim]),\
"Batch dimensions in data.shape and indices.shape must be compatible"
if is_fully_defined(indices_shape[:batch_dims]):
batch = indices_shape[:batch_dims].tolist()
elif is_fully_defined(data_shape[:batch_dims]):
batch = data_shape[:batch_dims].tolist()
else:
for ind in range(batch_dims):
if indices_shape[ind] != dynamic_dimension_value:
batch.append(indices_shape[ind])
elif data_shape[ind] != dynamic_dimension_value:
batch.append(data_shape[ind])
else:
batch.append(dynamic_dimension_value)
slice_shape = list(data_shape[(batch_dims + indices_shape[-1]):])
output_shape = batch + list(indices_shape[batch_dims:-1]) + slice_shape
output_shape = batch + list(indices_shape)[batch_dims:-1] + slice_shape
node.out_port(0).data.set_shape(output_shape)
# compute output value if all input values are defined
if is_fully_defined(indices_value) and is_fully_defined(data_value):
output_value = np.zeros(output_shape, dtype=data_value.dtype)
if batch_dims == 0:
output_indices_range = int64_array(indices_shape[:-1])
for output_index in np.ndindex(tuple(output_indices_range)):
indices_tuple = indices_value[output_index]
output_value[output_index] = data_value[tuple(indices_tuple.T)]
else:
batch_dims_range = int64_array(indices_shape[:batch_dims])
for batch_indices in np.ndindex(tuple(batch_dims_range)):
# compute batch index in output tensor
batch_ind = 0
num_elements = 1
for ind in reversed(range(len(batch_dims_range))):
batch_ind += batch_indices[ind] * num_elements
num_elements *= batch_dims_range[ind]
output_indices_range = int64_array(indices_shape[batch_dims:-1])
for output_index in np.ndindex(tuple(output_indices_range)):
tmp_ind = batch_indices + output_index
indices_tuple = tuple(indices_value[tmp_ind].T)
full_input_ind = batch_indices + indices_tuple
full_output_ind = tuple(np.array([batch_ind]).T) + output_index
output_value[full_output_ind] = data_value[full_input_ind]
# compute output value if all input indices are defined
if is_fully_defined(indices_value) and data_value is not None:
batch_dims_size = 1
for i in range(batch_dims):
batch_dims_size *= indices_shape[i]
output_data = []
reshaped_indices = indices_value.reshape(batch_dims_size, -1, indices_shape[-1])
reshaped_data = data_value.reshape((batch_dims_size,) + tuple((data_shape[batch_dims:])))
for batch_dim in range(reshaped_indices.shape[0]):
for outer_dim in range(reshaped_indices.shape[1]):
gather_index = tuple(reshaped_indices[batch_dim][outer_dim])
output_data.append(reshaped_data[(batch_dim,) + gather_index])
output_value = np.asarray(output_data, dtype=data_value.dtype).reshape(output_shape)
node.out_port(0).data.set_value(output_value)
@@ -14,7 +14,7 @@ nodes_attributes = {'data': {'kind': 'op'},
'data_data': {'shape': None, 'value': None, 'kind': 'data'},
'indices': {'kind': 'op'},
'indices_data': {'shape': None, 'value': None, 'kind': 'data'},
'gathernd_node': {'op': 'GatherNDUpdate', 'kind': 'op', 'batch_dims': 0},
'gathernd_node': {'op': 'GatherNDUpdate', 'kind': 'op', 'batch_dims': 0, 'version': 'opset8'},
'output': {'shape': None, 'value': None, 'kind': 'data'}}
# graph 1
@@ -25,17 +25,21 @@ edges = [('data', 'data_data', {'in': 0}),
('gathernd_node', 'output', {'out': 0})]
# test data for partial infer: gather elements
inputs1 = {'data_data': {'shape': int64_array([10, 40]), 'value': None},
inputs = {'data_data': {'shape': int64_array([10, 40]), 'value': None},
'indices_data': {'shape': int64_array([3, 2]), 'value': None}}
# test data for partial infer: gather slices
inputs2 = {'data_data': {'shape': int64_array([10, 40, 30]), 'value': None},
inputs1 = {'data_data': {'shape': int64_array([10, 40, 30]), 'value': None},
'indices_data': {'shape': int64_array([3, 2]), 'value': None}}
# test data for partial infer: gather slices and batch_dims=2
inputs3 = {'data_data': {'shape': int64_array([10, 40, 4, 9]), 'value': None},
inputs2 = {'data_data': {'shape': int64_array([10, 40, 4, 9]), 'value': None},
'indices_data': {'shape': int64_array([10, 40, 3, 5, 1]), 'value': None}}
# test data for partial infer: gather slices and batch_dims=3 and indices.shape[-1]=len(data.shape)-batch_dims
inputs3 = {'data_data': {'shape': int64_array([1, 64, 64, 320]), 'value': None},
'indices_data': {'shape': int64_array([1, 64, 64, 1, 1]), 'value': None}}
# test data for constant folding: gather elements, batch_dims = 0
inputs4 = {'data_data': {'shape': int64_array([2, 2]), 'value': int64_array([[1, 2],
[3, 4]])},
@@ -110,6 +114,14 @@ output8 = int64_array([[3, 8, 6],
inputs9 = {'data_data': {'shape': shape_array([dynamic_dimension_value, 40, 4, 9]), 'value': None},
'indices_data': {'shape': shape_array([dynamic_dimension_value, 40, 3, 5, 1]), 'value': None}}
# test data for partial infer: gather slices and batch_dims=2
inputs10 = {'data_data': {'shape': shape_array([40, dynamic_dimension_value, 4, 9]), 'value': None},
'indices_data': {'shape': shape_array([40, dynamic_dimension_value, 3, 5, 1]), 'value': None}}
# test data for partial infer: gather slices and batch_dims=2
inputs11 = {'data_data': {'shape': shape_array([dynamic_dimension_value, 40, 4, 9]), 'value': None},
'indices_data': {'shape': shape_array([40, dynamic_dimension_value, 3, 5, 1]), 'value': None}}
# invalid test case with incorrect rank for indices
inputs_inv1 = {'data_data': {'shape': int64_array([10, 40]), 'value': None},
'indices_data': {'shape': int64_array([5, 3, 4]), 'value': None}}
@@ -123,12 +135,13 @@ inputs_inv3 = {'data_data': {'shape': int64_array([10, 40, 20, 10, 2]), 'value':
'indices_data': {'shape': int64_array([10, 40, 4]), 'value': None}}
class TestGatherNDUpdate(unittest.TestCase):
class TestGatherND_5(unittest.TestCase):
def setUp(self):
nodes_attributes['gathernd_node']['batch_dims'] = 0
nodes_attributes['gathernd_node']['version'] = 'opset5'
def test_partial_infer_gather_element(self):
graph = build_graph(nodes_attributes, edges, inputs1)
graph = build_graph(nodes_attributes, edges, inputs)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
@@ -142,7 +155,7 @@ class TestGatherNDUpdate(unittest.TestCase):
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice(self):
graph = build_graph(nodes_attributes, edges, inputs2)
graph = build_graph(nodes_attributes, edges, inputs1)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
@@ -157,7 +170,7 @@ class TestGatherNDUpdate(unittest.TestCase):
def test_partial_infer_gather_slice_batch_dims2(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
graph = build_graph(nodes_attributes, edges, inputs3)
graph = build_graph(nodes_attributes, edges, inputs2)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
@@ -170,7 +183,22 @@ class TestGatherNDUpdate(unittest.TestCase):
self.assertTrue(np.array_equal(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic(self):
def test_partial_infer_gather_slice_batch_dims3(self):
nodes_attributes['gathernd_node']['batch_dims'] = 3
graph = build_graph(nodes_attributes, edges, inputs3)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = int64_array([4096, 1])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(np.array_equal(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic1(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
graph = build_graph(nodes_attributes, edges, inputs9)
gathernd_node = Node(graph, 'gathernd_node')
@@ -185,6 +213,36 @@ class TestGatherNDUpdate(unittest.TestCase):
self.assertTrue(strict_compare_tensors(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic2(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
graph = build_graph(nodes_attributes, edges, inputs10)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = shape_array([dynamic_dimension_value, 3, 5, 9])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(strict_compare_tensors(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic3(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
graph = build_graph(nodes_attributes, edges, inputs11)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = shape_array([dynamic_dimension_value, 3, 5, 9])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(strict_compare_tensors(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_infer4(self):
graph = build_graph(nodes_attributes, edges, inputs4)
gathernd_node = Node(graph, 'gathernd_node')
@@ -205,7 +263,7 @@ class TestGatherNDUpdate(unittest.TestCase):
res_output_value = graph.node['output']['value']
self.assertTrue(np.array_equal(output5, res_output_value),
'values do not match expected: {} and given: {}'.format(output4, res_output_value))
'values do not match expected: {} and given: {}'.format(output5, res_output_value))
def test_infer6(self):
nodes_attributes['gathernd_node']['batch_dims'] = 1
@@ -217,7 +275,7 @@ class TestGatherNDUpdate(unittest.TestCase):
res_output_value = graph.node['output']['value']
self.assertTrue(np.array_equal(output6, res_output_value),
'values do not match expected: {} and given: {}'.format(output4, res_output_value))
'values do not match expected: {} and given: {}'.format(output6, res_output_value))
def test_infer7(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
@@ -228,8 +286,9 @@ class TestGatherNDUpdate(unittest.TestCase):
# get the result
res_output_value = graph.node['output']['value']
self.assertTrue(np.array_equal(output7, res_output_value),
'values do not match expected: {} and given: {}'.format(output4, res_output_value))
output = output7.reshape([6, 1])
self.assertTrue(np.array_equal(output, res_output_value),
'values do not match expected: {} and given: {}'.format(output, res_output_value))
def test_infer8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
@@ -241,7 +300,32 @@ class TestGatherNDUpdate(unittest.TestCase):
res_output_value = graph.node['output']['value']
self.assertTrue(np.array_equal(output8, res_output_value),
'values do not match expected: {} and given: {}'.format(output4, res_output_value))
'values do not match expected: {} and given: {}'.format(output8, res_output_value))
def test_infer9(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
graph = build_graph(nodes_attributes, edges, inputs8)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# get the result
res_output_value = graph.node['output']['value']
self.assertTrue(np.array_equal(output8, res_output_value),
'values do not match expected: {} and given: {}'.format(output8, res_output_value))
def test_infer9_opset_5(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
graph = build_graph(nodes_attributes, edges, inputs8)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# get the result
res_output_value = graph.node['output']['value']
output = output8.reshape([6, 3])
self.assertTrue(np.array_equal(output, res_output_value),
'values do not match expected: {} and given: {}'.format(output, res_output_value))
def test_infer_invalid1(self):
graph = build_graph(nodes_attributes, edges, inputs_inv1)
@@ -259,3 +343,114 @@ class TestGatherNDUpdate(unittest.TestCase):
graph = build_graph(nodes_attributes, edges, inputs_inv3)
gathernd_node = Node(graph, 'gathernd_node')
self.assertRaises(AssertionError, GatherND.infer, gathernd_node)
def test_partial_infer_gather_slice_batch_dims2_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs2)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = int64_array([10, 40, 3, 5, 9])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(np.array_equal(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims3_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 3
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs3)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = int64_array([1, 64, 64, 1])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(np.array_equal(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic1_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs9)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = shape_array([dynamic_dimension_value, 40, 3, 5, 9])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(strict_compare_tensors(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic2_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs10)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = shape_array([40, dynamic_dimension_value, 3, 5, 9])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(strict_compare_tensors(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_partial_infer_gather_slice_batch_dims2_dynamic3_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs11)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# prepare reference results
ref_output_shape = shape_array([40, 40, 3, 5, 9])
# get the result
res_output_shape = graph.node['output']['shape']
self.assertTrue(strict_compare_tensors(ref_output_shape, res_output_shape),
'values do not match expected: {} and given: {}'.format(ref_output_shape, res_output_shape))
def test_infer7_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs7)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# get the result
res_output_value = graph.node['output']['value']
output = output7.reshape([2, 3, 1])
self.assertTrue(np.array_equal(output, res_output_value),
'values do not match expected: {} and given: {}'.format(output, res_output_value))
def test_infer8_opset8(self):
nodes_attributes['gathernd_node']['batch_dims'] = 2
nodes_attributes['gathernd_node']['version'] = 'opset8'
graph = build_graph(nodes_attributes, edges, inputs8)
gathernd_node = Node(graph, 'gathernd_node')
GatherND.infer(gathernd_node)
# get the result
res_output_value = graph.node['output']['value']
output = output8.reshape([2, 3, 3])
self.assertTrue(np.array_equal(output, res_output_value),
'values do not match expected: {} and given: {}'.format(output, res_output_value))