def build_and_test_reverse_inference(order, out_shape, ref_shape): nodes = { **shaped_parameter('data', None, { 'reverse_infer': Parameter.reverse_infer }), **valued_const_with_data('order', int64_array(order)), **regular_op_with_empty_data( 'transpose', { 'op': 'Transpose', 'infer': Transpose.infer, 'reverse_infer': Transpose.reverse_infer }), **result('res'), } edges = [ *connect('data', '0:transpose'), *connect('order', '1:transpose'), *connect('transpose', 'res') ] graph = build_graph(nodes, edges) graph.stage = 'middle' Node(graph, 'transpose').out_port(0).data.set_shape(shape_array(out_shape)) partial_infer(graph) actual_shape = Node(graph, 'data').out_port(0).data.get_shape() assert strict_compare_tensors(actual_shape, shape_array(ref_shape))
def build_and_test_shape_inference(data_shape, indices_shape, axis, batch_dims, ref_shape): nodes = { **shaped_parameter('data', int64_array(data_shape)), **shaped_parameter('indices', int64_array(indices_shape)), **valued_const_with_data('axis', int64_array(axis)), **regular_op_with_empty_data('gather', { 'op': 'Gather', 'batch_dims': batch_dims, 'infer': Gather.infer }), **result('res'), } edges = [ *connect('data', '0:gather'), *connect('indices', '1:gather'), *connect('axis', '2:gather'), *connect('gather', 'res') ] graph = build_graph(nodes, edges) graph.stage = 'middle' partial_infer(graph) node = Node(graph, 'gather') res = node.out_port(0).data.get_shape() npt.assert_array_equal(res, ref_shape)
def build_and_test_shape_inference(self, input_indices_sparse_shape, input_actual_shape, new_shape, ref_out_shape, input_indices=None, ref_out_indices=None): # sparse tensor is stored in COO format nodes = { **shaped_parameter('input_indices', shape_array(input_indices_sparse_shape), { 'value': input_indices }), **valued_const_with_data('input_shape', shape_array(input_actual_shape)), **valued_const_with_data('new_shape', shape_array(new_shape)), **regular_op_with_empty_data( 'sparse_reshape_node', { 'op': 'SparseReshape', 'special_zero': True, 'infer': SparseReshape.infer }), **empty_data('sparse_reshape_node_d:out_port_1'), **result('output_indices'), **result('output_shape'), } edges = [ *connect('input_indices', '0:sparse_reshape_node'), *connect('input_shape', '1:sparse_reshape_node'), *connect('new_shape', '2:sparse_reshape_node'), *connect('sparse_reshape_node:0', 'output_indices'), ('sparse_reshape_node', 'sparse_reshape_node_d:out_port_1', { 'out': 1 }), ('sparse_reshape_node_d:out_port_1', 'output_shape', { 'in': 0 }), ] graph = build_graph( nodes, edges, update_attributes={'input_indices_d': { 'value': input_indices }}) graph.stage = 'middle' partial_infer(graph) node = Node(graph, 'sparse_reshape_node') output_indices = node.out_port(0).data.get_value() actual_output_shape = node.out_port(1).data.get_value() self.assertTrue( strict_compare_tensors(actual_output_shape, ref_out_shape)) self.assertTrue(strict_compare_tensors(output_indices, ref_out_indices))
def test_for_is_cyclic1(self): # Test for case of cyclic graph without is_cyclic attrs graph = build_graph(nodes_attributes, [('node_1', 'node_1_data'), ('node_1_data', 'node_3'), ('node_3', 'node_3_data'), ('node_3_data', 'node_1')], nodes_with_edges_only=True) with self.assertRaisesRegex(Error, 'Graph contains a cycle. Can not proceed.*'): partial_infer(graph)
def test_partial_infer(self): graph = build_graph(nodes_attributes, [('node_1', 'concat'), ('node_2', 'concat'), ('concat', 'node_3'), ('node_3', 'op_output')], { 'node_3': { 'kind': 'data', 'shape': None, 'infer': None }, 'node_1': { 'kind': 'data', 'shape': np.array([1, 3, 227, 227]), 'infer': None }, 'node_2': { 'kind': 'data', 'shape': np.array([1, 3, 227, 227]), 'infer': None }, 'concat': { 'kind': 'op', 'axis': 2, 'infer': concat_infer } }, nodes_with_edges_only=True) start_node = 'concat' partial_infer(graph, start_node) node = Node(graph, start_node) self.assertTrue(node.is_partial_inferred) self.assertTrue(node.out_node().is_partial_inferred) # check if previous nodes are not inferred node = Node(graph, start_node) while True: # collect nodes in a list if isinstance(node.in_nodes(), list): in_nodes = node.in_nodes() else: in_nodes = [y for x, y in node.in_nodes().items()] # check parents and find next parent for n in in_nodes: if 'embedded_input_' not in n.id: node = n self.assertFalse(n.has('is_partial_inferred')) if not len(in_nodes): break
def build_and_test_reverse_inference(inp_shape_1, inp_shape_2, out_shape, ref_shape, auto_broadcast='numpy'): in_port_with_defined_shape = 0 if inp_shape_1 is not None else 1 defined_shape = shape_array( inp_shape_1 if inp_shape_1 is not None else inp_shape_2) nodes = { **shaped_parameter('undefined_shape_data', None, { 'reverse_infer': Parameter.reverse_infer }), **shaped_parameter('data', shape_array(defined_shape), { 'reverse_infer': Parameter.reverse_infer }), **regular_op_with_empty_data( 'elementwise', { 'op': 'Add', 'type': 'Add', 'infer': eltwise_infer, 'reverse_infer': eltwise_reverse_infer, 'auto_broadcast': auto_broadcast }), **result('res'), } edges = [ *connect( 'undefined_shape_data', '{}:elementwise'.format( int(not in_port_with_defined_shape))), *connect('data', '{}:elementwise'.format(in_port_with_defined_shape)), *connect('elementwise', 'res') ] graph = build_graph(nodes, edges) graph.stage = 'middle' Node(graph, 'elementwise').out_port(0).data.set_shape(shape_array(out_shape)) Node(graph, 'elementwise').in_port( in_port_with_defined_shape).data.set_shape(defined_shape) partial_infer(graph) actual_shape = Node( graph, 'undefined_shape_data').out_port(0).data.get_shape() if ref_shape is None: assert actual_shape == ref_shape else: assert strict_compare_tensors(actual_shape, shape_array(ref_shape))
def build_range_test_graphs(start=0, limit=10, delta=1, dst_type_str='FP16', src_type_str='FP32', returns_shape_value=None): nodes = { **valued_const_with_data('start', float32_array(start)), **valued_const_with_data('limit', float32_array(limit)), **valued_const_with_data('delta', float32_array(delta)), **regular_op_with_empty_data('range', {'type': 'Range', 'op': 'Range', 'returns_shape_value': returns_shape_value, 'output_type': data_type_str_to_np(src_type_str), 'infer': Range.infer}), **result('res'), } nodes_ref = deepcopy(nodes) nodes_ref.update({ **regular_op_with_empty_data('range', {'type': 'Range', 'op': 'Range', 'returns_shape_value': returns_shape_value, 'output_type': data_type_str_to_np(dst_type_str), 'infer': Range.infer}), }) edges = [ *connect('start', '0:range'), *connect('limit', '1:range'), *connect('delta', '2:range'), *connect('range', 'res'), ] graph = build_graph(nodes, edges) graph_ref = build_graph(nodes_ref, edges) graph = partial_infer(graph) graph.graph['cmd_params'].data_type = dst_type_str convert_blobs(graph, dst_type_str) return graph, graph_ref
def build_cast_test_graphs(input_data, dst_type_str='FP16'): nodes = { **valued_const_with_data('input', float32_array(input_data)), **regular_op_with_empty_data('cast', {'type': 'Convert', 'op': 'Cast', 'dst_type': np.float32, 'infer': Cast.infer}), **result('res'), } nodes_ref = deepcopy(nodes) nodes_ref.update({ **regular_op_with_empty_data('cast', {'type': 'Convert', 'op': 'Cast', 'dst_type': data_type_str_to_np(dst_type_str), 'infer': Cast.infer}), }) edges = [ *connect('input', 'cast'), *connect('cast', 'res'), ] graph = build_graph(nodes, edges) graph_ref = build_graph(nodes_ref, edges) graph = partial_infer(graph) graph.graph['cmd_params'].data_type = dst_type_str convert_blobs(graph, dst_type_str) return graph, graph_ref
def find_and_replace_pattern(self, graph: Graph): dynamic_inputs = {} for parameter in graph.get_op_nodes(op='Parameter'): param_shape = parameter.soft_get('shape', shape_array(dynamic_dimension_value)) if not is_fully_defined(param_shape): parameter_name = parameter.soft_get('name', parameter.id) dynamic_inputs[parameter_name] = parameter if dynamic_inputs: log.error('The model contains input(s) with partially defined shapes: {}. ' 'Starting from the 2022.1 release the Model Optimizer can generate an IR with partially defined ' 'input shapes ("-1" dimension in the TensorFlow model or dimension with string value in the ONNX ' 'model). Some of the OpenVINO plugins require model input shapes to be static, so you should ' 'call "reshape" method in the Inference Engine and specify static input shapes. For optimal ' 'performance, it is still recommended to update input shapes with fixed ones using "--input" or ' '"--input_shape" command-line parameters.' .format(','.join('name="{}" shape="{}"'.format(name, Parameter.shape_serialize(parameter)) for name, parameter in dynamic_inputs.items())), extra={'is_warning': True}) partial_infer(graph)
def build_and_test_reverse_inference(data_shape, indices_shape, axis, batch_dims, out_shape, ref_shape): in_port_with_defined_shape = 0 if data_shape is not None else 1 defined_shape = shape_array( data_shape if data_shape is not None else indices_shape) nodes = { **shaped_parameter('data', data_shape, { 'reverse_infer': Parameter.reverse_infer }), **shaped_parameter('indices', indices_shape, { 'reverse_infer': Parameter.reverse_infer }), **valued_const_with_data('axis', int64_array(axis)), **regular_op_with_empty_data( 'gather', { 'op': 'Gather', 'batch_dims': batch_dims, 'infer': Gather.infer, 'reverse_infer': Gather.reverse_infer }), **result('res'), } edges = [ *connect('data', '0:gather'), *connect('indices', '1:gather'), *connect('axis', '2:gather'), *connect('gather', 'res') ] graph = build_graph(nodes, edges) graph.stage = 'middle' Node(graph, 'gather').out_port(0).data.set_shape(shape_array(out_shape)) Node(graph, 'gather').in_port( in_port_with_defined_shape).data.set_shape(defined_shape) partial_infer(graph) actual_shape = Node(graph, 'gather').in_port( int(not in_port_with_defined_shape)).data.get_shape() assert strict_compare_tensors(actual_shape, shape_array(ref_shape))
def test_custom_value_propagation(self, value, expected, custom_dtype): graph = build_graph(nodes(value, custom_dtype), [ *connect('value', 'convert'), *connect('convert', 'output'), ]) partial_infer(graph) graph_ref = build_graph( nodes(value, custom_dtype), [*connect('value', 'convert'), *connect('convert', 'output')], { 'convert_d': { 'force_type': custom_dtype, 'force_shape': np.array(value).shape, 'value': expected } }) (flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True) self.assertTrue(flag, resp)
def test_is_not_fully_inferred_param(self): # Node that have is_not_fully_inferred=True graph = build_graph(nodes_attributes, [('node_1', 'concat'), ('node_2', 'concat'), ('concat', 'node_3'), ('node_3', 'op_output')], { 'node_3': { 'kind': 'data', 'shape': None, 'infer': None }, 'node_1': { 'kind': 'data', 'shape': np.array([1, 3, 227, 227]), 'infer': None }, 'node_2': { 'kind': 'data', 'shape': np.array([1, 3, 227, 227]), 'infer': None }, 'concat': { 'kind': 'op', 'axis': 2, 'infer': concat_infer, 'is_not_fully_inferred': True } }, nodes_with_edges_only=True) start_node = 'concat' try: partial_infer(graph, start_node) except Error: self.fail("Unexpected Error raised") node = Node(graph, start_node) self.assertTrue(node.is_partial_inferred) self.assertTrue(node.out_node().is_partial_inferred)
def infer(loop_node: Node): Loop.updated_body_parameters_shape(loop_node) partial_infer(loop_node.body) Loop.updated_loop_output_ports_shape_and_value(loop_node)
def test_pad_fusing_shape_subgraph(self): nodes = { **shaped_parameter('input', shape_array([1, 3, 1020, 1020])), **regular_op_with_empty_data( 'input_shape', { 'type': 'ShapeOf', 'op': 'ShapeOf', 'output_type': np.int64, 'infer': Shape.infer }), **regular_op_with_empty_data('gathered_shape', { 'type': 'Gather', 'batch_dims': 0, 'infer': Gather.infer }), **valued_const_with_data('axis', np.array([0])), **valued_const_with_data('indices', np.array([2, 3])), **regular_op_with_empty_data( 'div', { 'type': 'Div', 'infer': lambda node: eltwise_infer( node, lambda a, b: a / b) }), **regular_op_with_empty_data( 'sub_1', { 'type': 'Sub', 'infer': lambda node: eltwise_infer( node, lambda a, b: a - b) }), **regular_op_with_empty_data( 'sub_2', { 'type': 'Sub', 'infer': lambda node: eltwise_infer( node, lambda a, b: a - b) }), **valued_const_with_data('div_const', shape_array([2])), **valued_const_with_data('sub_const', shape_array([512])), **regular_op_with_empty_data('pad', { 'type': 'Pad', 'op': 'Pad', 'infer': Pad.infer, 'mode': 'constant' }), **regular_op_with_empty_data('concat', { 'type': 'Concat', 'op': 'Concat', 'axis': 0, 'infer': concat_infer }), **valued_const_with_data('pad_end', shape_array([0, 0, 0, 0])), **valued_const_with_data('blank_zeros', shape_array([0, 0])), **regular_op_with_empty_data( 'conv', { 'type': 'Convolution', 'op': 'Convolution', 'pad': np.array([[0, 0], [0, 0], [0, 0], [0, 0]]), 'dilation': np.array([1, 1, 1, 1]), 'stride': np.array([1, 1, 1, 1]), 'group': 1, 'kernel_spatial_idx': np.array([2, 3]), 'output': 64, 'spatial_dims': np.array([2, 3]), 'channel_dims': np.array([1]), 'batch_dims': np.array([0]), 'input_feature_channel': 1, 'output_feature_channel': 0, 'infer': Convolution.infer }), **valued_const_with_data('weights', shape_array(np.zeros([3, 16, 4, 4]))), **result(), } graph = build_graph( nodes_attrs=nodes, update_attributes={ 'gathered_shape_d': { 'kind': 'data', 'value': shape_array([256, 256]), 'shape': shape_array([2]) } }, edges=[ *connect('input', 'input_shape', skip_data=True), *connect('input_shape', '0:gathered_shape'), *connect('indices', '1:gathered_shape'), *connect('axis', '2:gathered_shape'), *connect('gathered_shape', 'sub_1'), *connect('sub_const', 'sub_1'), *connect('sub_1', 'div'), *connect('div_const', 'div'), *connect('div', '0:sub_2'), *connect('sub_1', '1:sub_2'), *connect('input', '0:pad'), *connect('blank_zeros', '0:concat'), *connect('sub_2', '1:concat'), *connect('concat', '1:pad'), *connect('pad_end', '2:pad'), *connect('pad', '0:conv'), *connect('weights', '1:conv'), *connect('conv', 'output'), ], nodes_with_edges_only=True) graph.graph['layout'] = 'NCHW' graph.stage = 'middle' graph = partial_infer(graph) # graph must remain unchanged graph_ref = graph.copy() mark_shape_of_sugraph_as_unfusable(graph) for_graph_and_each_sub_graph_recursively(graph, fuse_pad) (flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True) self.assertTrue(flag, resp)
def test_simple_shape_inf(self, cond, output_port_0_shape, output_port_1_shape): then_graph_nodes = { **regular_op_with_empty_data( 'param_1', { 'type': 'Parameter', 'kind': 'op', 'input_id': 1, 'shape': None, 'infer': Parameter.infer }), **regular_op_with_empty_data( 'param_2', { 'type': 'Parameter', 'kind': 'op', 'input_id': 2, 'shape': None, 'infer': Parameter.infer }), **regular_op_with_empty_data( 'add', { 'type': 'Add', 'kind': 'op', 'op': 'Add', 'infer': lambda node: eltwise_infer(node, Add.operation) }), **regular_op_with_empty_data( 'mul', { 'type': 'Mul', 'kind': 'op', 'op': 'Mul', 'infer': lambda node: eltwise_infer(node, Mul.operation) }), **regular_op_with_empty_data( 'res1', { 'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0, 'output_id': 0 }), **regular_op_with_empty_data( 'res2', { 'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0, 'output_id': 1 }) } then_graph_edges = [ *connect('param_1', '0:add'), *connect('param_2', '1:add'), *connect('param_1', '1:mul'), *connect('param_2', '0:mul'), *connect('add', 'res1'), *connect('mul', 'res2'), ] else_graph_nodes = { **regular_op_with_empty_data( 'param_1', { 'type': 'Parameter', 'kind': 'op', 'input_id': 1, 'shape': None, 'infer': Parameter.infer }), **regular_op_with_empty_data( 'param_2', { 'type': 'Parameter', 'kind': 'op', 'input_id': 3, 'shape': None, 'infer': Parameter.infer }), **regular_op_with_empty_data('identity', { 'kind': 'op', 'op': 'Identity', 'infer': Identity.infer }), **regular_op_with_empty_data('identity_1', { 'kind': 'op', 'op': 'Identity', 'infer': Identity.infer }), **regular_op_with_empty_data( 'res1', { 'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0, 'output_id': 0 }), **regular_op_with_empty_data( 'res2', { 'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0, 'output_id': 1 }) } else_graph_edges = [ *connect('param_1', 'identity'), *connect('param_2', 'identity_1'), *connect('identity_1', 'res2'), *connect('identity', 'res1'), ] then_graph = build_graph_with_edge_attrs(then_graph_nodes, then_graph_edges) else_graph = build_graph_with_edge_attrs(else_graph_nodes, else_graph_edges) external_graph_nodes = { **valued_const_with_data('cond', cond), **valued_const_with_data('input_2', int64_array([3, 2, 1])), **valued_const_with_data('input_1', int64_array([1, 2, 3])), **valued_const_with_data('input_3', int64_array([8, 4])), **regular_op( 'if', { 'kind': 'op', 'op': 'If', 'then_graph': then_graph, 'else_graph': else_graph, 'infer': If.infer }), **empty_data('if_d_1'), **empty_data('if_d_2'), **result('res_1'), **result('res_2') } external_graph_edges = [ *connect('cond', '0:if'), *connect('input_1', '1:if'), *connect('input_2', '2:if'), *connect('input_3', '3:if'), ('if', 'if_d_1', { 'out': 0 }), ('if', 'if_d_2', { 'out': 1 }), ('if_d_1', 'res_1'), ('if_d_2', 'res_2') ] graph = build_graph(external_graph_nodes, external_graph_edges) graph.stage = 'middle' partial_infer(graph) if_node = Node(graph, 'if') self.assertTrue( strict_compare_tensors( if_node.out_port(0).data.get_shape(), output_port_0_shape)) # shape of the "then" branch is [3] and shape of the "else" branch is [2], so the output shape is "[dynamic]" self.assertTrue( strict_compare_tensors( if_node.out_port(1).data.get_shape(), output_port_1_shape))
def test_fake_results(self): then_graph_nodes = { **valued_const_with_data('fake_const', int64_array(0)), **regular_op_with_empty_data( 'shapeof', { 'kind': 'op', 'type': 'ShapeOf', 'op': 'ShapeOf', 'infer': Shape.infer, 'output_type': np.int64 }), **regular_op_with_empty_data( 'res_1', { 'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0, 'output_id': 0 }) } then_graph_edges = [ *connect('fake_const', 'shapeof'), *connect('shapeof', 'res_1'), ] else_graph_nodes = { **regular_op_with_empty_data( 'param_1', { 'type': 'Parameter', 'kind': 'op', 'input_id': 1, 'shape': None, 'infer': Parameter.infer }), **regular_op_with_empty_data( 'res_1', { 'kind': 'op', 'type': 'Result', 'op': 'Result', 'infer': lambda x: 0, 'output_id': 0 }) } else_graph_edges = [*connect('param_1', 'res_1')] then_graph = build_graph_with_edge_attrs(then_graph_nodes, then_graph_edges) else_graph = build_graph_with_edge_attrs(else_graph_nodes, else_graph_edges) external_graph_nodes = { **valued_const_with_data('cond', shape_array([dynamic_dimension_value])), **valued_const_with_data( 'input_1', int64_array([1, 2, 3, 3, 2, 3]).reshape((2, 3))), **regular_op_with_empty_data( 'if', { 'kind': 'op', 'op': 'If', 'then_graph': then_graph, 'else_graph': else_graph, 'infer': If.infer }), **result('res_1') } external_graph_edges = [ *connect('cond', '0:if'), *connect('input_1', '1:if'), *connect('if', 'res_1') ] graph = build_graph(external_graph_nodes, external_graph_edges) graph.stage = 'middle' partial_infer(graph) npt.assert_array_equal( Node(graph, 'if').out_port(0).data.get_shape(), int64_array([2, 3]))
def test_pad_fusing(self): nodes = { **shaped_parameter('input', shape_array([1, 3, 248, 248])), **valued_const_with_data('pads_begin', shape_array([0, 0, 1, 1])), **valued_const_with_data('pads_end', shape_array([0, 0, 1, 1])), **valued_const_with_data('fill_value', shape_array(0.0)), **valued_const_with_data('weights', shape_array(np.zeros([3, 16, 4, 4]))), **regular_op_with_empty_data('pad', { 'type': 'Pad', 'op': 'Pad', 'infer': Pad.infer, 'mode': 'constant' }), **regular_op_with_empty_data( 'conv', { 'type': 'Convolution', 'op': 'Convolution', 'infer': Convolution.infer, # zeros, no paddings 'pad': np.array([[0, 0], [0, 0], [0, 0], [0, 0]]), 'dilation': np.array([1, 1, 1, 1]), 'stride': np.array([1, 1, 1, 1]), 'group': 1, 'kernel_spatial_idx': np.array([2, 3]), 'output': 64, 'spatial_dims': np.array([2, 3]), 'channel_dims': np.array([1]), 'batch_dims': np.array([0]), 'input_feature_channel': 1, 'output_feature_channel': 0 }), **result(), } graph = build_graph(nodes_attrs=nodes, edges=[ *connect('input', '0:pad'), *connect('pads_begin', '1:pad'), *connect('pads_end', '2:pad'), *connect('fill_value', '3:pad'), *connect('pad', '0:conv'), *connect('weights', '1:conv'), *connect('conv', 'output'), ], nodes_with_edges_only=True) graph.graph['layout'] = 'NCHW' graph.stage = 'middle' graph = partial_infer(graph) mark_shape_of_sugraph_as_unfusable(graph) for_graph_and_each_sub_graph_recursively(graph, fuse_pad) graph.clean_up() conv_fused_with_pad = regular_op_with_empty_data( 'conv', { 'type': 'Convolution', 'op': 'Convolution', # ones are taken from fused Pad 'pad': np.array([[0, 0], [0, 0], [1, 1], [1, 1]]), 'dilation': np.array([1, 1, 1, 1]), 'stride': np.array([1, 1, 1, 1]), 'group': 1, 'kernel_spatial_idx': np.array([2, 3]), 'output': 64, 'spatial_dims': np.array([2, 3]), 'channel_dims': np.array([1]), 'batch_dims': np.array([0]), 'input_feature_channel': 1, 'output_feature_channel': 0, 'infer': Convolution.infer }) graph_ref = build_graph(nodes_attrs=nodes, update_attributes=conv_fused_with_pad, edges=[ *connect('input', '0:conv'), *connect('weights', '1:conv'), *connect('conv', 'output'), ], nodes_with_edges_only=True) graph_ref.graph['layout'] = 'NCHW' graph_ref.stage = 'middle' (flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True) self.assertTrue(flag, resp)
def infer(if_node: Node): If.update_body_parameters_shape(if_node, True) If.update_body_parameters_shape(if_node, False) partial_infer(if_node.then_graph) partial_infer(if_node.else_graph) If.update_if_output_ports_shape(if_node)