# Copyright (c) 2022  PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from x2paddle.decoder.onnx_decoder import ONNXGraph, ONNXGraphNode, ONNXGraphDataNode
from x2paddle.core.graph import GraphNode
from x2paddle.core.util import *
from functools import reduce
import numpy as np
import onnx
import onnx.numpy_helper as numpy_helper
from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE
import logging as _logging
from collections import OrderedDict
import math
import os
import copy
import sys
import shutil

_logger = _logging.getLogger()


def _const_weight_or_none(node, necessary=False):
    if 'Constant' in node.layer_type:
        return node.value
    if isinstance(node, ONNXGraphDataNode):
        return node.weight
    if necessary:
        assert '{} should be an initializer or Constant operator.'.format(
            node.name)
    return None


def _rename_or_remove_weight(weights,
                             origin_name,
                             target_name=None,
                             is_remove=True,
                             rename_mapper=None):
    '''
    Rename parameters by Paddle's naming rule of parameters.

    Args:
        weights(dict[String:np.ndarray]): Dict stored paramters, the key in weights is name of parameter.
        origin_name(String): Name of parameter to rename or remove.
        target_name(String, optional): if target_name is not None, add new key-value pair
            {target_name:weights[origin_name]} to weights, and target_name must follow paddle's
            naming rule of parameters. Default: None.
        is_remove: if is_remove is True, remove origin key-value pair. Default: True.
        rename_mapper: Solved the same data is used for multiple OPs, key is old_name, value is new_name.
    Returns:
        None
    '''
    if rename_mapper is not None and origin_name in rename_mapper:
        origin_name = rename_mapper[origin_name]
        is_remove = False
    if origin_name not in weights:
        raise KeyError('{} not a key in {}'.format(origin_name, weights.keys()))
    if is_remove:
        # remove weight
        data = weights.pop(origin_name)
    else:
        data = weights[origin_name]
    if target_name is not None:
        # rename weight
        weights[target_name] = data
        rename_mapper[origin_name] = target_name


def _is_static_shape(shape):
    negtive_dims = 0
    error_dims = 0
    for dim in shape:
        if dim < 0:
            negtive_dims += 1
        if dim < -1:
            error_dims += 1
    if negtive_dims > 1:
        return False
    if error_dims > 0:
        return False
    return True


def _get_same_padding(in_size, kernel_size, stride, autopad):
    new_size = int(math.ceil(in_size * 1.0 / stride))
    pad_size = (new_size - 1) * stride + kernel_size - in_size
    pad0 = int(pad_size / 2)
    pad1 = pad_size - pad0
    if autopad == "SAME_UPPER":
        return [pad0, pad1]
    if autopad == "SAME_LOWER":
        return [pad1, pad0]


def print_mapping_info(func):
    def run_mapping(*args, **kwargs):
        node = args[1]
        try:
            res = func(*args, **kwargs)
        except:
            raise Exception("convert failed node:{}, op_type is {}".format(
                node.name[9:], node.layer_type))
        else:
            return res

    return run_mapping


