diff --git a/paddle/fluid/inference/tensorrt/convert/transpose_op.cc b/paddle/fluid/inference/tensorrt/convert/transpose_op.cc index c6f2d0174eac83c5f8530e019ebd9e239f41f87d..ab85ef992f2264e6e4945619fcd9ed957ccb53a0 100644 --- a/paddle/fluid/inference/tensorrt/convert/transpose_op.cc +++ b/paddle/fluid/inference/tensorrt/convert/transpose_op.cc @@ -9,7 +9,6 @@ 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. */ -#include #include "paddle/fluid/inference/tensorrt/convert/op_converter.h" namespace paddle { @@ -43,32 +42,11 @@ class TransposeOpConverter : public OpConverter { axis[i]--; } } - nvinfer1::Permutation perm; for (int i = 0; i < dims; i++) { int j = engine_->with_dynamic_shape() ? i : i + 1; perm.order[i] = axis[j]; } - - // Permutation is valid if it has nbDims unique values from range [0, - // nbDims-1] - auto is_valid_permutation = [&](int dims, - const nvinfer1::Permutation& permutation) { - std::bitset found; - for (int i = 0; i < dims; ++i) { - const int x = permutation.order[i]; - if ((x < 0) || (x >= dims) || found[x]) - return false; // Out of bounds or duplicate - found.set(x); - } - return true; - }; - - PADDLE_ENFORCE_EQ(is_valid_permutation(dims, perm), true, - platform::errors::InvalidArgument( - "Invalid permutation dimensions for trt transpose op " - "converter: duplicate or out of bound.")); - auto* layer = TRT_ENGINE_ADD_LAYER(engine_, Shuffle, *input); layer->setFirstTranspose(perm); diff --git a/paddle/fluid/inference/tensorrt/op_teller.cc b/paddle/fluid/inference/tensorrt/op_teller.cc index ebb2ecc136f031b5bb6302c915e050c8cb41a424..3067c2893825d3ce2e092959ad24d2ba8b1f2a01 100644 --- a/paddle/fluid/inference/tensorrt/op_teller.cc +++ b/paddle/fluid/inference/tensorrt/op_teller.cc @@ -13,7 +13,7 @@ // limitations under the License. #include "paddle/fluid/inference/tensorrt/op_teller.h" - +#include #include "paddle/fluid/framework/block_desc.h" #include "paddle/fluid/framework/data_layout.h" @@ -316,11 +316,36 @@ bool OpTeller::Tell(const framework::ir::Node* node, bool use_no_calib_int8, if (op_type == "transpose2" || op_type == "transpose") { if (!desc.HasAttr("axis")) { return false; - } else { - std::vector axis = - BOOST_GET_CONST(std::vector, desc.GetAttr("axis")); - if (!with_dynamic_shape && axis[0] != 0) return false; - if (axis.size() >= nvinfer1::Dims::MAX_DIMS) return false; + } + std::vector axis = + BOOST_GET_CONST(std::vector, desc.GetAttr("axis")); + if (!with_dynamic_shape && axis[0] != 0) return false; + if (axis.size() >= nvinfer1::Dims::MAX_DIMS) return false; + if (axis[0] == 0 && axis.size() == 2) return false; + + auto* block = desc.Block(); + auto x_var_name = desc.Input("X")[0]; + auto* x_var_desc = block->FindVar(x_var_name); + const auto x_shape = x_var_desc->GetShape(); + int dims = x_shape.size(); + std::vector perm(nvinfer1::Dims::MAX_DIMS); + for (int i = 0; i < dims; i++) { + perm[i] = axis[i]; + } + auto is_valid_permutation = [&](int dims, + const std::vector& permutation) { + std::bitset found; + for (int i = 0; i < dims; ++i) { + const int x = permutation[i]; + if ((x < 0) || (x >= dims) || found[x]) + return false; // Out of bounds or duplicate + found.set(x); + } + return true; + }; + if (!is_valid_permutation(dims, perm)) { + VLOG(3) << "Invalid permutation dimensions for trt transpose op " + "converter: duplicate or out of bound."; } } if (op_type == "flatten2" || op_type == "flatten") { diff --git a/python/paddle/fluid/tests/unittests/ir/inference/test_trt_convert_transpose.py b/python/paddle/fluid/tests/unittests/ir/inference/test_trt_convert_transpose.py new file mode 100644 index 0000000000000000000000000000000000000000..ad325bb0ab3b0cb90379b3b80f25a0e0bb9e1cb1 --- /dev/null +++ b/python/paddle/fluid/tests/unittests/ir/inference/test_trt_convert_transpose.py @@ -0,0 +1,165 @@ +# Copyright (c) 2021 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 trt_layer_auto_scan_test import TrtLayerAutoScanTest, SkipReasons +from program_config import TensorConfig, ProgramConfig +import numpy as np +import paddle.inference as paddle_infer +from functools import partial +from typing import Optional, List, Callable, Dict, Any, Set + + +class TrtConvertTransposeTest(TrtLayerAutoScanTest): + def is_program_valid(self, program_config: ProgramConfig) -> bool: + inputs = program_config.inputs + weights = program_config.weights + outputs = program_config.outputs + + attrs = [ + program_config.ops[i].attrs + for i in range(len(program_config.ops)) + ] + + #The shape of input and axis should be equal. + if len(inputs['transpose_input'].shape) != len(attrs[0]['axis']): + return False + + return True + + def sample_program_configs(self): + def generate_input1(attrs: List[Dict[str, Any]], batch): + if self.dims == 4: + return np.ones([batch, 3, 24, 24]).astype(np.float32) + elif self.dims == 3: + return np.ones([batch, 3, 24]).astype(np.float32) + elif self.dims == 2: + return np.ones([batch, 24]).astype(np.float32) + + for dims in [2, 3, 4]: + for batch in [1, 2, 4]: + for axis in [[0, 1, 3, 2], [0, 3, 2, 1], [3, 2, 0, 1], + [0, 1, 2, 3], [0, 1, 2], [2, 0, 1], [1, 0], + [0, 1]]: + self.dims = dims + dics = [{"axis": axis}, {}] + ops_config = [{ + "op_type": "transpose", + "op_inputs": { + "X": ["transpose_input"] + }, + "op_outputs": { + "Out": ["transpose_out"] + }, + "op_attrs": dics[0] + }] + ops = self.generate_op_config(ops_config) + program_config = ProgramConfig( + ops=ops, + weights={}, + inputs={ + "transpose_input": TensorConfig(data_gen=partial( + generate_input1, dics, batch)) + }, + outputs=["transpose_out"]) + + yield program_config + + def sample_predictor_configs( + self, program_config) -> (paddle_infer.Config, List[int], float): + def generate_dynamic_shape(attrs): + if self.dims == 4: + self.dynamic_shape.min_input_shape = { + "transpose_input": [1, 3, 24, 24] + } + self.dynamic_shape.max_input_shape = { + "transpose_input": [9, 6, 48, 48] + } + self.dynamic_shape.opt_input_shape = { + "transpose_input": [1, 3, 48, 24] + } + elif self.dims == 3: + self.dynamic_shape.min_input_shape = { + "transpose_input": [1, 3, 24] + } + self.dynamic_shape.max_input_shape = { + "transpose_input": [9, 6, 48] + } + self.dynamic_shape.opt_input_shape = { + "transpose_input": [1, 3, 24] + } + elif self.dims == 2: + self.dynamic_shape.min_input_shape = { + "transpose_input": [1, 24] + } + self.dynamic_shape.max_input_shape = { + "transpose_input": [9, 48] + } + self.dynamic_shape.opt_input_shape = { + "transpose_input": [1, 24] + } + + def clear_dynamic_shape(): + self.dynamic_shape.min_input_shape = {} + self.dynamic_shape.max_input_shape = {} + self.dynamic_shape.opt_input_shape = {} + + def generate_trt_nodes_num(attrs, dynamic_shape): + if dynamic_shape == True: + return 1, 2 + else: + if attrs[0]['axis'][0] == 0: + return 1, 2 + else: + return 0, 3 + + attrs = [ + program_config.ops[i].attrs + for i in range(len(program_config.ops)) + ] + # for static_shape + clear_dynamic_shape() + self.trt_param.precision = paddle_infer.PrecisionType.Float32 + yield self.create_inference_config(), generate_trt_nodes_num( + attrs, False), 1e-5 + self.trt_param.precision = paddle_infer.PrecisionType.Half + yield self.create_inference_config(), generate_trt_nodes_num( + attrs, False), 1e-5 + + # for dynamic_shape + generate_dynamic_shape(attrs) + self.trt_param.precision = paddle_infer.PrecisionType.Float32 + yield self.create_inference_config(), generate_trt_nodes_num(attrs, + True), 1e-5 + self.trt_param.precision = paddle_infer.PrecisionType.Half + yield self.create_inference_config(), generate_trt_nodes_num(attrs, + True), 1e-5 + + def add_skip_trt_case(self): + def teller1(program_config, predictor_config): + if program_config.ops[0].attrs['axis'] == [0, 1]: + return True + return False + + self.add_skip_case( + teller1, SkipReasons.TRT_NOT_IMPLEMENTED, + "INPUT AXIS [0, 1] NOT SUPPORT: we need to add support in the future" + ) + + def test(self): + self.add_skip_trt_case() + self.run_test() + + +if __name__ == "__main__": + unittest.main()