class OpSet():
    def __init__(self, decoder, paddle_graph):
        super(OpSet, self).__init__()
        self.graph = decoder.graph
        self.paddle_graph = paddle_graph
        self.inputs_info = dict()
        self.weights = dict()
        self.nn_name2id = dict()
        self.done_weight_list = list()
        # solve for same data is used as an argument to multiple OPs.
        # PR link(wangjunjie06): https://github.com/PaddlePaddle/X2Paddle/pull/728
        self.rename_mapper = dict()
        self.elementwise_ops = {
            'Add': 'paddle.add',
            'Div': 'paddle.divide',
            'Sub': 'paddle.subtract',
            'Mul': 'paddle.multiply',
            'Pow': 'paddle.pow',
            'Less': 'paddle.less_than',
            'LessOrEqual': 'paddle.less_equal',
        }

        self.directly_map_ops = {
            'Ceil': ['paddle.ceil'],
            # reduce function
            'ReduceMean': [
                'paddle.mean', dict(
                    axes='axis', keepdims='keepdim'), dict(
                        axes=None, keepdims=True)
            ],
            'ReduceMin': [
                'paddle.min', dict(
                    axes='axis', keepdims='keepdim'), dict(
                        axes=None, keepdim=True)
            ],
            'ReduceMax': [
                'paddle.max', dict(
                    axes='axis', keepdims='keepdim'), dict(
                        axes=None, keepdim=True)
            ],
            'ReduceProd': [
                'paddle.prod', dict(
                    axes='axis', keepdims='keepdim'), dict(
                        axes=None, keepdim=True)
            ],
            # active function
            'Relu': ['paddle.nn.ReLU'],
            'LeakyRelu': [
                'paddle.nn.LeakyReLU', dict(alpha='negative_slope'),
                dict(negative_slope=.01)
            ],
            'Elu':
            ['paddle.nn.functional.elu', dict(alpha='alpha'), dict(alpha=1.)],
            'ThresholdedRelu': [
                'paddle.nn.functional.thresholded_relu',
                dict(alpha='threshold'), dict(alpha=1.)
            ],
            'Tanh': ['paddle.nn.Tanh'],
            'Sigmoid': ['paddle.nn.Sigmoid'],
            'Softsign': ['paddle.nn.Softsign'],
            'Softplus': [
                'paddle.nn.Softplus', dict(threshold='threshold'),
                dict(threshold=float(sys.maxsize))
            ],
            'Exp': ['paddle.exp'],
            'Log': ['paddle.log'],
            'LogSoftmax': [
                'paddle.nn.functional.log_softmax', dict(axis='axis'),
                dict(axis=1)
            ],
            'Softmax': ['paddle.nn.Softmax', dict(axis='axis'), dict(axis=1)],
            'Sqrt': ['paddle.sqrt'],
            'Floor': ['paddle.floor'],
            'Abs': ['paddle.abs'],
            'Erf': ['paddle.erf'],
            'Sin': ['paddle.sin'],
            'Cos': ['paddle.cos'],
        }

    @print_mapping_info
    def directly_map(self, node, *args, **kwargs):
        inputs = node.layer.input
        assert len(inputs) == 1, 'directly_map error with multi inputs'
        input = self.graph.get_input_node(node, idx=0, copy=True)
        onnx_attrs = node.attr_map
        if '' in onnx_attrs:
            onnx_attrs.pop('')
        if '_' in onnx_attrs:
            onnx_attrs.pop('_')
        op_info = self.directly_map_ops[node.layer_type]
        paddle_op = op_info[0]
        layer_attrs = dict()
        if len(op_info) > 1:
            attrs_name_map_dict = op_info[1]
            for onnx_attr_name, pd_attr_name in attrs_name_map_dict.items():
                if onnx_attr_name in onnx_attrs:
                    layer_attrs[pd_attr_name] = onnx_attrs[onnx_attr_name]
                else:
                    layer_attrs[pd_attr_name] = op_info[2][onnx_attr_name]
        if paddle_op.startswith("paddle.nn") and 'functional' not in paddle_op:
            op_name = paddle_op[10:].lower()
            op_name = name_generator(op_name, self.nn_name2id)
            output_name = node.name
            layer_outputs = [op_name, output_name]

            self.paddle_graph.add_layer(
                kernel=paddle_op,
                inputs={"x": input.name},
                outputs=layer_outputs,
                **layer_attrs)
        else:
            self.paddle_graph.add_layer(
                kernel=paddle_op,
                inputs={"x": input.name},
                outputs=[node.name],
                **layer_attrs)

    @print_mapping_info
    def elementwise_map(self, node):
        op_type = self.elementwise_ops[node.layer_type]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_input_node(node, idx=1, copy=True)
        inputs_dict = {'x': val_x.name, 'y': val_y.name}
        self.paddle_graph.add_layer(
            op_type, inputs=inputs_dict, outputs=[node.name])

    @print_mapping_info
    def place_holder(self, node):
        shape = node.out_shapes[0]
        for i, dim_shape in enumerate(shape):
            if dim_shape == 0 and i == 0:
                shape[i] = 1
            if dim_shape == 0 and i != 0:
                assert 'shape of input is not assigned'
        self.paddle_graph.add_layer(
            kernel="paddle.to_tensor",
            inputs={},
            outputs=[node.name],
            data=node.name)
        self.inputs_info[node.name] = [shape, node.dtype]

    @print_mapping_info
    def create_parameter(self, node, parameter=None):
        if parameter is not None:
            node = parameter
        dtype = node.dtype
        shape = node.out_shapes[0]

        if hasattr(node.weight, "shape") and len(node.weight.shape) == 0:
            if node.weight == float('inf') or node.weight == float('-inf'):
                node.weight = string(node.weight)
            self.paddle_graph.add_layer(
                "paddle.full",
                inputs={},
                outputs=[node.name],
                dtype=string(dtype),
                shape=[1],
                fill_value=node.weight)
        else:
            self.weights[node.name] = node.weight
            self.paddle_graph.add_layer(
                "self.create_parameter",
                inputs={},
                outputs=[node.name],
                shape=shape,
                attr=string(node.name),
                dtype=string(dtype),
                default_initializer="paddle.nn.initializer.Constant(value=0.0)")

    def _pad_if_asymmetric(self, node, pads, val_name):  # pads: SSEE
        assert len(pads) & 1 == 0
        symmetric = True
        ndims = len(pads) // 2
        for idx_dim in range(ndims):
            if pads[idx_dim] != pads[ndims + idx_dim]:
                symmetric = False
                break
        if symmetric:
            return pads[:ndims], val_name
        val_padded = self.Pad(node, op_independent=False)
        return [0] * ndims, val_padded

    def _interpolate(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        inputs = {'x': val_x.name}
        attrs = dict()
        val_x_shape = val_x.out_shapes[0]
        if node.layer_type == 'Resize':
            if len(node.layer.input) == 2:
                # opset 10
                val_scales = self.graph.get_input_node(node, idx=1, copy=True)
                # TODO(syf): paddle.nn.functional.interpolate will support the length
                # which is the same as the rank of input.
                scale_values = _const_weight_or_none(val_scales)
                if scale_values is not None:
                    attrs['scale_factor'] = self.weights[
                        val_scales.name].tolist()[2:]
                else:
                    var_nc, var_hw = val_scales.name + '_nc', val_scales.name + '_hw'
                    self.paddle_graph.add_layer(
                        'paddle.split',
                        inputs={"x": val_scales.name},
                        outputs=[var_nc, var_hw],
                        num_or_sections=[2, 2],
                        axis=0)
                    inputs['scale_factor'] = var_hw
                mode = node.get_attr('mode', 'nearest')
                attrs.update({
                    "align_corners": False,
                    "mode": string(mode),
                    "align_mode": 1
                })
                if mode == "linear" and len(val_x_shape) == 4:
                    attrs["mode"] = string("bilinear")
                self.paddle_graph.add_layer(
                    kernel="paddle.nn.functional.interpolate",
                    inputs=inputs,
                    outputs=[node.name],
                    **attrs)
                return
            elif len(node.layer.input) == 3:
                # opset 11
                try:
                    #to avoid the error causeed by NULL value of resize inputs.
                    val_scales = self.graph.get_input_node(
                        node, idx=2, copy=True)
                except:
                    val_scales = self.graph.get_input_node(
                        node, idx=1, copy=True)
                # TODO(syf): paddle.nn.functional.interpolate will support the length
                # which is the same as the rank of input.
                attrs['scale_factor'] = self.weights[val_scales.name].tolist()[
                    2:]
            elif len(node.layer.input) == 4:
                # opset 11
                val_sizes = self.graph.get_input_node(node, idx=3, copy=True)
                size_values = _const_weight_or_none(val_sizes)
                if len(val_x_shape) == 3:
                    var_n, var_hw = val_sizes.name + '_n', val_sizes.name + '_hw'
                    self.paddle_graph.add_layer(
                        'paddle.split',
                        inputs={"x": val_sizes.name},
                        outputs=[var_n, var_hw],
                        num_or_sections=[1, 2],
                        axis=0)
                    self.paddle_graph.add_layer(
                        "paddle.cast",
                        inputs={"x": var_hw},
                        outputs=[var_hw],
                        dtype=string('int32'))
                    inputs['size'] = var_hw
                    attrs = {
                        "align_corners": False,
                        "mode": string(node.get_attr('mode', 'nearest'))
                    }
                    mode = node.get_attr('mode', 'nearest')
                    if mode == "linear":
                        attrs["mode"] = string("bilinear")
                    if node.get_attr('coordinate_transformation_mode',
                                     'half_pixel') == 'pytorch_half_pixel':
                        attrs["align_corners"] = False
                        attrs["align_mode"] = 0
                    if node.get_attr('coordinate_transformation_mode',
                                     'half_pixel') == 'align_corners':
                        attrs["align_corners"] = True
                    self.paddle_graph.add_layer(
                        'paddle.unsqueeze',
                        inputs={"x": val_x.name},
                        outputs=[val_x.name],
                        axis=0)
                    self.paddle_graph.add_layer(
                        kernel="paddle.nn.functional.interpolate",
                        inputs=inputs,
                        outputs=[node.name],
                        **attrs)
                    self.paddle_graph.add_layer(
                        'paddle.squeeze',
                        inputs={"x": node.name},
                        outputs=[node.name],
                        axis=0)
                else:
                    if size_values is not None:
                        attrs["size"] = [size_values[2], size_values[3]]
                    else:
                        var_nc, var_hw = val_sizes.name + '_nc', val_sizes.name + '_hw'
                        self.paddle_graph.add_layer(
                            'paddle.split',
                            inputs={"x": val_sizes.name},
                            outputs=[var_nc, var_hw],
                            num_or_sections=[2, 2],
                            axis=0)
                        self.paddle_graph.add_layer(
                            "paddle.cast",
                            inputs={"x": var_hw},
                            outputs=[var_hw],
                            dtype=string('int32'))
                        inputs['size'] = var_hw
                    attrs.update({
                        "align_corners": False,
                        "mode": string(node.get_attr('mode', 'nearest'))
                    })
                    mode = node.get_attr('mode', 'nearest')
                    if mode == "linear":
                        attrs["mode"] = string("bilinear")
                    if node.get_attr('coordinate_transformation_mode',
                                     'half_pixel') == 'pytorch_half_pixel':
                        attrs["align_corners"] = False
                        attrs["align_mode"] = 0
                    if node.get_attr('coordinate_transformation_mode',
                                     'half_pixel') == 'align_corners':
                        attrs["align_corners"] = True
                    self.paddle_graph.add_layer(
                        kernel="paddle.nn.functional.interpolate",
                        inputs=inputs,
                        outputs=[node.name],
                        **attrs)
                return
        elif node.layer_type == 'Upsample':
            if len(node.layer.input) == 2:
                val_scales = self.graph.get_input_node(node, idx=1, copy=True)
                self.paddle_graph.add_layer(
                    "paddle.slice",
                    inputs={"input": val_scales.name},
                    outputs=[val_scales.name],
                    axes=[0],
                    starts=[2],
                    ends=[4])
                inputs['scale_factor'] = val_scales.name
            else:
                val_scales = node.get_attr('scales')[2:]

        mode = node.get_attr('mode', 'nearest')
        attrs.update({
            "align_corners": False,
            "mode": string(mode),
            "align_mode": 1
        })
        if len(node.layer.input) == 1:
            attrs["scale_factor"] = val_scales
        if mode == "linear" and len(val_x_shape) == 4:
            attrs["mode"] = string("bilinear")
            if node.get_attr('coordinate_transformation_mode',
                             'half_pixel') == 'pytorch_half_pixel':
                attrs["align_corners"] = False
                attrs["align_mode"] = 0
            else:
                attrs["align_corners"] = True
        self.paddle_graph.add_layer(
            kernel="paddle.nn.functional.interpolate",
            inputs=inputs,
            outputs=[node.name],
            **attrs)

    @print_mapping_info
    def CumSum(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        axis = self.graph.get_input_node(node, idx=1, copy=True)
        axis_values = _const_weight_or_none(axis)
        assert axis_values is not None, 'Axis only support constant tensor!'
        layer_attrs = {'axis': axis_values}
        self.paddle_graph.add_layer(
            'paddle.cumsum',
            inputs={"x": val_x.name},
            outputs=[node.name],
            **layer_attrs)

    @print_mapping_info
    def HardSigmoid(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        alpha = node.get_attr('alpha', 0.2)
        beta = node.get_attr('beta', 0.5)
        self.paddle_graph.add_layer(
            kernel="paddle.scale",
            inputs={"x": val_x.name},
            outputs=[node.name + "_val"],
            scale=alpha,
            bias=beta)
        self.paddle_graph.add_layer(
            kernel="paddle.clip",
            inputs={"x": node.name + "_val"},
            outputs=[node.name],
            min=0.0,
            max=1.0)

    @print_mapping_info
    def Shape(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        self.paddle_graph.add_layer(
            kernel="paddle.shape",
            inputs={"input": val_x.name},
            outputs=[node.name])
        self.paddle_graph.add_layer(
            'paddle.cast',
            inputs={"x": node.name},
            outputs=[node.name],
            dtype=string('int64'))

    @print_mapping_info
    def RoiAlign(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_rois = self.graph.get_input_node(node, idx=1, copy=True)

        pooled_height = node.get_attr('output_height')
        pooled_width = node.get_attr('output_width')
        spatial_scale = node.get_attr('spatial_scale')
        sampling_ratio = node.get_attr('sampling_ratio')
        val_rois_shape = val_rois.name + '_shape'
        self.paddle_graph.add_layer(
            kernel="paddle.shape",
            inputs={"input": val_rois.name},
            outputs=[val_rois_shape])
        val_rois_num = val_rois.name + '_num'
        if len(val_rois.out_shapes[0]) == 4:
            self.paddle_graph.add_layer(
                'paddle.split',
                inputs={"x": val_rois_shape},
                outputs=[val_rois_num, ' _', ' _', ' _'],
                num_or_sections=[1, 1, 1, 1],
                axis=0)
        elif len(val_rois.out_shapes[0]) == 2:
            self.paddle_graph.add_layer(
                'paddle.split',
                inputs={"x": val_rois_shape},
                outputs=[val_rois_num, ' _'],
                num_or_sections=[1, 1],
                axis=0)
        layer_attrs = {
            'pooled_height': pooled_height,
            'pooled_width': pooled_width,
            'spatial_scale': spatial_scale,
            'sampling_ratio': sampling_ratio,
        }
        self.paddle_graph.add_layer(
            'custom_layer:ROIAlign',
            inputs={
                'input': val_x.name,
                'rois': val_rois.name,
                'rois_num': val_rois_num
            },
            outputs=[node.name],
            **layer_attrs)

    @print_mapping_info
    def MaxRoiPool(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_rois = self.graph.get_input_node(node, idx=1, copy=True)

        spatial_scale = node.get_attr('spatial_scale')
        pooled_height, pooled_width = node.get_attr('pooled_shape')
        layer_attrs = {
            'pooled_height': pooled_height,
            'pooled_width': pooled_width,
            'spatial_scale': spatial_scale,
        }
        self.paddle_graph.add_layer(
            'custom_layer:ROIPooling',
            inputs={'input': val_x.name,
                    'rois': val_rois.name},
            outputs=[node.name],
            **layer_attrs)

    @print_mapping_info
    def Pad(self, node, op_independent=True):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        pads = node.get_attr('pads')
        is_pads_attr = True
        if pads is None:
            val_pad = self.graph.get_input_node(node, idx=1, copy=True)
            pad_shape = val_pad.out_shapes[0]
            is_pads_attr = False
            pads = _const_weight_or_none(val_pad)
            if pads is not None:
                is_pads_attr = True
        mode = node.get_attr('mode', 'constant')
        if mode in ["edge"]:
            mode = "replicate"
        value = node.get_attr('value', 0.)
        data_shape = val_x.out_shapes[0]
        output_shape = node.out_shapes[0]
        assume_pad = False
        layer_attrs = {}
        layer_attrs['mode'] = string(mode)
        layer_attrs['value'] = value
        if not op_independent:
            output_name = node.name + '_paded'
        else:
            output_name = node.name
        nn_op_name = name_generator("pad", self.nn_name2id)
        layer_outputs = [nn_op_name, output_name]
        if is_pads_attr:
            paddings = []
            if len(pads) == 10 and sum(pads) == 0:
                pads = pads[0:6]
            if len(pads) in [2, 4, 6]:
                if data_shape:
                    assume_pad |= data_shape and 2 * (len(data_shape) - 2
                                                      ) == len(pads)  # NCHW
                if output_shape:
                    assume_pad |= output_shape and 2 * (len(output_shape) - 2
                                                        ) == len(pads)  # NCHW
                if assume_pad:
                    paddle_op = 'paddle.nn.Pad{}D'.format(len(output_shape) - 2)
                    paddings = np.array(pads).reshape(
                        (2, -1)).transpose().astype("int32")
                    paddings = np.flip(paddings, axis=0).flatten().tolist()
                    layer_attrs['padding'] = paddings
                else:
                    if data_shape:
                        assume_pad |= data_shape and 2 * len(data_shape) == len(
                            pads)  # NCHW
                    if output_shape:
                        assume_pad |= output_shape and 2 * len(
                            output_shape) == len(pads)  # NCHW
                    if assume_pad:
                        paddle_op = 'paddle.nn.functional.pad'
                        paddings = np.array(pads).reshape(
                            (2,
                             -1)).transpose().astype("int32").flatten().tolist()
                        layer_attrs['pad'] = paddings
                    else:
                        raise Exception("The padding value {} is wrong!".format(
                            pads))
            elif len(pads) == 8:
                if data_shape:
                    assume_pad |= data_shape and 2 * len(data_shape) == len(
                        pads)  # NCHW
                if output_shape:
                    assume_pad |= output_shape and 2 * len(output_shape) == len(
                        pads)  # NCHW
                if assume_pad:
                    paddle_op = 'paddle.nn.Pad2D'
                    # x1_begin,x2_begin,x3_begin,x4_begin,x1_end,x2_end,x3_end,x4_end->x1_begin,x1_end,x2_begin,x2_end,x3_begin,x3_end,x4_begin,x4_end
                    paddings = np.array(pads).reshape(
                        (2, -1)).transpose().astype("int32")
                    if mode == 'constant':
                        paddings = paddings.flatten().tolist()
                        layer_attrs['padding'] = paddings
                    else:
                        paddings = np.flip(paddings, axis=0).flatten().tolist()
                        if sum(paddings[:4]) == 0:
                            paddings = paddings[4:]
                            layer_attrs['padding'] = paddings
                        else:
                            layer_attrs["pad"] = paddings
                            paddle_op = "custom_layer:PadAllDim4WithOneInput"
                else:
                    paddle_op = 'paddle.nn.functional.pad'
                    layer_attrs["pad"] = np.array(pads).tolist()
            else:
                pad_data_temp = pads[0::2]
                pad_data_all = []
                for i in range(len(pad_data_temp)):
                    pad_data_all.append(pads[i])
                    pad_data_all.append(pads[len(pad_data_temp) + i])

                layer_attrs["pad"] = pad_data_all
                self.paddle_graph.add_layer(
                    'paddle.nn.functional.pad',
                    inputs={'x': val_x.name},
                    outputs=layer_outputs[1:],
                    **layer_attrs)
                return

            self.paddle_graph.add_layer(
                paddle_op,
                inputs={'x': val_x.name},
                outputs=layer_outputs[1:]
                if paddle_op == 'paddle.nn.functional.pad' else layer_outputs,
                **layer_attrs)
            if not op_independent:
                return node.name + '_paded'
        else:
            pads_len = val_pad.out_shapes[0][0]
            if pads_len in [2, 4, 6]:
                if data_shape:
                    assume_pad |= data_shape and 2 * (len(data_shape) - 2
                                                      ) == pads_len  # NCHW
                if output_shape:
                    assume_pad |= output_shape and 2 * (len(output_shape) - 2
                                                        ) == pads_len  # NCHW
                if assume_pad:
                    if pads_len == 2:
                        data_format = "NCL"
                    elif pads_len == 4:
                        data_format = "NCHW"
                    else:
                        data_format = "NCDHW"
                    self.paddle_graph.add_layer(
                        "custom_layer:PadWithTwoInput",
                        inputs={'x': val_x.name,
                                'pad': val_pad.name},
                        outputs=layer_outputs,
                        value=value,
                        mode=string(mode),
                        data_format=string(data_format))
                else:
                    if data_shape:
                        assume_pad |= data_shape and 2 * len(
                            data_shape) == pads_len  # NCHW
                    if output_shape:
                        assume_pad |= output_shape and 2 * len(
                            output_shape) == pads_len  # NCHW
                    if assume_pad:
                        if pads_len == 4:
                            self.paddle_graph.add_layer(
                                "custom_layer:PadAllDim2",
                                inputs={'x': val_x.name,
                                        'pad': val_pad.name},
                                outputs=layer_outputs,
                                value=value,
                                mode=string(mode))
                        else:
                            raise Exception("The padding value is wrong!")
            elif pads_len == 8:
                if data_shape:
                    assume_pad |= data_shape and 2 * len(
                        data_shape) == pads_len  # NCHW
                if output_shape:
                    assume_pad |= output_shape and 2 * len(
                        output_shape) == pads_len  # NCHW
                if assume_pad:
                    self.paddle_graph.add_layer(
                        "custom_layer:PadAllDim4",
                        inputs={'x': val_x.name,
                                'pad': val_pad.name},
                        outputs=layer_outputs,
                        value=value,
                        mode=string(mode))
            else:
                raise Exception("The padding value is wrong!")
            if not op_independent:
                return node.name + '_paded'

    @print_mapping_info
    def Unsqueeze(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        axes = node.get_attr('axes')
        if axes is None:
            axes_node = self.graph.get_input_node(node, idx=1, copy=True)
            axes = _const_weight_or_none(axes_node, necessary=True)
        # deal with scalar(0D) tensor
        if len(val_x.out_shapes[0]) == 0 and len(axes) == 1 and axes[0] == 0:
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": val_x.name},
                outputs=[node.name],
                shape=[1])
        else:
            self.paddle_graph.add_layer(
                'paddle.unsqueeze',
                inputs={"x": val_x.name},
                axis=axes,
                outputs=[node.name])

    @print_mapping_info
    def Shrink(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        bias = node.get_attr('bias')
        lambd = node.get_attr('lambd')
        assert bias == 0.0, 'not support bias!=0'
        self.paddle_graph.add_layer(
            'paddle.nn.functional.hardshrink',
            inputs={"x": val_x.name},
            outputs=[node.name],
            threshold=lambd)

    @print_mapping_info
    def Constant(self, node):
        val_output = self.graph.get_node(node.layer.output[0], copy=True)

        value = node.get_attr('value')
        dtype = np.dtype(value.dtype)
        output_dtype = val_output.dtype
        if output_dtype:
            assert dtype == output_dtype, 'tensor dtype unmatches storage dtype'

        shape = node.get_attr('shape', None)

        if shape is None:
            shape = val_output.out_shapes[0]
        if shape is None:
            shape = list(value.shape)
            _logger.warning('in (Constant -> %s): '
                            'attribute "shape" of %s not inferred, '
                            'using value as 1-D tensor may lead to fails',
                            val_output.name, val_output.name)
        if len(value) == 1:
            value = value.tolist()
            value = value[0]
            if value == float('inf') or value == float('-inf'):
                value = string(value)
            self.paddle_graph.add_layer(
                "paddle.full",
                inputs={},
                outputs=[node.name],
                dtype=string(dtype),
                shape=[1],
                fill_value=value)
        else:
            value = np.reshape(value, shape)
            self.weights[node.name] = value
            self.paddle_graph.add_layer(
                "self.create_parameter",
                inputs={},
                outputs=[node.name],
                shape=shape,
                attr=string(node.name),
                dtype=string(dtype),
                default_initializer="paddle.nn.initializer.Constant(value=0.0)")

    @print_mapping_info
    def Resize(self, node):
        self._interpolate(node)

    @print_mapping_info
    def Upsample(self, node):
        self._interpolate(node)

    @print_mapping_info
    def InstanceNormalization(self, node):
        op_name = name_generator("instanse_norm", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_scale = self.graph.get_input_node(node, idx=1, copy=True)
        val_b = self.graph.get_input_node(node, idx=2, copy=True)
        epsilon = node.get_attr('epsilon', 1e-5)
        self.weights[op_name + '.scale'] = self.weights[val_scale.name]
        self.weights[op_name + '.bias'] = self.weights[val_b.name]
        layer_attrs = {
            'num_features': node.out_shapes[0][1],
            'epsilon': epsilon,
        }
        dim = len(val_x.out_shapes[0])
        if dim == 3:
            paddle_op = "paddle.nn.InstanceNorm1D"
        elif dim == 4:
            paddle_op = "paddle.nn.InstanceNorm2D"
        elif dim == 5:
            paddle_op = "paddle.nn.InstanceNorm3D"
        else:
            raise Exception(
                "The paddle only support 2D, 3D, 4D or 5D input in InstanceNormalization."
            )
        self.paddle_graph.add_layer(
            paddle_op,
            inputs={"x": val_x.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def Expand(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_shape = self.graph.get_input_node(node, idx=1, copy=True)
        val_x_dtype = val_x.dtype
        name_ones = node.name + '_ones'
        shape_values = _const_weight_or_none(val_shape)
        if shape_values is None:
            attr_ones = {
                'shape': val_shape.name,
                'dtype': string(val_x_dtype),
                'fill_value': 1
            }
        else:
            attr_ones = {
                'shape': shape_values.tolist(),
                'dtype': string(val_x_dtype),
                'fill_value': 1
            }
        self.paddle_graph.add_layer(
            'paddle.full', inputs={}, outputs=[name_ones], **attr_ones)
        inputs_dict = {'x': name_ones, 'y': val_x.name}
        self.paddle_graph.add_layer(
            'paddle.multiply', inputs=inputs_dict, outputs=[node.name])

    @print_mapping_info
    def GatherND(self, node):
        x = self.graph.get_input_node(node, idx=0, copy=True)
        index = self.graph.get_input_node(node, idx=1, copy=True)
        inputs = {'x': x.name, 'index': index.name}
        self.paddle_graph.add_layer(
            "paddle.gather_nd", inputs=inputs, outputs=[node.name])

    @print_mapping_info
    def Gather(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        indices = self.graph.get_input_node(node, idx=1, copy=True)
        indices_values = _const_weight_or_none(indices, necessary=True)
        if isinstance(indices_values, np.ndarray):
            indices_values = indices_values.tolist()
        indices_shape = indices.out_shapes[0]
        val_x_shape = val_x.out_shapes[0]
        axis = node.get_attr('axis', 0)
        if len(indices_shape) == 1 or \
            (indices_values is not None and isinstance(indices_values, int)) or \
            (indices_values is not None and len(indices_values) == 1):
            self.paddle_graph.add_layer(
                'paddle.gather',
                inputs={'x': val_x.name,
                        'index': indices.name},
                outputs=[node.name],
                axis=axis)
            # deal with indice is scalar(0D) Tensor
            if isinstance(indices_values, int) and len(val_x_shape) > 1:
                self.paddle_graph.add_layer(
                    'paddle.squeeze',
                    inputs={'x': node.name},
                    outputs=[node.name],
                    axis=[axis])
        else:
            # if val_x is DataNode, convert gather to embedding
            if axis == 0 and isinstance(val_x, ONNXGraphDataNode):
                indices_cast = indices.name + '_cast'
                self.paddle_graph.add_layer(
                    'paddle.cast',
                    inputs={"x": indices.name},
                    outputs=[indices_cast],
                    dtype=string('int64'))
                op_name = name_generator("embedding", self.nn_name2id)
                output_name = node.name
                layer_outputs = [op_name, output_name]
                self.weights[op_name + '.weight'] = _const_weight_or_none(val_x)
                self.paddle_graph.add_layer(
                    'paddle.nn.Embedding',
                    inputs={"x": indices_cast},
                    outputs=layer_outputs,
                    num_embeddings=val_x_shape[0],
                    embedding_dim=val_x_shape[1])
            else:
                self.paddle_graph.add_layer(
                    'paddle.reshape',
                    inputs={"x": indices.name},
                    outputs=[indices.name + "_reshape"],
                    shape=[-1])
                gather_1d = node.name + '_1D'
                self.paddle_graph.add_layer(
                    'paddle.gather',
                    inputs={
                        'x': val_x.name,
                        'index': indices.name + "_reshape"
                    },
                    outputs=[gather_1d],
                    axis=axis)
                # if shape is known
                if len(indices_shape) != 0 and len(val_x_shape) != 0:
                    self.paddle_graph.add_layer(
                        'paddle.reshape',
                        inputs={'x': gather_1d},
                        outputs=[node.name],
                        shape=val_x_shape[:axis] + indices_shape +
                        val_x_shape[axis + 1:])
                else:
                    all_shape_name = list()
                    self.paddle_graph.add_layer(
                        kernel="paddle.shape",
                        inputs={"input": val_x.name},
                        outputs=[val_x.name + "_shape"])
                    self.paddle_graph.add_layer(
                        kernel="paddle.shape",
                        inputs={"input": indices.name},
                        outputs=[indices.name + "_shape"])
                    self.paddle_graph.add_layer(
                        "paddle.slice",
                        inputs={"input": val_x.name + "_shape"},
                        outputs=[val_x.name + "_shape_slice_start"],
                        axes=[0],
                        starts=[0],
                        ends=[axis])
                    all_shape_name.append(val_x.name + "_shape_slice_start")
                    all_shape_name.append(indices.name + "_shape")
                    self.paddle_graph.add_layer(
                        "paddle.slice",
                        inputs={"input": val_x.name + "_shape"},
                        outputs=[val_x.name + "_shape_slice_end"],
                        axes=[0],
                        starts=[axis + 1],
                        ends=[2147483647])
                    all_shape_name.append(val_x.name + "_shape_slice_end")
                    self.paddle_graph.add_layer(
                        'paddle.concat',
                        inputs={"x": all_shape_name},
                        outputs=[node.name + "_all_shape"],
                        axis=0)
                    self.paddle_graph.add_layer(
                        'paddle.reshape',
                        inputs={'x': gather_1d},
                        outputs=[node.name],
                        shape=node.name + "_all_shape")

    @print_mapping_info
    def ScatterND(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        indices = self.graph.get_input_node(node, idx=1, copy=True)
        updates = self.graph.get_input_node(node, idx=2, copy=True)
        if len(indices.out_shapes[0]) == 1:
            self.paddle_graph.add_layer(
                'paddle.scatter',
                inputs={
                    'x': val_x.name,
                    'index': indices.name,
                    'updates': updates.name
                },
                outputs=[node.name])
        else:
            input_inner_indices = node.name + '_input_inner_indices'
            shape = val_x.out_shapes[0]
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": indices.name},
                outputs=[indices.name],
                shape=indices.out_shapes[0])

            zeros_like_val_x = val_x.name + '_zeros'
            self.paddle_graph.add_layer(
                'paddle.zeros_like',
                inputs={"x": val_x.name},
                outputs=[zeros_like_val_x])
            self.paddle_graph.add_layer(
                'paddle.scatter_nd_add',
                inputs={
                    'x': zeros_like_val_x,
                    'index': indices.name,
                    'updates': updates.name
                },
                outputs=[input_inner_indices])
            indices_mask = node.name + '_indices_mask'
            constant_minus_one = node.name + '_constant_minus_one'
            # full_like support create tensor shape like input tensor
            self.paddle_graph.add_layer(
                'paddle.full_like',
                inputs={"x": updates.name},
                outputs=[constant_minus_one],
                dtype=string(updates.dtype),
                fill_value=-1)
            self.paddle_graph.add_layer(
                'paddle.scatter_nd_add',
                inputs={
                    'x': zeros_like_val_x,
                    'index': indices.name,
                    'updates': constant_minus_one
                },
                outputs=[indices_mask])
            constant_one = node.name + '_constant_1'
            # full_like support create tensor shape like input tensor
            self.paddle_graph.add_layer(
                'paddle.full_like',
                inputs={"x": val_x.name},
                outputs=[constant_one],
                dtype=string(val_x.dtype),
                fill_value=1)
            input_out_indices_mask = node.name + '_input_out_indices_mask'
            self.paddle_graph.add_layer(
                "paddle.add",
                inputs={"x": indices_mask,
                        "y": constant_one},
                outputs=[input_out_indices_mask])

            input_out_indices = node.name + '_input_out_indices'
            self.paddle_graph.add_layer(
                "paddle.multiply",
                inputs={"x": val_x.name,
                        "y": input_out_indices_mask},
                outputs=[input_out_indices])

            self.paddle_graph.add_layer(
                "paddle.add",
                inputs={"x": input_inner_indices,
                        "y": input_out_indices},
                outputs=[node.name])

    @print_mapping_info
    def Range(self, node):
        val_start = self.graph.get_input_node(node, idx=0, copy=True)
        val_limit = self.graph.get_input_node(node, idx=1, copy=True)
        val_delta = self.graph.get_input_node(node, idx=2, copy=True)
        dtype = val_start.dtype
        inputs = {
            'start': val_start.name,
            'end': val_limit.name,
            'step': val_delta.name
        }
        self.paddle_graph.add_layer(
            'paddle.arange',
            inputs=inputs,
            outputs=[node.name],
            dtype=string(dtype))

    @print_mapping_info
    def Slice(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        starts, ends, axes, steps = None, None, None, None
        layer_attrs = {}
        if val_x.dtype == 'uint8':
            self.paddle_graph.add_layer(
                'paddle.cast',
                inputs={"x": val_x.name},
                outputs=[val_x.name],
                dtype=string('int32'))
        if len(node.inputs) > 1:
            starts = self.graph.get_input_node(node, idx=1, copy=True)
            ends = self.graph.get_input_node(node, idx=2, copy=True)
            starts_value = _const_weight_or_none(starts)
            if starts_value is not None:
                starts_value = starts_value.tolist()
            ends_value = _const_weight_or_none(ends)
            if ends_value is not None:
                ends_value = ends_value.tolist()
            if len(node.inputs) > 2:
                s_len = len(val_x.out_shapes[0])
                axes = list(range(s_len))
            if len(node.inputs) > 3:
                axes_node = self.graph.get_input_node(node, idx=3, copy=True)
                axes = _const_weight_or_none(axes_node, necessary=True).tolist()
            if len(node.inputs) > 4:
                steps = self.graph.get_input_node(node, idx=4, copy=True)
                steps = _const_weight_or_none(steps).tolist()

            layer_attrs = {
                "axes": axes,
                "starts": starts.name,
                "ends": ends.name
            }
            if starts_value is not None and ends_value is not None and axes is not None:
                starts_value = starts_value.copy()
                ends_value = ends_value.copy()
                for idx in range(len(ends_value)):
                    if len(val_x.out_shapes[0]) != 0 and starts_value[
                            idx] >= val_x.out_shapes[0][axes[
                                idx]] and val_x.out_shapes[0][axes[idx]] > 0:
                        starts_value[idx] = val_x.out_shapes[0][axes[idx]] - 1
                        ends_value[idx] = val_x.out_shapes[0][axes[idx]]
                    elif ends_value[idx] > 2**31 - 1:
                        ends_value[idx] = 2**31 - 1

                layer_attrs = {
                    "axes": axes,
                    "starts": starts_value,
                    "ends": ends_value
                }
            else:
                if starts.dtype != 'int32':
                    starts_cast = starts.name + '_cast'
                    self.paddle_graph.add_layer(
                        'paddle.cast',
                        inputs={"x": starts.name},
                        outputs=[starts_cast],
                        dtype=string('int32'))
                    layer_attrs['starts'] = starts_cast
                if ends.dtype != 'int32':
                    ends_cast = ends.name + '_cast'
                else:
                    ends_cast = ends.name
                self.paddle_graph.add_layer(
                    'paddle.cast',
                    inputs={"x": ends.name},
                    outputs=[ends_cast],
                    dtype=string('int32'))
                layer_attrs['ends'] = ends_cast
        else:
            starts = node.get_attr('starts')
            ends = node.get_attr('ends')
            axes = node.get_attr('axes')
            output_shape = val_x.out_shapes[0]

            if axes is None:
                axes = [i for i in range(len(starts))]
            for idx in range(len(ends)):
                if ends[idx] > 2**31 - 1:
                    ends[idx] = 2**31 - 1
            layer_attrs = {"axes": axes, "starts": starts, "ends": ends}

        if steps is not None:
            layer_attrs['strides'] = steps
            self.paddle_graph.add_layer(
                'paddle.strided_slice',
                inputs={"x": val_x.name},
                outputs=[node.name],
                **layer_attrs)
        else:
            self.paddle_graph.add_layer(
                'paddle.slice',
                inputs={"input": val_x.name},
                outputs=[node.name],
                **layer_attrs)
        if val_x.dtype == 'uint8':
            self.paddle_graph.add_layer(
                'paddle.cast',
                inputs={"x": node.name},
                outputs=[node.name],
                dtype=string('uint8'))

    @print_mapping_info
    def ConstantOfShape(self, node):
        val_shape = self.graph.get_input_node(node, idx=0, copy=True)

        value = node.get_attr('value')
        dtype = value.dtype
        value = value.tolist()
        assert len(value) == 1, ('given value not Scalar, shape of value > 1, '
                                 'this is not supported')
        if len(value) == 1:
            value = value[0]
            if value == float('inf') or value == float('-inf'):
                value = string(value)
            layer_attrs = {'dtype': string(dtype), 'fill_value': value}
            self.paddle_graph.add_layer(
                "paddle.full",
                inputs={'shape': val_shape.name},
                outputs=[node.name],
                **layer_attrs)

    @print_mapping_info
    def Clip(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_node(node.layer.output[0], copy=True)
        max_value, min_value = None, None
        if len(node.inputs) == 1:
            max_value = node.get_attr('max')
            min_value = node.get_attr('min')
            layer_attrs = {
                'max': max_value,
                'min': min_value,
            }

            self.paddle_graph.add_layer(
                'paddle.clip',
                inputs={"x": val_x.name},
                outputs=[node.name],
                **layer_attrs)
        else:
            if len(node.inputs) == 2:
                val_ipt = self.graph.get_input_node(node, idx=1, copy=True)

                index = node.get_input_index(val_ipt.name)

                val_value = _const_weight_or_none(val_ipt)
                if val_value.shape == (1, ):
                    val_value = val_value[0]

                if index == 1:
                    layer_attrs = {'min': val_value}

                if index == 2:
                    layer_attrs = {'max': val_value}

                self.paddle_graph.add_layer(
                    'paddle.clip',
                    inputs={"x": val_x.name},
                    outputs=[node.name],
                    **layer_attrs)
            else:
                if len(node.inputs) == 3:
                    min_ipt = self.graph.get_input_node(node, idx=1, copy=True)
                    max_ipt = self.graph.get_input_node(node, idx=2, copy=True)
                    self.paddle_graph.add_layer(
                        'paddle.clip',
                        inputs={
                            "x": val_x.name,
                            "min": min_ipt.name,
                            "max": max_ipt.name
                        },
                        outputs=[node.name])
                else:
                    raise Exception("max_value or min_value can't be None")

    @print_mapping_info
    def ReduceSum(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        if len(node.inputs) == 1:
            keepdims = node.get_attr('keepdims')
            if keepdims is None:
                keepdims = True
            axes_value = node.get_attr('axes')
            layer_attrs = {'axis': axes_value, 'keepdim': keepdims}
            self.paddle_graph.add_layer(
                'paddle.sum',
                inputs={"x": val_x.name},
                outputs=[node.name],
                **layer_attrs)
        else:
            axes = self.graph.get_input_node(node, idx=1, copy=True)
            axes_value = _const_weight_or_none(axes)
            if axes_value.shape == (1, ):
                axes_value = axes_value[0]
            keepdims = node.get_attr('keepdims')
            if keepdims is None:
                layer_attrs = {'axis': axes_value}
            else:
                layer_attrs = {'axis': axes_value, 'keepdim': keepdims}

            self.paddle_graph.add_layer(
                'paddle.sum',
                inputs={"x": val_x.name},
                outputs=[node.name],
                **layer_attrs)

    @print_mapping_info
    def Max(self, node):
        if len(node.inputs) == 2:
            val_x = self.graph.get_input_node(node, idx=0, copy=True)
            val_y = self.graph.get_input_node(node, idx=1, copy=True)
            self.paddle_graph.add_layer(
                "paddle.maximum",
                inputs={"x": val_x.name,
                        "y": val_y.name},
                outputs=[node.name])
        else:
            val_x = self.graph.get_input_node(node, idx=0, copy=True)
            temp_name = "max_"
            for i in range(1, len(node.inputs)):
                val_y = self.graph.get_input_node(node, idx=i, copy=True)
                temp_name = temp_name + str(i)
                if i == len(node.inputs) - 1:
                    self.paddle_graph.add_layer(
                        "paddle.maximum",
                        inputs={"x": val_x.name,
                                "y": val_y.name},
                        outputs=[node.name])
                else:
                    self.paddle_graph.add_layer(
                        "paddle.maximum",
                        inputs={"x": val_x.name,
                                "y": val_y.name},
                        outputs=[temp_name])
                val_x.name = temp_name

    @print_mapping_info
    def Min(self, node):
        if len(node.inputs) == 2:
            val_x = self.graph.get_input_node(node, idx=0, copy=True)
            val_y = self.graph.get_input_node(node, idx=1, copy=True)
            self.paddle_graph.add_layer(
                "paddle.minimum",
                inputs={"x": val_x.name,
                        "y": val_y.name},
                outputs=[node.name])
        else:
            val_x = self.graph.get_input_node(node, idx=0, copy=True)
            temp_name = "min_"
            for i in range(1, len(node.inputs)):
                val_y = self.graph.get_input_node(node, idx=i, copy=True)
                temp_name = temp_name + str(i)
                if i == len(node.inputs) - 1:
                    self.paddle_graph.add_layer(
                        "paddle.minimum",
                        inputs={"x": val_x.name,
                                "y": val_y.name},
                        outputs=[node.name])
                else:
                    self.paddle_graph.add_layer(
                        "paddle.minimum",
                        inputs={"x": val_x.name,
                                "y": val_y.name},
                        outputs=[temp_name])
                val_x.name = temp_name

    @print_mapping_info
    def GreaterOrEqual(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_input_node(node, idx=1, copy=True)
        self.paddle_graph.add_layer(
            "paddle.greater_equal",
            inputs={"x": val_x.name,
                    "y": val_y.name},
            outputs=[node.name])

    @print_mapping_info
    def And(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_input_node(node, idx=1, copy=True)
        self.paddle_graph.add_layer(
            "paddle.logical_and",
            inputs={"x": val_x.name,
                    "y": val_y.name},
            outputs=[node.name])

    @print_mapping_info
    def Split(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        paddle_op = 'split'
        split = node.get_attr('split')
        axis = node.get_attr('axis', 0)
        if split is None:
            split_num = len(node.layer.output)
            try:
                #split is an input of this node
                split_node = self.graph.get_input_node(node, idx=1, copy=True)
                split_value = _const_weight_or_none(split_node)
                layer_attrs = {
                    'num_or_sections': split_value.tolist(),
                    'axis': axis,
                }
            except:
                layer_attrs = {
                    'num_or_sections': split_num,
                    'axis': axis,
                }
            outputs_list = list()
            for i in range(len(node.layer.output)):
                if hasattr(node, 'index'):
                    outputs_list.append("{}_p{}".format(node.layer_name, i))
                else:
                    outputs_list.append("{}".format(node.layer_name))
            if split_num > 1:
                self.paddle_graph.add_layer(
                    'paddle.split',
                    inputs={"x": val_x.name},
                    outputs=outputs_list,
                    **layer_attrs)
            else:
                self.paddle_graph.add_layer(
                    "paddle.cast",
                    inputs={"x": val_x.name},
                    outputs=outputs_list,
                    dtype=string(val_x.dtype))

        else:
            layer_attrs = {
                'num_or_sections': split,
                'axis': axis,
            }
            outputs_list = list()
            if isinstance(split, list) or isinstance(split, tuple):
                if len(split) == 1:
                    outputs_list.append(node.name)
                else:
                    for i in range(len(split)):
                        outputs_list.append("{}_p{}".format(node.layer_name, i))
            else:
                outputs_list.append(node.name)
            if len(split) > 1:
                self.paddle_graph.add_layer(
                    'paddle.split',
                    inputs={"x": val_x.name},
                    outputs=outputs_list,
                    **layer_attrs)
            else:
                self.paddle_graph.add_layer(
                    "paddle.cast",
                    inputs={"x": val_x.name},
                    outputs=outputs_list,
                    dtype=string(val_x.dtype))

    @print_mapping_info
    def Reshape(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_shape = self.graph.get_input_node(node, idx=1, copy=True)
        val_reshaped = self.graph.get_node(node.layer.output[0], copy=True)
        shape_value = _const_weight_or_none(val_shape)
        shape_dims = len(val_shape.out_shapes[0])

        if shape_value is not None:
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={'x': val_x.name},
                outputs=[node.name],
                shape=shape_value.tolist())
        elif len(node.out_shapes[0]) > 0 and _is_static_shape(node.out_shapes[
                0]):
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={'x': val_x.name},
                outputs=[node.name],
                shape=node.out_shapes[0])
        else:
            # shape may be [], come form Gather by scalar indices
            if len(val_shape.out_shapes[0]) > 0:
                self.paddle_graph.add_layer(
                    'paddle.reshape',
                    inputs={'x': val_shape.name},
                    outputs=[val_shape.name],
                    shape=val_shape.out_shapes[0])
            if val_shape.dtype != "int32":
                self.paddle_graph.add_layer(
                    'paddle.cast',
                    inputs={'x': val_shape.name},
                    outputs=[val_shape.name],
                    dtype=string("int32"))
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={'x': val_x.name,
                        'shape': val_shape.name},
                outputs=[node.name])

    @print_mapping_info
    def Cast(self, node):
        val_input = self.graph.get_input_node(node, idx=0, copy=True)
        val_output = self.graph.get_node(node.layer.output[0], copy=True)

        dtype = node.get_attr('to')
        if not isinstance(dtype, np.dtype):
            dtype = TENSOR_TYPE_TO_NP_TYPE[dtype]

        output_dtype = val_output.dtype
        if output_dtype:
            assert dtype == output_dtype, 'dtype of to unmatches output'
        self.paddle_graph.add_layer(
            'paddle.cast',
            inputs={'x': val_input.name},
            outputs=[node.name],
            dtype=string(dtype))

    @print_mapping_info
    def Not(self, node):
        val_input = self.graph.get_input_node(node, idx=0, copy=True)
        self.paddle_graph.add_layer(
            'paddle.logical_not',
            inputs={'x': val_input.name},
            outputs=[node.name])

    @print_mapping_info
    def AveragePool(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)

        auto_pad = node.get_attr('auto_pad', 'NOTSET')
        kernel_shape = node.get_attr("kernel_shape")
        poolnd = len(kernel_shape)
        strides = node.get_attr("strides")
        pad_mode = node.get_attr("pads")
        ceil_mode = bool(node.get_attr('ceil_mode', 0))
        pads = node.get_attr('pads', [0] * (poolnd * 2))

        paddings, val_x = self._pad_if_asymmetric(node, pads, val_x)

        if auto_pad == "SAME_UPPER" or auto_pad == "SAME_LOWER":
            input_shape = val_x.out_shapes[0]
            pad_h = _get_same_padding(input_shape[2], kernel_shape[0],
                                      strides[0], auto_pad)
            pad_w = _get_same_padding(input_shape[3], kernel_shape[1],
                                      strides[1], auto_pad)
            paddings = pad_h + pad_w

        op_name = name_generator("pool", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        paddle_op = 'paddle.nn.AvgPool{}D'.format(poolnd)
        assert 1 <= poolnd <= 3, 'only Pool1D, Pool2D and Pool3D are supported'
        layer_attrs = {
            "kernel_size": kernel_shape,
            "stride": strides,
            "padding": paddings,
            "ceil_mode": ceil_mode,
            "exclusive": 'True',
        }
        self.paddle_graph.add_layer(
            paddle_op,
            inputs={'x': val_x if isinstance(val_x, str) else val_x.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def Concat(self, node):
        inputs_list = []
        dtypes = set()
        for i in range(len(node.layer.input)):
            ipt = self.graph.get_input_node(node, idx=i, copy=True)
            inputs_list.append(ipt.name)
            dtypes.add(ipt.dtype)
        if len(dtypes) > 1:
            assert 'Unspported situation happened, please create issue on https://github.com/PaddlePaddle/X2Paddle/issues.'
        axis = node.get_attr('axis')
        self.paddle_graph.add_layer(
            'paddle.concat',
            inputs={"x": inputs_list},
            outputs=[node.name],
            axis=axis)

    @print_mapping_info
    def Flatten(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        output_shape = val_x.out_shapes[0]
        axis = node.get_attr('axis', 1)
        if axis == 0:
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": val_x.name},
                outputs=[node.name],
                shape=[1, -1])
        else:
            if len(output_shape) != 0:
                shape_list = [1, 1]
                for s in output_shape[:axis]:
                    shape_list[0] *= s
                for s in output_shape[axis:]:
                    shape_list[1] *= s
                self.paddle_graph.add_layer(
                    'paddle.reshape',
                    inputs={"x": val_x.name},
                    outputs=[node.name],
                    shape=shape_list)
            else:
                # flatten + reshape
                self.paddle_graph.add_layer(
                    "paddle.flatten",
                    inputs={"input": val_x.name},
                    outputs=[val_x.name + "_flatten"],
                    start_axis=[0],
                    stop_axis=[axis])
                self.paddle_graph.add_layer(
                    'paddle.reshape',
                    inputs={'x': val_x.name + "_flatten"},
                    outputs=[node.name],
                    shape=[0, -1])

    @print_mapping_info
    def Gemm(self, node):
        val_a = self.graph.get_input_node(node, idx=0, copy=True)
        val_b = self.graph.get_input_node(node, idx=1, copy=True)
        val_c = self.graph.get_input_node(node, idx=2, copy=True)

        alpha = node.get_attr('alpha', 1.)  # optional
        beta = node.get_attr('beta', 1.)  # optional
        trans_a = bool(node.get_attr('transA', 0))  # optional
        trans_b = bool(node.get_attr('transB', 0))  # optional
        val_mm = node.name + '_mm'
        matmul_inputs = {"x": val_a.name, "y": val_b.name}
        attr_matmul = {
            "transpose_x": trans_a,
            "transpose_y": trans_b,
        }
        if abs(alpha - 1.0) < 1e-5:
            if abs(beta - 0.0) < 1e-5:
                self.paddle_graph.add_layer(
                    'paddle.matmul',
                    inputs=matmul_inputs,
                    outputs=[node.name],
                    **attr_matmul)
            else:
                self.paddle_graph.add_layer(
                    'paddle.matmul',
                    inputs=matmul_inputs,
                    outputs=[val_mm],
                    **attr_matmul)
                if abs(beta - 1.0) < 1e-5:
                    add_inputs = {"x": val_mm, "y": val_c.name}
                    self.paddle_graph.add_layer(
                        "paddle.add", inputs=add_inputs, outputs=[node.name])
                else:
                    var_beta = node.name + '_beta'
                    self.paddle_graph.add_layer(
                        "paddle.scale",
                        inputs={"x": val_c.name},
                        outputs=[var_beta],
                        scale=beta)
                    add_inputs = {"x": val_mm, "y": var_beta}
                    self.paddle_graph.add_layer(
                        "paddle.add", inputs=add_inputs, outputs=[node.name])
        else:
            if abs(beta - 0.0) < 1e-5:
                self.paddle_graph.add_layer(
                    'paddle.matmul',
                    inputs=matmul_inputs,
                    outputs=[val_mm],
                    **attr_matmul)
                self.paddle_graph.add_layer(
                    "paddle.scale",
                    inputs={"x": val_mm},
                    outputs=[node.name],
                    scale=alpha)
            else:
                self.paddle_graph.add_layer(
                    'paddle.matmul',
                    inputs=[matmul_inputs],
                    outputs=[val_mm],
                    **attr_matmul)
                self.paddle_graph.add_layer(
                    "paddle.scale",
                    inputs={"x": val_mm},
                    outputs=[val_mm],
                    scale=alpha)
                if abs(beta - 1.0) < 1e-5:
                    add_inputs = {"x": val_mm, "y": val_c.name}
                    self.paddle_graph.add_layer(
                        "paddle.add", inputs=add_inputs, outputs=[node.name])
                else:
                    var_beta = node.name + '_beta'
                    self.paddle_graph.add_layer(
                        "paddle.scale",
                        inputs={"x": val_c.name},
                        outputs=[var_beta],
                        scale=beta)
                    add_inputs = {"x": val_mm, "y": var_beta}
                    self.paddle_graph.add_layer(
                        "paddle.add", inputs=add_inputs, outputs=[node.name])

    @print_mapping_info
    def Sum(self, node):
        val_inps = node.layer.input
        inputs_dict = {
            "x": self.graph.get_input_node(
                node, idx=0, copy=True).name,
            "y": self.graph.get_input_node(
                node, idx=1, copy=True).name,
        }
        self.paddle_graph.add_layer(
            "paddle.add", inputs=inputs_dict, outputs=[node.name])

        for idx, ipt in enumerate(val_inps[2:]):
            y = self.graph.get_input_node(node, idx=idx, copy=True)
            inputs_dict = {
                "x": node.name,
                "y": y.name,
            }
            self.paddle_graph.add_layer(
                "paddle.add", inputs=inputs_dict, outputs=[node.name])

    @print_mapping_info
    def MatMul(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_input_node(node, idx=1, copy=True)
        x_shape = val_x.out_shapes[0]
        y_shape = val_y.out_shapes[0]
        inputs_dict = {"x": val_x.name, "y": val_y.name}
        if len(y_shape) != 0 and y_shape[0] == 1 and len(
                x_shape) != 0 and x_shape[-1] != 1 and x_shape[0] != 1:
            y_squeeze = val_y.name + '_squeeze'
            self.paddle_graph.add_layer(
                "paddle.squeeze",
                inputs={"x": val_y.name},
                outputs=[y_squeeze],
                axis=[0])
            inputs_dict['y'] = y_squeeze
            self.paddle_graph.add_layer(
                "paddle.matmul", inputs=inputs_dict, outputs=[node.name])
        else:
            self.paddle_graph.add_layer(
                "paddle.matmul", inputs=inputs_dict, outputs=[node.name])

    @print_mapping_info
    def BatchNormalization(self, node):
        op_name = name_generator("batchnorm", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_scale = self.graph.get_input_node(node, idx=1, copy=True)
        val_b = self.graph.get_input_node(node, idx=2, copy=True)
        val_mean = self.graph.get_input_node(node, idx=3, copy=True)
        val_var = self.graph.get_input_node(node, idx=4, copy=True)

        momentum = node.get_attr('momentum', .9)
        epsilon = node.get_attr('epsilon', 1e-5)
        c = val_x.out_shapes[0][1]

        # solved the same data is used as an argument to multiple OPs.
        _rename_or_remove_weight(
            self.weights,
            val_scale.name,
            op_name + '.weight',
            rename_mapper=self.rename_mapper)
        _rename_or_remove_weight(
            self.weights,
            val_b.name,
            op_name + '.bias',
            rename_mapper=self.rename_mapper)
        _rename_or_remove_weight(
            self.weights,
            val_var.name,
            op_name + '._variance',
            rename_mapper=self.rename_mapper)
        _rename_or_remove_weight(
            self.weights,
            val_mean.name,
            op_name + '._mean',
            rename_mapper=self.rename_mapper)

        # Attribute: spatial is used in BatchNormalization-1,6,7
        spatial = bool(node.get_attr('spatial'))
        layer_attrs = {
            "num_channels": c,
            "momentum": momentum,
            "epsilon": epsilon,
            "is_test": True,
            "use_global_stats": False,
        }
        self.paddle_graph.add_layer(
            "paddle.nn.BatchNorm",
            inputs={"x": val_x.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def Transpose(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        s_len = len(val_x.out_shapes[0])
        perm_default = list(range(s_len))
        perm_default.reverse()
        perm = node.get_attr('perm', perm_default)
        self.paddle_graph.add_layer(
            "paddle.transpose",
            inputs={"x": val_x.name},
            outputs=[node.name],
            perm=perm)

    @print_mapping_info
    def PRelu(self, node):
        op_name = name_generator("prelu", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_slope = self.graph.get_input_node(node, idx=1, copy=True)

        mode = 'channel'
        shape_slope = val_slope.out_shapes[0]
        if shape_slope == [1] * len(shape_slope):
            mode = 'all'

        if mode == "element":
            self.paddle_graph.add_layer(
                "paddle.zeros",
                inputs={},
                outputs=[output_name + "__zeros"],
                shape=shape_slope,
                dtype=string(node.dtype))
            self.paddle_graph.add_layer(
                "paddle.maximum",
                inputs={"x": val_x.name,
                        "y": output_name + "__zeros"},
                outputs=[output_name + "__max"])
            self.paddle_graph.add_layer(
                "paddle.minimum",
                inputs={"x": val_x.name,
                        "y": output_name + "__zeros"},
                outputs=[output_name + "__min"])
            self.paddle_graph.add_layer(
                "paddle.multiply",
                inputs={"x": val_slope.name,
                        "y": output_name + "__min"},
                outputs=[output_name + "__mul"])
            self.paddle_graph.add_layer(
                "paddle.add",
                inputs={
                    "x": output_name + "__max",
                    "y": output_name + "__mul"
                },
                outputs=[output_name])
        else:
            if mode == 'channel':
                slope_data = _const_weight_or_none(val_slope)
                if slope_data is None:
                    self.paddle_graph.add_layer(
                        "paddle.reshape",
                        inputs={"x": val_slope.name},
                        outputs=[val_slope.name],
                        shape=[shape_slope[0]])
                    self.paddle_graph.add_layer(
                        "paddle.nn.functional.prelu",
                        inputs={"x": val_x.name,
                                "weight": val_slope.name},
                        outputs=[node.name])
                    return
                _rename_or_remove_weight(self.weights, val_slope.name)
                if len(shape_slope) > 1:
                    self.weights[op_name + '._weight'] = np.reshape(
                        slope_data, shape_slope[0])
                num_parameters = val_x.out_shapes[0][1]
            else:
                num_parameters = 1
                slope_data = self.weights[val_slope.name]
                _rename_or_remove_weight(self.weights, val_slope.name)
                self.weights[op_name + '._weight'] = np.reshape(slope_data, [1])
            self.paddle_graph.add_layer(
                "paddle.nn.PReLU",
                inputs={"x": val_x.name},
                outputs=layer_outputs,
                num_parameters=num_parameters)

    @print_mapping_info
    def Squeeze(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        axes = node.get_attr('axes')
        if axes is None:
            axes_node = self.graph.get_input_node(node, idx=1, copy=True)
            axes = _const_weight_or_none(axes_node, necessary=True)
        # deal with scalar(0D) tensor
        if len(val_x.out_shapes[0]) <= 1 and len(axes) == 1 and axes[0] == 0:
            self.paddle_graph.add_layer(
                "paddle.cast",
                inputs={"x": val_x.name},
                outputs=[node.name],
                dtype=string(val_x.dtype))
        else:
            self.paddle_graph.add_layer(
                "paddle.squeeze",
                inputs={"x": val_x.name},
                outputs=[node.name],
                axis=axes)

    @print_mapping_info
    def Equal(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_input_node(node, idx=1, copy=True)
        self.paddle_graph.add_layer(
            "paddle.equal",
            inputs={'x': val_x.name,
                    'y': val_y.name},
            outputs=[node.name])

    @print_mapping_info
    def Greater(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_y = self.graph.get_input_node(node, idx=1, copy=True)
        self.paddle_graph.add_layer(
            "paddle.greater_than",
            inputs={'x': val_x.name,
                    'y': val_y.name},
            outputs=[node.name])

    @print_mapping_info
    def Where(self, node):
        condition = self.graph.get_input_node(node, idx=0, copy=True)
        val_x = self.graph.get_input_node(node, idx=1, copy=True)
        val_y = self.graph.get_input_node(node, idx=2, copy=True)

        self.paddle_graph.add_layer(
            "paddle.where",
            inputs={
                'condition': condition.name,
                'x': val_x.name,
                'y': val_y.name
            },
            outputs=[node.name])

    @print_mapping_info
    def NonZero(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        self.paddle_graph.add_layer(
            "paddle.nonzero",
            inputs={"x": val_x.name},
            outputs=[val_x.name],
            as_tuple=False)
        self.paddle_graph.add_layer(
            'paddle.transpose',
            inputs={"x": val_x.name},
            outputs=[node.name],
            perm=[1, 0])

    @print_mapping_info
    def Identity(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        self.paddle_graph.add_layer(
            "paddle.assign", inputs={"x": val_x.name}, outputs=[node.name])

    @print_mapping_info
    def Tile(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_repeats = self.graph.get_input_node(node, idx=1, copy=True)
        repeats = _const_weight_or_none(val_repeats)

        if repeats is None:
            repeats = val_repeats.name
            if val_repeats.dtype != 'int32':
                self.paddle_graph.add_layer(
                    "paddle.cast",
                    inputs={"x": repeats},
                    outputs=["{}_tmp".format(repeats)],
                    dtype=string("int32"))
                repeats = "{}_tmp".format(repeats)

        elif isinstance(repeats, int):
            repeats = [repeats]

        elif type(repeats) is np.ndarray:
            repeats = repeats.tolist()

        attr = {
            'expand_times': repeats,
            "name": string(node.name),
        }
        self.paddle_graph.add_layer(
            "paddle.tile",
            inputs={"x": val_x.name},
            outputs=[node.name],
            repeat_times=repeats)

    @print_mapping_info
    def MaxPool(self, node):
        op_name = name_generator("pool", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        auto_pad = node.get_attr('auto_pad', 'NOTSET')
        assert node.get_attr(
            "dilations") is None, 'only dilations = 0 is supported'  # optional

        kernel_shape = node.get_attr("kernel_shape")
        poolnd = len(kernel_shape)
        strides = node.get_attr("strides")
        pad_mode = node.get_attr("pads")
        ceil_mode = bool(node.get_attr('ceil_mode', 0))  # optional
        pads = node.get_attr('pads', [0] * (poolnd * 2))  # optional
        paddle_op = 'paddle.nn.MaxPool{}D'.format(poolnd)
        assert 1 <= poolnd <= 3, 'only Pool1D, Pool2D and Pool3D are supported'

        paddings, val_x = self._pad_if_asymmetric(node, pads, val_x)

        if auto_pad == "SAME_UPPER" or auto_pad == "SAME_LOWER":
            input_shape = val_x.out_shapes[0]
            pad_h = _get_same_padding(input_shape[2], kernel_shape[0],
                                      strides[0], auto_pad)
            pad_w = _get_same_padding(input_shape[3], kernel_shape[1],
                                      strides[1], auto_pad)
            paddings = pad_h + pad_w

        layer_attrs = {
            "kernel_size": kernel_shape,
            "stride": strides,
            "padding": paddings,
            "ceil_mode": ceil_mode,
        }
        self.paddle_graph.add_layer(
            paddle_op,
            inputs={'x': val_x if isinstance(val_x, str) else val_x.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def GlobalMaxPool(self, node):
        op_name = name_generator("pool", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        input_shape = val_x.out_shapes[0]
        if len(input_shape) == 4:
            poolnd = 2
        elif len(input_shape) == 5:
            poolnd = 3
        elif len(input_shape) == 3:
            poolnd = 1
        paddle_op = 'paddle.nn.AdaptiveMaxPool{}D'.format(poolnd)
        assert 1 <= poolnd <= 3, 'only Pool1D, Pool2D and Pool3D are supported'
        output_shape = node.out_shapes[0]
        self.paddle_graph.add_layer(
            paddle_op,
            inputs={'x': val_x.name},
            outputs=layer_outputs,
            output_size=output_shape[2:])

    @print_mapping_info
    def Neg(self, node):
        import paddle
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        v0, v1, v2 = paddle.__version__.split('.')
        if int(v0) >= 2 and int(v1) >= 2:
            self.paddle_graph.add_layer(
                "paddle.neg", inputs={'x': val_x.name}, outputs=[node.name])
        else:
            val_y = node.name + "_y"
            dtype = np.dtype(val_x.dtype)
            self.paddle_graph.add_layer(
                "paddle.full",
                inputs={},
                outputs=[val_y],
                dtype=string(dtype),
                shape=[1],
                fill_value=-1)
            self.paddle_graph.add_layer(
                "paddle.multiply",
                inputs={'x': val_x.name,
                        'y': val_y},
                outputs=[node.name])

    @print_mapping_info
    def SpaceToDepth(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        blocksize = node.get_attr('blocksize')
        val_x_shape = val_x.out_shapes[0]
        b, c, h, w = val_x_shape
        self.paddle_graph.add_layer(
            'paddle.reshape',
            inputs={"x": val_x.name},
            outputs=[node.name],
            shape=[b, c, h // blocksize, blocksize, w // blocksize, blocksize])
        self.paddle_graph.add_layer(
            'paddle.transpose',
            inputs={"x": node.name},
            outputs=[node.name],
            perm=[0, 3, 5, 1, 2, 4])
        self.paddle_graph.add_layer(
            'paddle.reshape',
            inputs={"x": node.name},
            outputs=[node.name],
            shape=[b, c * (blocksize**2), h // blocksize, w // blocksize])

    @print_mapping_info
    def GatherElements(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        indices = self.graph.get_input_node(node, idx=1, copy=True)
        axis = node.get_attr('axis')
        val_x_shape = val_x.out_shapes[0]
        indices_shape = indices.out_shapes[0]
        axis = axis if axis >= 0 else axis + len(val_x_shape)
        if axis == 0:
            axis_perm = [i for i in range(len(val_x_shape))]
            data_swaped = val_x.name
            index_swaped = indices.name
        else:
            axis_perm = [i for i in range(len(val_x_shape))]
            axis_perm[axis] = 0
            axis_perm[0] = axis
            data_swaped = val_x.name + "_transpose"
            self.paddle_graph.add_layer(
                "paddle.transpose",
                inputs={'x': val_x.name},
                perm=axis_perm,
                outputs=[data_swaped])
            index_swaped = indices.name + "_transpose"
            self.paddle_graph.add_layer(
                "paddle.transpose",
                inputs={'x': indices.name},
                perm=axis_perm,
                outputs=[index_swaped])
            temp = indices_shape[0]
            indices_shape[0] = indices_shape[axis]
            indices_shape[axis] = temp

        idx_tensors_per_axis_pre = [
            indices_shape[i] for i in range(len(indices_shape))
        ]
        name_list = list()
        for i in range(len(idx_tensors_per_axis_pre)):
            tensor_name = val_x.name + "_meshgrid_" + str(i)
            self.paddle_graph.add_layer(
                kernel="paddle.linspace",
                inputs={},
                outputs=[tensor_name],
                start=0,
                stop=idx_tensors_per_axis_pre[i] - 1,
                num=idx_tensors_per_axis_pre[i])
            name_list.append(tensor_name)

        self.paddle_graph.add_layer(
            "paddle.meshgrid", inputs=name_list, outputs=name_list)

        self.paddle_graph.add_layer(
            "paddle.cast",
            inputs={"x": index_swaped},
            outputs=[index_swaped],
            dtype=string("float32"))
        import copy
        copy_name_list = copy.copy(name_list)
        copy_name_list[0] = index_swaped
        new_name_list = list()
        for i in range(len(copy_name_list)):
            unsqueeze_name = copy_name_list[i] + "_unsqueeze"
            self.paddle_graph.add_layer(
                "paddle.unsqueeze",
                inputs={"x": copy_name_list[i]},
                axis=-1,
                outputs=[unsqueeze_name])
            new_name_list.append(unsqueeze_name)
        concat_name = val_x.name + "_concated_layer"
        self.paddle_graph.add_layer(
            "paddle.concat",
            inputs={'x': new_name_list},
            axis=-1,
            outputs=[concat_name])
        self.paddle_graph.add_layer(
            "paddle.cast",
            inputs={"x": concat_name},
            outputs=[concat_name],
            dtype=string("int32"))
        gather_nd_name = "gather_nd_layer"
        self.paddle_graph.add_layer(
            "paddle.gather_nd",
            inputs={'x': data_swaped,
                    "index": concat_name},
            outputs=[gather_nd_name])

        self.paddle_graph.add_layer(
            "paddle.transpose",
            inputs={'x': gather_nd_name},
            perm=axis_perm,
            outputs=[node.name])

    @print_mapping_info
    def GlobalAveragePool(self, node):
        op_name = name_generator("pool", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        input_shape = val_x.out_shapes[0]
        if len(input_shape) == 4:
            poolnd = 2
        elif len(input_shape) == 5:
            poolnd = 3
        elif len(input_shape) == 3:
            poolnd = 1
        paddle_op = 'paddle.nn.AdaptiveAvgPool{}D'.format(poolnd)
        assert 1 <= poolnd <= 3, 'only Pool1D, Pool2D and Pool3D are supported'
        output_shape = node.out_shapes[0]
        self.paddle_graph.add_layer(
            paddle_op,
            inputs={'x': val_x.name},
            outputs=layer_outputs,
            output_size=output_shape[2:])

    @print_mapping_info
    def Conv(self, node):
        output_name = node.name
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_w = self.graph.get_input_node(node, idx=1, copy=True)

        if val_w.name in self.weights.keys():
            op_name = name_generator("conv", self.nn_name2id)
        else:
            op_name = output_name

        layer_outputs = [op_name, output_name]

        has_bias = len(node.layer.input) == 3
        if has_bias:
            val_b = self.graph.get_input_node(node, idx=2, copy=True)
        auto_pad = node.get_attr('auto_pad', 'NOTSET')

        kernel_shape = node.get_attr('kernel_shape')
        convnd = len(kernel_shape)
        assert 2 <= convnd <= 3, 'only Conv2D and Conv3D is supported'
        num_out_channels = val_w.out_shapes[0][0]
        num_in_channels = val_w.out_shapes[0][1]
        paddle_op = 'paddle.nn.Conv{}D'.format(convnd)

        num_groups = node.get_attr('group', 1)
        strides = node.get_attr('strides', [1] * convnd)
        dilations = node.get_attr('dilations', [1] * convnd)
        pads = node.get_attr('pads', [0] * (convnd * 2))

        input_shape = val_x.out_shapes[0]
        paddings = np.array(pads).reshape((2, -1)).transpose().astype("int32")
        paddings = paddings.flatten().tolist()

        if auto_pad in ["SAME_UPPER", "SAME_LOWER"]:
            # Warning: SAME_UPPER and SAME_LOWER does not yet support dynamic shapes
            if input_shape[2] == -1 or input_shape[3] == -1:
                _logger.warning(
                    'SAME_UPPER and SAME_LOWER does not yet support dynamic shapes, the conversion result may have a diff!!!'
                )
            pad_h = _get_same_padding(input_shape[2], kernel_shape[0],
                                      strides[0], auto_pad)
            pad_w = _get_same_padding(input_shape[3], kernel_shape[1],
                                      strides[1], auto_pad)
            paddings = pad_h + pad_w

        layer_inputs = {'x': val_x if isinstance(val_x, str) else val_x.name}
        if val_w.name not in self.weights.keys():
            layer_attrs = {
                "stride": strides,
                "padding": paddings,
                "dilation": dilations,
                "groups": num_groups,
            }
            layer_inputs['weight'] = val_w.name
            if has_bias:
                layer_inputs['bias'] = val_b.name

            paddle_op = 'paddle.nn.functional.conv{}d'.format(convnd)
            self.paddle_graph.add_layer(
                paddle_op,
                inputs=layer_inputs,
                outputs=[node.name],
                **layer_attrs)
            return

        layer_attrs = {
            "in_channels": num_in_channels * num_groups,
            "out_channels": num_out_channels,
            "kernel_size": kernel_shape,
            "stride": strides,
            "padding": paddings,
            "dilation": dilations,
            "groups": num_groups,
        }
        remove_weight = True if val_w.name in self.done_weight_list else False
        if remove_weight:
            self.done_weight_list.append(val_w.name)
        _rename_or_remove_weight(
            self.weights,
            val_w.name,
            op_name + '.weight',
            remove_weight,
            rename_mapper=self.rename_mapper)
        if has_bias:
            remove_bias = True if val_b.name in self.done_weight_list else False
            if remove_bias:
                self.done_weight_list.append(val_b.name)
            _rename_or_remove_weight(
                self.weights,
                val_b.name,
                op_name + '.bias',
                remove_bias,
                rename_mapper=self.rename_mapper)
        else:
            layer_attrs["bias_attr"] = False
        # deal with dynamic shape
        if len(input_shape) == 0:
            self.paddle_graph.add_layer(
                "paddle.reshape",
                inputs=layer_inputs,
                outputs=[layer_inputs["x"]],
                shape=[0, num_in_channels * num_groups, 0, -1])
        if len(input_shape) != 0 and reduce(
                lambda x, y: x * y,
                input_shape) in [1, -1] and 1 not in input_shape:
            input_shape[1] = num_in_channels * num_groups
            input_shape[0] = 0
            input_shape[2] = 0
            self.paddle_graph.add_layer(
                "paddle.reshape",
                inputs=layer_inputs,
                outputs=[layer_inputs["x"]],
                shape=input_shape)
        self.paddle_graph.add_layer(
            paddle_op,
            inputs=layer_inputs,
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def ConvTranspose(self, node):
        output_name = node.name
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_w = self.graph.get_input_node(node, idx=1, copy=True)

        if val_w.name in self.weights.keys():
            op_name = name_generator("conv_trans", self.nn_name2id)
        else:
            op_name = output_name

        layer_outputs = [op_name, output_name]

        val_b = None
        if len(node.layer.input) > 2:
            val_b = self.graph.get_input_node(node, idx=2, copy=True)
        auto_pad = node.get_attr('auto_pad', 'NOTSET')
        out_padding = node.get_attr('output_padding', [0, 0])
        kernel_shape = node.get_attr('kernel_shape')
        assert kernel_shape, 'kernel_shape not inferred'
        convnd = len(kernel_shape)
        assert 2 <= convnd <= 3, 'only Conv2DTranspose and Conv3DTranspose supported'
        num_in_channels = val_w.out_shapes[0][0]
        num_out_channels = val_w.out_shapes[0][1]
        paddle_op = 'paddle.nn.Conv{}DTranspose'.format(convnd)

        num_groups = node.get_attr('group', 1)
        strides = node.get_attr('strides', [1] * convnd)
        dilations = node.get_attr('dilations', [1] * convnd)
        output_size = node.get_attr('output_shape', [])
        pads = node.get_attr('pads', [0] * (convnd * 2))

        paddings = np.array(pads).reshape((2, -1)).transpose().astype("int32")
        paddings = paddings.flatten().tolist()

        if len(output_size) != 0:
            paddings = [0] * 4
            total_paddings = list()
            total_paddings.append((val_x.out_shapes[0][2] - 1) * strides[
                0] + dilations[0] * (kernel_shape[0] - 1) + 1 + out_padding[0] -
                                  output_size[0])
            total_paddings.append((val_x.out_shapes[0][3] - 1) * strides[
                1] + dilations[1] * (kernel_shape[1] - 1) + 1 + out_padding[1] -
                                  output_size[1])
            if auto_pad == "SAME_UPPER":
                for i in range(len(total_paddings)):
                    paddings[2 * i] = total_paddings[0] - total_paddings[0] // 2
                    paddings[2 * i + 1] = total_paddings[0] // 2
            else:
                for i in range(len(total_paddings)):
                    paddings[2 * i] = total_paddings[0] // 2
                    paddings[2 * i + 1] = total_paddings[0] - total_paddings[
                        0] // 2
        else:
            output_size = [0, 0]

            output_size[0] = (
                val_x.out_shapes[0][2] - 1
            ) * strides[0] - 2 * paddings[0] + dilations[0] * (
                kernel_shape[0] - 1) + 1 + out_padding[0]
            output_size[1] = (
                val_x.out_shapes[0][3] - 1
            ) * strides[1] - 2 * paddings[1] + dilations[1] * (
                kernel_shape[1] - 1) + 1 + out_padding[1]

        # Conv2DTranspose缺少output_size，只能在forward里头传进output_size
        inputs_dict = {'x': val_x if isinstance(val_x, str) else val_x.name}
        if val_w.name not in self.weights.keys():
            layer_attrs = {
                "stride": strides,
                "dilation": dilations,
                "padding": paddings,
                "groups": num_groups,
                "output_padding": out_padding
            }
            paddle_op = 'paddle.nn.functional.conv{}d_transpose'.format(convnd)

            inputs_dict['weight'] = val_w.name
            if len(node.layer.input) > 2:
                inputs_dict['bias'] = val_b.name

            self.paddle_graph.add_layer(
                paddle_op,
                inputs=inputs_dict,
                outputs=[node.name],
                **layer_attrs)
            return

        layer_attrs = {
            "in_channels": num_in_channels,
            "out_channels": num_out_channels * num_groups,
            "kernel_size": kernel_shape,
            "stride": strides,
            "dilation": dilations,
            "padding": paddings,
            "groups": num_groups,
            "output_padding": out_padding
        }

        _rename_or_remove_weight(
            self.weights,
            val_w.name,
            op_name + '.weight',
            rename_mapper=self.rename_mapper)
        if val_b is not None:
            _rename_or_remove_weight(
                self.weights,
                val_b.name,
                op_name + '.bias',
                rename_mapper=self.rename_mapper)
        else:
            layer_attrs["bias_attr"] = False
        self.paddle_graph.add_layer(
            kernel=paddle_op,
            inputs=inputs_dict,
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def ArgMax(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        axis = node.get_attr('axis')
        keepdims = False if node.get_attr('keepdims') == 0 else True
        layer_attrs = {'axis': axis, 'keepdim': keepdims}
        self.paddle_graph.add_layer(
            'paddle.argmax',
            inputs={"x": val_x.name},
            outputs=[node.name],
            **layer_attrs)

    @print_mapping_info
    def Size(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        self.paddle_graph.add_layer(
            "paddle.shape", inputs={"input": val_x.name}, outputs=[node.name])
        self.paddle_graph.add_layer(
            'paddle.cast',
            inputs={"x": node.name},
            outputs=[node.name],
            dtype=string('int64'))
        self.paddle_graph.add_layer(
            "paddle.prod", inputs={"x": node.name}, outputs=[node.name])

    @print_mapping_info
    def Sign(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        if node.dtype not in ["float16", "float32", "float64"]:
            self.paddle_graph.add_layer(
                "paddle.cast",
                inputs={"x": val_x.name},
                outputs=[val_x.name],
                dtype=string("float32"))
        self.paddle_graph.add_layer(
            "paddle.sign", inputs={"x": val_x.name}, outputs=[node.name])
        if node.dtype not in ["float16", "float32", "float64"]:
            self.paddle_graph.add_layer(
                "paddle.cast",
                inputs={"x": node.name},
                outputs=[node.name],
                dtype=string(node.dtype))

    @print_mapping_info
    def OneHot(self, node):
        nn_op_name = name_generator("onehot", self.nn_name2id)
        output_name = node.name
        layer_outputs = [nn_op_name, output_name]
        indices = self.graph.get_input_node(node, idx=0, copy=True)
        depth = self.graph.get_input_node(node, idx=1, copy=True)
        values = self.graph.get_input_node(node, idx=2, copy=True)
        axis = node.get_attr('axis', -1)
        self.paddle_graph.add_layer(
            "custom_layer:OneHot",
            inputs={
                "indices": indices.name,
                "depth": depth.name,
                "values": values.name
            },
            outputs=layer_outputs,
            axis=axis)

    @print_mapping_info
    def Reciprocal(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        self.paddle_graph.add_layer(
            "paddle.reciprocal", inputs={"x": val_x.name}, outputs=[node.name])

    @print_mapping_info
    def LSTM(self, node):
        x = self.graph.get_input_node(node, idx=0, copy=True)
        input_weight = self.graph.get_input_node(node, idx=1, copy=True)
        hidden_weight = self.graph.get_input_node(node, idx=2, copy=True)

        input_nums = len(node.layer.input)
        exist_input_nums = 3
        have_bias = False
        if input_nums > 3 and node.layer.input[3] != '':
            bias = self.graph.get_input_node(
                node, idx=exist_input_nums, copy=True)
            have_bias = True
            exist_input_nums += 1
        if input_nums > 4 and node.layer.input[4] != '':
            sequence_lens = self.graph.get_input_node(
                node, idx=exist_input_nums, copy=True)
            exist_input_nums += 1
        if input_nums > 5 and node.layer.input[5] != '':
            init_h = self.graph.get_input_node(
                node, idx=exist_input_nums, copy=True)
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": init_h.name},
                outputs=[init_h.name],
                shape=init_h.out_shapes[0])
            exist_input_nums += 1
        if input_nums > 6 and node.layer.input[6] != '':
            init_c = self.graph.get_input_node(
                node, idx=exist_input_nums, copy=True)
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": init_c.name},
                outputs=[init_c.name],
                shape=init_c.out_shapes[0])

        input_weight_np = _const_weight_or_none(input_weight)
        _rename_or_remove_weight(self.weights, input_weight.name)
        hidden_size = node.get_attr('hidden_size', input_weight_np.shape[1] / 4)
        input_size = input_weight_np.shape[2]
        hidden_weight_np = _const_weight_or_none(hidden_weight)
        _rename_or_remove_weight(self.weights, hidden_weight.name)
        bias_np = _const_weight_or_none(bias)
        _rename_or_remove_weight(self.weights, bias.name)
        input_bias_np = bias_np[:, :4 * hidden_size]
        hidden_bias_np = bias_np[:, 4 * hidden_size:]

        # parameters order in paddle:lstm:
        # 1. gate order in paddle is: input, forget, cell, output.
        # 2. gate orfer in onnx is: input, output, forget, cell.

        def reform_weights(w, n, intervals):
            slices = [w[:, x * n:y * n] for x, y in intervals]
            return np.concatenate(slices, axis=1)

        def transform_weight_with_bias(weights, n, intervals):
            return [reform_weights(w, n, intervals) for w in weights]

        reform_permutation = [(0, 1), (2, 4), (1, 2)]

        weights = transform_weight_with_bias(
            [input_weight_np, hidden_weight_np, input_bias_np, hidden_bias_np],
            hidden_size, reform_permutation)

        op_name = name_generator("lstm", self.nn_name2id)
        y_out = node.output(0)
        yh_out = node.output(1)
        yc_out = node.output(2)
        direction = node.get_attr('direction', 'forward')

        def generate_paddle_param_names(op_name, suffix=''):
            param_names = []
            param_names.extend(['{}.weight_ih_l0{}', '{}.weight_hh_l0{}'])
            if have_bias != False: param_names.append('{}.bias_ih_l0{}')
            if have_bias != False: param_names.append('{}.bias_hh_l0{}')
            param_names = [x.format(op_name, suffix) for x in param_names]
            return param_names

        def assign_params(op_name, weights, weight_idx=0, suffix=''):
            param_names = generate_paddle_param_names(op_name, suffix)
            for param_name, weight in zip(param_names, weights):
                self.weights[param_name] = weight[weight_idx]

        if direction == 'backward':
            raise Exception(
                "LSTM support 'forward' or 'bidirectional', except '{}'.".
                format(direction))
        else:
            assign_params(op_name, weights)
            if direction == 'bidirectional':
                assign_params(op_name, weights, 1, '_reverse')

        self.paddle_graph.add_layer(
            'paddle.nn.LSTM',
            inputs={
                'input': x.name,
                'initial_states': (init_h.name, init_c.name)
            },
            outputs=[op_name, y_out, yh_out, yc_out],
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=1,
            direction=string(direction),
            time_major=True)

        self.paddle_graph.add_layer(
            'paddle.reshape',
            inputs={"x": y_out},
            outputs=[y_out],
            shape=[0, 0, -1, hidden_size])
        self.paddle_graph.add_layer(
            'paddle.transpose',
            inputs={"x": y_out},
            outputs=[y_out],
            perm=[0, 2, 1, 3])

    @print_mapping_info
    def TopK(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        val_k = self.graph.get_input_node(node, idx=1, copy=True)
        layer_attrs = dict()
        layer_attrs["axis"] = node.get_attr('axis', -1)
        layer_attrs["largest"] = True if node.get_attr('largest',
                                                       1) == 1 else False
        layer_attrs["sorted"] = True if node.get_attr('sorted',
                                                      1) == 1 else False
        k = _const_weight_or_none(val_k)
        if isinstance(k, (list, tuple, np.ndarray)):
            k = k[0]
        # If k can get the value directly, it is used as an attribute; otherwise it is used as an input tensor
        if k is not None:
            layer_attrs["k"] = k
            self.paddle_graph.add_layer(
                "paddle.topk",
                inputs={"x": val_x.name},
                outputs=[
                    "{}_p{}".format(node.layer_name, 0),
                    "{}_p{}".format(node.layer_name, 1)
                ],
                **layer_attrs)
        else:
            if val_k.dtype != "int32":
                self.paddle_graph.add_layer(
                    "paddle.cast",
                    inputs={"x": val_k.name},
                    outputs=[val_k.name],
                    dtype=string('int32'))
            self.paddle_graph.add_layer(
                "paddle.topk",
                inputs={"x": val_x.name,
                        "k": val_k.name},
                outputs=[
                    "{}_p{}".format(node.layer_name, 0),
                    "{}_p{}".format(node.layer_name, 1)
                ],
                **layer_attrs)

    @print_mapping_info
    def LRN(self, node):
        op_name = name_generator("lrn", self.nn_name2id)
        output_name = node.name
        layer_outputs = [op_name, output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        alpha = node.get_attr('alpha', 0.0001)
        beta = node.get_attr('beta', 0.75)
        bias = node.get_attr('bias', 1.0)
        size = node.get_attr('size')
        layer_attrs = {'size': size, 'alpha': alpha, 'beta': beta, 'k': bias}
        self.paddle_graph.add_layer(
            "paddle.nn.LocalResponseNorm",
            inputs={"x": val_x.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def DepthToSpace(self, node):
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        blocksize = node.get_attr('blocksize')
        mode = node.get_attr('mode', "DCR")
        val_x_shape = val_x.out_shapes[0]
        b, c, h, w = val_x_shape
        if mode == "DCR":
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": val_x.name},
                outputs=[node.name],
                shape=[b, blocksize, blocksize, c // (blocksize**2), h, w])
            self.paddle_graph.add_layer(
                'paddle.transpose',
                inputs={"x": node.name},
                outputs=[node.name],
                perm=[0, 3, 4, 1, 5, 2])
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": node.name},
                outputs=[node.name],
                shape=[b, c // (blocksize**2), h * blocksize, w * blocksize])
        else:
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": val_x.name},
                outputs=[node.name],
                shape=[b, c // (blocksize**2), blocksize, blocksize, h, w])
            self.paddle_graph.add_layer(
                'paddle.transpose',
                inputs={"x": node.name},
                outputs=[node.name],
                perm=[0, 1, 4, 2, 5, 3])
            self.paddle_graph.add_layer(
                'paddle.reshape',
                inputs={"x": node.name},
                outputs=[node.name],
                shape=[b, c // (blocksize**2), h * blocksize, w * blocksize])

    @print_mapping_info
    def NonMaxSuppression(self, node):
        nn_op_name = name_generator("nms", self.nn_name2id)
        output_name = node.name
        layer_outputs = [nn_op_name, output_name]
        boxes = self.graph.get_input_node(node, idx=0, copy=True)
        scores = self.graph.get_input_node(node, idx=1, copy=True)
        inputs_len = len(node.layer.input)
        layer_attrs = dict()
        layer_attrs["keep_top_k"] = -1
        layer_attrs["nms_threshold"] = 0.0
        layer_attrs["score_threshold"] = 0.0
        if inputs_len > 2:
            max_output_boxes_per_class = self.graph.get_input_node(
                node, idx=2, copy=True)
            max_output_boxes_per_class = _const_weight_or_none(
                max_output_boxes_per_class)
            if len(scores.out_shapes[0]) != 0:
                num_classes = scores.out_shapes[0][1]
            else:
                num_classes = 1
            if max_output_boxes_per_class is not None:
                max_output_boxes_per_class = max_output_boxes_per_class.tolist()
                if isinstance(max_output_boxes_per_class, int):
                    layer_attrs[
                        "keep_top_k"] = max_output_boxes_per_class * num_classes
                else:
                    layer_attrs["keep_top_k"] = max_output_boxes_per_class[
                        0] * num_classes
        if inputs_len > 3:
            iou_threshold = self.graph.get_input_node(node, idx=3, copy=True)
            layer_attrs["nms_threshold"] = _const_weight_or_none(
                iou_threshold).tolist()[0]
        if inputs_len > 4:
            score_threshold = self.graph.get_input_node(node, idx=4, copy=True)
            layer_attrs["score_threshold"] = _const_weight_or_none(
                score_threshold).tolist()[0]
        self.paddle_graph.add_layer(
            "custom_layer:NMS",
            inputs={"bboxes": boxes.name,
                    "scores": scores.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def ReduceL1(self, node):
        output_name = node.name
        layer_outputs = [output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        axes = node.get_attr('axes')
        keepdims = False if node.get_attr('keepdims') == 0 else True
        layer_attrs = {'p': 1, 'axis': axes, 'keepdim': keepdims}
        self.paddle_graph.add_layer(
            "paddle.norm",
            inputs={"x": val_x.name},
            outputs=layer_outputs,
            **layer_attrs)

    @print_mapping_info
    def ReduceL2(self, node):
        output_name = node.name
        layer_outputs = [output_name]
        val_x = self.graph.get_input_node(node, idx=0, copy=True)
        axes = node.get_attr('axes')
        keepdims = False if node.get_attr('keepdims') == 0 else True
        layer_attrs = {'p': 2, 'axis': axes, 'keepdim': keepdims}
        self.paddle_graph.add_layer(
            "paddle.norm",
            inputs={"x": val_x.name},
            outputs=layer_outputs,
            **layer_attrs)
