| /* Copyright 2018 The TensorFlow 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. |
| ==============================================================================*/ |
| |
| #include "tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <functional> |
| #include <memory> |
| #include <type_traits> |
| #include <unordered_map> |
| #include <vector> |
| |
| #if GOOGLE_CUDA && GOOGLE_TENSORRT |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include "absl/algorithm/container.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "third_party/gpus/cuda/include/cuda.h" |
| #include "third_party/gpus/cuda/include/cuda_runtime_api.h" |
| #include "tensorflow/cc/framework/ops.h" |
| #include "tensorflow/cc/framework/scope.h" |
| #include "tensorflow/cc/ops/nn_ops_internal.h" |
| #include "tensorflow/cc/ops/standard_ops.h" |
| #include "tensorflow/compiler/tf2tensorrt/common/datavec.h" |
| #include "tensorflow/compiler/tf2tensorrt/common/utils.h" |
| #include "tensorflow/compiler/tf2tensorrt/convert/utils.h" |
| #include "tensorflow/compiler/tf2tensorrt/utils/trt_engine_utils.h" |
| #include "tensorflow/compiler/tf2tensorrt/utils/trt_logger.h" |
| #include "tensorflow/compiler/tf2tensorrt/utils/trt_testutils.h" |
| #include "tensorflow/core/common_runtime/gpu/gpu_managed_allocator.h" |
| #include "tensorflow/core/framework/allocator.h" |
| #include "tensorflow/core/framework/node_def.pb.h" // NOLINT |
| #include "tensorflow/core/framework/tensor.h" |
| #include "tensorflow/core/framework/tensor.pb.h" // NOLINT |
| #include "tensorflow/core/framework/tensor_shape.h" |
| #include "tensorflow/core/framework/tensor_testutil.h" |
| #include "tensorflow/core/framework/types.h" |
| #include "tensorflow/core/grappler/costs/graph_properties.h" |
| #include "tensorflow/core/lib/core/status.h" |
| #include "tensorflow/core/lib/core/status_test_util.h" |
| #include "tensorflow/core/lib/strings/str_util.h" |
| #include "tensorflow/core/lib/strings/strcat.h" |
| #include "tensorflow/core/platform/protobuf.h" |
| #include "tensorflow/core/platform/status_matchers.h" |
| #include "tensorflow/core/platform/test.h" |
| #include "tensorflow/core/protobuf/config.pb.h" // NOLINT |
| #include "tensorflow/core/public/session.h" |
| #include "third_party/tensorrt/NvInfer.h" |
| |
| namespace tensorflow { |
| namespace tensorrt { |
| |
| // TensorRT modes for testing. We define the following three modes: |
| // 1. Implicit batch mode: The tensors have static (known) input shape and the |
| // the batch dimension (first dim) is removed from the TRT tensor shape. In |
| // a loose notation: trt_shape = tf_shape[1:]. |
| // 2. Explicit batch mode: static (known) input shape, but the batch dimension |
| // is part of the trt tensor shape. (trt_shape = tf_shape) |
| // 3. Dynamic shape mode allows unknown input shapes, and requires explicit |
| // batch size definition (trt_shape = tf_shape). |
| // |
| // Note that the Converter only distinguishes between two modes: |
| // - use_implicit_batch == true, this corresponds to kImplicitBatch, |
| // - use_implicit_batch == false which includes both kExplicitBatch and |
| // kDynamicShape. |
| // |
| // For the converter, the distinction between explicit batch or dynamic shape |
| // mode follows from the input tensors of the network: dynamic shape input |
| // implies dynamic shape mode, while static shape input tensors imply explicit |
| // batch mode. We want to test all these modes, therefore we define the |
| // TrtTestMode with the following three options. |
| enum class TrtTestMode { |
| kImplicitBatch = 0, |
| kExplicitBatch = 1, |
| kDynamicShape = 2 |
| }; |
| |
| string DebugString(const TrtTestMode mode) { |
| switch (mode) { |
| case TrtTestMode::kImplicitBatch: |
| return "kImplicitBatch"; |
| case TrtTestMode::kExplicitBatch: |
| return "kExplicitBatch"; |
| case TrtTestMode::kDynamicShape: |
| return "kDynamicShape"; |
| default: |
| return "Invalid TrtTestMode"; |
| } |
| } |
| |
| namespace convert { |
| |
| using absl::StrCat; |
| using ::testing::ElementsAre; |
| using ::testing::ElementsAreArray; |
| using ::testing::HasSubstr; |
| using ::testing::Matcher; |
| using ::testing::PrintToString; |
| |
| using ::tensorflow::testing::IsOk; |
| using ::tensorflow::testing::StatusIs; |
| |
| constexpr std::array<TrtTestMode, 3> ValidTrtModes = { |
| TrtTestMode::kImplicitBatch, TrtTestMode::kExplicitBatch, |
| TrtTestMode::kDynamicShape}; |
| |
| bool TrtShapedWeightsEquals(const TRT_ShapedWeights& lhs, |
| const TRT_ShapedWeights& rhs) { |
| return lhs.shape_ == rhs.shape_ && lhs.TrtDType() == rhs.TrtDType() && |
| lhs.GetPointer<int8>() == rhs.GetPointer<int8>(); |
| } |
| |
| template <typename T> |
| void ValidateWeights(const TRT_ShapedWeights& weights, |
| const std::vector<int>& expected_dims, |
| const std::vector<T>& expected_value) { |
| EXPECT_THAT(weights.shape_, DimsAreArray(expected_dims)); |
| ASSERT_EQ(expected_value.size(), weights.count()) << weights.DebugString(); |
| const T* actual_values = weights.GetPointer<T>(); |
| for (int i = 0; i < expected_value.size(); ++i) { |
| EXPECT_EQ(expected_value[i], actual_values[i]); |
| } |
| } |
| |
| TEST(TRT_ShapedWeights_Test, Basic) { |
| // Test constructor with no arguments. |
| { |
| TRT_ShapedWeights weights; |
| TRT_ShapedWeights copy(weights); |
| for (auto ptr : {&weights, ©}) { |
| nvinfer1::Weights trt_weights = ptr->GetTrtWeights(); |
| EXPECT_EQ(nvinfer1::DataType::kFLOAT, trt_weights.type); |
| EXPECT_EQ(nullptr, trt_weights.values); |
| EXPECT_EQ(0, trt_weights.count); |
| |
| EXPECT_EQ(nullptr, ptr->GetPointer<int8>()); |
| EXPECT_EQ(0, ptr->count()); |
| EXPECT_EQ(0, ptr->size_bytes()); |
| } |
| } |
| // Test constructor with DataType argument. |
| { |
| TRT_ShapedWeights weights(nvinfer1::DataType::kFLOAT); |
| TRT_ShapedWeights copy(weights); |
| for (auto ptr : {&weights, ©}) { |
| nvinfer1::Weights trt_weights = ptr->GetTrtWeights(); |
| EXPECT_EQ(nvinfer1::DataType::kFLOAT, trt_weights.type); |
| EXPECT_EQ(nullptr, trt_weights.values); |
| EXPECT_EQ(0, trt_weights.count); |
| |
| EXPECT_EQ(nullptr, ptr->GetPointer<int8>()); |
| EXPECT_EQ(0, ptr->count()); |
| EXPECT_EQ(0, ptr->size_bytes()); |
| } |
| } |
| // Test constructor with DataType and nvinfer1::Dims arguments. |
| { |
| TrtWeightStore store; |
| TRT_ShapedWeights weights = |
| store.GetTempWeights(nvinfer1::DataType::kFLOAT, CreateDims({2, 5})); |
| TRT_ShapedWeights copy(weights); |
| for (auto ptr : {&weights, ©}) { |
| nvinfer1::Weights trt_weights = ptr->GetTrtWeights(); |
| EXPECT_EQ(nvinfer1::DataType::kFLOAT, trt_weights.type); |
| EXPECT_NE(nullptr, trt_weights.values); |
| EXPECT_EQ(10, trt_weights.count); |
| |
| EXPECT_EQ(trt_weights.values, ptr->GetPointer<int8>()); |
| EXPECT_EQ(10, ptr->count()); |
| EXPECT_EQ(40, ptr->size_bytes()); |
| } |
| // Test that it doesn't copy the underlying buffer. |
| EXPECT_EQ(weights.GetPointer<int8>(), copy.GetPointer<int8>()); |
| } |
| } |
| |
| TEST(TRT_TensorOrWeights_Test, Basic) { |
| // Test constructor with no arguments. |
| { |
| TRT_TensorOrWeights tw; |
| TRT_TensorOrWeights copy(tw); |
| TRT_TensorOrWeights assigned; |
| assigned = tw; |
| for (auto ptr : {&tw, ©, &assigned}) { |
| EXPECT_EQ(false, ptr->is_tensor()); |
| EXPECT_EQ(false, ptr->is_weights()); |
| EXPECT_EQ(-1, ptr->batch_size()); |
| } |
| } |
| |
| // Test constructor with ITensor and batch size argument. |
| { |
| nvinfer1::Dims dims; |
| dims.nbDims = 1; |
| dims.d[0] = 1; |
| ITensorProxyPtr itensor(dims); |
| TRT_TensorOrWeights tw(itensor); |
| TRT_TensorOrWeights tw1(itensor, /*batch_size=*/1); |
| |
| for (auto original_ptr : {&tw, &tw1}) { |
| TRT_TensorOrWeights copy(*original_ptr); |
| TRT_TensorOrWeights assigned; |
| assigned = *original_ptr; |
| |
| for (auto ptr : {original_ptr, ©, &assigned}) { |
| ASSERT_TRUE(ptr->is_tensor()); |
| EXPECT_EQ(false, ptr->is_weights()); |
| if (original_ptr == &tw) { |
| EXPECT_EQ(-1, ptr->batch_size()); |
| } else { |
| EXPECT_EQ(1, ptr->batch_size()); |
| } |
| EXPECT_EQ(itensor->simple_tensor(), ptr->tensor()->simple_tensor()); |
| EXPECT_THAT(ptr->GetTrtDims(), DimsAreArray({1})); |
| } |
| } |
| } |
| // Test constructor which creates and owns an ITensor. |
| { |
| nvinfer1::Dims dims; |
| dims.nbDims = 1; |
| dims.d[0] = 1; |
| TRT_TensorOrWeights tw(nvinfer1::DataType::kFLOAT, dims, /*batch_size=*/1); |
| TRT_TensorOrWeights copy(tw); |
| TRT_TensorOrWeights assigned; |
| assigned = tw; |
| |
| for (auto ptr : {&tw, ©, &assigned}) { |
| ASSERT_TRUE(ptr->is_tensor()); |
| EXPECT_EQ(false, ptr->is_weights()); |
| EXPECT_EQ(1, ptr->batch_size()); |
| EXPECT_NE(nullptr, ptr->tensor()->simple_tensor()); |
| EXPECT_THAT(ptr->GetTrtDims(), DimsAreArray({1})); |
| } |
| } |
| // Test constructor with TRT_ShapedWeights argument. |
| { |
| TRT_ShapedWeights weights; |
| TRT_TensorOrWeights tw(weights); |
| TRT_TensorOrWeights copy(tw); |
| TRT_TensorOrWeights assigned; |
| assigned = tw; |
| for (auto ptr : {&tw, ©, &assigned}) { |
| EXPECT_EQ(false, ptr->is_tensor()); |
| EXPECT_EQ(true, ptr->is_weights()); |
| EXPECT_TRUE(TrtShapedWeightsEquals(weights, ptr->weights())); |
| std::vector<int> empty_dims; |
| EXPECT_THAT(ptr->GetTrtDims(), DimsAreArray(empty_dims)); |
| } |
| } |
| } |
| |
| class ValidatorTest : public ::testing::Test { |
| public: |
| std::unordered_map<string, OpConverter>& op_validators( |
| TrtNodeValidator* validator) { |
| return validator->op_validators_; |
| } |
| |
| Status ConvertToTensorOrWeights(const Scope& scope, const Node* node, |
| int output_port, |
| TRT_TensorOrWeights* tensor_or_weights) { |
| grappler::GrapplerItem item; |
| TF_EXPECT_OK(scope.ToGraphDef(&item.graph)); |
| grappler::GraphProperties graph_properties(item); |
| TF_EXPECT_OK(graph_properties.InferStatically(true)); |
| |
| TrtNodeValidator validator(graph_properties, TrtPrecisionMode::FP32, |
| /*use_calibration=*/false, |
| /*use_implicit_batch=*/true); |
| return validator.ConvertToTensorOrWeights(node->def(), output_port, |
| tensor_or_weights); |
| } |
| |
| const std::set<string>* GetQuantizeOps(TrtNodeValidator* validator) { |
| return validator->quantize_ops; |
| } |
| }; |
| |
| TEST_F(ValidatorTest, QuantizeOpsAreRegistered) { |
| grappler::GrapplerItem item; |
| grappler::GraphProperties graph_properties(item); |
| TrtNodeValidator validator(graph_properties, TrtPrecisionMode::FP32, |
| /*use_calibration=*/false, |
| /*use_implicit_batch=*/true); |
| for (const string& quantize_op : *GetQuantizeOps(&validator)) { |
| QCHECK(op_validators(&validator).count(quantize_op)); |
| } |
| } |
| |
| TEST_F(ValidatorTest, ConvertToTensorOrWeights) { |
| // Convert Const. |
| { |
| Scope s = Scope::NewRootScope(); |
| auto node = |
| ops::Const(s.WithOpName("my_const"), {1.0f, 2.0f}, TensorShape({2})); |
| TRT_TensorOrWeights output; |
| EXPECT_THAT(ConvertToTensorOrWeights(s, node.op().node(), |
| /*output_port=*/0, &output), |
| IsOk()); |
| ValidateWeights<float>(output.weights(), {2}, {1.0, 2.0}); |
| } |
| |
| // Helper method to run ConvertToTensorOrWeights() with predefined parameters. |
| auto convert_to_tensor_or_weights = [this](const std::vector<int64>& dims, |
| TRT_TensorOrWeights* output) { |
| Scope s = Scope::NewRootScope(); |
| const auto attrs = ops::Placeholder::Shape(PartialTensorShape{dims}); |
| auto feed = ops::Placeholder(s.WithOpName("feed"), DT_FLOAT, attrs); |
| auto add = ops::Add(s.WithOpName("add"), feed, feed); |
| return this->ConvertToTensorOrWeights(s, add.operation.node(), |
| /*output_port=*/0, output); |
| }; |
| // Convert non-Const with #dims > nvinfer1::Dims::MAX_DIMS+1. |
| { |
| TRT_TensorOrWeights output; |
| EXPECT_THAT( |
| convert_to_tensor_or_weights( |
| std::vector<int64>(nvinfer1::Dims::MAX_DIMS + 2, 1), &output), |
| StatusIs(error::OUT_OF_RANGE, |
| HasSubstr("Input tensor rank is greater than 9"))); |
| } |
| // Convert non-Const with #dims < 1. |
| { |
| TRT_TensorOrWeights output; |
| EXPECT_THAT(convert_to_tensor_or_weights({}, &output), |
| StatusIs(error::INVALID_ARGUMENT, |
| HasSubstr("Scalar input tensor is not supported since " |
| "the first dimension " |
| "is treated as batch dimension by TRT"))); |
| } |
| // Convert non-Const. We test the case where the non-batch dimension is |
| // unknown as well, to make sure the validator allows that. |
| for (const int32 non_batch_dim : {-1, 2}) { |
| const int32 batch_size = 12; |
| TRT_TensorOrWeights output; |
| EXPECT_THAT( |
| convert_to_tensor_or_weights({batch_size, non_batch_dim}, &output), |
| IsOk()); |
| ASSERT_TRUE(output.is_tensor()); |
| EXPECT_EQ(batch_size, output.batch_size()); |
| EXPECT_NE(nullptr, output.tensor()->simple_tensor()); |
| EXPECT_THAT(output.GetTrtDims(), DimsAreArray({non_batch_dim})); |
| } |
| } |
| |
| TEST_F(ValidatorTest, IsTensorRTCandidate_Basics) { |
| Scope s = Scope::NewRootScope(); |
| auto input = |
| ops::Const(s.WithOpName("const"), {1.0f, 2.0f}, TensorShape({2})); |
| auto add = ops::Add(s.WithOpName("add"), input, input); |
| const Node* add_node = add.operation.node(); |
| |
| grappler::GrapplerItem item; |
| TF_EXPECT_OK(s.ToGraphDef(&item.graph)); |
| grappler::GraphProperties graph_properties(item); |
| TF_EXPECT_OK(graph_properties.InferStatically(true)); |
| TrtNodeValidator validator(graph_properties, TrtPrecisionMode::FP32, |
| /*use_calibration=*/false, |
| /*use_implicit_batch=*/true); |
| |
| bool start_conversion = false; |
| bool should_fail = false; |
| auto op_converter = [&start_conversion, |
| &should_fail](OpConverterParams* params) -> Status { |
| if (should_fail) return errors::InvalidArgument(""); |
| if (!params->validation_only) start_conversion = true; |
| return Status::OK(); |
| }; |
| |
| // Validator not registered. |
| ASSERT_EQ(1, op_validators(&validator).erase("Add")); |
| EXPECT_THAT(validator.IsTensorRTCandidate(add_node), |
| StatusIs(error::UNIMPLEMENTED, |
| HasSubstr("Op type Add is not supported."))); |
| |
| // Register validator. |
| op_validators(&validator)["Add"] = op_converter; |
| TF_EXPECT_OK(validator.IsTensorRTCandidate(add_node)); |
| EXPECT_EQ(false, start_conversion); |
| |
| // Let the converter return error. |
| should_fail = true; |
| EXPECT_THAT(validator.IsTensorRTCandidate(add_node), |
| StatusIs(error::INVALID_ARGUMENT)); |
| } |
| |
| TEST(TrtNodeValidator, IsTensorRTCandidate) { |
| // Create a graph containing both TRT-compatible and TRT-incompatible nodes |
| // and use it to test TrtNodeValidator::IsTensorRTCandidate(). |
| const std::vector<int32> input_shape_array{2, 2}; |
| TensorShape input_shape; |
| TF_EXPECT_OK(TensorShapeUtils::MakeShape(input_shape_array, &input_shape)); |
| |
| Scope s = Scope::NewRootScope(); |
| ops::Placeholder::Attrs feed_attrs; |
| TF_EXPECT_OK( |
| TensorShapeUtils::MakeShape(input_shape_array, &feed_attrs.shape_)); |
| |
| // Compatible input. |
| auto feed = ops::Placeholder(s.WithOpName("feed"), DT_FLOAT, feed_attrs); |
| auto const_1 = ops::Const(s.WithOpName("const_1"), 1.0f, input_shape); |
| |
| // Compatible MatMul. |
| auto matmul = ops::MatMul(s.WithOpName("matmul"), feed, const_1); |
| |
| // Incompatible MatMul. |
| ops::MatMul::Attrs matmul_attrs; |
| matmul_attrs.transpose_a_ = true; |
| auto incompatible_matmul = ops::MatMul(s.WithOpName("incompatible_matmul"), |
| feed, const_1, matmul_attrs); |
| |
| // Unsupported op. |
| auto unsupported_op = ops::Erfc(s.WithOpName("sin"), feed); |
| |
| // Incompatible input. |
| auto incompatible_feed = ops::Placeholder(s.WithOpName("feed"), DT_DOUBLE); |
| auto const_2 = ops::Const(s.WithOpName("const_2"), 1.0, input_shape); |
| // Compatible op with incompatible input. |
| auto matmul_with_incompatible_input = |
| ops::MatMul(s.WithOpName("matmul_with_incompatible_input"), |
| incompatible_feed, const_2); |
| |
| // Quantize ops. |
| auto quantize_attrs = ops::FakeQuantWithMinMaxArgs::Min(-6.0f).Max(6.0f); |
| auto quantize = ops::FakeQuantWithMinMaxArgs(s.WithOpName("quantize"), feed, |
| quantize_attrs); |
| |
| // Get GrapplerItem and GraphProperties. |
| grappler::GrapplerItem item; |
| TF_EXPECT_OK(s.ToGraphDef(&item.graph)); |
| Tensor feed_tensor(DT_FLOAT, input_shape); |
| item.feed.push_back(std::make_pair("feed", feed_tensor)); |
| grappler::GraphProperties graph_properties(item); |
| TF_EXPECT_OK(graph_properties.InferStatically(true)); |
| |
| for (const TrtPrecisionMode precision_mode : |
| {TrtPrecisionMode::FP32, TrtPrecisionMode::INT8}) { |
| TrtNodeValidator validator(graph_properties, precision_mode, |
| /*use_calibration=*/false, |
| /*use_implicit_batch=*/true); |
| TF_EXPECT_OK(validator.IsTensorRTCandidate(matmul.operation.node())); |
| EXPECT_THAT( |
| validator.IsTensorRTCandidate(incompatible_matmul.operation.node()), |
| StatusIs(error::INVALID_ARGUMENT, |
| HasSubstr("MatMul with 2D tensors requires explicit batch " |
| "mode, or that tensor A " |
| "is not transposed and B is a constant tensor."))); |
| EXPECT_THAT(validator.IsTensorRTCandidate(unsupported_op.operation.node()), |
| StatusIs(error::UNIMPLEMENTED, |
| HasSubstr("Op type Erfc is not supported"))); |
| EXPECT_THAT( |
| validator.IsTensorRTCandidate( |
| matmul_with_incompatible_input.operation.node()), |
| StatusIs( |
| error::INTERNAL, |
| HasSubstr( |
| "Failed to convert input feed_1 to a TRT_TensorOrWeights"))); |
| if (precision_mode == TrtPrecisionMode::INT8) { |
| TF_EXPECT_OK(validator.IsTensorRTCandidate(quantize.operation.node())); |
| } else { |
| EXPECT_THAT( |
| validator.IsTensorRTCandidate(quantize.operation.node()), |
| StatusIs( |
| error::UNIMPLEMENTED, |
| HasSubstr("Op type FakeQuantWithMinMaxArgs is not supported"))); |
| } |
| } |
| } |
| |
| class ConverterTest : public ::testing::Test { |
| public: |
| ConverterTest() { Reset(); } |
| |
| void Reset() { |
| converter_ = |
| std::move(Converter::Create(TrtPrecisionMode::FP32, |
| /*use_calibration=*/false, &logger_, |
| /*use_implicit_batch=*/true, |
| /*engine_name=*/"TRTEngineOp_0_0") |
| .ValueOrDie()); |
| weight_store_ = &converter_->weight_store_; |
| } |
| |
| void AddOpConverter(const string& op_name, OpConverter op_converter) { |
| converter_->op_registry_[op_name] = op_converter; |
| } |
| |
| // Below we expose private methods of Converter for testing. |
| |
| Status MaybeUpdateBatchSize(int batch_size) { |
| return converter_->MaybeUpdateBatchSize(batch_size); |
| } |
| |
| Status AddTensorOrWeights(const string& name, TRT_TensorOrWeights input) { |
| return converter_->AddTensorOrWeights(name, input); |
| } |
| |
| Status GetTensorOrWeights(const string& name, TRT_TensorOrWeights* output) { |
| return converter_->GetTensorOrWeights(name, output); |
| } |
| |
| Status GetInputs(const NodeDef& node_def, |
| std::vector<TRT_TensorOrWeights>* inputs) const { |
| return converter_->GetInputs(node_def, inputs); |
| } |
| |
| Status GetWeightRange(const TRT_ShapedWeights& weights, float* out_min, |
| float* out_max) const { |
| return converter_->GetWeightRange(weights, out_min, out_max); |
| } |
| |
| int batch_size() const { return converter_->batch_size_; } |
| |
| std::unordered_map<ITensorProxyPtr*, float>& quantization_ranges_proxy() { |
| return converter_->quantization_ranges_proxy_; |
| } |
| |
| std::unordered_map<nvinfer1::ITensor*, float>& quantization_ranges() { |
| return converter_->quantization_ranges_; |
| } |
| |
| private: |
| Logger& logger_ = *Logger::GetLogger(); |
| |
| protected: |
| std::unique_ptr<Converter> converter_; |
| TrtWeightStore* weight_store_; |
| }; |
| |
| TEST_F(ConverterTest, ConvertNode) { |
| ITensorProxyPtr output_tensors[2]; |
| auto op_converter = [&output_tensors](OpConverterParams* params) -> Status { |
| nvinfer1::Dims dims = params->inputs[0].tensor()->getDimensions(); |
| for (int i = 0; i < 2; ++i) { |
| dims.d[0] += 1; |
| output_tensors[i]->setDimensions(dims); |
| params->outputs->push_back(TRT_TensorOrWeights(output_tensors[i])); |
| } |
| return Status::OK(); |
| }; |
| NodeDef node_def = MakeNodeDef("my_op", "MyOp", {"my_input"}); |
| TF_EXPECT_OK(converter_->AddInputTensor( |
| "my_input", nvinfer1::DataType::kFLOAT, CreateDims({123}), 1)); |
| |
| // Converter not registered. |
| EXPECT_THAT(converter_->ConvertNode(node_def), |
| StatusIs(error::UNIMPLEMENTED, |
| HasSubstr("No converter registered for op: MyOp"))); |
| |
| // Register the converter and retry. |
| AddOpConverter("MyOp", op_converter); |
| TF_EXPECT_OK(converter_->ConvertNode(node_def)); |
| |
| TRT_TensorOrWeights actual_output_1; |
| TF_EXPECT_OK(GetTensorOrWeights("my_op", &actual_output_1)); |
| EXPECT_EQ(output_tensors[0]->simple_tensor(), |
| actual_output_1.tensor()->simple_tensor()); |
| EXPECT_EQ(124, actual_output_1.tensor()->getDimensions().d[0]); |
| |
| TRT_TensorOrWeights actual_output_2; |
| TF_EXPECT_OK(GetTensorOrWeights("my_op:1", &actual_output_2)); |
| EXPECT_EQ(output_tensors[1]->simple_tensor(), |
| actual_output_2.tensor()->simple_tensor()); |
| EXPECT_EQ(125, actual_output_2.tensor()->getDimensions().d[0]); |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, AddAndGetInputs) { |
| NodeDef node_def; |
| node_def.add_input("^control_input"); |
| node_def.add_input("input"); |
| node_def.add_input("input:0"); |
| node_def.add_input("input:1"); |
| node_def.add_input("weird_input:2:3:4:0"); |
| |
| TF_EXPECT_OK(converter_->AddInputTensor("input", nvinfer1::DataType::kFLOAT, |
| CreateDims({1}), 1)); |
| TF_EXPECT_OK(converter_->AddInputTensor("input:1", nvinfer1::DataType::kINT32, |
| CreateDims({2, 3}), 1)); |
| TF_EXPECT_OK(converter_->AddInputTensor( |
| "weird_input:2:3:4", nvinfer1::DataType::kHALF, CreateDims({5, 3}), 1)); |
| |
| std::vector<TRT_TensorOrWeights> inputs; |
| TF_EXPECT_OK(GetInputs(node_def, &inputs)); |
| |
| EXPECT_EQ(4, inputs.size()); |
| EXPECT_EQ(inputs[0].tensor()->trt_tensor(), inputs[1].tensor()->trt_tensor()); |
| |
| EXPECT_EQ(nvinfer1::DataType::kFLOAT, inputs[0].tensor()->getType()); |
| EXPECT_EQ(nvinfer1::DataType::kINT32, inputs[2].tensor()->getType()); |
| EXPECT_EQ(nvinfer1::DataType::kHALF, inputs[3].tensor()->getType()); |
| EXPECT_THAT(inputs[0].tensor()->getDimensions(), DimsAreArray({1})); |
| EXPECT_THAT(inputs[2].tensor()->getDimensions(), DimsAreArray({2, 3})); |
| EXPECT_THAT(inputs[3].tensor()->getDimensions(), DimsAreArray({5, 3})); |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, RenameAndMarkOutputTensors) { |
| // Test that the tensor are actually named and marked as output after |
| // Converter::RenameAndMarkOutputTensors() is called. |
| |
| // Register a custom converter which shuffles the input. We use it to build a |
| // TRT network whose output will be later marked. |
| std::vector<ITensorProxyPtr> output_tensors; |
| auto op_converter = [&output_tensors](OpConverterParams* params) -> Status { |
| nvinfer1::Permutation perm; |
| perm.order[0] = 1; |
| perm.order[1] = 0; |
| for (int i = 0; i < 2; ++i) { |
| ITensorProxyPtr input_tensor = params->inputs[0].tensor(); |
| nvinfer1::IShuffleLayer* layer = |
| params->converter->network()->addShuffle(*input_tensor->trt_tensor()); |
| layer->setFirstTranspose(perm); |
| ITensorProxyPtr output_tensor = layer->getOutput(0); |
| params->outputs->emplace_back(output_tensor); |
| output_tensors.push_back(output_tensor); |
| } |
| TRT_ShapedWeights output_weights(nvinfer1::DataType::kFLOAT); |
| params->outputs->emplace_back(output_weights); |
| return Status::OK(); |
| }; |
| AddOpConverter("MyOp", op_converter); |
| |
| // Run the conversion. |
| NodeDef node_def = MakeNodeDef("my_op", "MyOp", {"my_input"}); |
| TF_EXPECT_OK(converter_->AddInputTensor( |
| "my_input", nvinfer1::DataType::kFLOAT, CreateDims({1, 2}), 1)); |
| TF_EXPECT_OK(converter_->ConvertNode(node_def)); |
| |
| // Mark a weight as output, should fail. |
| EXPECT_THAT( |
| converter_->RenameAndMarkOutputTensors({{"my_op:2", "my_output"}}), |
| StatusIs(error::INVALID_ARGUMENT, |
| HasSubstr("Output my_op:2 is weights not tensor"))); |
| |
| // Mark tensors as output, should pass. |
| TF_EXPECT_OK(converter_->RenameAndMarkOutputTensors( |
| {{"my_op", "my_output"}, {"my_op:1", "my_output_1"}})); |
| EXPECT_EQ(2, output_tensors.size()); |
| for (auto output_tensor : output_tensors) { |
| EXPECT_THAT(output_tensor->getDimensions(), DimsAreArray({2, 1})); |
| } |
| EXPECT_EQ("my_output", string(output_tensors[0]->getName())); |
| EXPECT_EQ("my_output_1", string(output_tensors[1]->getName())); |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, TransposeTensor) { |
| ITensorProxyPtr input_tensor = converter_->network()->addInput( |
| "", nvinfer1::DataType::kFLOAT, CreateDims({2, 3, 5})); |
| ITensorProxyPtr output_tensor = nullptr; |
| NodeDef dummy_node_def = MakeNodeDef("dummy_op", "DummyOp", {}); |
| // Rank doesn't match. |
| EXPECT_THAT(converter_->TransposeTensor(input_tensor, {0, 1}, &output_tensor, |
| dummy_node_def, "sub1"), |
| StatusIs(error::INVALID_ARGUMENT, |
| HasSubstr("Rank of perm for transpose does not match " |
| "with that of the input"))); |
| |
| // Transpose at batch dimension. |
| EXPECT_THAT( |
| converter_->TransposeTensor(input_tensor, {1, 0, 2, 3}, &output_tensor, |
| dummy_node_def, "sub2"), |
| StatusIs(error::UNIMPLEMENTED, |
| HasSubstr("Transpose at batch dimension is not supported."))); |
| |
| // OK. |
| TF_EXPECT_OK(converter_->TransposeTensor( |
| input_tensor, {0, 3, 1, 2}, &output_tensor, dummy_node_def, "sub3")); |
| EXPECT_THAT(output_tensor->getDimensions(), DimsAreArray({5, 2, 3})); |
| EXPECT_THAT(converter_->network(), |
| LayerNamesAreArray({"TRTEngineOp_0_0/dummy_op-sub3:SHUFFLE"})); |
| } |
| |
| void TestPrepareTensorForShape( |
| const std::vector<int>& input_dims, const std::vector<int>& reshape_dims, |
| const std::vector<int>& expected_tensor_dims, bool input_is_tensor, |
| Converter* converter, TrtWeightStore* weight_store, |
| error::Code expected_code = error::OK, |
| const char* expected_error_msg_substr = nullptr) { |
| TRT_TensorOrWeights input; |
| if (input_is_tensor) { |
| input = TRT_TensorOrWeights(converter->network()->addInput( |
| "", nvinfer1::DataType::kFLOAT, CreateDims(input_dims))); |
| } else { |
| input = TRT_TensorOrWeights(weight_store->GetTempWeights( |
| nvinfer1::DataType::kFLOAT, CreateDims(input_dims))); |
| } |
| ITensorProxyPtr output_tensor = nullptr; |
| |
| NodeDef dummy_node_def = MakeNodeDef("dummy_op", "DummyOp", {}); |
| for (bool validation_only : {false, true}) { |
| const Status status = |
| PrepareTensorForShape(converter, input, CreateDims(reshape_dims), |
| validation_only, &output_tensor, dummy_node_def); |
| if (expected_code == error::OK) { |
| TF_EXPECT_OK(status); |
| if (validation_only) { |
| EXPECT_EQ(nullptr, *output_tensor); |
| } else { |
| EXPECT_THAT(output_tensor->getDimensions(), |
| DimsAreArray(expected_tensor_dims)); |
| } |
| } else { |
| EXPECT_THAT(status, StatusIs(expected_code, |
| HasSubstr(expected_error_msg_substr))); |
| } |
| } |
| } |
| |
| TEST_F(ConverterTest, PrepareTensorForShape) { |
| for (bool input_is_tensor : {true, false}) { |
| // Shape size doesn't match. |
| Reset(); |
| TestPrepareTensorForShape({2, 3, 5}, {2, 3, 6}, {}, input_is_tensor, |
| converter_.get(), weight_store_, |
| error::INVALID_ARGUMENT, "Incompatible shapes"); |
| |
| // Regular shape. |
| Reset(); |
| TestPrepareTensorForShape({2, 3, 5}, {10, 3}, {10, 3}, input_is_tensor, |
| converter_.get(), weight_store_); |
| |
| // Reshape to zero rank. |
| Reset(); |
| TestPrepareTensorForShape({1, 1}, {}, {}, input_is_tensor, converter_.get(), |
| weight_store_); |
| } |
| |
| // Tensor input with zero rank. |
| Reset(); |
| TestPrepareTensorForShape({}, {1, 1}, {1, 1}, /*input_is_tensor=*/true, |
| converter_.get(), weight_store_); |
| |
| // TODO(aaroey): we should check the case where uninferred dimensions are |
| // not an exact divisor of input dim ensions, e.g. for dims {-1, 7}. |
| |
| // Infer tensor shape, ok. |
| Reset(); |
| TestPrepareTensorForShape({2, 3, 5}, {-1, 2}, {15, 2}, |
| /*input_is_tensor=*/true, converter_.get(), |
| weight_store_); |
| |
| // Infer weight shape, should fail. |
| Reset(); |
| TestPrepareTensorForShape({2, 3, 5}, {-1, 2}, {15, 2}, |
| /*input_is_tensor=*/false, converter_.get(), |
| weight_store_, error::INVALID_ARGUMENT, |
| "Shape is not fully defined"); |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, MaybeUpdateBatchSize) { |
| EXPECT_EQ(-1, batch_size()); |
| |
| TF_EXPECT_OK(MaybeUpdateBatchSize(-1)); |
| EXPECT_EQ(-1, batch_size()); |
| |
| TF_EXPECT_OK(MaybeUpdateBatchSize(123)); |
| EXPECT_EQ(123, batch_size()); |
| |
| TF_EXPECT_OK(MaybeUpdateBatchSize(123)); |
| EXPECT_EQ(123, batch_size()); |
| |
| TF_EXPECT_OK(MaybeUpdateBatchSize(-1)); |
| EXPECT_EQ(123, batch_size()); |
| |
| EXPECT_THAT( |
| MaybeUpdateBatchSize(124), |
| StatusIs(error::INVALID_ARGUMENT, |
| HasSubstr( |
| "Provided batch size does not match converter batch size"))); |
| } |
| |
| TEST_F(ConverterTest, AddAndGetTensorOrWeights) { |
| // Add a tensor. |
| ITensorProxyPtr simple_tensor; |
| TRT_TensorOrWeights tensor(simple_tensor); |
| EXPECT_EQ(-1, tensor.batch_size()); |
| TF_EXPECT_OK(MaybeUpdateBatchSize(123)); |
| TF_EXPECT_OK(AddTensorOrWeights("my_tensor", tensor)); |
| |
| // Get the added tensor. |
| TRT_TensorOrWeights added_tensor; |
| TF_EXPECT_OK(GetTensorOrWeights("my_tensor", &added_tensor)); |
| EXPECT_EQ(123, added_tensor.batch_size()); |
| |
| // Add the same tensor again. |
| EXPECT_THAT(AddTensorOrWeights("my_tensor", tensor), |
| StatusIs(error::ALREADY_EXISTS, |
| HasSubstr("tensor/weights my_tensor already exist"))); |
| } |
| |
| template <typename T> |
| void TestGetWeightRange(ConverterTest* test, TrtWeightStore* weight_store) { |
| nvinfer1::DataType trt_type; |
| TF_ASSERT_OK(TfTypeToTrtType(DataTypeToEnum<T>::v(), &trt_type)); |
| TRT_ShapedWeights weights = |
| weight_store->GetTempWeights(trt_type, CreateDims({2, 3})); |
| const std::vector<T> values = {T(3), T(1), T(2), T(6), T(5), T(4)}; |
| memcpy(weights.GetPointer<int8>(), values.data(), weights.size_bytes()); |
| |
| float out_min = 0.0f; |
| float out_max = 0.0f; |
| TF_EXPECT_OK(test->GetWeightRange(weights, &out_min, &out_max)); |
| EXPECT_EQ(1.0f, out_min); |
| EXPECT_EQ(6.0f, out_max); |
| } |
| |
| TEST_F(ConverterTest, GetWeightRange) { |
| TestGetWeightRange<float>(this, weight_store_); |
| TestGetWeightRange<Eigen::half>(this, weight_store_); |
| TestGetWeightRange<int32>(this, weight_store_); |
| } |
| |
| TEST_F(ConverterTest, ProvideQuantizationRange) { |
| ITensorProxyPtr simple_tensor; |
| // Asymmetric range |
| converter_->ProvideQuantizationRange(&simple_tensor, 0.0f, 6.0f); |
| EXPECT_EQ(6.0f, quantization_ranges_proxy()[&simple_tensor]); |
| converter_->ProvideQuantizationRange(&simple_tensor, 1.0f, 6.0f); |
| EXPECT_EQ(6.0f, quantization_ranges_proxy()[&simple_tensor]); |
| converter_->ProvideQuantizationRange(&simple_tensor, -8.0f, 6.0f); |
| EXPECT_EQ(8.0f, quantization_ranges_proxy()[&simple_tensor]); |
| converter_->ProvideQuantizationRange(&simple_tensor, -8.123f, -6.123f); |
| EXPECT_EQ(8.123f, quantization_ranges_proxy()[&simple_tensor]); |
| // Symmetric range |
| converter_->ProvideQuantizationRange(&simple_tensor, -6.123f, 6.123f); |
| EXPECT_EQ(6.123f, quantization_ranges_proxy()[&simple_tensor]); |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, MaybeApplyQuantizationRanges) { |
| ITensorProxyPtr input; |
| ITensorProxyPtr not_infer; |
| Logger& logger = *Logger::GetLogger(); |
| auto int8_converter = Converter::Create(TrtPrecisionMode::INT8, |
| /*use_calibration=*/true, &logger, |
| /*use_implicit_batch=*/true, |
| /*engine_name=*/"") |
| .ValueOrDie(); |
| int8_converter->ProvideQuantizationRange(&input, -5.0f, 5.0f); |
| int8_converter->ProvideQuantizationRange(¬_infer, -100.0f, 100.0f); |
| |
| int8_converter->MaybeApplyQuantizationRanges(); |
| EXPECT_EQ(input->getDynamicRangeMax(), 5.0f); |
| EXPECT_EQ(not_infer->getDynamicRangeMax(), 100.0f); |
| |
| EXPECT_THAT(int8_converter->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, GetTrtBroadcastShape) { |
| const bool kIsTensor = true; |
| const bool kIsNotTensor = false; |
| auto symmetric_test = [this](const std::vector<int>& operand_1_shape, |
| const std::vector<int>& operand_2_shape, |
| const bool operand_1_is_tensor, |
| const bool operand_2_is_tensor, |
| const std::vector<int>& expected_operand_1_shape, |
| const std::vector<int>& expected_operand_2_shape, |
| error::Code expected_code = error::OK, |
| const char* expected_error_msg_substr = "", |
| const int operand_1_batch_size = -1, |
| const int operand_2_batch_size = -1) { |
| auto create_tensor_or_weights = [](const std::vector<int>& shape, |
| bool is_tensor, int batch_size = -1) { |
| if (is_tensor) { |
| return TRT_TensorOrWeights{nvinfer1::DataType::kFLOAT, |
| CreateDims(shape), batch_size}; |
| } |
| TRT_ShapedWeights weights; |
| weights.shape_ = CreateDims(shape); |
| return TRT_TensorOrWeights(weights); |
| }; |
| |
| nvinfer1::Dims operand_1_new_dims, operand_2_new_dims; |
| TRT_TensorOrWeights operand_1 = create_tensor_or_weights( |
| operand_1_shape, operand_1_is_tensor, operand_1_batch_size); |
| TRT_TensorOrWeights operand_2 = create_tensor_or_weights( |
| operand_2_shape, operand_2_is_tensor, operand_2_batch_size); |
| |
| // operand_1 broadcast operand_2 |
| EXPECT_THAT( |
| GetTrtBroadcastShape(operand_1, operand_2, /*check_feasibility=*/true, |
| /*use_implicit_batch=*/true, &operand_1_new_dims, |
| &operand_2_new_dims), |
| StatusIs(expected_code, HasSubstr(expected_error_msg_substr))); |
| if (expected_code == error::OK) { |
| EXPECT_THAT(operand_1_new_dims, DimsAreArray(expected_operand_1_shape)); |
| EXPECT_THAT(operand_2_new_dims, DimsAreArray(expected_operand_2_shape)); |
| } |
| // operand_2 broadcast operand_1 |
| EXPECT_THAT( |
| GetTrtBroadcastShape(operand_2, operand_1, /*check_feasibility=*/true, |
| /*use_implicit_batch=*/true, &operand_2_new_dims, |
| &operand_1_new_dims), |
| StatusIs(expected_code, HasSubstr(expected_error_msg_substr))); |
| if (expected_code == error::OK) { |
| EXPECT_THAT(operand_1_new_dims, DimsAreArray(expected_operand_1_shape)); |
| EXPECT_THAT(operand_2_new_dims, DimsAreArray(expected_operand_2_shape)); |
| } |
| }; |
| |
| // Both inputs are weights. |
| symmetric_test( |
| {1}, {1}, kIsNotTensor, kIsNotTensor, {}, {}, error::INVALID_ARGUMENT, |
| "Broadcasting requires at least one of the operands be tensors"); |
| |
| // One tensor and one weights. |
| symmetric_test({1, 1, 1}, {2}, kIsTensor, kIsNotTensor, {1, 1, 1}, {1, 1, 2}); |
| symmetric_test({1, 1, 2}, {2}, kIsTensor, kIsNotTensor, {1, 1, 2}, {1, 1, 2}); |
| symmetric_test({1, 3, 2}, {1}, kIsTensor, kIsNotTensor, {1, 3, 2}, {1, 1, 1}); |
| symmetric_test({1, 1, 1}, {2, 3}, kIsTensor, kIsNotTensor, {1, 1, 1}, |
| {1, 2, 3}); |
| symmetric_test({1, 1, 1}, {2, 3, 4}, kIsTensor, kIsNotTensor, {1, 1, 1}, |
| {2, 3, 4}); |
| symmetric_test({1, 1, 1}, {1, 2, 3, 4}, kIsTensor, kIsNotTensor, {1, 1, 1}, |
| {2, 3, 4}); |
| symmetric_test({1, 3, 4}, {1, 2, 1, 4}, kIsTensor, kIsNotTensor, {1, 3, 4}, |
| {2, 1, 4}); |
| symmetric_test({1, 1, 1}, {2, 1, 1, 1}, kIsTensor, kIsNotTensor, {}, {}, |
| error::INVALID_ARGUMENT, "Infeasible broadcast scheme"); |
| symmetric_test({1, 1, 1}, {2, 1, 1, 1}, kIsTensor, kIsNotTensor, {}, {}, |
| error::INVALID_ARGUMENT, "Infeasible broadcast scheme", |
| /*operand_1_batch_size=*/2); |
| symmetric_test({1, 1, 1}, {1, 1, 1, 1, 1}, kIsTensor, kIsNotTensor, {}, {}, |
| error::INVALID_ARGUMENT, |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims 4 vs broadcast #dims 5)"); |
| symmetric_test({3}, {1, 1, 3}, kIsTensor, kIsNotTensor, {}, {}, |
| error::INVALID_ARGUMENT, |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims 2 vs broadcast #dims 3)", |
| /*operand_1_batch_size=*/2); |
| |
| // Both inputs are tensors. |
| symmetric_test({1, 1, 1}, {1, 1}, kIsTensor, kIsTensor, {}, {}, |
| error::INVALID_ARGUMENT, |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims 3 vs broadcast #dims 4)"); |
| symmetric_test({1, 3}, {3}, kIsTensor, kIsTensor, {}, {}, |
| error::INVALID_ARGUMENT, |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims 2 vs broadcast #dims 3)"); |
| symmetric_test({1, 3, 4}, {2, 1, 4}, kIsTensor, kIsTensor, {1, 3, 4}, |
| {2, 1, 4}); |
| symmetric_test({1, 1, 1}, {1, 1, 1, 1}, kIsTensor, kIsTensor, {}, {}, |
| error::INVALID_ARGUMENT, |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims 4 vs broadcast #dims 5)"); |
| symmetric_test({2, 3}, {7, 5}, kIsTensor, kIsTensor, {}, {}, |
| error::INVALID_ARGUMENT, "Infeasible broadcast scheme"); |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| TEST_F(ConverterTest, CreateConstantLayer) { |
| for (auto dtype : {nvinfer1::DataType::kFLOAT, nvinfer1::DataType::kINT32}) { |
| TRT_ShapedWeights weights = |
| weight_store_->GetTempWeights(dtype, CreateDims({2, 3, 5})); |
| ITensorProxyPtr tensor = |
| converter_->CreateConstantLayer(weights, CreateDims({3, 10})); |
| ASSERT_NE(nullptr, tensor->trt_tensor()); |
| EXPECT_EQ(dtype, tensor->getType()) |
| << "Expected " << DebugString(dtype) << " vs. actual " |
| << DebugString(tensor->getType()); |
| EXPECT_THAT(tensor->getDimensions(), DimsAreArray({3, 10})); |
| } |
| |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| |
| class ConvertGraphDefToEngineTest : public ::testing::Test { |
| public: |
| Status RunConvertGraphDefToEngine(Scope* s) { |
| GraphDef gdef; |
| TF_EXPECT_OK(s->ToGraphDef(&gdef)); |
| std::vector<PartialTensorShape> input_shapes; |
| int batch_size = -1; |
| for (const NodeDef& node : gdef.node()) { |
| absl::string_view node_name(node.name()); |
| if (absl::ConsumePrefix(&node_name, IONamePrefixes::kInputPHName)) { |
| int port = -1; |
| EXPECT_TRUE(absl::SimpleAtoi(node_name, &port)) << node.name(); |
| if (input_shapes.size() < port + 1) input_shapes.resize(port + 1); |
| input_shapes[port] = |
| PartialTensorShape(node.attr().at("shape").shape()); |
| if (batch_size == -1) { |
| batch_size = input_shapes[port].dim_size(0); |
| } else { |
| EXPECT_EQ(batch_size, input_shapes[port].dim_size(0)); |
| } |
| } |
| } |
| // TODO(laigd): execute the engine and get outputs. |
| return ConvertGraphDefToEngine( |
| gdef, TrtPrecisionMode::FP32, /*max_batch_size=*/1, |
| /*max_workspace_size_bytes=*/64 << 20, input_shapes, &logger_, |
| /*allocator=*/nullptr, /*calibrator=*/nullptr, &engine_, |
| /*use_calibration=*/false, /*use_implicit_batch=*/true, |
| /*convert_successfully=*/nullptr, /*profiles=*/nullptr, |
| "TRTEngineOp_0_0"); |
| } |
| |
| protected: |
| TrtUniquePtrType<nvinfer1::ICudaEngine> engine_; |
| |
| private: |
| Logger& logger_ = *Logger::GetLogger(); |
| }; |
| |
| TEST_F(ConvertGraphDefToEngineTest, IdentityGraph) { |
| Scope s = Scope::NewRootScope(); |
| auto input = |
| ops::Placeholder(s.WithOpName(StrCat(IONamePrefixes::kInputPHName, 0)), |
| DT_FLOAT, ops::Placeholder::Shape({1, 1})); |
| auto output = ops::Identity(s.WithOpName("identity1"), input); |
| output = ops::Identity(s.WithOpName("identity2"), output); |
| output = ops::Identity(s.WithOpName(StrCat(IONamePrefixes::kOutputPHName, 0)), |
| output); |
| // If the converter marks the input tensor as output tensor, the conversion |
| // below will fail with: |
| // > TensorRTOutputPH_0 cannot be both input and output |
| // > Network must have at least one output |
| TF_EXPECT_OK(RunConvertGraphDefToEngine(&s)); |
| } |
| |
| // Returns a vector of shapes from a vector of input tensors. This can be used |
| // to create optimization profiles. |
| Status GetShapeFromDataVec(DataVec input_data, |
| std::vector<TensorShape>* shape_vec) { |
| shape_vec->reserve(input_data.size()); |
| std::transform(input_data.begin(), input_data.end(), |
| std::back_inserter(*shape_vec), |
| [](InputOutputData x) { return x.tensor.shape(); }); |
| return Status::OK(); |
| } |
| |
| template <typename T> |
| inline absl::Span<const T> GetSpanForData(const InputOutputData& data) { |
| const auto& tensor_map = data.tensor.flat<T>(); |
| return absl::Span<const T>(tensor_map.data(), tensor_map.size()); |
| } |
| |
| std::vector<float> GetDataAsFloat(InputOutputData& data) { |
| if (data.tensor.dtype() == DT_FLOAT) { |
| auto span = GetSpanForData<float>(data); |
| return std::vector<float>(span.begin(), span.end()); |
| } |
| if (data.tensor.dtype() == DT_HALF) { |
| return CastVector<Eigen::half, float>(GetSpanForData<Eigen::half>(data)); |
| } |
| if (data.tensor.dtype() == DT_INT32) { |
| return CastVector<int32, float>(GetSpanForData<int32>(data)); |
| } |
| LOG(FATAL) << "DataType not supported for testing " |
| << DataTypeString(data.tensor.dtype()); |
| |
| return {}; |
| } |
| // Class to test various op converters, using both a TrtNodeValidator and |
| // Converter. |
| class OpConverterTest : public ::testing::Test { |
| public: |
| OpConverterTest() |
| : tensor_buffer_allocator_(new GpuManagedAllocator()), |
| scope_(Scope::NewRootScope()) { |
| QCHECK_EQ(0, cudaStreamCreate(&stream_)); |
| Reset(); |
| } |
| |
| ~OpConverterTest() noexcept override { |
| QCHECK_EQ(0, cudaStreamDestroy(stream_)); |
| } |
| |
| Status GetTensorOrWeights(const string& name, TRT_TensorOrWeights* output) { |
| return converter_->GetTensorOrWeights(name, output); |
| } |
| |
| void Reset(TrtPrecisionMode precision_mode_to_test = TrtPrecisionMode::FP32, |
| TrtTestMode trt_mode = TrtTestMode::kImplicitBatch) { |
| // Destroy existing TRT objects in a proper order. |
| converter_.reset(nullptr); |
| engine_.reset(nullptr); |
| |
| // Re-create them in proper order. |
| converter_ = |
| std::move(Converter::Create(precision_mode_to_test, |
| /*use_calibration=*/false, &logger_, |
| /*use_implicit_batch=*/trt_mode == |
| TrtTestMode::kImplicitBatch, |
| /*engine_name=*/"") |
| .ValueOrDie()); |
| |
| // Reset other related artifacts. |
| scope_ = Scope::NewRootScope(); |
| } |
| |
| // Constructs a flat tensor with 'vals' in Unified Memory. |
| template <typename T> |
| Tensor AsTensor(gtl::ArraySlice<T> vals) { // non-absl ok |
| Tensor ret(tensor_buffer_allocator_.get(), DataTypeToEnum<T>::value, |
| {static_cast<int64>(vals.size())}); |
| std::copy_n(vals.data(), vals.size(), ret.flat<T>().data()); |
| return ret; |
| } |
| |
| // Constructs a tensor of "shape" with values "vals" in Unified Memory. |
| template <typename T> |
| Tensor AsTensor(gtl::ArraySlice<T> vals, // non-absl ok |
| const TensorShape& shape) { |
| Tensor ret(tensor_buffer_allocator_.get(), DataTypeToEnum<T>::value, |
| {static_cast<int64>(vals.size())}); |
| CHECK(ret.CopyFrom(AsTensor(vals), shape)); |
| return ret; |
| } |
| |
| // Constructs a tensor with given values (vals). The tensor type is defined by |
| // the tf_type argument, its shape is given by input_dims. The tensor is |
| // constructed using the allocator of OpConverterTest in Unified Memory. |
| template <typename T> |
| Tensor AsTensor(std::vector<T> vals, const std::vector<int> input_dims, |
| DataType tf_type) { |
| Tensor ret(tensor_buffer_allocator_.get(), tf_type, |
| {static_cast<int64>(vals.size())}); |
| if (tf_type == DT_FLOAT) { |
| auto conv_vals = CastVector<T, float>(vals); |
| std::copy_n(conv_vals.data(), conv_vals.size(), ret.flat<float>().data()); |
| } else if (tf_type == DT_HALF) { |
| auto conv_vals = CastVector<T, Eigen::half>(vals); |
| std::copy_n(conv_vals.data(), conv_vals.size(), |
| ret.flat<Eigen::half>().data()); |
| } else if (tf_type == DT_INT32) { |
| auto conv_vals = CastVector<T, int32>(vals); |
| std::copy_n(conv_vals.data(), conv_vals.size(), ret.flat<int32>().data()); |
| } else { |
| LOG(FATAL) << "Cannot create tensor with type " |
| << DataTypeString(tf_type); |
| } |
| TensorShape shape; |
| TF_EXPECT_OK(TensorShapeUtils::MakeShape(input_dims, &shape)); |
| CHECK(ret.CopyFrom(ret, shape)); |
| return ret; |
| } |
| |
| // Constructs a flat tensor in Unified Memory. |
| template <typename T> |
| Tensor ConstructTensor(int data_size, const T& value = T()) { |
| std::vector<T> values(data_size, value); |
| return AsTensor<T>(values); |
| } |
| |
| // Constructs a flat tensor in Unified Memory. |
| template <typename T> |
| Tensor ConstructTensor(int data_size, const T& value, DataType tf_type) { |
| std::vector<T> values(data_size, value); |
| return AsTensor<T>(values, {data_size}, tf_type); |
| } |
| |
| void CheckDataTypeMatches(const DataVec& datas) { |
| if (VLOG_IS_ON(2)) { |
| int nbBindings = engine_->getNbBindings(); |
| VLOG(2) << "Number of engine bindings: " << nbBindings; |
| for (int i = 0; i < nbBindings; i++) { |
| VLOG(2) << "Binding " << i << " name: " << engine_->getBindingName(i); |
| } |
| } |
| for (const auto& data : datas) { |
| VLOG(2) << "Checking if data type matches for tensor " << data.name; |
| const int input_index = engine_->getBindingIndex(data.name.c_str()); |
| ASSERT_NE(-1, input_index); |
| const nvinfer1::DataType trt_dtype = |
| engine_->getBindingDataType(input_index); |
| DataType tf_type; |
| TF_ASSERT_OK(TrtTypeToTfType(trt_dtype, &tf_type)); |
| ASSERT_EQ(data.tensor.dtype(), tf_type) |
| << DataTypeString(data.tensor.dtype()) << " vs. " |
| << DataTypeString(tf_type); |
| } |
| } |
| |
| Status BuildAndRun(const DataVec& input_data, DataVec* output_data, |
| const int batch_size = 1) { |
| // Mark the output tensor as TRT engine output. |
| std::vector<Converter::EngineOutputInfo> output_info; |
| for (const auto& data : *output_data) { |
| nvinfer1::DataType trt_type; |
| TF_RETURN_IF_ERROR(TfTypeToTrtType(data.tensor.dtype(), &trt_type)); |
| output_info.push_back({data.name, data.name, trt_type}); |
| } |
| TF_RETURN_IF_ERROR(converter_->RenameAndMarkOutputTensors(output_info)); |
| |
| // Build the TRT engine. |
| if (engine_.get() != nullptr) { |
| return errors::Internal("Engine already exists"); |
| } |
| TrtShapeOptimizationProfile profiles; |
| if (!converter_->use_implicit_batch()) { |
| profiles.SetShapeTensorMask(converter_->network()); |
| TF_RETURN_IF_ERROR(profiles.CollectShapeValues(input_data)); |
| // Create a single optimization profile for explicit batch mode |
| std::vector<TensorShape> input_shapes; |
| TF_RETURN_IF_ERROR(GetShapeFromDataVec(input_data, &input_shapes)); |
| profiles.AddShape(input_shapes); |
| std::vector<PartialTensorShape> input_partial_shapes; |
| TF_RETURN_IF_ERROR( |
| GetNetworkInputShapes(converter_->network(), &input_partial_shapes)); |
| profiles.InitProfiles(input_partial_shapes, |
| ProfileStrategy::kImplicitBatchModeCompatible); |
| } |
| TF_RETURN_IF_ERROR( |
| converter_->BuildCudaEngine(&engine_, |
| /*max_batch_size=*/batch_size, |
| /*max_workspace_size_bytes=*/1 << 26, |
| /*allocator=*/nullptr, |
| /*calibrator=*/nullptr, |
| /*profiles=*/&profiles)); |
| CHECK_NOTNULL(engine_.get()); |
| CheckDataTypeMatches(input_data); |
| CheckDataTypeMatches(*output_data); |
| |
| const int num_bindings = input_data.size() + output_data->size(); |
| std::vector<void*> buffers(num_bindings); |
| |
| if (engine_->getNbBindings() != num_bindings) { |
| return errors::Internal("Number of bindings do not match"); |
| } |
| // Since we have only 1 optimization profile (which is enabled by default) |
| // it is fine to create execution context directly, instead of calling |
| // profiles.CreateExecutionContexts() |
| TrtUniquePtrType<nvinfer1::IExecutionContext> execution_context( |
| engine_->createExecutionContext()); |
| |
| // Prepare input bindings. |
| TF_RETURN_IF_ERROR( |
| SetTrtEngineInputs(engine_.get(), execution_context.get(), 0, buffers, |
| converter_->use_implicit_batch(), batch_size, |
| profiles, nullptr, &input_data)); |
| // Prepare output bindings. |
| TF_RETURN_IF_ERROR(SetTrtEngineOutputs( |
| engine_.get(), execution_context.get(), 0, buffers, |
| converter_->use_implicit_batch(), batch_size, nullptr, output_data)); |
| // Execute the TRT engine. |
| TF_RETURN_IF_ERROR(TrtEnqueue(execution_context.get(), buffers, stream_, |
| converter_->use_implicit_batch(), |
| batch_size)); |
| cudaStreamSynchronize(stream_); |
| return Status::OK(); |
| } |
| |
| // Adds ITensor for both validation and conversion, assuming explicit batch |
| // dimension is included in dims (ie for an NCHW tensor dims = {N, C, H, W}). |
| void AddTestTensorWithTFDims( |
| const string& name, const std::vector<int32>& dims, |
| nvinfer1::DataType trt_type = nvinfer1::DataType::kFLOAT, |
| Status add_input_status = Status::OK()) { |
| DataType tf_type; |
| TF_ASSERT_OK(TrtTypeToTfType(trt_type, &tf_type)); |
| ops::Placeholder::Attrs attrs; |
| TF_EXPECT_OK(TensorShapeUtils::MakeShape(dims, &attrs.shape_)); |
| |
| auto input = ops::Placeholder(scope_.WithOpName(name), tf_type, attrs); |
| node_inputs_[name] = input.output; |
| |
| // Add a real ITensor for conversion conditionally. |
| nvinfer1::Dims trt_dims; |
| Status status = TensorShapeToTrtDims( |
| attrs.shape_, converter_->use_implicit_batch(), &trt_dims); |
| if (converter_->use_implicit_batch() && !status.ok()) { |
| ASSERT_EQ(add_input_status, status); |
| return; |
| } else { |
| TF_EXPECT_OK(status); |
| } |
| if (!converter_->use_implicit_batch() || HasStaticShape(trt_dims)) { |
| int batch_size = dims.size() > 0 ? dims[0] : 0; |
| Status status = |
| converter_->AddInputTensor(name, trt_type, trt_dims, batch_size); |
| ASSERT_EQ(add_input_status, status); |
| } |
| } |
| |
| // Adds ITensor for both validation and conversion. The difference compared to |
| // AddTestTensorWithTFDims is in the meaning of the dims parameter. To define |
| // a tensor with NCHW shape, here we set dims = {C,H,W} and batch_size = N. |
| // TODO(tfeher) remove this function once all test are updated to use the |
| // other version of AddTestTensor (defined by |
| // ParameterizedOpConverterTestBase). |
| void AddTestTensor( |
| const string& name, const std::vector<int32>& dims, int batch_size = 1, |
| nvinfer1::DataType trt_dtype = nvinfer1::DataType::kFLOAT) { |
| std::vector<int32> dims_with_batch(dims.size() + 1); |
| dims_with_batch[0] = batch_size; |
| std::copy(dims.begin(), dims.end(), dims_with_batch.begin() + 1); |
| AddTestTensorWithTFDims(name, dims_with_batch, trt_dtype); |
| if (HasStaticShape(dims)) { |
| ASSERT_EQ(batch_size, converter_->batch_size_); |
| } |
| } |
| |
| // Add weights for both validation and conversion. |
| template <typename T> |
| void AddTestWeights(const string& name, const std::vector<int>& dims, |
| const std::vector<T>& values) { |
| // Add weights for validation. |
| TensorShape shape; |
| TF_EXPECT_OK(TensorShapeUtils::MakeShape(dims, &shape)); |
| Tensor t = AsTensor<T>(values, shape); |
| node_inputs_[name] = ops::Const(scope_.WithOpName(name), t); |
| |
| // Add weights for conversion. |
| nvinfer1::DataType dtype; |
| TF_ASSERT_OK(TfTypeToTrtType(DataTypeToEnum<T>::v(), &dtype)); |
| const nvinfer1::Dims trt_dims = CreateDims(dims); |
| const int64_t num_elements = TRT_ShapedWeights::count(trt_dims); |
| QCHECK_EQ(num_elements, values.size()) |
| << num_elements << " vs " << values.size(); |
| TRT_ShapedWeights weights(dtype); |
| if (num_elements) { |
| weights = converter_->weight_store_.GetTempWeights(dtype, trt_dims); |
| QCHECK_EQ(weights.size_bytes(), sizeof(T) * values.size()) |
| << weights.size_bytes() << " vs " << sizeof(T) * values.size(); |
| memcpy(weights.GetPointer<int8>(), values.data(), weights.size_bytes()); |
| } |
| TF_EXPECT_OK( |
| converter_->AddTensorOrWeights(name, TRT_TensorOrWeights{weights})); |
| } |
| |
| template <typename T = int32> |
| void AddTestWeights(const string& name, const std::vector<int>& dims, |
| const std::vector<T>& values, DataType tf_type) { |
| if (tf_type == DT_FLOAT) { |
| AddTestWeights(name, dims, CastVector<T, float>(values)); |
| } else if (tf_type == DT_HALF) { |
| AddTestWeights(name, dims, CastVector<T, Eigen::half>(values)); |
| } else if (tf_type == DT_INT32) { |
| AddTestWeights(name, dims, CastVector<T, int32>(values)); |
| } else { |
| FAIL() << "Cannot create test weights with type " |
| << DataTypeString(tf_type); |
| } |
| } |
| |
| // Test validation in validation-only mode. |
| Status RunValidation(const Node* node) { |
| grappler::GrapplerItem item; |
| TF_EXPECT_OK(scope_.ToGraphDef(&item.graph)); |
| grappler::GraphProperties graph_properties(item); |
| TF_EXPECT_OK(graph_properties.InferStatically(true)); |
| |
| TrtNodeValidator validator(graph_properties, converter_->precision_mode(), |
| /*use_calibration=*/false, |
| converter_->use_implicit_batch()); |
| return validator.IsTensorRTCandidate(node); |
| } |
| |
| void RunConversion(const Node* node, error::Code expected_code = error::OK, |
| const std::string& expected_msg_substr = "") { |
| EXPECT_THAT(converter_->ConvertNode(node->def()), |
| StatusIs(expected_code, HasSubstr(expected_msg_substr))); |
| if (expected_code == error::OK) { |
| EXPECT_THAT(converter_->network(), LayerNamesNonEmpty()); |
| } |
| } |
| |
| // Helper method to run both validation and conversion, when the expected |
| // output are same. |
| void RunValidationAndConversion(const NodeDef& node_def, |
| error::Code expected_code = error::OK, |
| const std::string& expected_msg_substr = "", |
| bool should_run_conversion = true) { |
| // Add the node to the graph. |
| // TODO(laigd): we should accept a function that adds the node using |
| // `scope_`, so individual test case can reuse the scope object and we don't |
| // need to add the edges here by ourselves. |
| Graph* graph = scope_.graph(); |
| Status status; |
| Node* node = graph->AddNode(std::move(node_def), &status); |
| TF_EXPECT_OK(status); |
| for (int i = 0; i < node_def.input().size(); ++i) { |
| const string& input_name = node_def.input(i); |
| const auto& itr = node_inputs_.find(input_name); |
| QCHECK(itr != node_inputs_.end()); |
| const Output& input = itr->second; |
| graph->AddEdge(input.node(), input.index(), node, i); |
| } |
| |
| status = RunValidation(node); |
| if (should_run_conversion && status.ok()) { |
| RunConversion(node, expected_code, expected_msg_substr); |
| } else { |
| EXPECT_THAT(status, |
| StatusIs(expected_code, HasSubstr(expected_msg_substr))); |
| } |
| } |
| |
| // Helper method to run both validation and conversion, and check the output |
| // shapes. |
| void RunValidationAndConversion( |
| const NodeDef& node_def, const Status& status, |
| const std::string& output_name, |
| const std::vector<std::vector<int>>& exp_out_dims) { |
| RunValidationAndConversion(node_def, status.code(), status.error_message(), |
| true); |
| |
| if (status.ok()) { |
| // TODO(tfeher): Enable this check in explicit_batch_mode. |
| // In dynamic shape mode the output dims cannot be tested here. In that |
| // case we need to wait for the concrate input shapes to be defined (by |
| // setBindingDimensions before enqueue) before we can check the output |
| // dims. |
| if (converter_->use_implicit_batch()) { |
| for (int i = 0; i < exp_out_dims.size(); i++) { |
| TRT_TensorOrWeights output; |
| string name = i == 0 ? output_name : StrCat(output_name, ":", i); |
| TF_EXPECT_OK(GetTensorOrWeights(name.c_str(), &output)); |
| ASSERT_TRUE(output.is_tensor()); |
| if (!exp_out_dims[i].empty()) { |
| // Removing batch dim. |
| auto out_dims = std::vector<int>(exp_out_dims[i].begin() + 1, |
| exp_out_dims[i].end()); |
| VLOG(2) << "Testing output shape for tensor " << name; |
| EXPECT_THAT(output.tensor()->getDimensions(), |
| DimsAreArray(out_dims)); |
| } |
| } |
| } |
| } |
| } |
| |
| // Expose quantization_ranges_ for tests |
| std::unordered_map<ITensorProxyPtr*, float>& quantization_ranges_proxy() { |
| return converter_->quantization_ranges_proxy_; |
| } |
| |
| // Expose quantization_ranges_ for tests |
| std::unordered_map<nvinfer1::ITensor*, float>& quantization_ranges() { |
| return converter_->quantization_ranges_; |
| } |
| |
| std::unique_ptr<Converter> converter_; |
| |
| private: |
| Logger& logger_ = *Logger::GetLogger(); |
| TrtUniquePtrType<nvinfer1::ICudaEngine> engine_; |
| cudaStream_t stream_; |
| std::unique_ptr<Allocator> tensor_buffer_allocator_; |
| // The scope that contains the graph being converted. Because |
| // tensor_buffer_allocator_ provides the storage for tensor contents that are |
| // represented as attributes for graph nodes within scope_, |
| // tensor_buffer_allocator_ needs to be available when destructing scope_. |
| // Therefore, scope_ comes after tensor_buffer_allocator_ in the class member |
| // field list. |
| Scope scope_; |
| std::unordered_map<string, Output> node_inputs_; |
| }; |
| |
| // General test parameters to be used with ops that take a single input tensor. |
| struct TestParamBase { |
| // Concrete input dimensions for the test (including the batch dim) |
| std::vector<int> input_dims; |
| |
| // Dimensions to define an input with PartialTensorShape. This can be used to |
| // define networks with dynamic input shape. It can be left empty, in that |
| // case AddTestTensor sets partial shapes that are appropriate to TrtTestMode. |
| std::vector<int> partial_input_dims; |
| |
| // Concrete (static) output dimensions, including batch size as first dim |
| std::vector<int> expected_output_dims; |
| |
| // Parameter vector, has converter specific meaning. |
| std::vector<int> param; |
| |
| // Expected status of conversion (with concrete error message) |
| Status status; |
| |
| // Expected status of BuildAndRun |
| Status runtime_status; |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, const TestParamBase& p) { |
| os << "input_dims" << PrintToString(p.input_dims); |
| if (!p.partial_input_dims.empty()) { |
| os << ", partial_input_dims" << PrintToString(p.partial_input_dims); |
| } |
| if (!p.expected_output_dims.empty()) { |
| os << ", exp_out_dims" << PrintToString(p.expected_output_dims); |
| } |
| if (!p.param.empty()) { |
| os << ", param" << PrintToString(p.param); |
| } |
| os << ", " << p.status; |
| return os; |
| } |
| |
| // Parameterized version of OpConverterTest. We have the following parameters: |
| // 1. TrtTestMode: implicit batch, explicit batch, dynamic shape modes |
| // 2. DataType of the input TF tensors: DT_FLOAT, DT_HALF, DT_INT32 |
| // 3. TrtPrecisionMode argument for the Converter: FP32, FP16, INT8 |
| // We will introduce subclasses that will be instantiated using different |
| // combinations of the DataType and TrtPrecisionMode parameters. |
| class ParameterizedOpConverterTestBase |
| : public OpConverterTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TrtTestMode, DataType, TrtPrecisionMode>> { |
| public: |
| ParameterizedOpConverterTestBase() |
| : trt_mode_(std::get<0>(GetParam())), |
| tf_type_(std::get<1>(GetParam())), |
| converter_precision_(std::get<2>(GetParam())) { |
| LOG(INFO) << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"; |
| LOG(INFO) << "tf_type_: " << DebugString(tf_type_); |
| LOG(INFO) << "trt_mode_: " << DebugString(trt_mode_); |
| LOG(INFO) << "converter_precision_: " << DebugString(converter_precision_); |
| LOG(INFO) << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"; |
| } |
| |
| void Reset() { |
| OpConverterTest::Reset(converter_precision_, trt_mode_); |
| input_data_.clear(); |
| } |
| |
| void Reset(TrtPrecisionMode precision) { |
| OpConverterTest::Reset(precision, trt_mode_); |
| input_data_.clear(); |
| } |
| |
| // Getters of protected attributes |
| DataType get_tf_type() { return tf_type_; } |
| TrtTestMode get_trt_mode() { return trt_mode_; } |
| TrtPrecisionMode get_converter_precision() { return converter_precision_; } |
| |
| // Adds an input ITensor for TRT network. Also creates the corresponding TF |
| // tensor, and stores it in the list of inputs (input_data_). |
| // |
| // The TF tensor is always created with concrete static input shape given by |
| // dims. The ITensor can have static or dynamic shape based on the trt_mode |
| // attribute. The ITensor shape is set automatically according to the trt_mode |
| // parameter, unless the user overrides it with an explicit |
| // partial_input_shape_dims argument. |
| // |
| // Parameters: |
| // - name of the input node |
| // - dims actual dimensions of the tensor that we will use during the test |
| // (including explicit batch dim) |
| // - values initial values for the TF tensor |
| // - dtype data type of the tensor |
| // - partial_input_shape dimensions which can include unknown shapes. This can |
| // be empty, in that case the partial_input_shape will be set automatically |
| // depending on the trt_mode argument. (This argument also includes explicit |
| // batch dim). |
| // - add_input_status adding ITensor to the network can fail in implicit batch |
| // mode if the batch size is inconsistent. Using the add_input_status arg we |
| // can test such errors. |
| // |
| template <typename T = int> |
| void AddTestTensor(const string& name, const std::vector<int32>& dims, |
| DataType tf_type, const std::vector<T>& values, |
| const std::vector<int32>& partial_input_shape_dims = {}, |
| Status add_input_status = Status::OK()) { |
| if (!dims.empty()) { |
| const auto num_elements = std::accumulate( |
| std::begin(dims), std::end(dims), 1, std::multiplies<double>()); |
| if (!values.empty() && num_elements != values.size()) { |
| // Note: for conversion only tests, it is valid to have empty values, |
| // otherwise the number of elements should match. |
| LOG(WARNING) << "Expected Test Tensor Shape: " << DebugString(dims) |
| << ", Received Input Tensor: " << DebugString(values); |
| } |
| } |
| |
| std::vector<int32> partial_shape; |
| if (!partial_input_shape_dims.empty()) { |
| partial_shape = partial_input_shape_dims; |
| } else { |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| // In dynamic shape mode we make all dims unknown. |
| partial_shape = std::vector<int32>(dims.size(), -1); |
| } else { |
| // Use static (known) input shapes. |
| partial_shape = dims; |
| } |
| } |
| nvinfer1::DataType trt_type; |
| TF_ASSERT_OK(TfTypeToTrtType(tf_type, &trt_type)); |
| AddTestTensorWithTFDims(name, partial_shape, trt_type, add_input_status); |
| if (!values.empty()) { |
| VLOG(2) << "Adding test tensor: " << name << " " |
| << DataTypeString(tf_type); |
| InputOutputData data{name, AsTensor(values, dims, tf_type)}; |
| VLOG(2) << "Added tensor: " << data.name << " with dtype " |
| << DataTypeString(data.tensor.dtype()); |
| input_data_.push_back(data); |
| } |
| } |
| |
| // Adds test tensor (same as above) but with the default tf_type defined by |
| // the test params. |
| template <typename T = int> |
| void AddTestTensor(const string& name, const std::vector<int32>& dims, |
| const std::vector<T>& values = {}, |
| const std::vector<int32>& partial_input_shape_dims = {}) { |
| AddTestTensor<T>(name, dims, tf_type_, values, partial_input_shape_dims); |
| } |
| |
| // Builds and runs the converted network. Checks output tensor shape. Tests |
| // output values using a matcher. The network can have multiple input and |
| // output tensors. The inputs are defined by the input_data_ member variable. |
| void BuildAndRun(const string& name, |
| const std::vector<std::vector<int>>& expected_output_dims, |
| const Status& expected_runtime_status, |
| const std::vector<Matcher<std::vector<float>>>& matcher, |
| const std::vector<DataType>& out_tf_types = {}) { |
| TensorShape shape; |
| const int n_output = expected_output_dims.size(); |
| ASSERT_EQ(n_output, matcher.size()); |
| DataVec output_data; |
| for (int i = 0; i < n_output; i++) { |
| TF_EXPECT_OK( |
| TensorShapeUtils::MakeShape(expected_output_dims[i], &shape)); |
| string out_name = (i == 0) ? name : StrCat(name, ":", i); |
| DataType out_tf_type = |
| out_tf_types.size() > i ? out_tf_types[i] : tf_type_; |
| InputOutputData data{ |
| out_name, ConstructTensor(shape.num_elements(), 0, out_tf_type)}; |
| output_data.push_back(data); |
| } |
| const int batch_size = |
| input_data_.empty() || |
| TensorShapeUtils::IsScalar(input_data_[0].tensor.shape()) |
| ? 1 |
| : input_data_[0].tensor.shape().dim_size(0); |
| Status stat = |
| OpConverterTest::BuildAndRun(input_data_, &output_data, batch_size); |
| ASSERT_EQ(expected_runtime_status.ok(), stat.ok()) |
| << "expected status: " << expected_runtime_status |
| << ", actual status: " << stat; |
| if (expected_runtime_status.ok() && stat.ok()) { |
| for (int i = 0; i < n_output; i++) { |
| // Check the shape of the actual output tensors |
| TF_EXPECT_OK( |
| TensorShapeUtils::MakeShape(expected_output_dims[i], &shape)); |
| EXPECT_TRUE(output_data[i].tensor.shape() == shape) |
| << "Expected shape: " << shape.DebugString() << ", actual shape: " |
| << output_data[i].tensor.shape().DebugString(); |
| EXPECT_THAT(GetDataAsFloat(output_data[i]), matcher[i]); |
| } |
| } |
| } |
| |
| // Runs validation and conversion. If conversion is successfull then builds |
| // the TRT network, executes it and checks the output. Handles multiple output |
| // tensors. |
| void TestOpConverterMultiOut( |
| const string& name, const NodeDef node_def, |
| const std::vector<std::vector<int>>& expected_output_dims, |
| const Status& expected_conversion_status, |
| const Status& expected_runtime_status, |
| const std::vector<Matcher<std::vector<float>>>& matcher, |
| const std::vector<DataType>& out_tf_type = {}) { |
| RunValidationAndConversion(node_def, expected_conversion_status, name, |
| expected_output_dims); |
| if (expected_conversion_status.ok()) { |
| BuildAndRun(name, expected_output_dims, expected_runtime_status, matcher, |
| out_tf_type); |
| } |
| } |
| |
| // Runs validation and conversion. If conversion is successfull then builds |
| // the TRT network, executes it and checks the output. |
| void TestOpConverter(const string& name, const NodeDef node_def, |
| const std::vector<int>& expected_output_dims, |
| const Status& expected_conversion_status, |
| const Status& expected_runtime_status, |
| const Matcher<std::vector<float>>& matcher, |
| const std::vector<DataType>& out_tf_types = {}) { |
| RunValidationAndConversion( |
| node_def, expected_conversion_status, name, |
| std::vector<std::vector<int>>({expected_output_dims})); |
| if (expected_conversion_status.ok()) { |
| BuildAndRun(name, std::vector<std::vector<int>>({expected_output_dims}), |
| expected_runtime_status, |
| std::vector<Matcher<std::vector<float>>>({matcher}), |
| out_tf_types); |
| } |
| } |
| |
| protected: |
| const TrtTestMode trt_mode_; |
| const DataType tf_type_; |
| const TrtPrecisionMode converter_precision_; |
| DataVec input_data_; |
| }; |
| |
| // Op converter test in FP32 mode. While for debugging purposes it might make |
| // sense to run over all possible combinations, normally a subset of them |
| // would be sufficient: |
| // - All valid options to TrtTestMode (implicit, explicit, dynamic shape) |
| // - DataType: is the TF data type of the input tensors. This usually only |
| // influences the data type added by Converter::AddInputTensor. We test the |
| // valid combinations of input data types in AddAndGetInputs, therefore |
| // for most of the OpConverterTest its is sufficient to test for DT_FLOAT. |
| // - TrtPrecisionMode: valid options are FP32, FP16 and INT8. This influences |
| // how TRT handles the precision inside the TRT network, but should not matter |
| // for the TF -> TRT conversion. Therefore it should be sufficient to test |
| // for FP32. |
| class OpConverter_FP32_Test : public ParameterizedOpConverterTestBase {}; |
| // Base class for tests that need to be tested for both FP32 and FP16. |
| class OpConverter_FP32_FP16_Test : public ParameterizedOpConverterTestBase {}; |
| // Base class for tests that need to be tested for FP32, FP16, and INT32 |
| class OpConverter_FP32_FP16_INT32_Test |
| : public ParameterizedOpConverterTestBase {}; |
| |
| // Instantiate parameter combinations to OpConverter_<DT_X...>_Test |
| INSTANTIATE_TEST_CASE_P( |
| OpConvTestInstantiation, OpConverter_FP32_Test, |
| ::testing::Combine(::testing::ValuesIn(ValidTrtModes), |
| ::testing::Values(DT_FLOAT), |
| ::testing::Values(TrtPrecisionMode::FP32))); |
| |
| INSTANTIATE_TEST_CASE_P( |
| OpConvTestInstantiation, OpConverter_FP32_FP16_Test, |
| ::testing::Combine(::testing::ValuesIn(ValidTrtModes), |
| ::testing::Values(DT_FLOAT, DT_HALF), |
| ::testing::Values(TrtPrecisionMode::FP32))); |
| |
| INSTANTIATE_TEST_CASE_P( |
| OpConvTestInstantiation, OpConverter_FP32_FP16_INT32_Test, |
| ::testing::Combine(::testing::ValuesIn(ValidTrtModes), |
| ::testing::Values(DT_FLOAT, DT_HALF, DT_INT32), |
| ::testing::Values(TrtPrecisionMode::FP32))); |
| |
| template <typename T> |
| void CopyTensorElements(const Tensor& tensor, protobuf::RepeatedField<T>* out) { |
| out->Clear(); |
| if (tensor.NumElements() == 0) return; |
| |
| // TensorProto does not need to have all the elements present and can truncate |
| // trailing elements with the same value for compressed representation. Such |
| // elements are derived based on the tensor shape. |
| const auto flat = tensor.flat<T>(); |
| int64 last_index = 0; |
| for (int64 i = 0; i < tensor.NumElements(); ++i) { |
| if (flat(i) != flat(last_index)) { |
| last_index = i; |
| } |
| } |
| |
| int num_out_elements = last_index + 1; |
| out->Reserve(num_out_elements); |
| out->AddNAlreadyReserved(num_out_elements); |
| const T* src = flat.data(); |
| T* dst = out->mutable_data(); |
| std::copy(src, src + num_out_elements, dst); |
| } |
| |
| template <DataType dtype, typename InputCType, typename OutputCType> |
| void TestConvertConst(OpConverterTest* test) { |
| NodeDef node_def; |
| node_def.set_name("my_const"); |
| node_def.set_op("Const"); |
| |
| auto reset_and_test = [&node_def, test]( |
| const Tensor& tensor, const bool as_tensor_content, |
| const std::vector<int>& expected_dims, |
| const std::vector<OutputCType>& expected_value) { |
| test->Reset(); |
| |
| TensorProto* tensor_attr = |
| (*node_def.mutable_attr())["value"].mutable_tensor(); |
| tensor_attr->Clear(); |
| |
| if (as_tensor_content) { |
| tensor.AsProtoTensorContent(tensor_attr); |
| } else { |
| tensor.shape().AsProto(tensor_attr->mutable_tensor_shape()); |
| tensor_attr->set_dtype(tensor.dtype()); |
| |
| if (tensor.dtype() == DT_FLOAT) { |
| CopyTensorElements<float>(tensor, tensor_attr->mutable_float_val()); |
| } else if (tensor.dtype() == DT_INT32) { |
| CopyTensorElements<int32>(tensor, tensor_attr->mutable_int_val()); |
| } else { |
| tensor.AsProtoField(tensor_attr); |
| } |
| } |
| test->RunValidationAndConversion(node_def); |
| TRT_TensorOrWeights output; |
| TF_EXPECT_OK(test->GetTensorOrWeights("my_const", &output)); |
| EXPECT_THAT(output.weights(), ShapedWeightsHasDimsAndValues<OutputCType>( |
| expected_dims, expected_value)); |
| }; |
| |
| auto& attr = *node_def.mutable_attr(); |
| attr["dtype"].set_type(dtype); |
| { |
| // By default empty tensor will pick DT_FLOAT as data type and we fix it |
| // here. |
| Tensor t(dtype); // Empty tensor. |
| reset_and_test(t, false, {}, {}); |
| } |
| { |
| Tensor t = test::AsScalar<InputCType>(12); |
| std::vector<int> expected_dims{1}; |
| // Scalars are represented as rank 0 tensors. |
| expected_dims.clear(); |
| reset_and_test(t, false, expected_dims, {12}); |
| reset_and_test(t, true, expected_dims, {12}); |
| } |
| { |
| Tensor t = test->AsTensor<InputCType>({1, 2}); |
| reset_and_test(t, false, {2}, {1, 2}); |
| reset_and_test(t, true, {2}, {1, 2}); |
| } |
| { |
| Tensor t = |
| test->AsTensor<InputCType>({1, 2, 3, 4, 5, 6}, TensorShape({2, 3})); |
| reset_and_test(t, false, {2, 3}, {1, 2, 3, 4, 5, 6}); |
| reset_and_test(t, true, {2, 3}, {1, 2, 3, 4, 5, 6}); |
| } |
| { |
| // Set all tensor elements to the same value. Such tensors are encoded |
| // using a single element list in tensor proto. |
| Tensor t = |
| test->AsTensor<InputCType>({1, 1, 1, 1, 1, 1}, TensorShape({2, 3})); |
| reset_and_test(t, false, {2, 3}, {1, 1, 1, 1, 1, 1}); |
| reset_and_test(t, true, {2, 3}, {1, 1, 1, 1, 1, 1}); |
| } |
| { |
| // Set trailing tensor elements to the same value. Such tensors are |
| // encoded by truncating all equal elements except the first one. |
| Tensor t = |
| test->AsTensor<InputCType>({2, 2, 1, 1, 1, 1}, TensorShape({2, 3})); |
| reset_and_test(t, false, {2, 3}, {2, 2, 1, 1, 1, 1}); |
| reset_and_test(t, true, {2, 3}, {2, 2, 1, 1, 1, 1}); |
| } |
| } |
| |
| TEST_F(OpConverterTest, ConvertConst) { |
| { |
| Reset(); |
| NodeDef node_def = MakeConstNodeDef<double>("my_const", {}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Unsupported tensorflow data type double"); |
| } |
| { |
| Reset(); |
| Tensor tensor = AsTensor<int64>({1, std::numeric_limits<int64>::max(), 1, 1, |
| 1, std::numeric_limits<int64>::lowest()}, |
| TensorShape({2, 3})); |
| NodeDef node_def; |
| node_def.set_name("my_const"); |
| node_def.set_op("Const"); |
| (*node_def.mutable_attr())["dtype"].set_type(DT_INT64); |
| TensorProto* tensor_attr = |
| (*node_def.mutable_attr())["value"].mutable_tensor(); |
| tensor_attr->Clear(); |
| tensor.AsProtoTensorContent(tensor_attr); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "outside the range of int32"); |
| } |
| |
| TestConvertConst<DT_FLOAT, float, float>(this); |
| TestConvertConst<DT_INT8, int8, int32>(this); |
| TestConvertConst<DT_UINT8, uint8, int32>(this); |
| TestConvertConst<DT_INT16, int16, int32>(this); |
| TestConvertConst<DT_UINT16, uint16, int32>(this); |
| TestConvertConst<DT_INT32, int32, int32>(this); |
| TestConvertConst<DT_UINT32, uint32, int32>(this); |
| TestConvertConst<DT_INT64, int64, int32>(this); |
| TestConvertConst<DT_UINT64, uint64, int32>(this); |
| } |
| |
| template <typename T> |
| NodeDef CreateFusedBatchNormOp(DataType tf_type, std::string data_format, |
| bool is_training, float epsilon) { |
| Scope s = Scope::NewRootScope(); |
| auto x = ops::Placeholder(s.WithOpName("x"), tf_type); |
| auto scale = ops::Placeholder(s.WithOpName("scale"), tf_type); |
| auto offset = ops::Placeholder(s.WithOpName("offset"), tf_type); |
| auto mean = ops::Placeholder(s.WithOpName("mean"), tf_type); |
| auto variance = ops::Placeholder(s.WithOpName("variance"), tf_type); |
| typename T::Attrs attrs; |
| attrs.data_format_ = data_format; |
| attrs.is_training_ = is_training; |
| if (epsilon > 0) { |
| attrs.epsilon_ = epsilon; |
| } else { |
| EXPECT_GE(epsilon, 0); |
| } |
| return T(s.WithOpName("my_batchnorm"), x, scale, offset, mean, variance, |
| attrs) |
| .operation.node() |
| ->def(); |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertFusedBatchNorm) { |
| using OpFunc = std::function<NodeDef(DataType, std::string, bool, float)>; |
| std::vector<OpFunc> get_node_def_vec{ |
| CreateFusedBatchNormOp<ops::FusedBatchNorm>, |
| CreateFusedBatchNormOp<ops::FusedBatchNormV2>, |
| CreateFusedBatchNormOp<ops::FusedBatchNormV3>}; |
| |
| struct TestParam { |
| std::string data_format; |
| int tensor_input_idx; // Index of an input that will be provided as tensor. |
| bool is_training; |
| float epsilon; |
| Status conversion_status; |
| bool keep_channel_unknown; |
| }; |
| |
| struct NodeInput { |
| std::string name; |
| std::vector<int> dims; |
| std::vector<float> val; |
| }; |
| std::vector<NodeInput> node_input{ |
| {"x", {2, 3, 2, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, |
| {"scale", {3}, {7, 8, 9}}, |
| {"offset", {3}, {10, 20, 30}}, |
| {"mean", {3}, {1, 2, 3}}, |
| {"variance", {3}, {4, 5, 6}}}; |
| |
| std::vector<float> expected_output{10.0, 13.495633, 23.574135, 27.148273, |
| 37.342354, 41.013527, 30.9738, 34.469433, |
| 45.018955, 48.59309, 59.369415, 63.04059}; |
| for (auto get_node_def : get_node_def_vec) { |
| NodeDef tmp_node_def = get_node_def(tf_type_, "NCHW", true, 0); |
| std::string op_name = tmp_node_def.op(); |
| std::vector<TestParam> test_param{ |
| {"NHWC", 0, false, 0, |
| errors::Unimplemented(StrCat( |
| op_name, " only supports data_format=NCHW, at my_batchnorm"))}, |
| {"NCHW", 0, true, 0, |
| errors::Unimplemented(StrCat( |
| op_name, " only supports is_training=false, at my_batchnorm"))}, |
| {"NCHW", 1, false, 0, |
| errors::Unimplemented(StrCat("The input \"scale\" for ", op_name, |
| " must be a constant, at my_batchnorm"))}, |
| {"NCHW", 2, false, 0, |
| errors::Unimplemented(StrCat("The input \"offset\" for ", op_name, |
| " must be a constant, at my_batchnorm"))}, |
| {"NCHW", 3, false, 0, |
| errors::Unimplemented(StrCat("The input \"mean\" for ", op_name, |
| " must be a constant, at my_batchnorm"))}, |
| {"NCHW", 4, false, 0, |
| errors::Unimplemented(StrCat("The input \"variance\" for ", op_name, |
| " must be a constant, at my_batchnorm"))}, |
| {"NCHW", 0, false, 0.01}}; // The last one is the only test that runs. |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| test_param.push_back( |
| {"NCHW", 0, false, 0.01, |
| errors::InvalidArgument( |
| "Channel dimension must be static, at my_batchnorm"), |
| true}); |
| } |
| for (auto p : test_param) { |
| Reset(); |
| NodeDef node_def = |
| get_node_def(tf_type_, p.data_format, p.is_training, p.epsilon); |
| for (int i = 0; i < node_input.size(); i++) { |
| if (i == 0 || i == p.tensor_input_idx) { |
| // The first input (x) is always added as a tensor, and it hase shape |
| // NCHW. The other inputs are per channel values (1D, size C). |
| // |
| // In implicit batch mode, it is not possible to add any of the 1D |
| // inputs as a tensor: the first dim is always treated as batch dim in |
| // implicit batch mode, and that has to agree for all tensors. We have |
| // two input tensors with shapes NCHW and C and in general N != C. |
| // The converter already picked up N from the fist input, and reports |
| // an error when we try to add any other tensors with not matching |
| // first dim. |
| // |
| // This restriction does not apply in explicit batch mode: the tensors |
| // can have different first dim. The converter still expects that only |
| // the first arg is a tensor. TODO(tfeher) Check if one can relax this |
| // restriction. |
| Status expected_status = |
| (i != 0 && trt_mode_ == TrtTestMode::kImplicitBatch) |
| ? errors::InvalidArgument( |
| StrCat("Batch size doesn't match for tensor ", |
| node_input[i].name, |
| ": Provided batch size does not match " |
| "converter batch size: 3 vs 2")) |
| : Status::OK(); |
| std::vector<int> partial_input_shape; |
| if (i == 0 && trt_mode_ == TrtTestMode::kDynamicShape && |
| !p.keep_channel_unknown) { |
| // keep channel dim static (known) |
| partial_input_shape.resize(4, -1); |
| partial_input_shape[1] = node_input[i].dims[1]; |
| } |
| AddTestTensor(node_input[i].name, node_input[i].dims, tf_type_, |
| node_input[i].val, partial_input_shape, |
| expected_status); |
| |
| } else { |
| AddTestWeights(node_input[i].name, node_input[i].dims, |
| node_input[i].val, tf_type_); |
| } |
| } |
| TestOpConverter("my_batchnorm", node_def, node_input[0].dims, |
| p.conversion_status, Status::OK(), |
| ArrayFloatNear(expected_output)); |
| } |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertTranspose) { |
| // Get the NodeDef for Transpose. |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), DT_INT32); |
| auto transpose = ops::Transpose(s.WithOpName("my_transpose"), input, weights); |
| const NodeDef& node_def = transpose.operation.node()->def(); |
| |
| std::vector<TestParamBase> test_params = { |
| // For the first test we leave param empty. This signals to use a |
| // input as weight which will be invalid |
| TestParamBase{{3, 1, 2, 1}, |
| {}, |
| {}, |
| {}, |
| Status(error::UNIMPLEMENTED, |
| "The input \"perm\" for Transpose must be a " |
| "constant, at my_transpose")}, |
| TestParamBase{{1, 1, 2, 3}, |
| {}, |
| {}, |
| {0, 1, 2}, |
| Status(error::INVALID_ARGUMENT, |
| "Rank of perm for transpose does not match with " |
| "that of the input.")}, |
| // Transpose batch dim |
| TestParamBase{ |
| {1, 1, 2, 3}, |
| {}, |
| {3, 2, 1, 1}, |
| {3, 2, 1, 0}, |
| (trt_mode_ == TrtTestMode::kImplicitBatch) |
| ? Status(error::UNIMPLEMENTED, |
| "Transpose at batch dimension is not supported") |
| : Status::OK()}, |
| TestParamBase{{1, 1, 2, 3}, {}, {1, 3, 1, 2}, {0, 3, 1, 2}}, |
| }; |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| // Dynamic shape tests where some shapes are known |
| test_params.push_back(TestParamBase{ |
| {1, 1, 2, 3}, {-1, 1, 2, -1}, {1, 3, 1, 2}, {0, 3, 1, 2}}); |
| } |
| std::vector<float> expected_values{1, 4, 2, 5, 3, 6}; |
| for (auto p : test_params) { |
| SCOPED_TRACE(p); |
| Reset(); |
| AddTestTensor("input", p.input_dims, {1, 2, 3, 4, 5, 6}, |
| p.partial_input_dims); |
| if (p.param.empty()) { |
| AddTestTensor("weights", {3}); |
| } else { |
| AddTestWeights<int32>("weights", {static_cast<int>(p.param.size())}, |
| p.param); |
| } |
| TestOpConverter("my_transpose", node_def, p.expected_output_dims, p.status, |
| p.runtime_status, ElementsAreArray(expected_values)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertReshape) { |
| // Get the NodeDef for Reshape. |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), DT_INT32); |
| auto reshape = ops::Reshape(s.WithOpName("my_reshape"), input, weights); |
| const NodeDef& node_def = reshape.operation.node()->def(); |
| |
| if (trt_mode_ == TrtTestMode::kImplicitBatch) { |
| // Shape is a tensor, should fail in implicit batch mode. |
| Reset(); |
| AddTestTensor("input", {3, 2, 1}); |
| AddTestTensor("weights", {3}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "The input \"shape\" for Reshape must be a constant in implicit batch " |
| "mode, at my_reshape"); |
| } else if (!IS_TRT_VERSION_GE(7, 1, 3, 0)) { |
| // Shape is a tensor, should fail before TRT 7.1.3 even in explicit batch / |
| // dynamic shape mode. |
| Reset(); |
| AddTestTensor("input", {3, 2, 1}); |
| AddTestTensor("weights", {3}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Non constant shape input tensor for Reshape requires minimum TRT " |
| "7.1.3"); |
| } |
| |
| Status reshape_from_scalar_status = |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Internal( |
| "Failed to convert input input to a TRT_TensorOrWeights: " |
| "Scalar input tensor is not supported since the first " |
| "dimension is treated as batch dimension by TRT") |
| : Status::OK(); |
| Status add_scalar_tensor_status = |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Internal( |
| "Scalars cannot be represented in implicit batch mode") |
| : Status::OK(); |
| Status reshape_to_scalar_status = |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Unimplemented( |
| "Reshape to shape=[] is not supported, at my_reshape") |
| : Status::OK(); |
| Status reshape_batch_status = |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Unimplemented( |
| "Reshape on batch dimension is not supported, at my_reshape") |
| : Status::OK(); |
| |
| struct TestParams { |
| std::vector<int> tensor_dims; |
| std::vector<int> shape; |
| std::vector<int> expected_shape; |
| Status conversion_status; |
| Status runtime_status; |
| std::vector<int> shape_prof; // needed concrete values if shape == -1. |
| Status add_test_tensor_status; |
| }; |
| |
| std::vector<TestParams> params = { |
| // Reshape scalar to tensor, should fail in implicit batch mode. |
| TestParams{{}, |
| {1, 1}, |
| {}, |
| reshape_from_scalar_status, |
| {}, |
| {}, |
| add_scalar_tensor_status}, |
| // Reshape tensor to scalar, should fail in implicit batch mode. |
| // - In explicit batch mode if shape is set as weight it works. |
| // - In explicit batch mode && using shape as tensor input it should |
| // fail. In that case we set the expected conversion status in the |
| // test loop. |
| TestParams{{1, 1}, {}, {}, reshape_to_scalar_status}, |
| // Reshape at batch dimension, should fail in implicit batch mode. |
| TestParams{{1, 1, 2, 3}, {3, 1, 1, 2}, {}, reshape_batch_status}, |
| TestParams{{2, 1, 2, 3}, {-1, 1, 4}, {3, 1, 4}, reshape_batch_status}, |
| // Tests that should succeed in every trt_mode. |
| TestParams{{1, 1, 2, 3}, {-1, 1, 3, 2}, {1, 1, 3, 2}}, |
| TestParams{{1, 1, 2, 3}, {1, 1, -1}, {1, 1, 6}}, |
| TestParams{{1, 1, 2, 3}, {1, 1, 3, 2}}, |
| TestParams{{2, 1, 2, 3}, {2, 1, 3, 2}}, |
| TestParams{{1, 1, 1}, {1}}, |
| TestParams{{1}, {1, 1}}, |
| TestParams{{2, 1, 1}, {2}}, |
| TestParams{{2}, {2, 1}}, |
| }; |
| if (trt_mode_ == TrtTestMode::kImplicitBatch) { |
| // Reshape tensor with zero rank using an empty shape tensor, should fail in |
| // implicit batch mode. In explicit batch mode this is an identity operation |
| // and does not add a reshape layer therefore we do not test it. |
| params.push_back(TestParams{{}, |
| {}, |
| {}, |
| reshape_from_scalar_status, |
| {}, |
| {}, |
| add_scalar_tensor_status}); |
| } |
| // Testing the methods for representing the reshape shape for IShuffleLayer: |
| // as a weight (true) or as a tensor (false). |
| std::vector<bool> shape_input_options(1, true); |
| |
| if (trt_mode_ != TrtTestMode::kImplicitBatch && |
| IS_TRT_VERSION_GE(7, 1, 3, 0)) { |
| shape_input_options.push_back(false); |
| } |
| |
| for (auto p : params) { |
| for (auto shape_as_weight : shape_input_options) { |
| std::ostringstream oss; |
| oss << "shape " << PrintToString(p.shape); |
| SCOPED_TRACE(StrCat(oss.str(), shape_as_weight ? " weight" : " tensor")); |
| if (!shape_as_weight && p.shape.empty()) { |
| p.conversion_status = errors::Unimplemented( |
| "Reshape with dynamic input requires 1D input tensor, at " |
| "my_reshape"); |
| } |
| Reset(); |
| const int n_elements = |
| std::accumulate(p.tensor_dims.begin(), p.tensor_dims.end(), 1, |
| std::multiplies<int>()); |
| std::vector<float> input_vec(n_elements); |
| std::iota(input_vec.begin(), input_vec.end(), 1); |
| AddTestTensor("input", p.tensor_dims, tf_type_, input_vec, {}, |
| p.add_test_tensor_status); |
| if (shape_as_weight) { |
| AddTestWeights<int32>("weights", {static_cast<int>(p.shape.size())}, |
| p.shape); |
| } else { |
| std::vector<int32> dims; |
| std::vector<int32> values{p.shape}; |
| if (!p.shape.empty()) { |
| dims.push_back(p.shape.size()); |
| } else { |
| // If the shape is empty we use a dummy value to ensure that |
| // AddTestTensor creates the corresponding entry in InputOutputData. |
| values.push_back(1); |
| } |
| AddTestTensor("weights", dims, DT_INT32, values, dims); |
| } |
| std::vector<int> expected_shape = |
| p.expected_shape.empty() ? p.shape : p.expected_shape; |
| VLOG(2) << "Calling TestOpConverter"; |
| TestOpConverter("my_reshape", node_def, expected_shape, |
| p.conversion_status, p.runtime_status, |
| ElementsAreArray(input_vec)); |
| } |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertShape) { |
| // Get the NodeDef for Shape op. |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto shape = ops::Shape(s.WithOpName("my_shape"), input); |
| const NodeDef& node_def = shape.operation.node()->def(); |
| |
| Status conversion_status = |
| (trt_mode_ == TrtTestMode::kImplicitBatch) |
| ? errors::Unimplemented( |
| "Shape is only supported for explicit batch mode.") |
| : Status::OK(); |
| std::vector<TestParamBase> test_params = { |
| // TODO(b/166274212): Enable the test parameter for TensorRT 7.1.3. |
| #if !IS_TRT_VERSION_GE(7, 1, 3, 0) |
| TestParamBase{{1, 2, 3}, {}, {3}, {}, conversion_status}, |
| #endif |
| // Add input as weight (we use non empty param ({1}) to trigger this). |
| TestParamBase{{1, 2, 3}, {}, {3}, {1}, conversion_status}, |
| }; |
| |
| auto input_is_weight = [](const TestParamBase p) { return !p.param.empty(); }; |
| for (auto p : test_params) { |
| SCOPED_TRACE(p); |
| Reset(); |
| // The number of elements of the input tensor. We leave it 0 in case we do |
| // not need to add an input tensor. This happens in explicit batch mode: the |
| // shape is known at conversion time and therefore the shape is added to the |
| // network as a constant layer. In this case the single node network that |
| // we use for the unit test have no actual input tensor when it is converted |
| // to a TensorRT network. |
| int n_elements = 0; |
| if (input_is_weight(p) || trt_mode_ != TrtTestMode::kExplicitBatch) { |
| // Calculate the number of elements for adding input data. |
| n_elements = std::accumulate(p.input_dims.begin(), p.input_dims.end(), 1, |
| std::multiplies<int>()); |
| } |
| std::vector<float> input_val(n_elements, 1); |
| if (!input_is_weight(p)) { |
| AddTestTensor("input", p.input_dims, input_val); |
| } else { |
| AddTestWeights("input", p.input_dims, input_val, tf_type_); |
| } |
| TestOpConverter("my_shape", node_def, p.expected_output_dims, p.status, |
| p.runtime_status, ElementsAreArray(p.input_dims), |
| {DT_INT32}); |
| } |
| } |
| |
| struct MatMulTestParams { |
| std::vector<int> shape_a; |
| std::vector<int> values_a; |
| bool transpose_a; |
| std::vector<int> shape_b; |
| std::vector<int> values_b; |
| bool transpose_b; |
| std::vector<int> expected_shape; |
| std::vector<int> expected_output; |
| }; |
| |
| // Helper function for testing MatMul and BatchMatMul. get_matmul is a function |
| // used to generate the node. It accepts (DataType, transpose_a, transpose_b) as |
| // parameters. |
| void TestMatMulHelper( |
| ParameterizedOpConverterTestBase* test, |
| const std::function<NodeDef(DataType, bool, bool)>& get_matmul, |
| const std::vector<MatMulTestParams>& params) { |
| { |
| // Unsupported data type. |
| test->Reset(); |
| NodeDef node_def = get_matmul(DT_INT32, false, false); |
| test->AddTestTensor("input", {1, 2}, DT_INT32, {}); |
| test->AddTestWeights<int32>("weights", {2, 1}, {3, 5}); |
| test->RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| StrCat("Data type int32 is not supported for ", node_def.op(), |
| ", must be one of [float, half], at my_matmul")); |
| } |
| |
| // FC conversion depends on whether the last dim of A is known or not. In |
| // Dynamic shape mode, we will check whether A is handled correctly if it has |
| // a partially known input shape (last dim known). |
| std::vector<bool> a_test_partial_shape_values{false}; |
| if (test->get_trt_mode() == TrtTestMode::kDynamicShape) { |
| a_test_partial_shape_values.push_back(true); |
| } |
| |
| for (auto p : params) { |
| for (bool a_is_tensor : {true, false}) { |
| for (bool b_is_tensor : {true, false}) { |
| for (bool a_partial_shape : a_test_partial_shape_values) { |
| if (a_partial_shape && !a_is_tensor) { |
| // Only tensors can have partial shape. |
| continue; |
| } |
| if (!a_is_tensor && !b_is_tensor) { |
| // Skip test when both args are weights. We do not convert this |
| // since const folding eliminates this case. |
| continue; |
| } |
| SCOPED_TRACE(StrCat("A", p.transpose_a ? ".T" : "", " is ", |
| a_is_tensor ? "tensor" : "weight", ", B", |
| p.transpose_b ? ".T" : "", " is ", |
| b_is_tensor ? "tensor " : "weight, rank A ", |
| p.shape_a.size(), ", rank B ", p.shape_b.size())); |
| test->Reset(); |
| |
| NodeDef node_def = |
| get_matmul(test->get_tf_type(), p.transpose_a, p.transpose_b); |
| const bool is_batch_matmul = node_def.op() == "BatchMatMul"; |
| |
| if (a_is_tensor) { |
| if (a_partial_shape) { |
| // Prepare a partial shape for A where only the last dim is known. |
| std::vector<int> partial_shape(p.shape_a.size(), -1); |
| int k = p.shape_a.size() - 1; |
| partial_shape.at(k) = p.shape_a.at(k); |
| test->AddTestTensor("input", p.shape_a, test->get_tf_type(), |
| p.values_a, partial_shape); |
| } else { |
| test->AddTestTensor("input", p.shape_a, p.values_a); |
| } |
| } else { |
| test->AddTestWeights("input", p.shape_a, p.values_a, |
| test->get_tf_type()); |
| } |
| if (b_is_tensor) { |
| if (a_is_tensor && p.shape_a[0] != p.shape_b[0] && |
| test->get_trt_mode() == TrtTestMode::kImplicitBatch) { |
| VLOG(2) << "Skipping test with inpcompatible batch dimensions"; |
| continue; |
| } |
| test->AddTestTensor("weights", p.shape_b, p.values_b); |
| } else { |
| test->AddTestWeights("weights", p.shape_b, p.values_b, |
| test->get_tf_type()); |
| } |
| |
| Status conversion_status = Status::OK(); |
| if (test->get_trt_mode() == TrtTestMode::kImplicitBatch) { |
| // Implicit batch mode has several restriction. We change conversion |
| // status accordingly. |
| if (is_batch_matmul) { |
| if (a_is_tensor && p.shape_a.size() < p.shape_b.size()) { |
| conversion_status = errors::InvalidArgument( |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims ", |
| p.shape_a.size(), " vs broadcast #dims ", p.shape_b.size(), |
| ")"); |
| } |
| if (b_is_tensor && p.shape_b.size() < p.shape_a.size()) { |
| conversion_status = errors::InvalidArgument( |
| "Broadcasting beyond batch dimension is not supported " |
| "(tensor #dims ", |
| p.shape_b.size(), " vs broadcast #dims ", p.shape_a.size(), |
| ")"); |
| } |
| if ((!a_is_tensor || !b_is_tensor) && p.shape_a[0] != 1) { |
| conversion_status = errors::Unimplemented( |
| "TensorRT does not support batched constants in implicit " |
| "batch mode."); |
| } |
| } else if ((a_is_tensor && p.shape_a.size() <= 2 && |
| (p.transpose_a || b_is_tensor)) || |
| (b_is_tensor && p.shape_b.size() <= 2)) { |
| conversion_status = errors::InvalidArgument( |
| "MatMul with 2D tensors requires explicit batch mode, or that" |
| " tensor A is not transposed and B is a constant tensor."); |
| } |
| } |
| |
| test->TestOpConverter("my_matmul", node_def, p.expected_shape, |
| conversion_status, Status::OK(), |
| ElementsAreArray(p.expected_output)); |
| if (!conversion_status.ok()) { |
| VLOG(2) << "Converted with status " << conversion_status; |
| } |
| VLOG(2) << "== Finished test iteration =="; |
| } |
| } |
| } |
| } |
| } |
| |
| template <typename LayerType> |
| void CheckAddedLayers(OpConverterTest* test, bool expect_found) { |
| bool layer_found = false; |
| for (int i = 0; i < test->converter_->network()->getNbLayers(); i++) { |
| nvinfer1::ILayer* layer = test->converter_->network()->getLayer(i); |
| if (dynamic_cast<LayerType*>(layer)) { |
| layer_found = true; |
| } |
| } |
| EXPECT_EQ(expect_found, layer_found); |
| } |
| |
| std::vector<MatMulTestParams> GetMatMulTestParams() { |
| std::vector<MatMulTestParams> params{ |
| // clang-format off |
| MatMulTestParams{{2, 2}, {0, 1, 2, 3}, false, // A (shape, val, T?) |
| {2, 2}, {0, 1, 2, 3}, false, // B (shape, val, T?) |
| {2, 2}, {2, 3, 6, 11}}, // result (shape, val) |
| MatMulTestParams{{2, 2}, {0, 1, 2, 3}, false, |
| {2, 2}, {0, 1, 2, 3}, true, |
| {2, 2}, {1, 3, 3, 13}}, |
| MatMulTestParams{{2, 2}, {0, 1, 2, 3}, true, |
| {2, 2}, {0, 1, 2, 3}, false, |
| {2, 2}, {4, 6, 6, 10}}, |
| MatMulTestParams{{2, 2}, {0, 1, 2, 3}, true, |
| {2, 2}, {0, 1, 2, 3}, true, |
| {2, 2}, {2, 6, 3, 11}}, |
| MatMulTestParams{{2, 3}, {0, 1, 2, 3, 4, 5}, false, |
| {2, 3}, {1, 2, 3, 4, 5, 6}, true, |
| {2, 2}, {8, 17, 26, 62}}, |
| MatMulTestParams{{2, 3}, {0, 1, 2, 3, 4, 5}, true, |
| {2, 3}, {1, 2, 3, 4, 5, 6}, false, |
| {3, 3}, {12, 15, 18, 17, 22, 27, 22, 29, 36}}, |
| MatMulTestParams{{3, 2}, {0, 1, 2, 3, 4, 5}, false, |
| {2, 3}, {1, 2, 3, 4, 5, 6}, false, |
| {3, 3}, {4, 5, 6, 14, 19, 24, 24, 33, 42}}, |
| MatMulTestParams{{3, 2}, {0, 1, 2, 3, 4, 5}, true, |
| {2, 3}, {1, 2, 3, 4, 5, 6}, true, |
| {2, 2}, {16, 34, 22, 49}}, |
| // clang-format on |
| }; |
| return params; |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertMatMul) { |
| // Get the NodeDef for MatMul. |
| auto get_matmul_nodedef = [](DataType dtype, bool transpose_a, |
| bool transpose_b) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), dtype); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), dtype); |
| const auto matmul_attrs = |
| ops::MatMul::TransposeA(transpose_a).TransposeB(transpose_b); |
| auto matmul = |
| ops::MatMul(s.WithOpName("my_matmul"), input, weights, matmul_attrs); |
| return matmul.operation.node()->def(); |
| }; |
| |
| TestMatMulHelper(this, get_matmul_nodedef, GetMatMulTestParams()); |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertBatchMatMul) { |
| // Get the NodeDef for BatchMatMul. |
| auto get_batch_matmul_nodedef = [](DataType dtype, bool transpose_a, |
| bool transpose_b) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), dtype); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), dtype); |
| const auto matmul_attrs = |
| ops::BatchMatMul::AdjX(transpose_a).AdjY(transpose_b); |
| auto matmul = ops::BatchMatMul(s.WithOpName("my_matmul"), input, weights, |
| matmul_attrs); |
| return matmul.operation.node()->def(); |
| }; |
| |
| // We derive test data from the MatMul test params by adding extra leading |
| // dimensions. |
| std::vector<MatMulTestParams> params_2d = GetMatMulTestParams(); |
| std::vector<MatMulTestParams> params; |
| params.reserve(params_2d.size() * 3 + 1); |
| |
| auto insert_ones = [](std::vector<int> v, int n) { |
| std::vector<int> ones(n, 1); |
| ones.insert(ones.end(), v.begin(), v.end()); |
| return ones; |
| }; |
| |
| // Add a leading 1 dimension to A, B and result. |
| std::transform(params_2d.begin(), params_2d.end(), std::back_inserter(params), |
| [](MatMulTestParams p) { |
| p.shape_a.insert(p.shape_a.begin(), 1); |
| p.shape_b.insert(p.shape_b.begin(), 1); |
| p.expected_shape.insert(p.expected_shape.begin(), 1); |
| return p; |
| }); |
| |
| // Test with N > 1: weights cannot be batched in implicit batch mode. |
| // clang-format off |
| params.push_back( |
| MatMulTestParams{{2, 2, 2}, {0, 1, 2, 3, 0, 1, 2, 3}, false, // A |
| {2, 2, 2}, {0, 1, 2, 3, 0, 1, 2, 3}, false, // B |
| {2, 2, 2}, {2, 3, 6, 11, 2, 3, 6, 11}} // result |
| ); |
| |
| params.push_back( |
| MatMulTestParams{{2, 2, 3}, {0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5}, |
| false, |
| {2, 2, 3}, {1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}, true, |
| {2, 2, 2}, {8, 17, 26, 62, 8, 17, 26, 62}}); |
| // clang-format on |
| |
| // Add two leading 1 dimensions to A, B and result. |
| std::transform(params_2d.begin(), params_2d.end(), std::back_inserter(params), |
| [insert_ones](MatMulTestParams p) { |
| p.shape_a = insert_ones(p.shape_a, 2); |
| p.shape_b = insert_ones(p.shape_b, 2); |
| p.expected_shape = insert_ones(p.expected_shape, 2); |
| return p; |
| }); |
| |
| // Test broadcast: add two leading 1 dimensions to A, but not to B. |
| std::transform(params_2d.begin(), params_2d.end(), std::back_inserter(params), |
| [insert_ones](MatMulTestParams p) { |
| p.shape_a = insert_ones(p.shape_a, 2); |
| p.expected_shape = insert_ones(p.expected_shape, 2); |
| return p; |
| }); |
| |
| // Test broadcast: add a leading 1 dimension to A and two leading 1s to B. |
| // Broadcasting A need a dynamic brodacast which will be incompatible with |
| // FC layer. |
| std::transform(params_2d.begin(), params_2d.end(), std::back_inserter(params), |
| [insert_ones](MatMulTestParams p) { |
| p.shape_a = insert_ones(p.shape_a, 1); |
| p.shape_b = insert_ones(p.shape_b, 2); |
| p.expected_shape = insert_ones(p.expected_shape, 2); |
| return p; |
| }); |
| |
| // Test with N > 1: since weights cannot be batched in implicit batch mode. |
| // We tests with batch size 2. |
| std::transform(params_2d.begin(), params_2d.end(), std::back_inserter(params), |
| [insert_ones](MatMulTestParams p) { |
| p.shape_a.insert(p.shape_a.begin(), 2); |
| p.values_a.reserve(p.values_a.size() * 2); |
| p.values_a.insert(p.values_a.end(), p.values_a.begin(), |
| p.values_a.end()); |
| |
| p.shape_b.insert(p.shape_b.begin(), 2); |
| p.values_b.reserve(p.values_b.size() * 2); |
| p.values_b.insert(p.values_b.end(), p.values_b.begin(), |
| p.values_b.end()); |
| |
| p.expected_shape.insert(p.expected_shape.begin(), 2); |
| p.expected_output.reserve(p.expected_output.size() * 2); |
| p.expected_output.insert(p.expected_output.end(), |
| p.expected_output.begin(), |
| p.expected_output.end()); |
| return p; |
| }); |
| |
| // 4D tensor where the second "batch dim" is not 1 |
| params.push_back(MatMulTestParams{ |
| {1, 2, 4, 5}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, |
| 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}, |
| false, // A |
| {1, 2, 3, 5}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, |
| true, // B |
| {1, 2, 4, 3}, |
| {40, 90, 140, 115, 290, 465, 190, 490, |
| 790, 265, 690, 1115, 1990, 2540, 3090, 2440, |
| 3115, 3790, 2890, 3690, 4490, 3340, 4265, 5190}}); // result |
| |
| TestMatMulHelper(this, get_batch_matmul_nodedef, params); |
| } |
| |
| #if IS_TRT_VERSION_GE(7, 1, 3, 0) |
| TEST_P(OpConverter_FP32_Test, ConvertEinsum) { |
| // Get the NodeDef for Einsum. |
| auto get_einsum_nodedef = [](DataType dtype, std::string eq, |
| int n_inputs = 2) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto a = ops::Placeholder(s.WithOpName("input_a"), dtype); |
| std::vector<Input> input_vec{a}; |
| if (n_inputs > 1) { |
| auto b = ops::Placeholder(s.WithOpName("input_b"), dtype); |
| input_vec.push_back(b); |
| } |
| InputList inputs(input_vec); |
| auto einsum = ops::Einsum(s.WithOpName("my_einsum"), inputs, eq); |
| return einsum.operation.node()->def(); |
| }; |
| |
| if (trt_mode_ == TrtTestMode::kImplicitBatch) { |
| Reset(); |
| NodeDef node_def = get_einsum_nodedef(tf_type_, "ab,cb->ac"); |
| AddTestTensor("input_a", {2, 3}); |
| AddTestTensor("input_b", {2, 3}); |
| TestOpConverter( |
| "my_einsum", node_def, {2, 2}, |
| errors::Unimplemented("Einsum converter requires dynamic shape mode"), |
| Status::OK(), ElementsAreArray({13, 16, 40, 52})); |
| // No further tests. |
| return; |
| } |
| |
| struct TestParams { |
| std::string equation; |
| std::vector<int> shape_a; |
| std::vector<int> values_a; |
| std::vector<int> shape_b; |
| std::vector<int> values_b; |
| std::vector<int> expected_shape; |
| std::vector<int> expected_output; |
| Status conv_status; |
| }; |
| |
| Status unimplemented_eq = |
| errors::Unimplemented("No conversion for einsum equation."); |
| |
| std::vector<TestParams> params { |
| // Dot product. |
| TestParams{"i,i->", {2}, {2, 3}, {2}, {1, 2}, {1}, {8}, unimplemented_eq}, |
| // Outer product. |
| TestParams{"i,k->ik", |
| {2}, |
| {1, 2}, |
| {3}, |
| {1, 2, 3}, |
| {2, 3}, |
| {1, 2, 3, 2, 4, 6}, |
| unimplemented_eq}, |
| // Transpose. |
| TestParams{"ik->ki", {2, 3}, {0, 1, 2, 3, 4, 5}, {}, |
| {}, {3, 2}, {0, 3, 1, 4, 2, 5}, unimplemented_eq}, |
| // Diag. |
| TestParams{"ii->i", |
| {3, 3}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8}, |
| {}, |
| {}, |
| {3}, |
| {0, 4, 8}, |
| unimplemented_eq}, |
| // Trace. |
| TestParams{ |
| "ii", {3, 3}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {}, {}, {}, |
| {12}, unimplemented_eq}, |
| // MatMul with reduction. |
| TestParams{"abbc,dc->ad", |
| {1, 2, 2, 3}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, |
| {2, 3}, |
| {1, 2, 3, 4, 5, 6}, |
| {2, 3}, |
| {1, 2, 3, 2, 4, 6}, |
| unimplemented_eq}, |
| // Ellipsis with broadcast. |
| TestParams{"...ik,...jk->...ij", |
| {1, 3, 1, 4}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, |
| {2, 1, 1, 4}, |
| {1, 2, 3, 4, 5, 6, 7, 8}, |
| {2, 3, 1, 1}, |
| {20, 60, 100, 44, 148, 252}, |
| unimplemented_eq}, |
| // MatMul and Batched MatMul. |
| TestParams{"ab,bc->ac", {2, 3}, {0, 1, 2, 3, 4, 5}, {3, 2}, |
| {1, 2, 3, 4, 5, 6}, {2, 2}, {13, 16, 40, 52}}, |
| TestParams{"abc,cde->abde", |
| {1, 2, 3}, |
| {0, 1, 2, 3, 4, 5}, |
| {3, 2, 2}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, |
| {1, 2, 2, 2}, |
| {23, 26, 29, 32, 68, 80, 92, 104}}, |
| TestParams{"abcd,cde->abe", |
| {1, 2, 2, 3}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, |
| {2, 3, 2}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, |
| {1, 2, 2}, |
| {125, 140, 341, 392}}, |
| TestParams{"abc,cd->abd", {1, 2, 3}, {0, 1, 2, 3, 4, 5}, {3, 2}, |
| {1, 2, 3, 4, 5, 6}, {1, 2, 2}, {13, 16, 40, 52}}, |
| TestParams{"acbe,aecd->abcd", |
| {1, 2, 3, 4}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}, |
| {1, 4, 2, 3}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, |
| 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, |
| {1, 3, 2, 3}, |
| {90, 96, 102, 732, 786, 840, 250, 272, 294, 940, 1010, 1080, |
| 410, 448, 486, 1148, 1234, 1320}}, |
| TestParams{ |
| "aecd,abcd->acbe", |
| {1, 2, 3, 4}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}, |
| {1, 2, 3, 4}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, |
| 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, |
| {1, 3, 2, 2}, |
| {20, 140, 92, 788, 148, 460, 412, 1300, 404, 908, 860, 1940}}, |
| TestParams{"acd,dce->ae", |
| {1, 2, 3}, |
| {0, 1, 2, 3, 4, 5}, |
| {3, 2, 2}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, |
| {1, 2}, |
| {115, 130}}, |
| TestParams{"abcd,bace->bade", |
| {2, 3, 2, 1}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, |
| {3, 2, 2, 1}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, |
| {3, 2, 1, 1}, |
| {2, 46, 28, 128, 86, 242}}, |
| #if !IS_TRT_VERSION_GE(8, 0, 0, 0) |
| // Deactivating buggy test case for TRT8 per nvbug 3322485. |
| TestParams{"cebfad,fageb->abcdg", |
| {1, 1, 3, 3, 2, 2}, |
| {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, |
| 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}, |
| {3, 2, 2, 1, 3}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, |
| 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, |
| 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36}, |
| {2, 3, 1, 2, 2}, |
| {252, 288, 291, 336, 768, 912, 810, 963, |
| 1356, 1608, 1401, 1662, 438, 492, 495, 558, |
| 1176, 1338, 1236, 1407, 1986, 2256, 2049, 2328}}, |
| #endif |
| }; |
| |
| for (auto p : params) { |
| for (bool a_is_tensor : {true, false}) { |
| for (bool b_is_tensor : {true, false}) { |
| if (!a_is_tensor && !b_is_tensor) { |
| // Skip test when both args are weights. We do not convert this |
| // since const folding eliminates this case. |
| continue; |
| } |
| Reset(); |
| int n_inputs = p.shape_b.empty() ? 1 : 2; |
| NodeDef node_def = get_einsum_nodedef(tf_type_, p.equation, n_inputs); |
| if (a_is_tensor) { |
| AddTestTensor("input_a", p.shape_a, p.values_a); |
| } else { |
| AddTestWeights("input_a", p.shape_a, p.values_a, tf_type_); |
| } |
| if (!p.shape_b.empty()) { |
| if (b_is_tensor) { |
| AddTestTensor("input_b", p.shape_b, p.values_b); |
| } else { |
| AddTestWeights("input_b", p.shape_b, p.values_b, tf_type_); |
| } |
| } |
| TestOpConverter("my_einsum", node_def, p.expected_shape, p.conv_status, |
| Status::OK(), ElementsAreArray(p.expected_output)); |
| } |
| } |
| } |
| } |
| #endif // IS_TRT_VERSION_GE(7, 1, 3, 0) |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertBiasAdd) { |
| // Note that kINT32 is not supported by IScaleLayer, so we don't test |
| // DT_INT32 type here. DT_FLOAT and DT_HALF are tested. |
| // Get the NodeDef for BiasAdd. |
| auto get_biasadd_nodedef = [](const string& data_format, |
| DataType tf_type) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), tf_type); |
| const auto biasadd_attrs = ops::BiasAdd::DataFormat(data_format); |
| auto biasadd = |
| ops::BiasAdd(s.WithOpName("my_biasadd"), input, weights, biasadd_attrs); |
| return biasadd.operation.node()->def(); |
| }; |
| |
| for (const string& data_format : {"NHWC", "NCHW"}) { |
| for (const int trt_input_rank : {1, 2, 3, 4}) { |
| Reset(); |
| NodeDef node_def = get_biasadd_nodedef(data_format, tf_type_); |
| |
| // Add input, dims_array will be like {2, 1, ..., 1, 3} |
| std::vector<int32> dims_array(trt_input_rank + 1, 1); |
| if (trt_input_rank == 1) { |
| dims_array[1] = (data_format == "NHWC" ? 3 : 2); |
| } else { |
| dims_array[1] = 2; |
| dims_array[trt_input_rank] = 3; |
| } |
| const int num_input = TrtTensorDimsNumElements(CreateDims(dims_array)); |
| ASSERT_EQ(trt_input_rank > 1 ? 6 : (data_format == "NHWC" ? 3 : 2), |
| num_input); |
| std::vector<float> input_data(num_input, 0); |
| |
| AddTestTensor("input", dims_array, input_data); |
| |
| const int channel_size = (data_format == "NHWC" ? 3 : 2); |
| std::vector<float> bias(channel_size); |
| for (int i = 0; i < channel_size; ++i) { |
| bias[i] = i + 1; // bias will be {1, 2, 3, ...} |
| } |
| AddTestWeights("weights", {channel_size}, bias, tf_type_); |
| |
| // Build and run the engine. |
| std::vector<float> output_data; |
| |
| if (trt_input_rank == 1) { |
| if (data_format == "NHWC") { |
| output_data = {1, 2, 3}; |
| } else { |
| output_data = {1, 2}; |
| } |
| } else { |
| if (data_format == "NHWC") { |
| output_data = {1, 2, 3, 1, 2, 3}; |
| } else { |
| output_data = {1, 1, 1, 2, 2, 2}; |
| } |
| } |
| TestOpConverter("my_biasadd", node_def, dims_array, Status::OK(), |
| Status::OK(), ElementsAreArray(output_data)); |
| } |
| } |
| } |
| |
| template <typename OpType> |
| NodeDef GetBinaryOpNodeDef(DataType dtype) { |
| Scope s = Scope::NewRootScope(); |
| auto input_l = ops::Placeholder(s.WithOpName("input1"), dtype); |
| auto input_r = ops::Placeholder(s.WithOpName("input2"), dtype); |
| auto op = OpType(s.WithOpName("my_binary"), input_l, input_r); |
| return op.operation.node()->def(); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertBinary) { |
| { |
| AttrValue dtype; |
| dtype.set_type(tf_type_); |
| // Both inputs are weights. |
| Reset(); |
| NodeDef node_def = |
| MakeNodeDef("my_add", "Add", {"weights1", "weights2"}, {{"T", dtype}}); |
| AddTestWeights<float>("weights1", {1}, {1}); |
| AddTestWeights<float>("weights2", {1}, {1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "Constant folding is falled back to TensorFlow, binary op received " |
| "both input as constant at: my_add"); |
| } |
| |
| using OpFunc = std::function<NodeDef(DataType)>; |
| std::map<std::string, std::pair<OpFunc, std::vector<float>>> op_test_info; |
| #define ADD_OP(name, op, v1, v2, v3, v4, v5, v6, v7, v8) \ |
| op_test_info[name] = \ |
| std::make_pair(GetBinaryOpNodeDef<op>, \ |
| std::vector<float>(v1, v2, v3, v4, v5, v6, v7, v8)) |
| ADD_OP("Add", ops::Add, {5, 8, 6, 9, 5, 8, 6, 9}); |
| ADD_OP("AddV2", ops::AddV2, {5, 8, 6, 9, 5, 8, 6, 9}); |
| ADD_OP("Sub", ops::Sub, {1, 4, 0, 3, 1, 4, 0, 3}); |
| ADD_OP("Mul", ops::Mul, {6, 12, 9, 18, 6, 12, 9, 18}); |
| ADD_OP("Div", ops::Div, {1.5, 3, 1, 2, 1.5, 3, 1, 2}); |
| ADD_OP("RealDiv", ops::RealDiv, {1.5, 3, 1, 2, 1.5, 3, 1, 2}); |
| ADD_OP("FloorDiv", ops::FloorDiv, {1, 3, 1, 2, 1, 3, 1, 2}); |
| ADD_OP("Minimum", ops::Minimum, {2, 2, 3, 3, 2, 2, 3, 3}); |
| ADD_OP("Maximum", ops::Maximum, {3, 6, 3, 6, 3, 6, 3, 6}); |
| ADD_OP("Pow", ops::Pow, {9, 36, 27, 216, 9, 36, 27, 216}); |
| #undef ADD_OP |
| // Add all ops supported by ConvertBinary. |
| auto* supported_ops = BinaryOperationMap(); |
| // Test combinations of tensor vs weight inputs (except when both inputs are |
| // weights). |
| for (const bool operand_1_is_tensor : {true, false}) { |
| for (const bool operand_2_is_tensor : {true, false}) { |
| if (!operand_1_is_tensor && !operand_2_is_tensor) continue; |
| for (auto& iter : *supported_ops) { |
| string op_name = iter.first; |
| SCOPED_TRACE(StrCat(op_name, "_", operand_1_is_tensor ? "T" : "W", |
| operand_2_is_tensor ? "T" : "W")); |
| Reset(); |
| if (!op_test_info.count(op_name)) { |
| FAIL() << "Binary op test map does not contain op " << op_name; |
| } |
| NodeDef node_def = op_test_info[op_name].first(tf_type_); |
| std::vector<std::string> input_names; |
| std::vector<std::vector<int>> input_dims; |
| std::vector<std::vector<float>> input_values; |
| if (operand_1_is_tensor) { |
| AddTestTensor("input1", {2, 1, 2}, {3, 6, 3, 6}); |
| } else { |
| AddTestWeights("input1", {1, 2}, std::vector<float>{3, 6}, tf_type_); |
| } |
| if (operand_2_is_tensor) { |
| AddTestTensor("input2", {2, 2, 1}, {2, 3, 2, 3}); |
| } else { |
| AddTestWeights("input2", {2, 1}, std::vector<float>{2, 3}, tf_type_); |
| } |
| TestOpConverter("my_binary", node_def, {2, 2, 2}, Status::OK(), |
| Status::OK(), |
| ElementsAreArray(op_test_info[op_name].second)); |
| } |
| } |
| } |
| } |
| |
| NodeDef GetAddNNodeDef(const std::vector<string>& input_names, DataType dtype) { |
| Scope s = Scope::NewRootScope(); |
| OutputList inputs; |
| for (const string& name : input_names) { |
| inputs.push_back(ops::Placeholder(s.WithOpName(name), dtype)); |
| } |
| auto op = ops::AddN(s.WithOpName("my_addn"), inputs); |
| return op.operation.node()->def(); |
| } |
| |
| struct AddNTestParams { |
| std::vector<float> input_values; |
| std::vector<string> input_names; |
| std::vector<int> dimensions; |
| std::vector<float> expected_output; |
| Status status; |
| }; |
| |
| void TestAddN(ParameterizedOpConverterTestBase* test, AddNTestParams& p) { |
| // All inputs are tensors. |
| test->Reset(); |
| const NodeDef node_def = GetAddNNodeDef(p.input_names, test->get_tf_type()); |
| |
| if (p.input_values.size() % p.input_names.size() != 0) { |
| LOG(ERROR) << "The number of input values: `" << p.input_values.size() |
| << "` is not a multiple of the number of inputs: `" |
| << p.input_names.size() << "`"; |
| ASSERT_TRUE(false); |
| } |
| |
| DataVec input_data; |
| int input_offset = 0; |
| const int window_size = p.input_values.size() / p.input_names.size(); |
| for (const string& name : p.input_names) { |
| std::vector<float>::const_iterator start_pos = |
| p.input_values.begin() + input_offset; |
| std::vector<float>::const_iterator end_pos = start_pos + window_size; |
| std::vector<float> sub_input_val(start_pos, end_pos); |
| input_offset += window_size; |
| |
| test->AddTestTensor(name, p.dimensions, test->get_tf_type(), sub_input_val); |
| } |
| |
| test->TestOpConverter("my_addn", node_def, p.dimensions, |
| /*expected_conversion_status=*/p.status, |
| /*expected_runtime_status=*/p.status, |
| /*matcher=*/ElementsAreArray(p.expected_output), |
| /*out_tf_types=*/{test->get_tf_type()}); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertAddN) { |
| { |
| // Weights with batch dim that is not 1. |
| Reset(); |
| const NodeDef node_def = GetAddNNodeDef({"tensor", "weights"}, tf_type_); |
| AddTestTensor("tensor", /*dims=*/{1, 2}); |
| AddTestWeights<float>("weights", {2, 1, 2}, {0, 1, 2, 3}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Weights input to AddN is required to have batch dimension 1."); |
| } |
| |
| const std::vector<float> common_input = CreateVectorIota<float>(6); |
| |
| std::vector<AddNTestParams> params = { |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2", "inp3"}, |
| /*dimensions=*/{1, 1, 2, 1, 1}, |
| /*expected_output=*/{6, 9}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2"}, |
| /*dimensions=*/{1, 1, 3, 1, 1}, |
| /*expected_output=*/{3, 5, 7}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2", "inp3"}, |
| /*dimensions=*/{1, 2, 1, 1}, |
| /*expected_output=*/{6, 9}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2"}, |
| /*dimensions=*/{1, 1, 3, 1}, |
| /*expected_output=*/{3, 5, 7}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2", "inp3"}, |
| /*dimensions=*/{1, 2, 1}, |
| /*expected_output=*/{6, 9}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2"}, |
| /*dimensions=*/{1, 1, 3}, |
| /*expected_output=*/{3, 5, 7}, |
| /*status=*/Status::OK()}, |
| {/*input_value=*/common_input, |
| /*input_names=*/{"inp1", "inp2", "inp3"}, |
| /*dimensions=*/{2, 1}, |
| /*expected_output=*/{6, 9}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2"}, |
| /*dimensions=*/{1, 3}, |
| /*expected_output=*/{3, 5, 7}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2", "inp3"}, |
| /*dimensions=*/{2}, |
| /*expected_output=*/{6, 9}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2"}, |
| /*dimensions=*/{3}, |
| /*expected_output=*/{3, 5, 7}, |
| /*status=*/Status::OK()}, |
| {/*input_values=*/common_input, |
| /*input_names=*/{"inp1", "inp2", "inp3", "inp4", "inp5", "inp6"}, |
| /*dimensions=*/{1}, |
| /*expected_output=*/{15}, |
| /*status=*/Status::OK()}, |
| }; |
| |
| for (auto p : params) { |
| TestAddN(this, p); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertQuantize) { |
| { |
| // FakeQuantWithMinMaxArgs attributes are empty, should fail. |
| Reset(TrtPrecisionMode::INT8); |
| NodeDef node_def = |
| MakeNodeDef("my_quantize", "FakeQuantWithMinMaxArgs", {"input"}); |
| AddTestTensor("input", {1, 2, 3}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Min or max attribute not found for FakeQuantWithMinMaxArgs " |
| "at my_quantize"); |
| } |
| { |
| // FakeQuantWithMinMaxArgs ranges set via attributes, ok. |
| Reset(TrtPrecisionMode::INT8); |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); |
| auto quantize_attrs = ops::FakeQuantWithMinMaxArgs::Min(-6.0f).Max(6.0f); |
| auto quantize = ops::FakeQuantWithMinMaxArgs(s.WithOpName("my_quantize"), |
| input, quantize_attrs); |
| const NodeDef& node_def = quantize.operation.node()->def(); |
| AddTestTensor("input", {1, 2, 3}); |
| RunValidationAndConversion(node_def); |
| TRT_TensorOrWeights output; |
| TF_EXPECT_OK(GetTensorOrWeights("my_quantize", &output)); |
| ASSERT_TRUE(output.is_tensor()); |
| auto ranges = quantization_ranges(); |
| EXPECT_EQ(1, ranges.count(output.tensor()->trt_tensor())); |
| EXPECT_EQ(6.0f, ranges[output.tensor()->trt_tensor()]); |
| } |
| { |
| // FakeQuantWithMinMaxVars ranges set via inputs, ok. |
| Reset(TrtPrecisionMode::INT8); |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); |
| auto weights_min = ops::Placeholder(s.WithOpName("weights_min"), DT_FLOAT); |
| auto weights_max = ops::Placeholder(s.WithOpName("weights_max"), DT_FLOAT); |
| auto quantize = ops::FakeQuantWithMinMaxVars( |
| s.WithOpName("my_quantize"), input, weights_min, weights_max); |
| const NodeDef& node_def = quantize.operation.node()->def(); |
| AddTestTensor("input", {1, 2, 3}); |
| AddTestWeights<float>("weights_min", {1}, {-6.0f}); |
| AddTestWeights<float>("weights_max", {1}, {6.0f}); |
| RunValidationAndConversion(node_def); |
| TRT_TensorOrWeights output; |
| TF_EXPECT_OK(GetTensorOrWeights("my_quantize", &output)); |
| ASSERT_TRUE(output.is_tensor()); |
| auto ranges = quantization_ranges(); |
| EXPECT_EQ(1, ranges.count(output.tensor()->trt_tensor())); |
| EXPECT_EQ(6.0f, ranges[output.tensor()->trt_tensor()]); |
| } |
| { |
| // QuantizeAndDequantizeV2 ranges set via inputs, ok. |
| Reset(TrtPrecisionMode::INT8); |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); |
| auto weights_min = ops::Placeholder(s.WithOpName("weights_min"), DT_FLOAT); |
| auto weights_max = ops::Placeholder(s.WithOpName("weights_max"), DT_FLOAT); |
| auto quantize = ops::QuantizeAndDequantizeV2( |
| s.WithOpName("my_quantize"), input, weights_min, weights_max); |
| const NodeDef& node_def = quantize.operation.node()->def(); |
| AddTestTensor("input", {1, 2, 3}); |
| AddTestWeights<float>("weights_min", {1}, {-6.0f}); |
| AddTestWeights<float>("weights_max", {1}, {6.0f}); |
| RunValidationAndConversion(node_def); |
| TRT_TensorOrWeights output; |
| TF_EXPECT_OK(GetTensorOrWeights("my_quantize", &output)); |
| ASSERT_TRUE(output.is_tensor()); |
| auto ranges = quantization_ranges(); |
| EXPECT_EQ(1, ranges.count(output.tensor()->trt_tensor())); |
| EXPECT_EQ(6.0f, ranges[output.tensor()->trt_tensor()]); |
| } |
| { |
| // QuantizeAndDequantizeV2 Range inputs are tensors, should fail. |
| Reset(TrtPrecisionMode::INT8); |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); |
| auto weights_min = ops::Placeholder(s.WithOpName("weights_min"), DT_FLOAT); |
| auto weights_max = ops::Placeholder(s.WithOpName("weights_max"), DT_FLOAT); |
| auto quantize = ops::QuantizeAndDequantizeV2( |
| s.WithOpName("my_quantize"), input, weights_min, weights_max); |
| const NodeDef& node_def = quantize.operation.node()->def(); |
| AddTestTensor("input", {1, 2, 3}); |
| AddTestTensor("weights_min", {1}); |
| AddTestTensor("weights_max", {1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"input_min\" for QuantizeAndDequantizeV2 must be a constant" |
| ", at my_quantize"); |
| } |
| { |
| // QuantizeAndDequantizeV3 ranges set via inputs, ok. |
| Reset(TrtPrecisionMode::INT8); |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); |
| auto weights_min = ops::Placeholder(s.WithOpName("weights_min"), DT_FLOAT); |
| auto weights_max = ops::Placeholder(s.WithOpName("weights_max"), DT_FLOAT); |
| auto num_bits = ops::Placeholder(s.WithOpName("num_bits"), DT_INT32); |
| auto quantize = ops::QuantizeAndDequantizeV3( |
| s.WithOpName("my_quantize"), input, weights_min, weights_max, num_bits); |
| const NodeDef& node_def = quantize.operation.node()->def(); |
| AddTestTensor("input", {1, 2, 3}); |
| AddTestWeights<float>("weights_min", {1}, {-6.0f}); |
| AddTestWeights<float>("weights_max", {1}, {6.0f}); |
| AddTestWeights<int>("num_bits", {1}, {8}); |
| RunValidationAndConversion(node_def); |
| TRT_TensorOrWeights output; |
| TF_EXPECT_OK(GetTensorOrWeights("my_quantize", &output)); |
| ASSERT_TRUE(output.is_tensor()); |
| auto ranges = quantization_ranges(); |
| EXPECT_EQ(1, ranges.count(output.tensor()->trt_tensor())); |
| EXPECT_EQ(6.0f, ranges[output.tensor()->trt_tensor()]); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertSquare) { |
| { |
| // Input is weights, should fail. |
| Reset(); |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto square = ops::Square(s.WithOpName("my_square"), input); |
| NodeDef node_def = square.operation.node()->def(); |
| AddTestWeights("input", {1, 2, 3}, {1, 2, 3, 4, -5, 6}, tf_type_); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"x\" for Square must be a tensor, at my_square"); |
| } |
| |
| Reset(); |
| |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto square = ops::Square(s.WithOpName("my_square"), input); |
| NodeDef node_def = square.operation.node()->def(); |
| |
| const int num_inputs = 20; |
| std::vector<float> inputs(num_inputs); |
| std::vector<float> expected_outputs(num_inputs); |
| |
| for (int i = 0; i < num_inputs; ++i) { |
| const float value = (i - 9); |
| inputs[i] = value; |
| expected_outputs[i] = value * value; |
| } |
| AddTestTensor("input", {1, 1, 20}, tf_type_, inputs); |
| |
| TestOpConverter("my_square", node_def, {1, 1, 20}, Status::OK(), Status::OK(), |
| ArrayFloatNear(expected_outputs, 0)); |
| } |
| |
| #if IS_TRT_VERSION_GE(7, 1, 3, 0) |
| TEST_P(OpConverter_FP32_Test, ConvertCombinedNMS) { |
| // Get the NodeDef for CombinedNMS. |
| auto get_nms_nodedef = [](DataType tf_type, bool clip_boxes = true, |
| bool pad_per_class = false) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto boxes_tensor = ops::Placeholder(s.WithOpName("boxes"), tf_type); |
| auto scores_tensor = ops::Placeholder(s.WithOpName("scores"), tf_type); |
| auto max_output_size_per_class = |
| ops::Placeholder(s.WithOpName("max_output_size_per_class"), DT_INT32); |
| auto max_total_size = |
| ops::Placeholder(s.WithOpName("max_total_size"), DT_INT32); |
| auto iou_threshold = |
| ops::Placeholder(s.WithOpName("iou_threshold"), tf_type); |
| auto score_threshold = |
| ops::Placeholder(s.WithOpName("score_threshold"), tf_type); |
| auto nms_attrs = ops::CombinedNonMaxSuppression::Attrs() |
| .PadPerClass(pad_per_class) |
| .ClipBoxes(clip_boxes); |
| |
| auto nms_op = ops::CombinedNonMaxSuppression( |
| s.WithOpName("my_nms"), boxes_tensor, scores_tensor, |
| max_output_size_per_class, max_total_size, iou_threshold, |
| score_threshold, nms_attrs); |
| return nms_op.operation.node()->def(); |
| }; |
| |
| struct TestParams { |
| const std::string description; |
| const std::vector<int32> boxes_tensor_dims; |
| const std::vector<int32> scores_tensor_dims; |
| const std::vector<float> boxes_values; |
| const std::vector<float> scores_values; |
| const int32 max_output_size_per_class; |
| const int32 max_total_size; |
| const float iou_threshold; |
| const float score_threshold; |
| bool pad_per_class; |
| bool clip_boxes; |
| const std::vector<std::vector<int32>> expected_output_dims; |
| const std::vector<float> exp_boxes; |
| const std::vector<float> exp_scores; |
| const std::vector<float> exp_classes; |
| const std::vector<float> exp_num_detections; |
| Status conversion_status; |
| Status runtime_status; |
| }; |
| |
| Status conv_status = |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? errors::Unimplemented( |
| "TensorRT BatchedNMS Plugin requires input with static shape") |
| : Status::OK(); |
| |
| std::vector<TestParams> params = { |
| // TODO(aaroey): there is a bug in TRT's CombinedNonMaxSuppression |
| // implementation that, the extra output classes that are outside of the |
| // range specified by valid_detections[i] are not zeros but -1s. |
| TestParams{ |
| "Test 1: Original test", |
| {1, 1, 3, 4}, // boxes dims |
| {1, 1, 3}, // scores dims |
| {0, 0, 0.3, 0.4, 0, 0, 0.3, 0.4, 0, 0, 0.3, 0.4}, // boxes values |
| {0.4, 0.7, 0.3}, // scores values |
| 3, // max_output_size_per_class |
| 2, // max_total_size |
| .5f, // IOU threshold |
| 0, // score_threshold |
| false, // pad_per_class |
| true, // clip_boxes |
| {{1, 2, 4}, // expected_nmsed_boxes_dims |
| {1, 2}, // expected_nmsed_scores_dims |
| {1, 2}, // expected_nmsed_classes_dims |
| {1}}, // expected_valid_detections_dims |
| {0, 0, 0.3, 0.4, 0, 0, 0.3, 0.4}, // exp_boxes_values |
| {0.7, 0.4}, // exp_scores |
| {1, 0}, // exp_classes |
| {2}, // exp_num_detections |
| conv_status}, |
| // Test with clip_boxes = False |
| TestParams{ |
| "Test 2: clip_boxes", |
| {1, 5, 1, 4}, // boxes dims |
| {1, 5, 1}, // scores dims |
| // boxes values: |
| {0, 0, 5, 10, 0, 4, 5, 14, 8, 0, 12, 4, 6, 2, 10, 6, 8, 9, 11, 12}, |
| {5, 4, 3, 2, 1}, // scores values |
| 4, // max_output_size_per_class |
| 4, // max_total_size |
| 0.1, // IOU threshold |
| 0, // score threshold |
| false, // pad_per_class |
| false, // clip_boxes |
| {{1, 4, 4}, // expected nmsed_boxes_dims |
| {1, 4}, // expected nmsed_scores_dims |
| {1, 4}, // expected_nmsed_classes_dims |
| {1}}, // expected_valid_detections_dims |
| // exp_boxes_values: |
| {0, 0, 5, 10, 8, 0, 12, 4, 8, 9, 11, 12, 0, 0, 0, 0}, |
| {5, 3, 1, 0}, // exp_scores |
| {0, 0, 0, -1}, // exp_classes |
| {3}, // exp_num_detections |
| conv_status}, |
| // Test with clip_boxes = False, and nonzero score threshold |
| TestParams{ |
| "Test 3: score threshold", |
| {1, 5, 1, 4}, // boxes dims |
| {1, 5, 1}, // scores dims |
| // boxes values: |
| {0, 0, 5, 10, 0, 4, 5, 14, 8, 0, 12, 4, 6, 2, 10, 6, 8, 9, 11, 12}, |
| {5, 4, 3, 2, 1}, // scores values |
| 4, // max_output_size_per_class |
| 4, // max_total_size |
| 0.1, // IOU threshold |
| 2, // score threshold |
| false, // pad_per_class |
| false, // clip_boxes |
| {{1, 4, 4}, // expected nmsed_boxes_dims |
| {1, 4}, // expected nmsed_scores_dims |
| {1, 4}, // expected_nmsed_classes_dims |
| {1}}, // expected_valid_detections_dims |
| // exp_boxes_values: |
| {0, 0, 5, 10, 8, 0, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0}, |
| {5, 3, 0, 0}, // exp_scores |
| {0, 0, -1, -1}, // exp_classes |
| {2}, // exp_num_detections |
| conv_status}, |
| // Test where the boxes are defined as with max value first for the box |
| // coordinates. This test fails before TRT 7.1.3. |
| TestParams{ |
| "Test 4: max coord first", |
| {1, 5, 1, 4}, // boxes dims |
| {1, 5, 1}, // scores dims |
| // boxes values: |
| {5, 10, 0, 0, 5, 14, 0, 4, 12, 4, 8, 0, 10, 6, 6, 2, 11, 12, 8, 9}, |
| {5, 4, 3, 2, 1}, // scores values |
| 4, // max_output_size_per_class |
| 4, // max_total_size |
| 0.1, // IOU threshold |
| 0, // score threshold |
| false, // pad_per_class |
| false, // clip_boxes |
| {{1, 4, 4}, // expected nmsed_boxes_dims |
| {1, 4}, // expected nmsed_scores_dims |
| {1, 4}, // expected_nmsed_classes_dims |
| {1}}, // expected_valid_detections_dims |
| // exp_boxes_values: |
| {5, 10, 0, 0, 12, 4, 8, 0, 11, 12, 8, 9, 0, 0, 0, 0}, |
| {5, 3, 1, 0}, // exp_scores |
| {0, 0, 0, -1}, // exp_classes |
| {3}, // exp_num_detections |
| conv_status}, |
| TestParams{"Test 5: TopK error", |
| {1, 5000, 1, 4}, // boxes dims |
| {1, 5000, 1}, // scores dims |
| {}, // boxes values: |
| {}, // scores values |
| 4, // max_output_size_per_class |
| 4, // max_total_size |
| 0.1, // IOU threshold |
| 0, // score threshold |
| false, // pad_per_class |
| false, // clip_boxes |
| {}, // expected_valid_detections_dims |
| {}, // exp_boxes_values |
| {}, // exp_scores |
| {}, // exp_classes |
| {}, // exp_num_detections |
| conv_status.ok() |
| ? errors::InvalidArgument( |
| "TRT NMS plugin allow top_k<=4096, where top_k = " |
| "max(num_boxes, max_total_size). You can override " |
| "this by setting TF_TRT_ALLOW_NMS_TOPK_OVERRIDE=1 " |
| "environment variable, but this can result in a " |
| "loss of accuracy.") |
| : conv_status}, |
| }; |
| |
| for (auto p : params) { |
| Reset(); |
| SCOPED_TRACE(p.description); |
| AddTestTensor("boxes", p.boxes_tensor_dims, p.boxes_values); |
| AddTestTensor("scores", p.scores_tensor_dims, p.scores_values); |
| AddTestWeights<int32>("max_output_size_per_class", {1}, |
| {p.max_output_size_per_class}); |
| AddTestWeights<int32>("max_total_size", {1}, {p.max_total_size}); |
| AddTestWeights<float>("iou_threshold", {1}, {p.iou_threshold}, tf_type_); |
| AddTestWeights<float>("score_threshold", {1}, {p.score_threshold}, |
| tf_type_); |
| |
| auto node_def = get_nms_nodedef(tf_type_, p.clip_boxes, p.pad_per_class); |
| |
| TestOpConverterMultiOut("my_nms", node_def, p.expected_output_dims, |
| p.conversion_status, p.runtime_status, |
| { |
| ElementsAreArray(p.exp_boxes), |
| ElementsAreArray(p.exp_scores), |
| ElementsAreArray(p.exp_classes), |
| ElementsAreArray(p.exp_num_detections), |
| }, |
| {tf_type_, tf_type_, tf_type_, DT_INT32}); |
| } |
| } |
| #endif // IS_TRT_VERSION_GE(7, 1, 3, 0) |
| |
| template <typename T> |
| NodeDef CreateUnaryOp(DataType tf_type) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| return T(s.WithOpName("my_unary"), input).operation.node()->def(); |
| } |
| |
| constexpr float kLeakyReluAlpha = 0.2f; |
| template <> |
| NodeDef CreateUnaryOp<ops::internal::LeakyRelu>(DataType tf_type) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| return ops::internal::LeakyRelu( |
| s.WithOpName("my_unary"), input, |
| ops::internal::LeakyRelu::Alpha(kLeakyReluAlpha)) |
| .operation.node() |
| ->def(); |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertActivation) { |
| { |
| // Input is weights, should fail. |
| Reset(); |
| const NodeDef& node_def = CreateUnaryOp<ops::Relu>(tf_type_); |
| AddTestWeights<int32>("input", {1, 2, 3}, {-3, -2, -1, 0, 1, 2}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for Relu must be a tensor, at my_unary"); |
| } |
| |
| constexpr float kSeluAlpha = 1.7580993408473768599402175208123f; |
| constexpr float kSeluScale = 1.0507009873554804934193349852946f; |
| using OpFunc = std::function<NodeDef(DataType)>; |
| using ValFunc = float (*)(float); |
| std::map<std::string, std::pair<OpFunc, ValFunc>> op_map; |
| |
| #define ADD_OP(name, op, compute) \ |
| op_map[name] = std::make_pair(CreateUnaryOp<op>, compute) |
| ADD_OP("LeakyRelu", ops::internal::LeakyRelu, |
| [](float x) { return (x > 0.0f) ? x : x * kLeakyReluAlpha; }); |
| ADD_OP("Relu", ops::Relu, [](float x) { return (x > 0.0f) ? x : 0.0f; }); |
| ADD_OP("Relu6", ops::Relu6, |
| [](float x) { return std::min(std::max(x, 0.0f), 6.0f); }); |
| ADD_OP("Sigmoid", ops::Sigmoid, |
| [](float x) { return 1.0f / (1.0f + std::exp(-x)); }); |
| ADD_OP("Tanh", ops::Tanh, static_cast<ValFunc>(std::tanh)); |
| ADD_OP("Elu", ops::Elu, |
| [](float x) { return (x > 0.0f) ? x : std::exp(x) - 1; }); |
| ADD_OP("Selu", ops::Selu, [](float x) { |
| return (x > 0.0f) ? kSeluScale * x |
| : kSeluScale * kSeluAlpha * (std::exp(x) - 1); |
| }); |
| ADD_OP("Softsign", ops::Softsign, |
| [](float x) { return x / (std::abs(x) + 1); }); |
| ADD_OP("Softplus", ops::Softplus, |
| [](float x) { return std::log(std::exp(x) + 1); }); |
| #undef ADD_OP |
| |
| // Get list of ops to test. |
| std::vector<string> ops_to_test; |
| // Add all ops supported by ConvertActivation. |
| auto* map = ActivationTypeMap(); |
| ops_to_test.reserve(map->size()); |
| for (auto& pair : *map) { |
| ops_to_test.push_back(pair.first); |
| } |
| // Add other activation ops to test. |
| ops_to_test.push_back("Relu6"); |
| ops_to_test.push_back("LeakyRelu"); |
| auto p = TestParamBase{ |
| {1, 1, 2, 3}, // input dims |
| {}, // input partial dims |
| {1, 1, 2, 3}, // expected output dims |
| }; |
| // Ok. |
| for (const string& op_name : ops_to_test) { |
| if (!op_map.count(op_name)) { |
| FAIL() << "Activation op test map does not contain op " << op_name; |
| } |
| Reset(); |
| NodeDef node_def = op_map[op_name].first(tf_type_); |
| const std::vector<float> input = {-100, -2, -1, 0, 1, 88}; |
| AddTestTensor("input", p.input_dims, input); |
| |
| // std::exp in Softplus will overflow for input > 88 |
| std::vector<float> output_values; |
| std::transform(input.begin(), input.end(), |
| std::back_inserter(output_values), op_map[op_name].second); |
| |
| TestOpConverter("my_unary", node_def, p.expected_output_dims, Status::OK(), |
| Status::OK(), |
| #if IS_TRT_VERSION_GE(8, 0, 0, 0) |
| // NVBug # 3322482 - Known bug with TRT 8.0 on specific GPU |
| // architectures |
| ArrayFloatNear(output_values, 1e-4, false) |
| #else |
| ArrayFloatNear(output_values, 0, false) |
| #endif |
| ); |
| |
| TRT_TensorOrWeights output; |
| TF_EXPECT_OK(GetTensorOrWeights("my_unary", &output)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertExpandDims) { |
| // Get the NodeDef for ExpandDims. |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), DT_INT32); |
| auto expanddims = |
| ops::ExpandDims(s.WithOpName("my_expanddims"), input, weights); |
| const NodeDef& node_def = expanddims.operation.node()->def(); |
| { |
| // Input is weights, should fail. |
| Reset(); |
| AddTestWeights<int32>("input", {1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| AddTestWeights<int32>("weights", {1}, {1}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for ExpandDims must be a " |
| "tensor, at my_expanddims"); |
| } |
| { |
| // Axis is a tensor, should fail. |
| Reset(); |
| AddTestTensor("input", {3, 2, 1}); |
| AddTestTensor("weights", {3}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"axis\" for ExpandDims must be a " |
| "constant, at my_expanddims"); |
| } |
| std::vector<TestParamBase> test_params = { |
| TestParamBase{{1, 1, 2, 3}, |
| {}, |
| {1, 1, 1, 2, 3}, |
| {0}, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status(error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_expanddims") |
| : Status::OK()}, |
| TestParamBase{{1, 1, 2, 3}, |
| {}, |
| {1, 1, 1, 2, 3}, |
| {-5}, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status(error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_expanddims") |
| : Status::OK()}, |
| TestParamBase{{1, 1, 2, 3}, |
| {}, |
| {}, |
| {5}, |
| Status(error::INVALID_ARGUMENT, |
| "Axis value of 5 is out of bounds, must be in range" |
| " [-5, 5), at my_expanddims")}, |
| TestParamBase{{1, 1, 2, 3}, |
| {}, |
| {}, |
| {-6}, |
| Status(error::INVALID_ARGUMENT, |
| "Axis value of -6 is out of bounds, must be in range" |
| " [-5, 5), at my_expanddims")}, |
| TestParamBase{{1, 2, 3}, {}, {1, 1, 2, 3}, {1}}, |
| TestParamBase{{1, 2, 3}, {}, {1, 1, 2, 3}, {-3}}, |
| TestParamBase{{1, 2, 3}, {}, {1, 2, 3, 1}, {3}}, |
| TestParamBase{{1, 2, 3}, {}, {1, 2, 3, 1}, {-1}}, |
| TestParamBase{{1, 2, 3}, {}, {1, 2, 1, 3}, {2}}, |
| TestParamBase{{1, 2, 3}, {}, {1, 2, 1, 3}, {-2}}, |
| TestParamBase{{1, 6}, {}, {1, 1, 6}, {1}}, |
| TestParamBase{{1, 6}, {}, {1, 6, 1}, {-1}}, |
| }; |
| for (auto p : test_params) { |
| Reset(); |
| AddTestTensor("input", p.input_dims, {1, 2, 3, 4, 5, 6}); |
| AddTestWeights<int32>("weights", {1}, {p.param[0]}); |
| TestOpConverter("my_expanddims", node_def, p.expected_output_dims, p.status, |
| p.runtime_status, ElementsAreArray({1, 2, 3, 4, 5, 6})); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertSoftmax) { |
| // Get the NodeDef for SoftMax. |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("logits"), tf_type_); |
| auto softmax = ops::Softmax(s.WithOpName("my_softmax"), input); |
| const NodeDef& node_def = softmax.operation.node()->def(); |
| |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<float> expected_values; |
| }; |
| std::vector<TestParams> test_params = { |
| TestParams{{2, 3}, |
| {0.09003057, 0.24472848, 0.66524094, 0.09003057, 0.24472848, |
| 0.66524094}}, |
| TestParams{{6, 1}, {1, 1, 1, 1, 1, 1}}, // works with std input |
| TestParams{{1, 6}, // this works with arange(1,7) input |
| {0.00426978, 0.01160646, 0.03154963, 0.08576079, 0.23312202, |
| 0.6336913}}, |
| }; |
| std::vector<float> input_values{1, 2, 3, 4, 5, 6}; |
| for (auto p : test_params) { |
| Reset(); |
| AddTestTensor("logits", p.input_dims, input_values); |
| TestOpConverter("my_softmax", node_def, p.input_dims, Status::OK(), |
| Status::OK(), ArrayFloatNear(p.expected_values, 1e-3)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertSqueeze) { |
| const bool use_implicit_batch = (trt_mode_ == TrtTestMode::kImplicitBatch); |
| // Get the NodeDef for Squeeze. |
| auto get_squeeze_nodedef = [](std::vector<int> axes, |
| DataType tf_type) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| if (!axes.empty()) { |
| ops::Squeeze::Attrs squeeze_attrs; |
| squeeze_attrs.axis_ = gtl::ArraySlice<int>(axes); // non-absl ok |
| auto squeeze = |
| ops::Squeeze(s.WithOpName("my_squeeze"), input, squeeze_attrs); |
| return squeeze.operation.node()->def(); |
| } else { |
| auto squeeze = ops::Squeeze(s.WithOpName("my_squeeze"), input); |
| return squeeze.operation.node()->def(); |
| } |
| }; |
| std::vector<TestParamBase> test_params = { |
| TestParamBase{ |
| {1, 2, 1, 3}, // input dims |
| {}, // input partial dims |
| {2, 3}, // expected output dims |
| {}, // axis |
| trt_mode_ == TrtTestMode::kExplicitBatch |
| ? Status::OK() |
| : Status{error::UNIMPLEMENTED, |
| "Squeeze is not implemented for empty squeeze_dims, at " |
| "my_squeeze"}}, |
| TestParamBase{{1, 2, 1, 3}, |
| {}, |
| {2, 1, 3}, |
| {0}, |
| use_implicit_batch |
| ? Status{error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_squeeze"} |
| : Status::OK()}, |
| TestParamBase{{1, 2, 1, 3}, |
| {}, |
| {2, 1, 3}, |
| {-4}, |
| use_implicit_batch |
| ? Status{error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_squeeze"} |
| : Status::OK()}, |
| TestParamBase{ |
| {1, 1, 2, 3}, |
| {}, |
| {}, |
| {4}, |
| Status{error::INVALID_ARGUMENT, |
| "Axis value of 4 is out of bounds, must be in range [-4, 4), " |
| "at my_squeeze"}}, |
| TestParamBase{ |
| {1, 1, 2, 3}, |
| {}, |
| {}, |
| {-5}, |
| Status{error::INVALID_ARGUMENT, |
| "Axis value of -5 is out of bounds, must be in range [-4, 4), " |
| "at my_squeeze"}}, |
| TestParamBase{{1, 1, 2, 3}, {}, {1, 2, 3}, {1}}, |
| TestParamBase{{1, 1, 2, 3}, {}, {1, 2, 3}, {-3}}, |
| TestParamBase{{1, 2, 3, 1}, {}, {1, 2, 3}, {3}}, |
| TestParamBase{{1, 2, 3, 1}, {}, {1, 2, 3}, {-1}}, |
| TestParamBase{{1, 1, 2, 1, 3, 1}, {}, {1, 2, 3}, {1, 3, 5}}, |
| TestParamBase{{1, 1, 2, 1, 3, 1}, {}, {1, 2, 3}, {3, 1, 5}}, |
| TestParamBase{{1, 1, 2, 1, 3, 1}, {}, {1, 2, 3}, {-1, -3, -5}}, |
| TestParamBase{{1, 1, 2, 1, 3, 1}, {}, {1, 2, 3}, {1, -3, 5}}, |
| TestParamBase{{1, 1, 6}, {}, {1, 6}, {1}}, |
| TestParamBase{{1, 6, 1}, {}, {1, 6}, {2}}, |
| }; |
| auto squeeze_non_singleton = TestParamBase{ |
| {1, 1, 2, 3}, |
| {}, |
| {}, |
| {2}, |
| Status{error::INVALID_ARGUMENT, |
| "Dimension 2 with size 2 cannot be squeezed because it must be " |
| "size 1, at my_squeeze"}}; |
| |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| // In this test we try to squeeze axis=2 which has size > 1. In dynamic |
| // shape mode the converter sees only -1, so it cannot catch this error. |
| squeeze_non_singleton.status = Status::OK(); // conversion status |
| squeeze_non_singleton.runtime_status = |
| errors::InvalidArgument("Negative number of dimensions -1"); |
| // Dynamic shape tests with partially known input shape |
| test_params.push_back(TestParamBase{{2, 1, 3}, {2, -1, 3}, {2, 3}, {1}}); |
| test_params.push_back(TestParamBase{{2, 1, 3}, {2, 1, -1}, {2, 3}, {1}}); |
| } |
| test_params.push_back(squeeze_non_singleton); |
| |
| for (TestParamBase p : test_params) { |
| SCOPED_TRACE(p); |
| Reset(); |
| NodeDef node_def = get_squeeze_nodedef(p.param, tf_type_); |
| AddTestTensor("input", p.input_dims, {1, 2, 3, 4, 5, 6}, |
| p.partial_input_dims); |
| TestOpConverter("my_squeeze", node_def, p.expected_output_dims, p.status, |
| p.runtime_status, ElementsAreArray({1, 2, 3, 4, 5, 6})); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertStridedSlice) { |
| // Get nodedef for StridedSlice layer. |
| auto get_strided_slice_nodedef = |
| [](DataType tf_type, int64 begin_mask = 0, int64 end_mask = 0, |
| int64 ellipsis_mask = 0, int64 new_axis_mask = 0, |
| int64 shrink_axis_mask = 0) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| auto begin = ops::Placeholder(s.WithOpName("begin"), DT_INT32); |
| auto end = ops::Placeholder(s.WithOpName("end"), DT_INT32); |
| auto strides = ops::Placeholder(s.WithOpName("strides"), DT_INT32); |
| ops::StridedSlice::Attrs attrs = ops::StridedSlice::Attrs() |
| .BeginMask(begin_mask) |
| .EndMask(end_mask) |
| .EllipsisMask(ellipsis_mask) |
| .NewAxisMask(new_axis_mask) |
| .ShrinkAxisMask(shrink_axis_mask); |
| auto strided_slice = ops::StridedSlice(s.WithOpName("my_strided_slice"), |
| input, begin, end, strides, attrs); |
| return strided_slice.operation.node()->def(); |
| }; |
| |
| { |
| // Input is weights, should fail. |
| Reset(); |
| NodeDef node_def = get_strided_slice_nodedef(tf_type_); |
| AddTestWeights<int32>("input", {1, 1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| AddTestWeights<int32>("begin", {4}, {0, 0, 0, 0}); |
| AddTestWeights<int32>("end", {4}, {1, 1, 2, 3}); |
| AddTestWeights<int32>("strides", {4}, {1, 1, 1, 1}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for StridedSlice must " |
| "be a tensor, at my_strided_slice"); |
| } |
| { |
| // Begin, end, strides are tensors, should fail. |
| Reset(); |
| NodeDef node_def = get_strided_slice_nodedef(tf_type_); |
| AddTestTensor("input", {4, 1, 1, 1}); |
| AddTestTensor("begin", {4}); |
| AddTestTensor("end", {4}); |
| AddTestTensor("strides", {4}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"begin\" for StridedSlice must be a constant, at " |
| "my_strided_slice"); |
| } |
| |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<int> begin; |
| std::vector<int> end; |
| std::vector<int> strides; |
| int begin_mask; |
| int end_mask; |
| int ellipsis_mask; |
| int new_axis_mask; |
| int shrink_axis_mask; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output; |
| Status conversion_status; |
| Status runtime_status; |
| std::vector<int> partial_input_dims; |
| }; |
| |
| auto get_mask = [](const std::vector<int>& mask) { |
| int result = 0; |
| for (int i = 0; i < mask.size(); i++) { |
| if (mask[i]) result += (1 << i); |
| } |
| return result; |
| }; |
| |
| // Same input is used for all tests. |
| const std::vector<float> ok_input = {1, 2, 3, 4, 5, 6}; |
| |
| Status batch_conv_status = |
| (trt_mode_ == TrtTestMode::kImplicitBatch) |
| ? errors::Unimplemented( |
| "TensorRT does not allow modifications to " |
| "the batch dimension, at my_strided_slice") |
| : Status::OK(); |
| std::vector<TestParams> params = { |
| // Modify batch dim, should fail in implicit batch mode. |
| TestParams{ |
| /*input_dims=*/{2, 1, 1, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{1, 1, 1, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({0, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{1, 2}, |
| batch_conv_status, |
| }, |
| // Unknown batch size without end_mask. |
| TestParams{ |
| /*input_dims=*/{2, 1, 1, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{1, 1, 1, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({0, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{1, 2}, |
| batch_conv_status, |
| Status::OK(), |
| {-1, 1, 1, 3}, |
| }, |
| // Unknown batch size but using end_mask, ok. |
| TestParams{ |
| /*input_dims=*/{2, 1, 1, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 1, 1, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({1, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{2, 1, 1, 2}, |
| /*expected_output=*/{1, 2, 4, 5}, |
| Status::OK(), |
| Status::OK(), |
| {-1, 1, 1, 3}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 2, 0}, |
| /*end=*/{1, 1, 0, 3}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/0, |
| /*end_mask=*/0, |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{}, |
| /*expected_output=*/{}, |
| errors::InvalidArgument("\"size\" cannot be negative for " |
| "StridedSlice"), |
| }, |
| // 2D Crop. |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 0, 1, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{1, 2}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 1, 1}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{5, 6}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 1, 1}, |
| /*end=*/{0, 1, 2, 3}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{5, 6}, |
| }, |
| // 2D crop with negative stride |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 1, 2}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, 1, -1, -1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{6, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 1, 1}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, 1, -1, -1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 2}, |
| /*expected_output=*/{5, 4, 2, 1}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, 1, -1, -1}, |
| /*begin_mask=*/get_mask({0, 0, 1, 1}), |
| /*end_mask=*/get_mask({1, 1, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{6, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, -1, -1, -1}, |
| /*begin_mask=*/get_mask({1, 1, 1, 1}), |
| /*end_mask=*/get_mask({1, 1, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/{6, 5, 4, 3, 2, 1}, |
| }, |
| // 2D Crop, with transpose. |
| TestParams{ |
| /*input_dims=*/{1, 2, 3, 1}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 1, 2, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{1, 2}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 2, 3, 1}, |
| /*begin=*/{0, 1, 1, 0}, |
| /*end=*/{0, 2, 3, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{5, 6}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 2, 1, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 1, 1, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{1, 2}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 2, 1, 3}, |
| /*begin=*/{0, 1, 0, 1}, |
| /*end=*/{0, 2, 1, 3}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 2}, |
| /*expected_output=*/{5, 6}, |
| }, |
| // 2D Crop, with reshape. |
| TestParams{ |
| /*input_dims=*/{1, 2, 3}, |
| /*begin=*/{0, 0, 0}, |
| /*end=*/{0, 1, 2}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2}, |
| /*expected_output=*/{1, 2}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 2, 3}, |
| /*begin=*/{0, 1, 1}, |
| /*end=*/{0, 0, 0}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2}, |
| /*expected_output=*/{5, 6}, |
| }, |
| // 1D Crop. |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 0, 0, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 2}, |
| /*expected_output=*/{1, 2, 4, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 1, 0}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 1, 3}, |
| /*expected_output=*/{4, 5, 6}, |
| }, |
| // 1D Crop, with transpose. |
| TestParams{ |
| /*input_dims=*/{1, 2, 3, 1}, |
| /*begin=*/{0, 0, 0, 0}, |
| /*end=*/{0, 1, 0, 0}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 3, 1}, |
| /*expected_output=*/{1, 2, 3}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 2, 3, 1}, |
| /*begin=*/{0, 1, 0, 0}, |
| /*end=*/{0, 0, 0, 0}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 3, 1}, |
| /*expected_output=*/{4, 5, 6}, |
| }, |
| // 1D Crop, with reshape. |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 0}, |
| /*end=*/{0, 3}, |
| /*strides=*/{1, 1}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_output=*/{1, 2, 3}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 6}, |
| /*begin=*/{0, 0, 2}, |
| /*end=*/{0, 0, 5}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0}), |
| /*end_mask=*/get_mask({1, 1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 3}, |
| /*expected_output=*/{3, 4, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6, 1}, |
| /*begin=*/{0, 2, 0}, |
| /*end=*/{0, 5, 0}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3, 1}, |
| /*expected_output=*/{3, 4, 5}, |
| }, |
| // Negative axis. |
| TestParams{ |
| /*input_dims=*/{1, 6, 1}, |
| /*begin=*/{0, -6, 0}, |
| /*end=*/{0, -3, 0}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3, 1}, |
| /*expected_output=*/{1, 2, 3}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6, 1}, |
| /*begin=*/{0, 0, 0}, |
| /*end=*/{0, -1, 0}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 5, 1}, |
| /*expected_output=*/{1, 2, 3, 4, 5}, |
| }, |
| // Clamp out of bounds begin and end. |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, -9999, -9}, |
| /*end=*/{0, 1, 1000, 4}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/{1, 2, 3, 4, 5, 6}, |
| }, |
| // Strides |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 0}, |
| /*end=*/{0, 5}, |
| /*strides=*/{1, 2}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_output=*/{1, 3, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 0}, |
| /*end=*/{0, 6}, |
| /*strides=*/{1, 2}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_output=*/{1, 3, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 1}, |
| /*end=*/{0, 6}, |
| /*strides=*/{1, 2}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_output=*/{2, 4, 6}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 2}, |
| /*end=*/{0, 6}, |
| /*strides=*/{1, 3}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 2}, |
| /*expected_output=*/{3, 6}, |
| }, |
| // Negative non -1 strides |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 5}, |
| /*end=*/{0, 0}, |
| /*strides=*/{1, -2}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 1}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_output=*/{6, 4, 2}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 5}, |
| /*end=*/{0, 0}, |
| /*strides=*/{1, -2}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_output=*/{6, 4, 2}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 5}, |
| /*end=*/{0, 1}, |
| /*strides=*/{1, -3}, |
| /*begin_mask=*/get_mask({0, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 2}, |
| /*expected_output=*/{6, 3}, |
| }, |
| // ellipsis_mask |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 1}, |
| /*end=*/{0, 2}, |
| /*strides=*/{1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({0, 0, 0, 0}), |
| /*ellipsis_mask=*/get_mask({1, 0, 0, 0}), |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{2, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 1}, |
| /*end=*/{0, 0, 2}, |
| /*strides=*/{1, 1, 1}, |
| /*begin_mask=*/get_mask({1, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/get_mask({0, 1, 0, 0}), |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{2, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 1}, |
| /*end=*/{0, 1, 2, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({0, 0, 0, 0}), |
| /*ellipsis_mask=*/get_mask({1, 0, 0, 0}), |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{2, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 1}, |
| /*end=*/{1, 1, 2, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({0, 0, 0, 0}), |
| /*ellipsis_mask=*/get_mask({0, 1, 0, 0}), |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{2, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 0, 1}, |
| /*end=*/{0, 1, 1, 2, 2}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({0, 0, 0, 0}), |
| /*end_mask=*/get_mask({0, 0, 0, 0}), |
| /*ellipsis_mask=*/get_mask({1, 0, 0, 0}), |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/0, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{2, 5}, |
| }, |
| // shrink_axis_mask |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 1}, |
| /*end=*/{0, 0, 0, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({1, 1, 1, 0}), |
| /*end_mask=*/get_mask({1, 1, 1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/get_mask({0, 0, 0, 1}), |
| /*expected_output_dims=*/{1, 1, 2}, |
| /*expected_output=*/{2, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 1, 2, 3}, |
| /*begin=*/{0, 0, 0, 1}, |
| /*end=*/{0, 1, 2, 2}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*begin_mask=*/get_mask({1, 0, 0, 0}), |
| /*end_mask=*/get_mask({1, 0, 0, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/get_mask({0, 1, 0, 1}), |
| /*expected_output_dims=*/{1, 2}, |
| /*expected_output=*/{2, 5}, |
| }, |
| TestParams{ |
| /*input_dims=*/{1, 6}, |
| /*begin=*/{0, 0}, |
| /*end=*/{0, 1}, |
| /*strides=*/{1, 1}, |
| /*begin_mask=*/get_mask({1, 0}), |
| /*end_mask=*/get_mask({1, 0}), |
| /*ellipsis_mask=*/0, |
| /*new_axis_mask=*/0, |
| /*shrink_axis_mask=*/get_mask({0, 1}), |
| /*expected_output_dims=*/{1}, |
| /*expected_output=*/{1}, |
| }, |
| }; |
| |
| for (auto p : params) { |
| if (trt_mode_ == TrtTestMode::kDynamicShape || |
| (trt_mode_ == TrtTestMode::kExplicitBatch && |
| !HasStaticShape(p.partial_input_dims))) { |
| p.conversion_status = errors::Unimplemented( |
| "Strided slice op not implemented for dynamic shape input"); |
| } |
| Reset(); |
| NodeDef node_def = get_strided_slice_nodedef( |
| tf_type_, p.begin_mask, p.end_mask, p.ellipsis_mask, p.new_axis_mask, |
| p.shrink_axis_mask); |
| |
| VLOG(2) << "Preparing test case with dims " << DebugString(p.input_dims); |
| if (p.partial_input_dims.empty()) { |
| AddTestTensor("input", p.input_dims, ok_input); |
| } else { |
| AddTestTensor("input", p.input_dims, tf_type_, ok_input, |
| p.partial_input_dims); |
| } |
| VLOG(2) << "Adding weights begin: " << DebugString(p.begin) |
| << ", end: " << DebugString(p.end) |
| << ", strides: " << DebugString(p.strides); |
| AddTestWeights<int32>("begin", {static_cast<int>(p.begin.size())}, p.begin); |
| AddTestWeights<int32>("end", {static_cast<int>(p.end.size())}, p.end); |
| AddTestWeights<int32>("strides", {static_cast<int>(p.strides.size())}, |
| p.strides); |
| |
| TestOpConverter("my_strided_slice", node_def, p.expected_output_dims, |
| p.conversion_status, p.runtime_status, |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertSlice) { |
| // Get nodedef for Slice layer. |
| auto get_slice_nodedef = [](DataType tf_type) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| auto begin = ops::Placeholder(s.WithOpName("begin"), DT_INT32); |
| auto size = ops::Placeholder(s.WithOpName("size"), DT_INT32); |
| auto slice = ops::Slice(s.WithOpName("my_slice"), input, begin, size); |
| return slice.operation.node()->def(); |
| }; |
| |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<int> begin; |
| std::vector<int> size; |
| std::vector<int> expected_output_dims; |
| std::vector<int> expected_output; |
| Status conversion_status; |
| Status runtime_status; |
| }; |
| |
| Status conv_dynamic = |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? errors::Unimplemented( |
| "Strided slice op not implemented for dynamic shape input") |
| : Status::OK(); |
| Status conv_dynamic2 = |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? errors::Unimplemented( |
| "Input dims must be defined for size = -1, at my_slice") |
| : Status::OK(); |
| std::vector<TestParams> params = { |
| // Begin is below bounds, should fail. |
| TestParams{ |
| {1, 1, 2, 3}, |
| {0, 0, -1, 0}, |
| {1, 1, 2, 3}, |
| {}, |
| {}, |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? conv_dynamic |
| : errors::InvalidArgument("\"begin\" for dimension 2 in Slice " |
| "is out of range, at my_slice")}, |
| // Batch dimension is modified, should fail in implicit batch mode. |
| TestParams{ |
| {2, 1, 1, 3}, |
| {0, 0, 0, 0}, |
| {1, 1, 1, 3}, |
| {1, 1, 1, 3}, |
| {1, 2, 3}, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Unimplemented("TensorRT does not allow modifications" |
| " to the batch dimension, at my_slice") |
| : Status::OK()}, |
| // Dynamic batch size but using size[0] of -1, ok. |
| TestParams{{1, 1, 2, 3}, |
| {0, 0, 0, 0}, |
| {-1, 1, 2, 2}, |
| {1, 1, 2, 2}, |
| {1, 2, 4, 5}, |
| conv_dynamic2}, |
| // OK test: but converter fails in dynamic shape mode |
| TestParams{{1, 1, 2, 3}, |
| {0, 0, 0, 0}, |
| {-1, -1, -1, -1}, |
| {1, 1, 2, 3}, |
| {1, 2, 3, 4, 5, 6}, |
| conv_dynamic2}, |
| TestParams{{1, 1, 2, 3}, |
| {0, 0, 0, 0}, |
| {1, 1, 2, 3}, |
| {1, 1, 2, 3}, |
| {1, 2, 3, 4, 5, 6}}, |
| TestParams{{1, 1, 2, 3}, |
| {0, 0, 0, 0}, |
| {1, -1, 2, 2}, |
| {1, 1, 2, 2}, |
| {1, 2, 4, 5}, |
| conv_dynamic2}, |
| TestParams{{1, 6}, {0, 1}, {1, 5}, {1, 5}, {2, 3, 4, 5, 6}}, |
| TestParams{{1, 6}, {0, 1}, {-1, 3}, {1, 3}, {2, 3, 4}, conv_dynamic2}, |
| // |
| // In dynamic shape mode we do not know the input shape during |
| // conversion, therfore we cannot check out of bound access. |
| TestParams{ |
| {1, 1, 2, 3}, |
| {0, 0, 3, 0}, |
| {1, 1, 2, 3}, |
| {}, |
| {}, |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? Status::OK() |
| : errors::InvalidArgument("\"begin\" for dimension 2 in Slice " |
| "is out of range, at my_slice"), |
| errors::Internal("Internal: Failed to build TensorRT engine")}, |
| TestParams{{1, 1, 2, 3}, |
| {0, 0, 0, 0}, |
| {1, 1, 2, -2}, |
| {}, |
| {}, |
| errors::InvalidArgument("Invalid size value at my_slice")}, |
| TestParams{ |
| {1, 1, 2, 3}, |
| {0, 0, 0, 0}, |
| {1, 1, 3, 2}, |
| {}, |
| {}, |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? Status::OK() |
| : errors::InvalidArgument("\"begin\" + \"size\" for dimension " |
| "2 in Slice is out of range, at " |
| "my_slice"), |
| errors::Internal("Internal: Failed to build TensorRT engine")}, |
| }; |
| |
| for (auto p : params) { |
| Reset(); |
| NodeDef node_def = get_slice_nodedef(tf_type_); |
| AddTestTensor("input", p.input_dims, {1, 2, 3, 4, 5, 6}); |
| AddTestWeights<int32>("begin", {static_cast<int>(p.begin.size())}, p.begin); |
| AddTestWeights<int32>("size", {static_cast<int>(p.size.size())}, p.size); |
| |
| TestOpConverter("my_slice", node_def, p.expected_output_dims, |
| p.conversion_status, p.runtime_status, |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertConv2D) { |
| // Get nodedef for Conv2D layer. |
| DataType tf_type = tf_type_; |
| auto get_conv2d_nodedef = |
| [tf_type](std::vector<int> strides = {1, 1, 1, 1}, |
| string padding = "SAME", string data_format = "NCHW", |
| std::vector<int> dilations = {1, 1, 1, 1}) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| auto filter = ops::Placeholder(s.WithOpName("weights"), tf_type); |
| ops::Conv2D::Attrs attrs = |
| ops::Conv2D::Attrs().DataFormat(data_format).Dilations(dilations); |
| auto conv2d = ops::Conv2D(s.WithOpName("my_conv2d"), input, filter, strides, |
| padding, attrs); |
| return conv2d.operation.node()->def(); |
| }; |
| |
| { |
| // Input is weights, should fail. |
| Reset(); |
| NodeDef node_def = get_conv2d_nodedef(); |
| AddTestWeights<float>("input", {1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for Conv2D must be a tensor, at my_conv2d"); |
| } |
| { |
| // Filter is tensor, should fail. |
| Reset(); |
| NodeDef node_def = get_conv2d_nodedef(); |
| AddTestTensor("input", {3, 1, 2, 1}); |
| AddTestTensor("weights", {3, 3, 1, 1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"filter\" for Conv2D must be a constant, at my_conv2d"); |
| } |
| { |
| // Filter is not 4D, should fail. |
| Reset(); |
| NodeDef node_def = get_conv2d_nodedef(); |
| AddTestTensor("input", {1, 1, 2, 3}); |
| AddTestWeights<float>("weights", {3, 3, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Conv2D expects kernel of dimension 4, at my_conv2d"); |
| } |
| { |
| // Dilations is not 4D, should fail. |
| Reset(); |
| NodeDef node_def = |
| get_conv2d_nodedef({1, 1, 1, 1}, "SAME", "NCHW", {1, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Convolution dilations field must specify 4 dimensions, at my_conv2d"); |
| } |
| { |
| // Dilation value is not 1 for channel, should fail. |
| Reset(); |
| NodeDef node_def = |
| get_conv2d_nodedef({1, 1, 1, 1}, "SAME", "NCHW", {1, 2, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Dilation rate must be 1 for batch and channel " |
| "dimensions, at my_conv2d"); |
| } |
| { |
| // Dilation value is not 1 for channel (NHWC), should fail. |
| Reset(); |
| NodeDef node_def = |
| get_conv2d_nodedef({1, 1, 1, 1}, "SAME", "NHWC", {1, 1, 1, 2}); |
| AddTestTensor("input", {1, 2, 3, 1}); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Dilation rate must be 1 for batch and channel " |
| "dimensions, at my_conv2d"); |
| } |
| { |
| // Strides is not 4D, should fail. |
| Reset(); |
| NodeDef node_def = |
| get_conv2d_nodedef({1, 1, 1}, "SAME", "NCHW", {1, 1, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Convolution strides field must specify 4 dimensions, at my_conv2d"); |
| } |
| { |
| // Stride value is not 1 for channel, should fail. |
| Reset(); |
| NodeDef node_def = |
| get_conv2d_nodedef({1, 2, 1, 1}, "SAME", "NCHW", {1, 1, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "Stride must be 1 for batch and channel dimensions, at my_conv2d"); |
| } |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| Reset(); |
| NodeDef node_def = get_conv2d_nodedef(); |
| // Channel dim unknown, should fail. |
| nvinfer1::DataType trt_type; |
| TF_ASSERT_OK(TfTypeToTrtType(tf_type_, &trt_type)); |
| AddTestTensorWithTFDims("input", {-1, -1, -1, -1}, trt_type); |
| AddTestWeights<float>("weights", {1, 2, 1, 1}, {-1, 1}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Channel dimension must be static, at my_conv2d"); |
| } |
| |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<float> input; |
| std::vector<int> filter_dims; |
| std::vector<float> filter; |
| std::vector<int> strides; |
| string padding; |
| string data_format; |
| std::vector<int> dilations; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output; |
| }; |
| |
| // Ok. |
| std::vector<TestParams> ok_params = { |
| // Basic |
| TestParams{/*input_dims=*/{1, 1, 2, 3}, |
| /*input=*/{0, 1, 2, 3, 3, 4}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 2}, |
| /*expected_output=*/{1, 1, 0, 1}}, |
| // SAME padding (Asymmetric) |
| TestParams{/*input_dims=*/{1, 1, 2, 3}, |
| /*input=*/{0, 1, 2, 3, 3, 4}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/{1, 1, -2, 0, 1, -4}}, |
| // SAME padding (Symmetric) |
| TestParams{/*input_dims=*/{1, 1, 2, 3}, |
| /*input=*/{0, 1, 2, 3, 3, 4}, |
| /*filter_dims=*/{1, 3, 1, 1}, |
| /*filter=*/{-1, 0, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/{1, 2, -1, 3, 1, -3}}, |
| // NHWC |
| TestParams{/*input_dims=*/{1, 2, 3, 1}, |
| /*input=*/{0, 1, 2, 3, 3, 4}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NHWC", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 2, 2, 1}, |
| /*expected_output=*/{1, 1, 0, 1}}, |
| // Dilated |
| TestParams{/*input_dims=*/{1, 1, 2, 3}, |
| /*input=*/{0, 1, 2, 3, 3, 4}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 2}, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_output=*/{2, 1}}, |
| // Strided |
| TestParams{/*input_dims=*/{1, 1, 2, 4}, |
| /*input=*/{0, 1, 2, 2, 3, 4, 4, 7}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 2}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 2}, |
| /*expected_output=*/{1, 0, 1, 3}}, |
| }; |
| |
| for (int i = 0; i < ok_params.size(); i++) { |
| Reset(); |
| NodeDef node_def = |
| get_conv2d_nodedef(ok_params[i].strides, ok_params[i].padding, |
| ok_params[i].data_format, ok_params[i].dilations); |
| std::vector<int> partial_input_shape; |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| // The channel dim cannot have unknown size, fix that. |
| partial_input_shape.resize(ok_params[i].input_dims.size(), -1); |
| int channel_id = (ok_params[i].data_format == "NCHW") ? 1 : 3; |
| partial_input_shape[channel_id] = ok_params[i].input_dims[channel_id]; |
| } |
| |
| AddTestTensor("input", ok_params[i].input_dims, tf_type_, |
| ok_params[i].input, partial_input_shape); |
| AddTestWeights<float>("weights", ok_params[i].filter_dims, |
| ok_params[i].filter); |
| |
| TestOpConverter("my_conv2d", node_def, ok_params[i].expected_output_dims, |
| Status::OK(), Status::OK(), |
| ElementsAreArray(ok_params[i].expected_output)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertConv2DBackpropInput) { |
| // Get nodedef for Conv2D layer. |
| auto get_conv2d_backprop_input_nodedef = |
| [](DataType tf_type, std::vector<int> strides = {1, 1, 1, 1}, |
| string padding = "SAME", string data_format = "NCHW", |
| std::vector<int> dilations = {1, 1, 1, 1}) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| auto filter = ops::Placeholder(s.WithOpName("weights"), tf_type); |
| auto input_sizes = ops::Placeholder(s.WithOpName("input_sizes"), DT_INT32); |
| ops::Conv2DBackpropInput::Attrs attrs = ops::Conv2DBackpropInput::Attrs() |
| .DataFormat(data_format) |
| .Dilations(dilations); |
| auto conv2d = ops::Conv2DBackpropInput( |
| s.WithOpName("my_conv2d_backprop_input"), input_sizes, filter, input, |
| strides, padding, attrs); |
| return conv2d.operation.node()->def(); |
| }; |
| |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<float> input; |
| std::vector<int> filter_dims; |
| std::vector<float> filter; |
| std::vector<int> strides; |
| string padding; |
| string data_format; |
| std::vector<int> dilations; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output; |
| Status conversion_status; |
| // For dynamic shape mode, we must use the partial_input_dims for |
| // creating the test tensor if any of the input_dims are -1. |
| std::vector<int> partial_input_dims; |
| }; |
| |
| // Ok. |
| std::vector<TestParams> params = { |
| // Transpose Strided |
| TestParams{/*input_dims=*/{1, 1, 2, 2}, |
| /*input=*/{0, 1, 2, 3}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 2}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 4}, |
| /*expected_output=*/{0, 0, -1, 1, -2, 2, -3, 3}}, |
| // Transpose Strided NHWC |
| TestParams{/*input_dims=*/{1, 2, 2, 1}, |
| /*input=*/{0, 1, 2, 3}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 2, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NHWC", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 2, 4, 1}, |
| /*expected_output=*/{0, 0, -1, 1, -2, 2, -3, 3}}, |
| // Transpose Strided NHWC with VALID padding |
| TestParams{/*input_dims=*/{1, 3, 1, 1}, |
| /*input=*/{0, 1, 2}, |
| /*filter_dims=*/{2, 1, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 2, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NHWC", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 7, 1, 1}, |
| /*expected_output=*/{0, 0, -1, 1, -2, 2, 0}}, |
| TestParams{/*input_dims=*/{1, 1, 2, 2}, |
| /*input=*/{0, 1, 2, 3}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 2}, |
| /*padding=*/"EXPLICIT", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 4}, |
| /*expected_output=*/{0, 0, -1, 1, -2, 2, -3, 3}, |
| errors::Unimplemented("EXPLICIT padding type not " |
| "implemented, only VALID and SAME are" |
| " supported")}, |
| // Dilation + Conv2DBackpropInput, should fail. |
| TestParams{/*input_dims=*/{1, 1, 2, 2}, |
| /*input=*/{0, 1, 2, 3}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 2}, |
| {1, 1, 2, 2}, |
| {}, |
| errors::Unimplemented("Dilation with Conv2DBackpropInput " |
| "(conv2d_transpose) is not supported, " |
| "at my_conv2d_backprop_input")}, |
| }; |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| params.push_back(TestParams{ |
| /*input_dims=*/{1, 1, 2, 2}, |
| /*input=*/{0, 1, 2, 3}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 2}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 4}, |
| /*expected_output=*/{0, 0, -1, 1, -2, 2, -3, 3}, |
| errors::InvalidArgument( |
| "Channel dimension must be static, at my_conv2d_backprop_input"), |
| /*partial input dims=*/{1, -1, 2, 2}}); |
| // Test dynamic batch dimension. |
| params.push_back( |
| TestParams{/*input_dims=*/{2, 1, 2, 2}, |
| /*input=*/ |
| // clang-format off |
| {0, 1, 2, 3, |
| 3, 2, 1, 0}, |
| // clang-format on |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 2}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{2, 1, 2, 4}, |
| /*expected_output=*/ |
| // clang-format off |
| { 0, 0, -1, 1, -2, 2, -3, 3, |
| -3, 3, -2, 2, -1, 1, 0, 0}, |
| // clang-format on |
| /*conversion_status=*/Status::OK(), |
| /*partial input dims=*/{-1, 1, 2, 2}}); |
| |
| // Test dynamic height and width. |
| params.push_back(TestParams{ |
| /*input_dims=*/{1, 1, 2, 2}, |
| /*input=*/{0, 1, 2, 3}, |
| /*filter_dims=*/{1, 2, 1, 1}, |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 2}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCHW", |
| /*dilations=*/{1, 1, 1, 1}, |
| /*expected_output_dims=*/{1, 1, 2, 4}, |
| /*expected_output=*/ |
| {0, 0, -1, 1, -2, 2, -3, 3}, |
| /*conversion_status=*/ |
| errors::Unimplemented( |
| "Conv2dBackpropInput does not support input with unknown spatial " |
| "shape"), |
| /*partial input dims=*/{1, 1, -1, -1}}); |
| } |
| for (auto p : params) { |
| for (int input_sizes_length : {2, 4}) { |
| Reset(); |
| NodeDef node_def = get_conv2d_backprop_input_nodedef( |
| tf_type_, p.strides, p.padding, p.data_format, p.dilations); |
| |
| switch (trt_mode_) { |
| case TrtTestMode::kImplicitBatch: { |
| AddTestTensor("input", p.input_dims, p.input); |
| break; |
| } |
| case TrtTestMode::kExplicitBatch: { |
| AddTestTensor("input", p.input_dims, p.input); |
| break; |
| } |
| case TrtTestMode::kDynamicShape: { |
| AddTestTensor("input", p.input_dims, tf_type_, p.input, |
| p.partial_input_dims.size() > 0 ? p.partial_input_dims |
| : p.input_dims); |
| break; |
| } |
| default: { |
| ASSERT_TRUE(false) << "unknown test mode"; |
| } |
| } |
| |
| AddTestWeights<float>("weights", p.filter_dims, p.filter, tf_type_); |
| |
| if (input_sizes_length == 4) { |
| AddTestWeights<int>("input_sizes", {4}, p.expected_output_dims); |
| } else { |
| std::vector<int> tf_input_sizes(2); |
| // Remove the channel and batch dimensions. |
| if (p.data_format == "NHWC") { |
| std::copy(p.expected_output_dims.begin() + 1, |
| p.expected_output_dims.end() - 1, tf_input_sizes.begin()); |
| } else { |
| std::copy(p.expected_output_dims.begin() + 2, |
| p.expected_output_dims.end(), tf_input_sizes.begin()); |
| } |
| QCHECK_EQ(2, tf_input_sizes.size()); |
| AddTestWeights<int>("input_sizes", {2}, tf_input_sizes); |
| } |
| |
| TestOpConverter("my_conv2d_backprop_input", node_def, |
| p.expected_output_dims, p.conversion_status, Status::OK(), |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| } |
| |
| // Get the NodeDef for Pack. |
| NodeDef GetConv3DNodeDef(std::vector<int> strides = {1, 1, 1, 1, 1}, |
| string padding = "SAME", string data_format = "NCDHW", |
| std::vector<int> dilations = {1, 1, 1, 1, 1}, |
| bool is_conv3d_backprop_input = false) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_FLOAT); |
| auto filter = ops::Placeholder(s.WithOpName("weights"), DT_FLOAT); |
| |
| if (is_conv3d_backprop_input) { |
| auto input_sizes = ops::Placeholder(s.WithOpName("input_sizes"), DT_INT32); |
| ops::Conv3DBackpropInputV2::Attrs attrs = |
| ops::Conv3DBackpropInputV2::Attrs() |
| .DataFormat(data_format) |
| .Dilations(dilations); |
| auto conv3d = |
| ops::Conv3DBackpropInputV2(s.WithOpName("my_conv3d"), input_sizes, |
| filter, input, strides, padding, attrs); |
| return conv3d.operation.node()->def(); |
| } else { |
| ops::Conv3D::Attrs attrs = |
| ops::Conv3D::Attrs().DataFormat(data_format).Dilations(dilations); |
| auto conv3d = ops::Conv3D(s.WithOpName("my_conv3d"), input, filter, strides, |
| padding, attrs); |
| return conv3d.operation.node()->def(); |
| } |
| } |
| |
| struct Conv3DTestParams { |
| std::vector<int> input_dims; |
| std::vector<float> input; |
| std::vector<int> filter_dims; |
| std::vector<float> filter; |
| std::vector<int> strides; |
| string padding; |
| string data_format; |
| std::vector<int> dilations; |
| bool is_conv3d_backprop; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output; |
| bool allow_dynamic_channel_dim; |
| Status validation_status; |
| }; |
| |
| void TestConv3D(ParameterizedOpConverterTestBase* test, Conv3DTestParams& p) { |
| test->Reset(); |
| NodeDef node_def = GetConv3DNodeDef(p.strides, p.padding, p.data_format, |
| p.dilations, p.is_conv3d_backprop); |
| |
| std::vector<int> partial_input_shape; |
| if (!p.allow_dynamic_channel_dim && |
| test->get_trt_mode() == TrtTestMode::kDynamicShape) { |
| // The channel dim cannot have unknown size, fix that. |
| partial_input_shape.resize(p.input_dims.size(), -1); |
| int channel_id = (p.data_format == "NCDHW") ? 1 : 4; |
| partial_input_shape[channel_id] = p.input_dims[channel_id]; |
| } |
| |
| test->AddTestTensor("input", p.input_dims, test->get_tf_type(), p.input, |
| partial_input_shape); |
| test->AddTestWeights<float>("weights", p.filter_dims, p.filter); |
| |
| if (p.is_conv3d_backprop) { |
| test->AddTestWeights<float>("input_sizes", |
| {static_cast<int>(p.expected_output.size())}, |
| p.expected_output); |
| } |
| |
| test->TestOpConverter("my_conv3d", node_def, p.expected_output_dims, |
| /*expected_conversion_status=*/p.validation_status, |
| /*expected_runtime_status=*/Status::OK(), |
| /*matcher=*/ElementsAreArray(p.expected_output), |
| /*out_tf_types=*/{test->get_tf_type()}); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertConv3D) { |
| { |
| // Input is weights, should fail. |
| Reset(); |
| NodeDef node_def = GetConv3DNodeDef(); |
| |
| AddTestWeights<float>("input", {1, 1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| AddTestWeights<float>("weights", {1, 3, 3, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for Conv3D must be a tensor, at my_conv3d"); |
| } |
| { |
| // Filter is tensor, should fail. |
| Reset(); |
| NodeDef node_def = GetConv3DNodeDef(); |
| AddTestTensor("input", {1, 1, 2, 3}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestTensor("weights", {1, 3, 3, 1}, tf_type_, |
| CreateVectorIota<float>(9)); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"filter\" for Conv3D must be a constant, at my_conv3d"); |
| } |
| { |
| // Filter is not 5D, should fail. |
| Reset(); |
| NodeDef node_def = GetConv3DNodeDef(); |
| AddTestTensor("input", {1, 1, 2, 3}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestWeights<float>("weights", {3, 3, 1, 1}, {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Conv3D expects kernel of dimension 5, at my_conv3d"); |
| } |
| { |
| // Dilations is not 5D, should fail. |
| Reset(); |
| NodeDef node_def = |
| GetConv3DNodeDef({1, 1, 1, 1, 1}, "SAME", "NCDHW", {1, 1, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestWeights<float>( |
| "weights", {3, 3, 1, 1, 1}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9}); // Dimensions, then values |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Convolution dilations field must specify 5 dimensions, at my_conv3d"); |
| } |
| { |
| // Dilation value is not 1 for channel, should fail. |
| Reset(); |
| NodeDef node_def = |
| GetConv3DNodeDef({1, 1, 1, 1, 1}, "SAME", "NCDHW", {1, 2, 1, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestWeights<float>("weights", {3, 3, 1, 1, 1}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Dilation rate must be 1 for batch and channel " |
| "dimensions, at my_conv3d"); |
| } |
| { |
| // Dilation value is not 1 for channel (NDHWC), should fail. |
| Reset(); |
| NodeDef node_def = |
| GetConv3DNodeDef({1, 1, 1, 1, 1}, "SAME", "NDHWC", {1, 1, 1, 1, 2}); |
| AddTestTensor("input", {1, 2, 3, 1}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestWeights<float>("weights", {3, 3, 1, 1, 1}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Dilation rate must be 1 for batch and channel " |
| "dimensions, at my_conv3d"); |
| } |
| { |
| // Dilation + Conv3DBackpropInputV2, should fail. |
| Reset(); |
| NodeDef node_def = GetConv3DNodeDef({1, 1, 1, 1, 1}, "SAME", "NDHWC", |
| {1, 1, 2, 1, 1}, true); |
| AddTestTensor("input", {1, 2, 3, 1}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestWeights<float>("weights", {3, 3, 1, 1, 1}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| AddTestWeights<int>("input_sizes", {4}, {1, 2, 3, 1}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Dilation with Conv3DBackpropInputV2 " |
| "(conv3d_transpose) is not supported, " |
| "at my_conv3d"); |
| } |
| { |
| // Asymmetric+ Conv3DBackpropInputV2, should fail. |
| Reset(); |
| NodeDef node_def = GetConv3DNodeDef({1, 1, 1, 1, 1}, "SAME", "NDHWC", |
| {1, 1, 1, 1, 1}, true); |
| AddTestTensor("input", {1, 2, 2, 2}, tf_type_, CreateVectorIota<float>(8)); |
| AddTestWeights<float>("weights", {1, 1, 2, 1, 1}, {1, 1}); |
| AddTestWeights<int>("input_sizes", {8}, {1, 2, 3, 4, 5, 6, 7, 8}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Asymmetric padding with Conv3DBackpropInputV2 " |
| "(conv3d_transpose) is not supported, at " |
| "my_conv3d"); |
| } |
| { |
| // Strides is not 5D, should fail. |
| Reset(); |
| NodeDef node_def = |
| GetConv3DNodeDef({1, 1, 1, 1, 1, 1}, "SAME", "NCDHW", {1, 1, 1, 1, 1}); |
| AddTestTensor("input", {1, 2, 2, 2}, tf_type_, CreateVectorIota<float>(8)); |
| AddTestWeights<float>("weights", {1, 1, 2, 1, 1}, {1, 1}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Convolution strides field must specify 5 dimensions, at my_conv3d"); |
| } |
| { |
| // Stride value is not 1 for channel, should fail. |
| Reset(); |
| NodeDef node_def = |
| GetConv3DNodeDef({1, 2, 1, 1, 1}, "SAME", "NCDHW", {1, 1, 1, 1, 1}); |
| AddTestTensor("input", {1, 1, 2, 3}, tf_type_, CreateVectorIota<float>(6)); |
| AddTestWeights<float>("weights", {3, 3, 1, 1, 1}, |
| {1, 2, 3, 4, 5, 6, 7, 8, 9}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "Stride must be 1 for batch and channel dimensions, at my_conv3d"); |
| } |
| |
| // Start here |
| std::vector<Conv3DTestParams> ok_params = { |
| // Basic - just 1x1 conv - input = output |
| {/*input_dims=*/{1, 1, 3, 3, 3}, // CDHW |
| /*input=*/{1, 2, 15, 3, 6, -3, 22, 1, 88, 56, 36, 1, 1, 105, |
| 1, 16, -28, 1, 42, 9, 3, 1, 7, 1, 11, 61, 5}, |
| /*filter_dims=*/{1, 1, 1, 1, 1}, // DRSCK |
| /*filter=*/{1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 3, 3, 3}, |
| /*expected_output=*/{1, 2, 15, 3, 6, -3, 22, 1, 88, |
| 56, 36, 1, 1, 105, 1, 16, -28, 1, |
| 42, 9, 3, 1, 7, 1, 11, 61, 5}, |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| // Basic - 2x1 filter |
| {/*input_dims=*/{1, 1, 3, 3, 3}, // CDHW |
| /*input=*/{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6}, |
| /*filter_dims=*/{2, 1, 1, 1, 1}, // DRSCK |
| /*filter=*/{1, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 2, 3, 3}, |
| /*expected_output=*/ |
| {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7}, |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| // SAME padding (Asymmetric) |
| {/*input_dims=*/{1, 1, 2, 3, 2}, // CDHW |
| /*input=*/{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, |
| /*filter_dims=*/{2, 1, 1, 1, 1}, // DRSCK |
| /*filter=*/{-1, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 2, 3, 2}, |
| // Diff in first 2 depths is const 6. |
| /*expected_output=*/{6, 6, 6, 6, 6, 6, -6, -7, -8, -9, -10, -11}, |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| // SAME padding (Symmetric) |
| {/*input_dims=*/{1, 1, 2, 3, 2}, // CDHW |
| /*input=*/{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, |
| /*filter_dims=*/{3, 1, 1, 1, 1}, // DRSCK |
| /*filter=*/{-1, 0, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 2, 3, 2}, |
| // Swaps front two depths, negates |
| /*expected_output=*/{6, 7, 8, 9, 10, 11, 0, -1, -2, -3, -4, -5}, |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK() |
| |
| }, |
| // NDHWC (multi-channel) |
| {/*input_dims=*/{1, 2, 3, 2, 2}, // DHWC |
| /*input=*/{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, |
| /*filter_dims=*/{2, 1, 1, 2, 1}, // DRSCK |
| /*filter=*/{-1, 1, 1, -1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NDHWC", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 3, 2, 1}, |
| /*expected_output=*/{0, 0, 0, 0, 0, 0}, // Filters oppose each-other |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| // Dilated |
| {/*input_dims=*/{1, 1, 3, 3, 3}, // CDHW |
| /*input=*/{1, 1, 1, 1, 1, 1, 1, 1, 1, -10, -10, -10, -10, -10, |
| -10, -10, -10, -10, 7, 7, 7, 7, 7, 7, 7, 7, 7}, |
| /*filter_dims=*/{2, 1, 1, 1, 1}, // DRSCK |
| /*filter=*/{1, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 2, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 1, 3, 3}, |
| // Only front depth is valid, skips neg values |
| /*expected_output=*/{8, 8, 8, 8, 8, 8, 8, 8, 8}, |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| // Strided |
| {/*input_dims=*/{1, 1, 3, 3, 3}, |
| /*input=*/{1, 0, 2, 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 5, 0, 6, 0, 0, 0, 7, 0, 8}, |
| /*filter_dims=*/{1, 1, 1, 1, 1}, |
| /*filter=*/{1}, |
| /*strides=*/{1, 1, 2, 2, 2}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{1, 1, 2, 2, 2}, |
| // Should only pick up the corners |
| /*expected_output=*/{1, 2, 3, 4, 5, 6, 7, 8}, |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| // Transpose Strided |
| {/*input_dims=*/{1, 1, 2, 2, 2}, // CDHW |
| /*input=*/{1, 2, 3, 4, 5, 6, 7, 8}, |
| /*filter_dims=*/{1, 1, 1, 1, 1}, |
| /*filter=*/{1}, |
| /*strides=*/{1, 1, 2, 2, 2}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/true, |
| /*expected_output_dims=*/{1, 1, 3, 3, 3}, |
| /*expected_output=*/{1, 0, 2, 0, 0, 0, 3, 0, 4, // Cube expands and |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, // fills center |
| 5, 0, 6, 0, 0, 0, 7, 0, 8}, // with zeroes |
| /*allow_dynamic_channel_dim=*/false, |
| /*validation_status=*/Status::OK()}, |
| }; |
| |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| ok_params.reserve(ok_params.size() + 2); |
| const std::vector<float> common_input = CreateVectorIota<float>(3 * 3 * 3); |
| // NCDHW - Dynamic Channel - Should fail in kDynamicShape |
| ok_params.push_back(Conv3DTestParams{ |
| /*input_dims=*/{1, 1, 3, 3, 3}, |
| /*input=*/common_input, |
| /*filter_dims=*/{1, 1, 1, 1, 1}, |
| /*filter=*/{1}, |
| /*strides=*/{1, 1, 2, 2, 2}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{}, // ignore, will fail anyway |
| /*expected_output=*/{}, // ignore, will fail anyway |
| /*allow_dynamic_channel_dim=*/true, |
| /*validation_status=*/ |
| Status{error::INVALID_ARGUMENT, |
| "Channel dimension must be static, at my_conv3d"}}); |
| // NDHWC - Dynamic Channel - Should fail in kDynamicShape |
| ok_params.push_back(Conv3DTestParams{ |
| /*input_dims=*/{1, 3, 3, 3, 1}, |
| /*input=*/common_input, |
| /*filter_dims=*/{1, 1, 1, 1, 1}, |
| /*filter=*/{1}, |
| /*strides=*/{1, 2, 2, 2, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NDHWC", |
| /*dilations=*/{1, 1, 1, 1, 1}, |
| /*is_conv3d_backprop=*/false, |
| /*expected_output_dims=*/{}, // ignore, will fail anyway |
| /*expected_output=*/{}, // ignore, will fail anyway |
| /*allow_dynamic_channel_dim=*/true, |
| /*validation_status=*/ |
| Status{error::INVALID_ARGUMENT, |
| "Channel dimension must be static, at my_conv3d"}}); |
| } |
| |
| for (auto p : ok_params) { |
| TestConv3D(this, p); |
| } |
| } |
| |
| template <typename T> |
| NodeDef CreatePoolOp(DataType tf_type, std::vector<int> ksize, |
| std::vector<int> strides, string padding, |
| string data_format) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| typename T::Attrs attrs; |
| attrs.data_format_ = data_format; |
| return T(s.WithOpName("my_pool"), input, ksize, strides, padding, attrs) |
| .operation.node() |
| ->def(); |
| } |
| TEST_P(OpConverter_FP32_Test, ConvertPool) { |
| // Get nodedef for MaxPool and AvgPool layers (2D or 3D). |
| auto get_pool_nodedef = |
| [](DataType tf_type, int nDim, std::vector<int> ksize = {}, |
| std::vector<int> strides = {}, string padding = "SAME", |
| string data_format = "", const bool is_max_pooling = true) -> NodeDef { |
| if (ksize.empty()) { |
| ksize = nDim == 2 ? std::vector<int>{1, 1, 1, 1} |
| : std::vector<int>{1, 1, 1, 1, 1}; |
| } |
| if (strides.empty()) { |
| strides = nDim == 2 ? std::vector<int>{1, 1, 1, 1} |
| : std::vector<int>{1, 1, 1, 1, 1}; |
| } |
| if (data_format == "") { |
| data_format = nDim == 2 ? "NCHW" : "NCDHW"; |
| } |
| if (is_max_pooling) { |
| if (nDim == 3) { |
| return CreatePoolOp<ops::MaxPool3D>(tf_type, ksize, strides, padding, |
| data_format); |
| } else { |
| return CreatePoolOp<ops::MaxPool>(tf_type, ksize, strides, padding, |
| data_format); |
| } |
| } else { |
| if (nDim == 3) { |
| return CreatePoolOp<ops::AvgPool3D>(tf_type, ksize, strides, padding, |
| data_format); |
| } else { |
| return CreatePoolOp<ops::AvgPool>(tf_type, ksize, strides, padding, |
| data_format); |
| } |
| } |
| }; |
| |
| std::vector<int> test_nDims{2, 3}; |
| |
| for (int nDim : test_nDims) { |
| // Input is weights, should fail. |
| Reset(); |
| NodeDef node_def = get_pool_nodedef(tf_type_, nDim); |
| |
| AddTestWeights<float>("input", {1, 1, 1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| StrCat("The input \"input\" for ", node_def.op(), |
| " must be a tensor, at my_pool")); |
| } |
| |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<float> input; |
| std::vector<int> ksize; |
| std::vector<int> strides; |
| string padding; |
| string data_format; |
| std::vector<int> expected_output_dims; |
| // The expected outputs for the following operations: MaxPool2D, AvgPool2D, |
| // MaxPool3D, AvgPool3D |
| std::vector<std::vector<float>> expected_outputs; |
| }; |
| |
| // We use common_input as the input to test both 2D and 3D pooling operations, |
| // to simplify TestParams. For 2D operations, only the first 1/3 of the values |
| // are used. |
| const std::vector<float> common_input{-4, 2, 15, 3, 6, -3, 22, 1, 88, |
| 56, 36, 1, 1, 105, 1, 16, -28, 1, |
| 42, 9, 3, 1, 7, 1, 11, 61, 5}; |
| // The output of 2D ops for the case where the op is equivalent to the |
| // identity op. |
| const std::vector<float> common_2d_output{-4, 2, 15, 3, 6, -3, 22, 1, 88}; |
| std::vector<TestParams> ok_params = { |
| // Basic - just 1x1 max pooling - input = output |
| TestParams{ |
| /*input_dims=*/{1, 1, 3, 3, 3}, |
| /*input=*/common_input, |
| /*ksize=*/{1, 1, 1, 1, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*expected_output_dims=*/{1, 1, 3, 3, 3}, |
| /*expected_outputs=*/ |
| {common_2d_output, common_2d_output, common_input, common_input}}, |
| // Basic - just 1x1 max pooling - input = output, SAME padding |
| TestParams{ |
| /*input_dims=*/{1, 1, 3, 3, 3}, |
| /*input=*/common_input, |
| /*ksize=*/{1, 1, 1, 1, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"SAME", |
| /*data_format=*/"NCDHW", |
| /*expected_output_dims=*/{1, 1, 3, 3, 3}, |
| /*expected_outputs=*/ |
| {common_2d_output, common_2d_output, common_input, common_input}}, |
| // 3x3 pooling NCDHW |
| TestParams{/*input_dims=*/{1, 1, 3, 3, 3}, |
| /*input=*/common_input, |
| /*ksize=*/{1, 1, 3, 3, 3}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*expected_output_dims=*/{1, 1, 1, 1, 1}, |
| /*expected_outputs=*/{{88}, {14.444445}, {105}, {17}}}, |
| // 3x3 pooling, NDHWC |
| TestParams{/*input_dims=*/{1, 3, 3, 3, 1}, |
| /*input=*/common_input, |
| /*ksize=*/{1, 3, 3, 3, 1}, |
| /*strides=*/{1, 1, 1, 1, 1}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NDHWC", |
| /*expected_output_dims=*/{1, 1, 1, 1, 1}, |
| /*expected_outputs=*/{{88}, {14.444445}, {105}, {17}}}, |
| // Strided |
| TestParams{/*input_dims=*/{1, 1, 3, 3, 3}, |
| /*input=*/{1, 0, 2, 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 5, 0, 6, 0, 0, 0, 7, 0, 8}, |
| /*ksize=*/{1, 1, 1, 1, 1}, |
| /*strides=*/{1, 1, 2, 2, 2}, |
| /*padding=*/"VALID", |
| /*data_format=*/"NCDHW", |
| /*expected_output_dims=*/{1, 1, 2, 2, 2}, |
| /*expected_outputs=*/ |
| {{1, 2, 3, 4}, // Should only pick up the corners |
| {1, 2, 3, 4}, |
| {1, 2, 3, 4, 5, 6, 7, 8}, |
| {1, 2, 3, 4, 5, 6, 7, 8}}}, |
| }; |
| |
| for (auto p : ok_params) { |
| int test_counter = 0; |
| for (int nDim : test_nDims) { |
| auto input = p.input; |
| auto input_dims = p.input_dims; |
| auto ksize = p.ksize; |
| auto strides = p.strides; |
| auto expected_output_dims = p.expected_output_dims; |
| std::string data_format = p.data_format; |
| if (nDim == 2) { |
| input.resize(9); |
| data_format = p.data_format == "NDHWC" ? "NHWC" : "NCHW"; |
| // Remove one of the spatial dimensions |
| input_dims.erase(input_dims.begin() + 2); |
| ksize.erase(ksize.begin() + 2); |
| strides.erase(strides.begin() + 2); |
| expected_output_dims.erase(expected_output_dims.begin() + 2); |
| } |
| for (bool is_max_pooling : {true, false}) { |
| Reset(); |
| NodeDef node_def = |
| get_pool_nodedef(tf_type_, nDim, ksize, strides, p.padding, |
| data_format, is_max_pooling); |
| AddTestTensor("input", input_dims, input); |
| TestOpConverter("my_pool", node_def, expected_output_dims, Status::OK(), |
| Status::OK(), |
| ElementsAreArray(p.expected_outputs.at(test_counter))); |
| test_counter++; |
| } |
| } |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertTopK) { |
| // Get the NodeDef for TopKV2. |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type_); |
| auto weights = ops::Placeholder(s.WithOpName("weights"), DT_INT32); |
| auto topk = ops::TopK(s.WithOpName("my_topk"), input, weights); |
| const NodeDef& node_def = topk.operation.node()->def(); |
| { |
| // K is a tensor, should fail. |
| Reset(); |
| AddTestTensor("input", {1, 1, 2, 3}); |
| AddTestTensor("weights", {1}, DT_INT32, {}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"k\" for TopKV2 must be a constant, at my_topk"); |
| } |
| { |
| // Ok. |
| Reset(); |
| AddTestTensor("input", {1, 1, 2, 5}, {-9, 3, 5, 1, 6, -5, 7, 1, 0, -1}); |
| AddTestWeights<int32>("weights", {1}, {2}); |
| std::vector<std::vector<int>> expected_output_dims{{1, 1, 2, 2}, |
| {1, 1, 2, 2}}; |
| TestOpConverterMultiOut("my_topk", node_def, expected_output_dims, |
| Status::OK(), Status::OK(), |
| {ElementsAre(6, 5, 7, 1), ElementsAre(4, 2, 1, 2)}, |
| {tf_type_, DT_INT32}); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertGather) { |
| // Get the NodeDef for GatherV2. |
| Scope s = Scope::NewRootScope(); |
| auto params = ops::Placeholder(s.WithOpName("params"), tf_type_); |
| auto indices = ops::Placeholder(s.WithOpName("indices"), DT_INT32); |
| auto axis = ops::Placeholder(s.WithOpName("axis"), DT_INT32); |
| auto gather = ops::GatherV2(s.WithOpName("my_gather"), params, indices, axis); |
| const NodeDef& node_def = gather.operation.node()->def(); |
| { |
| // Axis is a tensor, should fail. |
| Reset(); |
| AddTestTensor("params", {1, 1, 2, 3}, tf_type_, {}); |
| AddTestTensor("indices", {1, 2}, DT_INT32, {}); |
| AddTestTensor("axis", {1}, DT_INT32, {}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"axis\" for GatherV2 must be a constant, at my_gather"); |
| } |
| { |
| // Axis is out of bounds, should fail. |
| Reset(); |
| AddTestTensor("params", {1, 1, 2, 3}); |
| AddTestTensor("indices", {1, 2}, DT_INT32, {}); |
| AddTestWeights<int32>("axis", {1}, {4}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Axis value of 4 is out of bounds, must be in " |
| "range [-4, 4), at my_gather"); |
| } |
| |
| struct TestParams { |
| // TF shape of the input 'params' (including batch dimension). |
| std::vector<int> params_shape; |
| // TF shape of the input 'indices' (including batch dimension). |
| std::vector<int> indices_shape; |
| std::vector<int> indices; |
| int axis; |
| // Expected TF shape of the output (including batch dimension). |
| std::vector<int> expected_output_shape; |
| std::vector<int> expected_output; |
| bool params_is_tensor; |
| Status status; |
| Status runtime_status; |
| Status add_index_status; |
| }; |
| |
| // Input is the same {1, 2, 3, 4, 5, 6} for all cases. |
| const std::vector<int> params_input = {1, 2, 3, 4, 5, 6}; |
| std::vector<TestParams> test_params = { |
| // Axis is batch dimension, should fail in implicit batch mode. |
| TestParams{/*params_shape=*/{2, 1, 1, 3}, |
| /*indices_shape=*/{2}, |
| /*indices=*/{1, 0}, |
| /*axis=*/0, |
| /*expected_output_shape=*/{2, 1, 1, 3}, |
| /*expected_output=*/{4, 5, 6, 1, 2, 3}, |
| /*params_is_tensor=*/true, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status{error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the" |
| " batch dimension, at my_gather"} |
| : Status::OK()}, |
| // Batch size of indices is not 1 when params is a tensor. |
| TestParams{/*params_shape=*/{2, 1, 3}, |
| /*indices_shape=*/{2, 1}, |
| /*indices=*/{2, 0}, |
| /*axis=*/2, |
| /*expected_output_shape=*/{2, 1, 2, 1}, |
| /*expected_output=*/{3, 1, 6, 4}, |
| /*params_is_tensor=*/true, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status{error::UNIMPLEMENTED, |
| "Indices must have a batch size of 1 when params" |
| " is a tensor."} |
| : Status::OK()}, |
| // Axis is not zero when params is a weight, should fail in implicit batch |
| // mode. |
| TestParams{/*params_shape=*/{2, 1, 3}, |
| /*indices_shape=*/{2}, |
| /*indices=*/{1, 2}, |
| /*axis=*/2, |
| /*expected_output_shape=*/{2, 1, 2}, |
| /*expected_output=*/{2, 3, 5, 6}, |
| /*params_is_tensor=*/false, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status{error::UNIMPLEMENTED, |
| "The input axis must be zero when params is a" |
| " weight."} |
| : Status::OK()}, |
| // Params with only batch dimension. |
| TestParams{/*params_shape=*/{6}, |
| /*indices_shape=*/{2}, |
| /*indices=*/{1, 3}, |
| /*axis=*/0, |
| /*expected_output_shape=*/{2}, |
| /*expected_output=*/{2, 4}, |
| /*params_is_tensor=*/true, |
| trt_mode_ == TrtTestMode::kImplicitBatch // conversion_status |
| ? Status{error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_gather"} |
| : Status::OK(), |
| Status::OK(), // runtime_status |
| trt_mode_ == TrtTestMode::kImplicitBatch // add_index_status |
| ? Status{error::INVALID_ARGUMENT, |
| "Batch size doesn't match for tensor indices: " |
| "Provided batch size does not match converter " |
| "batch size: 2 vs 6"} |
| : Status::OK()}, |
| // Vector indices, and output rank is rank(params). |
| TestParams{ |
| /*params_shape=*/{1, 1, 2, 3}, |
| /*indices_shape=*/{1}, |
| /*indices=*/{0}, |
| /*axis=*/3, |
| /*expected_output_shape=*/{1, 1, 2, 1}, |
| /*expected_output=*/{1, 4}, |
| /*params_is_tensor=*/true, |
| }, |
| TestParams{ |
| /*params_shape=*/{1, 1, 2, 3}, |
| /*indices_shape=*/{1}, |
| /*indices=*/{1}, |
| /*axis=*/2, |
| /*expected_output_shape=*/{1, 1, 1, 3}, |
| /*expected_output=*/{4, 5, 6}, |
| /*params_is_tensor=*/true, |
| }, |
| // Indices with rank>1, and output rank is rank(params) + rank(indices) - |
| // 1 |
| TestParams{ |
| /*params_shape=*/{1, 1, 2, 3}, |
| /*indices_shape=*/{1, 1}, |
| /*indices=*/{0}, |
| /*axis=*/3, |
| /*expected_output_shape=*/{1, 1, 2, 1, 1}, |
| /*expected_output=*/{1, 4}, |
| /*params_is_tensor=*/true, |
| }, |
| TestParams{ |
| /*params_shape=*/{1, 1, 2, 3}, |
| /*indices_shape=*/{1, 1}, |
| /*indices=*/{1}, |
| /*axis=*/3, |
| /*expected_output_shape=*/{1, 1, 2, 1, 1}, |
| /*expected_output=*/{2, 5}, |
| /*params_is_tensor=*/true, |
| }, |
| TestParams{ |
| /*params_shape=*/{1, 1, 2, 3}, |
| /*indices_shape=*/{1, 1}, |
| /*indices=*/{2}, |
| /*axis=*/-1, |
| /*expected_output_shape=*/{1, 1, 2, 1, 1}, |
| /*expected_output=*/{3, 6}, |
| /*params_is_tensor=*/true, |
| }, |
| TestParams{ |
| /*params_shape=*/{1, 1, 2, 3}, |
| /*indices_shape=*/{1, 3}, |
| /*indices=*/{2, 0, 1}, |
| /*axis=*/3, |
| /*expected_output_shape=*/{1, 1, 2, 1, 3}, |
| /*expected_output=*/{3, 1, 2, 6, 4, 5}, |
| /*params_is_tensor=*/true, |
| }, |
| TestParams{ |
| /*params_shape=*/{1, 3, 2}, |
| /*indices_shape=*/{1, 2, 2}, |
| /*indices=*/{0, 0, 1, 0}, |
| /*axis=*/2, |
| /*expected_output_shape=*/{1, 3, 1, 2, 2}, |
| /*expected_output=*/{1, 1, 2, 1, 3, 3, 4, 3, 5, 5, 6, 5}, |
| /*params_is_tensor=*/true, |
| }, |
| TestParams{ |
| /*params_shape=*/{1, 2, 3}, |
| /*indices_shape=*/{1}, |
| /*indices=*/{0}, |
| /*axis=*/0, |
| /*expected_output_shape=*/{1, 2, 3}, |
| /*expected_output=*/{1, 2, 3, 4, 5, 6}, |
| /*params_is_tensor=*/false, |
| }, |
| TestParams{ |
| /*params_shape=*/{3, 2}, |
| /*indices_shape=*/{1, 2}, |
| /*indices=*/{0, 1}, |
| /*axis=*/0, |
| /*expected_output_shape=*/{1, 2, 2}, |
| /*expected_output=*/{1, 2, 3, 4}, |
| /*params_is_tensor=*/false, |
| }, |
| TestParams{ |
| /*params_shape=*/{2, 3}, |
| /*indices_shape=*/{1, 1, 2}, |
| /*indices=*/{0, 1}, |
| /*axis=*/0, |
| /*expected_output_shape=*/{1, 1, 2, 3}, |
| /*expected_output=*/{1, 2, 3, 4, 5, 6}, |
| /*params_is_tensor=*/false, |
| }, |
| TestParams{ |
| /*params_shape=*/{3, 2}, |
| /*indices_shape=*/{2, 2}, |
| /*indices=*/{0, 2, 1, 0}, |
| /*axis=*/0, |
| /*expected_output_shape=*/{2, 2, 2}, |
| /*expected_output=*/{1, 2, 5, 6, 3, 4, 1, 2}, |
| /*params_is_tensor=*/false, |
| }, |
| }; |
| |
| for (auto p : test_params) { |
| Reset(); |
| if (p.params_is_tensor) { |
| AddTestTensor("params", p.params_shape, params_input); |
| } else { |
| AddTestWeights("params", p.params_shape, params_input, tf_type_); |
| } |
| AddTestTensor("indices", p.indices_shape, DT_INT32, p.indices, {}, |
| p.add_index_status); |
| AddTestWeights<int32>("axis", {1}, {p.axis}); |
| TestOpConverter("my_gather", node_def, p.expected_output_shape, p.status, |
| p.runtime_status, ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| template <typename OpType> |
| NodeDef CreateReduceOp(DataType tf_type, bool keep_dims) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), tf_type); |
| auto axis = ops::Placeholder(s.WithOpName("axis"), DT_INT32); |
| typename OpType::Attrs op_attrs; |
| op_attrs.keep_dims_ = keep_dims; |
| auto op = OpType(s.WithOpName("my_reduce"), input, axis, op_attrs); |
| return op.operation.node()->def(); |
| } |
| |
| // Applies reduction op on sub-sequences of input |
| // output[i] = reduce(input[m * i : m * (i +1)]) |
| std::vector<float> CalcReduce(string op_name, std::vector<float> input, int m, |
| float (*op)(float, float), float init) { |
| std::vector<float> output(input.size() / m); |
| for (int i = 0; i < output.size(); i++) { |
| auto begin = input.begin() + i * m; |
| auto end = input.begin() + (i + 1) * m; |
| output[i] = std::accumulate(begin, end, init, op); |
| if (op_name == "Mean") { |
| output[i] /= m; |
| } |
| } |
| return output; |
| } |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertReduce) { |
| { |
| // Input is weights, should fail. |
| Reset(); |
| const NodeDef node_def = CreateReduceOp<ops::Sum>(tf_type_, false); |
| AddTestWeights<float>("input", {1, 2, 3}, {-3, -2, -1, 0, 1, 2}); |
| AddTestWeights<int32>("axis", {1}, {1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for Sum must be a tensor, at my_reduce"); |
| } |
| { |
| // Axis is weights, should fail. |
| Reset(); |
| const NodeDef node_def = CreateReduceOp<ops::Sum>(tf_type_, false); |
| AddTestTensor("input", {1, 2, 3}, {-3, -2, -1, 0, 1, 2}); |
| AddTestTensor("axis", {1}, DT_INT32, {1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"axis\" for Sum must be a constant, at my_reduce"); |
| } |
| using OpFunc = std::function<NodeDef(DataType, bool)>; |
| using ValFunc = float (*)(float, float); |
| struct ReduceTestDescriptor { |
| string name; |
| OpFunc get_node; |
| ValFunc val_func; |
| float init_val; |
| }; |
| std::vector<ReduceTestDescriptor> op_test_info{ |
| {"Sum", CreateReduceOp<ops::Sum>, [](float x, float y) { return x + y; }, |
| 0}, |
| {"Prod", CreateReduceOp<ops::Prod>, |
| [](float x, float y) { return x * y; }, 1}, |
| {"Mean", CreateReduceOp<ops::Mean>, |
| [](float x, float y) { return x + y; }, 0}, |
| {"Min", CreateReduceOp<ops::Min>, |
| [](float x, float y) { return y < x ? y : x; }, 1000}, |
| {"Max", CreateReduceOp<ops::Max>, |
| [](float x, float y) { return x < y ? y : x; }, -1000}}; |
| |
| std::vector<float> input_values{1, 2, 3, 4, 5, 6}; |
| struct TestParams { |
| std::vector<int> input_dims; |
| std::vector<float> input_values; |
| // Helper array contains the same elements as input but permuted in a way |
| // that the reduction can be calculated over contiguous elements using |
| // CalcReduce |
| std::vector<float> helper_array; |
| std::vector<int> axis; |
| int stride; // product of input_dims along axis |
| Status conversion_status; |
| }; |
| std::vector<TestParams> params{ |
| // Out of range tests |
| TestParams{{2, 3, 1}, input_values, input_values, {3}, 3}, |
| TestParams{{2, 3, 1}, input_values, input_values, {-4}, 3}, |
| // Ok tests |
| TestParams{{2, 3, 1}, input_values, {1, 4, 2, 5, 3, 6}, {0}, 2}, |
| TestParams{{2, 3, 1}, input_values, input_values, {1}, 3}, |
| TestParams{{2, 3, 1}, input_values, input_values, {2}, 1}, |
| TestParams{{2, 3, 1}, input_values, input_values, {0, 1}, 6}, |
| // Ok tests with negative axis values |
| TestParams{{2, 3, 1}, input_values, {1, 4, 2, 5, 3, 6}, {-3}, 2}, |
| TestParams{{2, 3, 1}, input_values, input_values, {-2}, 3}, |
| TestParams{{2, 3, 1}, input_values, input_values, {-1}, 1}, |
| TestParams{{2, 3, 1}, input_values, input_values, {-3, 1}, 6}, |
| }; |
| |
| for (bool keep_dims : {false, true}) { |
| for (auto& op : op_test_info) { |
| VLOG(2) << "Processing " << op.name << " with keep_dims=" << keep_dims; |
| for (auto p : params) { |
| SCOPED_TRACE(StrCat(op.name, keep_dims ? " & keep_dims" : "")); |
| Reset(); |
| NodeDef node_def = op.get_node(tf_type_, keep_dims); |
| |
| AddTestTensor("input", p.input_dims, p.input_values); |
| AddTestWeights<int32>("axis", {static_cast<int>(p.axis.size())}, |
| p.axis); |
| std::vector<int> expected_output_dims(p.input_dims); |
| |
| // Set expected output dim and conversion error messages |
| for (int ax : p.axis) { |
| int rank = p.input_dims.size(); |
| if (ax >= rank || ax < -rank) { |
| p.conversion_status = |
| errors::InvalidArgument("Axis value of ", ax, |
| " is out of bounds, must be in " |
| "range [", |
| -rank, ", ", rank, "), at my_reduce"); |
| } else { |
| int ax_positive = ax >= 0 ? ax : ax + rank; |
| // Zero marks elements that we will remove later. |
| expected_output_dims[ax_positive] = keep_dims ? 1 : 0; |
| if (trt_mode_ == TrtTestMode::kImplicitBatch && |
| (ax == 0 || ax == -rank)) { |
| p.conversion_status = errors::Unimplemented( |
| "TensorRT does not allow manipulation of the batch " |
| "dimension, at my_reduce"); |
| } |
| } |
| } |
| expected_output_dims.erase(std::remove(expected_output_dims.begin(), |
| expected_output_dims.end(), 0), |
| expected_output_dims.end()); |
| VLOG(2) << "out dims " |
| << absl::StrCat("[", absl::StrJoin(expected_output_dims, ","), |
| "]"); |
| std::vector<float> expected_values = CalcReduce( |
| op.name, p.helper_array, p.stride, op.val_func, op.init_val); |
| |
| if (tf_type_ == DT_INT32) { |
| // We need to floor the float values in the `expected_values` vector. |
| std::for_each(expected_values.begin(), expected_values.end(), |
| [](float& _n) { _n = std::floor(_n); } |
| ); |
| } |
| |
| TestOpConverter("my_reduce", node_def, expected_output_dims, |
| p.conversion_status, Status::OK(), |
| ArrayFloatNear(expected_values)); |
| } |
| } |
| } |
| } |
| |
| NodeDef CreateCastOp(DataType tf_type) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), DT_HALF); |
| return ops::Cast(s.WithOpName("my_unary"), input, DT_FLOAT) |
| .operation.node() |
| ->def(); |
| } |
| |
| TEST_P(OpConverter_FP32_Test, ConvertUnary) { |
| { |
| // Input is weights, should fail. |
| Reset(); |
| const NodeDef node_def = CreateUnaryOp<ops::Neg>(tf_type_); |
| AddTestWeights<float>("input", {1, 2, 3}, {-3, -2, -1, 0, 1, 2}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"x\" for Neg must be a tensor, at my_unary"); |
| } |
| using OpFunc = std::function<NodeDef(DataType)>; |
| using ValFunc = float (*)(float); |
| std::map<std::string, std::pair<OpFunc, ValFunc>> op_map; |
| #define ADD_OP(name, op, compute) \ |
| op_map[name] = \ |
| std::make_pair(CreateUnaryOp<op>, static_cast<ValFunc>(compute)) |
| ADD_OP("Abs", ops::Abs, std::abs); |
| ADD_OP("Acos", ops::Acos, std::acos); |
| ADD_OP("Acosh", ops::Acosh, std::acosh); |
| ADD_OP("Asin", ops::Asin, std::asin); |
| ADD_OP("Asinh", ops::Asinh, std::asinh); |
| ADD_OP("Atan", ops::Atan, std::atan); |
| ADD_OP("Atanh", ops::Atanh, std::atanh); |
| op_map["Cast"] = std::make_pair(CreateCastOp, [](float x) { return x; }); |
| ADD_OP("Ceil", ops::Ceil, std::ceil); |
| ADD_OP("Cos", ops::Cos, std::cos); |
| ADD_OP("Cosh", ops::Cosh, std::cosh); |
| ADD_OP("Exp", ops::Exp, std::exp); |
| ADD_OP("Erf", ops::Erf, std::erf); |
| ADD_OP("Floor", ops::Floor, std::floor); |
| ADD_OP("Log", ops::Log, std::log); |
| ADD_OP("Neg", ops::Neg, [](float x) { return -x; }); |
| ADD_OP("Reciprocal", ops::Reciprocal, [](float x) { return 1.0f / x; }); |
| ADD_OP("Rsqrt", ops::Rsqrt, [](float x) { return 1.0f / std::sqrt(x); }); |
| ADD_OP("Sin", ops::Sin, std::sin); |
| ADD_OP("Sinh", ops::Sinh, std::sinh); |
| ADD_OP("Sqrt", ops::Sqrt, std::sqrt); |
| ADD_OP("Tan", ops::Tan, std::tan); |
| #undef ADD_OP |
| // Get list of ops to test. |
| std::vector<string> ops_to_test; |
| // Add all ops supported by ConvertUnary. |
| auto* map = UnaryOperationMap(); |
| ops_to_test.reserve(map->size()); |
| for (auto& pair : *map) { |
| ops_to_test.push_back(pair.first); |
| } |
| // Add other unary ops to test. |
| ops_to_test.push_back("Rsqrt"); |
| // Prepare test parameters |
| auto p = TestParamBase{ |
| {1, 1, 2, 3}, // input dims |
| {}, // input partial dims |
| {1, 1, 2, 3}, // expected output dims |
| }; |
| for (const string& op_name : ops_to_test) { |
| SCOPED_TRACE(op_name); |
| Reset(); |
| if (!op_map.count(op_name)) { |
| FAIL() << "Unary op test map does not contain op " << op_name; |
| } |
| NodeDef node_def = op_map[op_name].first(tf_type_); |
| |
| // TODO(bixia): we assume this test is only instantiated for DT_FLOAT for |
| // now. Need to find a better way to express input and output types. |
| // |
| // TODO(tfeher): improve tests by defining an expected output data type and |
| // check that. Currently only the shape and values of the output are |
| // checked. |
| DataType input_tf_type = op_name == "Cast" ? DT_HALF : tf_type_; |
| |
| std::vector<float> input_values{-0.9f, 0.6f, 0.0f, -3.5f, 100.0f, 2.9f}; |
| AddTestTensor("input", p.input_dims, input_tf_type, input_values); |
| std::vector<float> output; |
| std::transform(input_values.begin(), input_values.end(), |
| std::back_inserter(output), op_map[op_name].second); |
| TestOpConverter("my_unary", node_def, p.expected_output_dims, Status::OK(), |
| p.runtime_status, ArrayFloatNear(output, 0.0001, true)); |
| } |
| } |
| |
| // Get the NodeDef for ConcatV2. |
| // TODO(hinsu): Consider switching this to static function. |
| auto get_concat_nodedef = [](DataType dtype, int num_inputs) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| std::vector<Input> values; |
| for (int i = 0; i < num_inputs; ++i) { |
| const string input_name = StrCat("values_", i); |
| values.push_back(ops::Placeholder(s.WithOpName(input_name), dtype)); |
| } |
| auto axis = ops::Placeholder(s.WithOpName("axis"), DT_INT32); |
| auto concat = ops::Concat(s.WithOpName("my_concat"), |
| absl::Span<const Input>(values), axis); |
| return concat.operation.node()->def(); |
| }; |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertConcat) { |
| { |
| // Axis is a tensor, should fail. |
| Reset(); |
| NodeDef node_def = get_concat_nodedef(tf_type_, 2); |
| AddTestTensor("values_0", {1, 1, 2, 3}); |
| AddTestTensor("values_1", {1, 1, 2, 3}); |
| AddTestTensor("axis", {1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"axis\" for ConcatV2 must be a constant, at my_concat"); |
| } |
| { |
| // Axis is out of bounds, should fail. |
| Reset(); |
| NodeDef node_def = get_concat_nodedef(tf_type_, 2); |
| AddTestTensor("values_0", {1, 1, 2, 3}); |
| AddTestTensor("values_1", {1, 1, 2, 3}); |
| AddTestWeights<int32>("axis", {1}, {4}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Axis value of 4 is out of bounds, must be in " |
| "range [-4, 4), at my_concat"); |
| } |
| { |
| // Inputs have inconsistent ranks, should fail. |
| Reset(); |
| NodeDef node_def = get_concat_nodedef(tf_type_, 2); |
| AddTestTensor("values_0", {1, 1, 2, 3}); |
| AddTestTensor("values_1", {1, 1, 6}); |
| AddTestWeights<int32>("axis", {1}, {1}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Received inputs with inconsistent rank, at my_concat"); |
| } |
| |
| struct TestParams { |
| std::vector<std::vector<int>> input_shapes; |
| std::vector<std::vector<int>> input_values; |
| int axis; |
| std::vector<int> expected_output_dims; |
| std::vector<int> expected_output; |
| Status conversion_status; |
| Status run_status; |
| bool input_as_weight; |
| }; |
| |
| const std::vector<std::vector<int>> common_input{CreateVectorIota<int>(6), |
| CreateVectorIota<int>(6, 6)}; |
| |
| std::vector<TestParams> params = { |
| { |
| /*input_shapes=*/{{1, 1, 2, 3}, {1, 1, 2, 3}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1, 2, 2, 3}, |
| /*expected_output=*/CreateVectorIota<int>(12), |
| }, |
| { |
| /*input_shapes=*/{{1, 1, 2, 3}, {1, 1, 2, 3}}, |
| /*input_values=*/common_input, |
| /*axis=*/2, |
| /*expected_output_dims=*/{1, 1, 4, 3}, |
| /*expected_output=*/CreateVectorIota<int>(12), |
| }, |
| { |
| /*input_shapes=*/{{1, 1, 2, 3}, {1, 1, 2, 3}}, |
| /*input_values=*/common_input, |
| /*axis=*/3, |
| /*expected_output_dims=*/{1, 1, 2, 6}, |
| /*expected_output=*/ |
| {0, 1, 2, 6, 7, 8, 3, 4, 5, 9, 10, 11}, |
| }, |
| { |
| /*input_shapes=*/{{1, 1}, {1, 2}, {1, 3}, {1, 1}, {1, 1}, {1, 2}}, |
| /*input_values=*/ |
| {{1}, {2, 3}, {4, 5, 6}, {7}, {8}, {9, 10}}, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1, 10}, |
| /*expected_output=*/ |
| CreateVectorIota<int>(10, /*start_value=*/1), |
| }, |
| { |
| // An input is a weight |
| /*input_shapes=*/{{1, 1, 2, 3}, {1, 1, 2, 3}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1, 2, 2, 3}, |
| /*expected_output=*/CreateVectorIota<int>(12), |
| /*conversion_status=*/ |
| errors::Unimplemented("The input \"values_1\" for ConcatV2 " |
| "must be a tensor, at my_concat"), |
| /*run_status=*/Status::OK(), |
| /*input_as_weight=*/true, |
| }, |
| { |
| // Axis is batch dimension, should fail in implicit batch mode. |
| /*input_shapes=*/{{1, 1, 2, 3}, {1, 1, 2, 3}}, |
| /*input_values=*/common_input, |
| /*axis=*/0, |
| /*expected_output_dims=*/{2, 1, 2, 3}, |
| /*expected_output=*/CreateVectorIota<int>(12), |
| /*conversion_status=*/trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Unimplemented( |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_concat") |
| : Status::OK(), |
| }, |
| { |
| // Inconsistent input shape, runtime error in dynamic shape mode. |
| /*input_shapes=*/{{1, 1, 2, 3}, {1, 1, 3, 2}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{2, 1, 2, 3}, |
| /*expected_output=*/CreateVectorIota<int>(12), |
| trt_mode_ != TrtTestMode::kDynamicShape |
| ? errors::InvalidArgument( |
| "Received inputs with inconsistent shape, at my_concat") |
| : Status::OK(), |
| errors::InvalidArgument(""), |
| }}; |
| |
| for (auto p : params) { |
| Reset(); |
| const int num_inputs = p.input_shapes.size(); |
| EXPECT_EQ(num_inputs, p.input_values.size()); |
| NodeDef node_def = get_concat_nodedef(tf_type_, num_inputs); |
| // Create inputs. |
| for (int j = 0; j < num_inputs; ++j) { |
| string name = StrCat("values_", j); |
| if (j == 1 && p.input_as_weight) { |
| AddTestWeights(name, p.input_shapes[j], p.input_values[j], tf_type_); |
| } else { |
| AddTestTensor(name, p.input_shapes[j], p.input_values[j]); |
| } |
| } |
| AddTestWeights<int32>("axis", {1}, {p.axis}); |
| |
| TestOpConverter("my_concat", node_def, p.expected_output_dims, |
| p.conversion_status, p.run_status, |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| // Get the NodeDef for Split. |
| auto get_split_nodedef = [](DataType dtype, int num_split) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto axis = ops::Placeholder(s.WithOpName("axis"), DT_INT32); |
| auto value = ops::Placeholder(s.WithOpName("value"), dtype); |
| auto split = ops::Split(s.WithOpName("my_split"), axis, value, num_split); |
| return split.operation.node()->def(); |
| }; |
| |
| template <DataType dtype> |
| void TestConvertSplit(OpConverterTest* test) { |
| typedef typename EnumToDataType<dtype>::Type CType; |
| |
| struct TestParams { |
| std::vector<int> input_shape; |
| std::vector<CType> value; |
| int axis; |
| int num_split; |
| std::vector<int> expected_output_dims; |
| std::vector<std::vector<CType>> expected_outputs; |
| }; |
| |
| const std::vector<CType> common_input = CreateVectorIota<CType>(6); |
| std::vector<TestParams> ok_params = { |
| // Identity (num_split = 1) |
| {/*input_shape=*/{1, 2, 3}, /*value=*/common_input, /*axis=*/1, |
| /*num_split=*/1, /*expected_output_dims=*/{1, 2, 3}, |
| /*expected_outputs=*/{CreateVectorIota<CType>(6)}}, |
| {/*input_shape=*/{1, 2, 3}, |
| /*value=*/common_input, |
| /*axis=*/3, |
| /*num_split=*/3, |
| /*expected_output_dims=*/{1, 2, 1}, |
| /*expected_outputs=*/ |
| {{CType(0), CType(3)}, {CType(1), CType(4)}, {CType(2), CType(5)}}}, |
| {/*input_shape=*/{1, 6}, |
| /*value=*/common_input, |
| /*axis=*/2, |
| /*num_split=*/6, |
| /*expected_output_dims=*/{1, 1}, |
| /*expected_outputs=*/ |
| {{CType(0)}, |
| {CType(1)}, |
| {CType(2)}, |
| {CType(3)}, |
| {CType(4)}, |
| {CType(5)}}}, |
| {/*input_shape=*/{1, 6}, |
| /*value=*/common_input, |
| /*axis=*/-1, |
| /*num_split=*/2, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_outputs=*/ |
| {CreateVectorIota<CType>(3), CreateVectorIota<CType>(3, CType(3))}}, |
| }; |
| |
| for (int i = 0; i < ok_params.size(); ++i) { |
| test->Reset(); |
| NodeDef node_def = get_split_nodedef(dtype, ok_params[i].num_split); |
| // Create inputs. |
| test->AddTestWeights<int32>("axis", {1}, {ok_params[i].axis}); |
| nvinfer1::DataType trt_type; |
| TF_ASSERT_OK(TfTypeToTrtType(dtype, &trt_type)); |
| test->AddTestTensor("value", ok_params[i].input_shape, 1, trt_type); |
| // Convert. |
| test->RunValidationAndConversion(node_def); |
| |
| // Get output tensors and verify output dims. |
| EXPECT_EQ(ok_params[i].expected_outputs.size(), ok_params[i].num_split); |
| std::vector<TRT_TensorOrWeights> outputs(ok_params[i].num_split); |
| DataVec output_data; |
| for (int j = 0; j < outputs.size(); ++j) { |
| const string name = j == 0 ? StrCat("my_split") : StrCat("my_split:", j); |
| TF_EXPECT_OK(test->GetTensorOrWeights(name, &outputs[j])); |
| EXPECT_TRUE(outputs[j].is_tensor()); |
| EXPECT_THAT(outputs[j].tensor()->getDimensions(), |
| DimsAreArray(ok_params[i].expected_output_dims)); |
| // Create buffer to store output. |
| output_data.push_back( |
| {name, test->ConstructTensor<CType>( |
| ok_params[i].expected_outputs[j].size())}); |
| } |
| |
| // Verify output values are correct. |
| const DataVec input_data{ |
| {"value", test->AsTensor<CType>(ok_params[i].value)}}; |
| TF_EXPECT_OK(test->BuildAndRun(input_data, &output_data)); |
| for (int j = 0; j < outputs.size(); ++j) { |
| EXPECT_THAT(GetSpanForData<CType>(output_data[j]), |
| ElementsAreArray(ok_params[i].expected_outputs[j])); |
| } |
| } |
| } |
| |
| TEST_F(OpConverterTest, ConvertSplit) { |
| { |
| // Axis is a tensor, should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 1); |
| AddTestTensor("axis", {1}); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"axis\" for Split must be a constant, at my_split"); |
| } |
| { |
| // Axis is out of bounds, should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 1); |
| AddTestWeights<int32>("axis", {1}, {4}); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Axis value of 4 is out of bounds, must be in " |
| "range [-4, 4), at my_split"); |
| } |
| { |
| // Axis is out of bounds (negative), should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 1); |
| AddTestWeights<int32>("axis", {1}, {-5}); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Axis value of -5 is out of bounds, must be in " |
| "range [-4, 4), at my_split"); |
| } |
| { |
| // Axis is batch dimension, should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 1); |
| AddTestWeights<int32>("axis", {1}, {0}); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the " |
| "batch dimension, at my_split"); |
| } |
| { |
| // Value is a weight, should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 1); |
| AddTestWeights<int32>("axis", {1}, {1}); |
| AddTestWeights<float>("value", {1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"value\" for Split must be a tensor, at my_split"); |
| } |
| { |
| // Dim is not evenly divisibly by num_split, should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 2); |
| AddTestWeights<int32>("axis", {1}, {3}); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Dimension 3 of size 3 is not evenly divisible by 2, at my_split"); |
| } |
| { |
| // num_split > dim size, should fail. |
| Reset(); |
| NodeDef node_def = get_split_nodedef(DT_FLOAT, 4); |
| AddTestWeights<int32>("axis", {1}, {3}); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Dimension 3 of size 3 is not evenly divisible by 4, at my_split"); |
| } |
| |
| TestConvertSplit<DT_FLOAT>(this); |
| TestConvertSplit<DT_HALF>(this); |
| TestConvertSplit<DT_INT32>(this); |
| } |
| |
| // Get the NodeDef for Unpack (Unstack in TF API). |
| auto get_unpack_nodedef = [](DataType dtype, int num, int axis) -> NodeDef { |
| Scope s = Scope::NewRootScope(); |
| auto value = ops::Placeholder(s.WithOpName("value"), dtype); |
| auto unstack_attrs = ops::Unstack::Axis(axis); |
| auto unstack = |
| ops::Unstack(s.WithOpName("my_unpack"), value, num, unstack_attrs); |
| return unstack.operation.node()->def(); |
| }; |
| |
| struct UnpackTestParams { |
| std::vector<int> input_shape; |
| std::vector<float> input_value; |
| int axis; |
| int num; |
| std::vector<int> expected_output_dims; |
| std::vector<std::vector<float>> expected_outputs; |
| Status run_status; |
| }; |
| |
| void TestConvertUnpack(ParameterizedOpConverterTestBase* test, |
| UnpackTestParams& p) { |
| test->Reset(); |
| NodeDef node_def = get_unpack_nodedef(test->get_tf_type(), p.num, p.axis); |
| // Create inputs. |
| test->AddTestTensor("value", p.input_shape, test->get_tf_type(), |
| p.input_value); |
| |
| std::vector<Matcher<std::vector<float>>> matcher_vec; |
| std::vector<DataType> datatype_vec; |
| std::vector<std::vector<int>> expected_output_dims; |
| |
| for (int j = 0; j < p.expected_outputs.size(); ++j) { |
| matcher_vec.push_back(ElementsAreArray(p.expected_outputs[j])); |
| datatype_vec.push_back(test->get_tf_type()); |
| expected_output_dims.push_back(p.expected_output_dims); |
| } |
| |
| test->TestOpConverterMultiOut(/*name=*/"my_unpack", |
| /*node_def=*/node_def, |
| /*expected_output_dims=*/expected_output_dims, |
| /*expected_conversion_status=*/p.run_status, |
| /*expected_runtime_status=*/p.run_status, |
| /*matcher=*/matcher_vec, |
| /*out_tf_type=*/datatype_vec); |
| } |
| |
| // TODO: Reactivate when INT32 Segfault fixed |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertUnpack) { |
| // We need to skip error testing for Dynamic Shape mode, as it is impossible |
| // to convert Unpack in Dynamic Shape Mode. |
| if (trt_mode_ != TrtTestMode::kDynamicShape) { |
| { |
| // Value is weights, should fail. |
| Reset(); |
| NodeDef node_def = get_unpack_nodedef(tf_type_, /*num=*/3, /*axis=*/3); |
| AddTestWeights<float>("value", {1, 1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"value\" for Unpack must be a tensor, at my_unpack"); |
| } |
| { |
| // Axis is out of bounds, should fail. |
| Reset(); |
| NodeDef node_def = get_unpack_nodedef(tf_type_, /*num=*/1, /*axis=*/4); |
| AddTestTensor("value", {1, 1, 2, 3}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Axis value of 4 is out of bounds, must be in " |
| "range [-4, 4), at my_unpack"); |
| } |
| { |
| // Axis is out of bounds (negative), should fail. |
| Reset(); |
| NodeDef node_def = get_unpack_nodedef(tf_type_, /*num=*/1, /*axis=*/-5); |
| AddTestTensor("value", {1, 1, 2, 3}); |
| RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Axis value of -5 is out of bounds, must be " |
| "in range [-4, 4), at my_unpack"); |
| } |
| { |
| if (trt_mode_ != TrtTestMode::kExplicitBatch) { |
| // Axis is batch dimension, should fail. |
| Reset(); |
| NodeDef node_def = get_unpack_nodedef(tf_type_, /*num=*/1, /*axis=*/0); |
| AddTestTensor("value", {1, 2, 3}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of " |
| "the batch dimension, at my_unpack"); |
| } |
| } |
| { |
| // Dim size does not match num, should fail. |
| Reset(); |
| NodeDef node_def = get_unpack_nodedef(tf_type_, /*num=*/5, /*axis=*/2); |
| AddTestTensor("value", {1, 1, 6}); |
| RunValidationAndConversion( |
| node_def, error::INVALID_ARGUMENT, |
| "Dimension 2 has size 6 which is not equal to num of 5, at " |
| "my_unpack"); |
| } |
| { |
| // Output would be TF scalar, should fail. |
| Reset(); |
| NodeDef node_def = get_unpack_nodedef(tf_type_, /*num=*/1, /*axis=*/0); |
| AddTestTensor("value", {}, tf_type_, {}, {}, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Internal("Scalars cannot be represented in " |
| "implicit batch mode") |
| : Status::OK()); |
| |
| if (trt_mode_ == TrtTestMode::kImplicitBatch) { |
| RunValidationAndConversion( |
| node_def, error::INTERNAL, |
| "Failed to convert input value to a TRT_TensorOrWeights: Scalar " |
| "input tensor is not supported since the first dimension is " |
| "treated " |
| "as batch dimension by TRT"); |
| } else { |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Input \"value\" for Unpack must be rank 2 " |
| "or greater, at my_unpack"); |
| } |
| } |
| } |
| |
| const std::vector<float> common_input = CreateVectorIota<float>(6); |
| |
| Status run_status = trt_mode_ == TrtTestMode::kDynamicShape |
| ? errors::Unimplemented( |
| "Strided slice op not implemented for dynamic " |
| "shape input") |
| : Status::OK(); |
| |
| std::vector<UnpackTestParams> params = { |
| {/*input_shape=*/{1, 1, 2, 1, 3, 1}, |
| /*input_value=*/common_input, |
| /*axis=*/4, |
| /*num=*/3, |
| /*expected_output_dims=*/{1, 1, 2, 1, 1}, |
| /*expected_outputs=*/{{0, 3}, {1, 4}, {2, 5}}, |
| /*run_status=*/run_status}, |
| {/*input_shape=*/{1, 1, 2, 1, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/4, |
| /*num=*/3, |
| /*expected_output_dims=*/{1, 1, 2, 1}, |
| /*expected_outputs=*/{{0, 3}, {1, 4}, {2, 5}}, |
| /*run_status=*/run_status}, |
| {/*input_shape=*/{1, 1, 2, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/1, |
| /*num=*/1, |
| /*expected_output_dims=*/{1, 2, 3}, |
| /*expected_outputs=*/{CreateVectorIota<float>(6)}, |
| /*run_status=*/run_status}, |
| {/*input_shape=*/{1, 6, 1}, |
| /*input_value=*/common_input, |
| /*axis=*/-2, |
| /*num=*/6, |
| /*expected_output_dims=*/{1, 1}, |
| /*expected_outputs=*/{{0}, {1}, {2}, {3}, {4}, {5}}, |
| /*run_status=*/run_status}, |
| {/*input_shape=*/{1, 6}, |
| /*input_value=*/common_input, |
| /*axis=*/1, |
| /*num=*/6, |
| /*expected_output_dims=*/{1}, |
| /*expected_outputs=*/{{0}, {1}, {2}, {3}, {4}, {5}}, |
| /*run_status=*/run_status}, |
| }; |
| for (auto p : params) { |
| TestConvertUnpack(this, p); |
| } |
| } |
| |
| // Get the NodeDef for Pack. |
| NodeDef GetPackNodeDef(DataType dtype, int num_inputs, int axis) { |
| Scope s = Scope::NewRootScope(); |
| std::vector<Input> values; |
| for (int i = 0; i < num_inputs; ++i) { |
| const string input_name = StrCat("values_", i); |
| values.push_back(ops::Placeholder(s.WithOpName(input_name), dtype)); |
| } |
| // Pack op is renamed to Stack in APIs. |
| auto pack = |
| ops::Stack(s.WithOpName("my_pack"), absl::Span<const Input>(values), |
| ops::Stack::Axis(axis)); |
| return pack.operation.node()->def(); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertPack) { |
| struct TestParams { |
| std::vector<std::vector<int>> input_shapes; |
| std::vector<std::vector<int>> partial_input_shapes; |
| std::vector<std::vector<float>> input_values; |
| int axis; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output; |
| Status conversion_status; |
| Status runtime_status; |
| bool input_1_is_weight; |
| }; |
| |
| const std::vector<std::vector<float>> common_input{ |
| CreateVectorIota<float>(6), |
| CreateVectorIota<float>(6, /*start_value=*/6)}; |
| std::vector<TestParams> params = { |
| // Second input is weight, should fail in implicit batch mode |
| {/*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1, 2, 2, 3}, |
| /*expected_output=*/CreateVectorIota<float>(12), |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status{error::UNIMPLEMENTED, |
| "The input \"values_1\" for Pack must be a tensor, at " |
| "my_pack"} |
| : Status::OK(), |
| /*runtime_status*/ Status::OK(), |
| /*weight_input*/ true}, |
| // Axis is out of bounds, should fail. |
| { |
| /*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/-5, |
| /*expected_output_dims=*/{}, |
| /*expected_output=*/{}, |
| Status{error::INVALID_ARGUMENT, |
| "Axis value of -5 is out of bounds, must be in" |
| " range [-4, 4), at my_pack"}, |
| }, |
| // Axis is batch dimension, should fail in implicit batch mode. |
| {/*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/-4, |
| /*expected_output_dims=*/{2, 1, 2, 3}, |
| /*expected_output=*/CreateVectorIota<float>(12), |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? Status{error::UNIMPLEMENTED, |
| "TensorRT does not allow manipulation of the batch " |
| "dimension, at my_pack"} |
| : Status::OK()}, |
| // Inconsistent rank, should fail. |
| { |
| /*input_shapes=*/{{1, 2, 3}, {1, 6}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{}, |
| /*expected_output=*/{}, |
| Status{error::INVALID_ARGUMENT, |
| "Received inputs with inconsistent rank, at my_pack"}, |
| }, |
| { |
| /*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1, 2, 2, 3}, |
| /*expected_output=*/CreateVectorIota<float>(12), |
| }, |
| { |
| /*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/2, |
| /*expected_output_dims=*/{1, 2, 2, 3}, |
| /*expected_output=*/ |
| {0, 1, 2, 6, 7, 8, 3, 4, 5, 9, 10, 11}, |
| }, |
| { |
| /*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/3, |
| /*expected_output_dims=*/{1, 2, 3, 2}, |
| /*expected_output=*/ |
| {0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11}, |
| }, |
| { |
| /*input_shapes=*/{{1, 2, 3}}, |
| /*partial_input_shapes=*/{{}}, |
| /*input_values=*/{CreateVectorIota<float>(6)}, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/CreateVectorIota<float>(6), |
| }, |
| { |
| /*input_shapes=*/{{1, 2, 3}}, |
| /*partial_input_shapes=*/{{}}, |
| /*input_values=*/{CreateVectorIota<float>(6)}, |
| /*axis=*/2, |
| /*expected_output_dims=*/{1, 2, 1, 3}, |
| /*expected_output=*/CreateVectorIota<float>(6), |
| }, |
| }; |
| // Inputs have inconsistent shapes, should fail. |
| if (trt_mode_ != TrtTestMode::kDynamicShape) { |
| params.push_back(TestParams{ |
| /*input_shapes=*/{{1, 2, 3}, {1, 3, 2}}, |
| /*partial_input_shapes=*/{{}, {}}, |
| /*input_values=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{}, |
| /*expected_output=*/CreateVectorIota<float>(12), |
| Status{error::INVALID_ARGUMENT, |
| "Received inputs with inconsistent shape, at my_pack"}}); |
| } else { |
| // In dynamic shape mode we cannot catch inconsistent shapes at conversion |
| // time, only during runtime. But TensorRT does not raise a proper runtime |
| // error, instead it aborts the program with the following message: |
| // Assertion failed: t->start.d[i] + t->extent.d[i] <= r.dims.d[i] |
| // ../builder/cudnnBuilderGraph.cpp:862 |
| // Aborting... |
| // TODO(tfeher) Add dynamic shapes test once TRT handles shape error |
| // decently |
| } |
| if (trt_mode_ == TrtTestMode::kDynamicShape) { |
| // Test with mixed dynamic / static shape input tensors |
| params.push_back( |
| TestParams{/*input_shapes=*/{{1, 2, 3}, {1, 2, 3}}, |
| /*partial_input_shapes=*/{{-1, -1, -1}, {1, 2, 3}}, |
| /*input_values=*/common_input, |
| /*axis=*/2, |
| /*expected_output_dims=*/{1, 2, 2, 3}, |
| /*expected_output=*/ |
| {0, 1, 2, 6, 7, 8, 3, 4, 5, 9, 10, 11}}); |
| } |
| for (auto p : params) { |
| Reset(); |
| const int num_inputs = p.input_shapes.size(); |
| EXPECT_EQ(num_inputs, p.input_values.size()); |
| |
| NodeDef node_def = GetPackNodeDef(tf_type_, num_inputs, p.axis); |
| // Create inputs. |
| for (int j = 0; j < num_inputs; ++j) { |
| if (j == 1 && p.input_1_is_weight) { |
| AddTestWeights(StrCat("values_", j), p.input_shapes[j], |
| p.input_values[j], tf_type_); |
| } else { |
| AddTestTensor(StrCat("values_", j), p.input_shapes[j], tf_type_, |
| p.input_values[j], p.partial_input_shapes[j]); |
| } |
| } |
| TestOpConverter("my_pack", node_def, p.expected_output_dims, |
| p.conversion_status, p.runtime_status, |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| // Get the NodeDef for ArgMin or ArgMax. |
| template <typename OpType> |
| NodeDef GetArgMinMaxNodeDef(DataType input_dtype, DataType output_dtype) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), input_dtype); |
| auto dimension = ops::Placeholder(s.WithOpName("dimension"), DT_INT32); |
| auto attrs = OpType::OutputType(output_dtype); |
| auto arg = OpType(s.WithOpName("my_arg"), input, dimension, attrs); |
| return arg.operation.node()->def(); |
| } |
| |
| struct ArgMinMaxTestParams { |
| std::vector<int> input_shape; |
| std::vector<float> input_value; |
| int axis; |
| std::vector<int> expected_output_dims; |
| std::vector<int> expected_argmax_output; |
| std::vector<int> expected_argmin_output; |
| Status status; |
| }; |
| |
| template <typename OpType> |
| void TestConvertArgMinMax(ParameterizedOpConverterTestBase* test, |
| DataType _tf_type, ArgMinMaxTestParams& p) { |
| test->Reset(); |
| |
| NodeDef node_def = GetArgMinMaxNodeDef<OpType>(_tf_type, |
| /*output_dtype=*/DT_INT32); |
| |
| std::vector<int> expected_out; |
| if (node_def.op() == "ArgMax") { |
| expected_out = p.expected_argmax_output; |
| } else if (node_def.op() == "ArgMin") { |
| expected_out = p.expected_argmin_output; |
| } else { |
| ASSERT_TRUE(false); |
| } |
| |
| test->AddTestTensor("input", p.input_shape, _tf_type, p.input_value); |
| test->AddTestWeights("dimension", {1}, {p.axis}, DT_INT32); |
| |
| test->TestOpConverter("my_arg", node_def, p.expected_output_dims, |
| /*expected_conversion_status=*/p.status, |
| /*expected_runtime_status=*/Status::OK(), |
| /*matcher=*/ElementsAreArray(expected_out), {DT_INT32}); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertArgMinMax) { |
| { |
| // Dimension is a tensor, should fail. |
| Reset(); |
| NodeDef node_def = |
| GetArgMinMaxNodeDef<ops::ArgMax>(tf_type_, |
| /*output_dtype=*/DT_INT32); |
| AddTestTensor("input", {1, 2, 3}); |
| AddTestTensor("dimension", {1}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"dimension\" for ArgMax must be a constant, at my_arg"); |
| } |
| { |
| // Output type is INT64, should fail. |
| Reset(); |
| NodeDef node_def = |
| GetArgMinMaxNodeDef<ops::ArgMax>(tf_type_, |
| /*output_dtype=*/DT_INT64); |
| AddTestTensor("input", {1, 2, 3}); |
| AddTestWeights("dimension", {1}, {3}, DT_INT32); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "Output type int64 is not supported, at my_arg"); |
| } |
| |
| const std::vector<float> common_input = CreateVectorIota<float>(6); |
| std::vector<ArgMinMaxTestParams> params = { |
| {/*input_shape=*/{2, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/0, |
| /*expected_output_dims=*/{3}, |
| /*expected_argmax_output=*/{1, 1, 1}, |
| /*expected_argmin_output=*/{0, 0, 0}, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::Unimplemented("TensorRT does not allow manipulation of " |
| "the batch dimension, at my_arg") |
| : Status::OK()}, |
| { |
| /*input_shape=*/{1, 6}, |
| /*input_value=*/common_input, |
| /*axis=*/1, |
| /*expected_output_dims=*/{1}, |
| /*expected_argmax_output=*/{5}, |
| /*expected_argmin_output=*/{0}, |
| }, |
| { |
| /*input_shape=*/{1, 10}, |
| /*input_value=*/ |
| {-5.0f, 3.0f, 5.0f, 1.0f, 6.0f, -9.0f, 7.0f, 1.0f, 0.0f, -1.0f}, |
| /*axis=*/-1, |
| /*expected_output_dims=*/{1}, |
| /*expected_argmax_output=*/{6}, |
| /*expected_argmin_output=*/{5}, |
| }, |
| { |
| /*input_shape=*/{1, 2, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/2, |
| /*expected_output_dims=*/{1, 2}, |
| /*expected_argmax_output=*/{2, 2}, |
| /*expected_argmin_output=*/{0, 0}, |
| }, |
| { |
| /*input_shape=*/{1, 2, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/-2, |
| /*expected_output_dims=*/{1, 3}, |
| /*expected_argmax_output=*/{1, 1, 1}, |
| /*expected_argmin_output=*/{0, 0, 0}, |
| }, |
| { |
| /*input_shape=*/{1, 2, 1, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/3, |
| /*expected_output_dims=*/{1, 2, 1}, |
| /*expected_argmax_output=*/{2, 2}, |
| /*expected_argmin_output=*/{0, 0}, |
| }, |
| { |
| /*input_shape=*/{1, 2, 1, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/-3, |
| /*expected_output_dims=*/{1, 1, 3}, |
| /*expected_argmax_output=*/{1, 1, 1}, |
| /*expected_argmin_output=*/{0, 0, 0}, |
| }, |
| {/*input_shape=*/{1, 2, 1, 1, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/4, |
| /*expected_output_dims=*/{1, 2, 1, 1}, |
| /*expected_argmax_output=*/{2, 2}, |
| /*expected_argmin_output=*/{0, 0}, |
| #if !IS_TRT_VERSION_GE(7, 0, 0, 11) |
| errors::Unimplemented("op is not able to support tensors with 4+" |
| " dimensions (excluding batch size)") |
| #else |
| Status::OK() |
| #endif |
| }, |
| {/*input_shape=*/{1, 2, 1, 1, 3}, |
| /*input_value=*/common_input, |
| /*axis=*/-4, |
| /*expected_output_dims=*/{1, 1, 1, 3}, |
| /*expected_argmax_output=*/{1, 1, 1}, |
| /*expected_argmin_output=*/{0, 0, 0}, |
| #if !IS_TRT_VERSION_GE(7, 0, 0, 11) |
| errors::Unimplemented("op is not able to support tensors with 4+" |
| " dimensions (excluding batch size)") |
| #else |
| Status::OK() |
| #endif |
| }, |
| }; |
| |
| for (auto p : params) { |
| TestConvertArgMinMax<ops::ArgMin>(this, tf_type_, p); |
| TestConvertArgMinMax<ops::ArgMax>(this, tf_type_, p); |
| } |
| } |
| |
| // Get the NodeDef for DepthToSpace or SpaceToSpace. |
| template <typename OpType> |
| NodeDef GetDepthSpaceShuffleNodeDef(DataType dtype, int block_size, |
| string data_format) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), dtype); |
| auto attrs = OpType::DataFormat(data_format); |
| auto shuffle = OpType(s.WithOpName("my_shuffle"), input, block_size, attrs); |
| return shuffle.operation.node()->def(); |
| } |
| |
| struct DepthSpaceShuffleTestParams { |
| std::vector<int> input_dims; |
| std::vector<int> input_value; |
| int block_size; |
| string data_format; |
| std::vector<int> expected_output_dims; |
| std::vector<int> expected_output; |
| }; |
| |
| template <typename OpType> |
| void TestConvertDepthSpaceShuffle( |
| ParameterizedOpConverterTestBase* test, |
| const std::vector<DepthSpaceShuffleTestParams>& params) { |
| Status status = Status::OK(); |
| |
| { |
| // Input is a weight, should fail. |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<ops::DepthToSpace>( |
| test->get_tf_type(), 2, "NCHW"); |
| test->AddTestWeights<float>("input", {1, 4, 1, 1}, {1, 2, 3, 4}); |
| test->RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| StrCat("The input \"input\" for ", node_def.op(), |
| " must be a tensor, at my_shuffle")); |
| } |
| { |
| // Input rank != 4 |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<ops::DepthToSpace>( |
| test->get_tf_type(), 2, "NCHW"); |
| test->AddTestTensor("input", {1, 16, 32}); |
| test->RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| StrCat("The input to ", node_def.op(), |
| " must be rank 4, at " |
| "my_shuffle")); |
| } |
| { |
| // Unsupported format, should fail. |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<ops::DepthToSpace>( |
| test->get_tf_type(), 2, "NCHW_VECT_C"); |
| test->AddTestTensor("input", {1, 16, 32, 32}); |
| test->RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "Data format NCHW_VECT_C is not supported, at my_shuffle"); |
| } |
| if (test->get_trt_mode() != TrtTestMode::kDynamicShape) { |
| // In dynamic shape mode, we cannot check input dimension values at |
| // conversion time therefore we cannot confirm block_size vs input dim |
| // consistency. We rely on the user to provide a valid TF graph. Otherwise |
| // TRT will fail with a runtime error. |
| if (std::is_same<OpType, ops::DepthToSpace>::value) { |
| // Channels not divisible by block_size, should fail. |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<ops::DepthToSpace>( |
| test->get_tf_type(), 3, "NCHW"); |
| test->AddTestTensor("input", {1, 16, 32, 32}); |
| test->RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Number of channels must be divisible by" |
| " block_size*block_size, at my_shuffle"); |
| } else { |
| { // Width not divisible by block_size, should fail. |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<ops::SpaceToDepth>( |
| test->get_tf_type(), 3, "NCHW"); |
| test->AddTestTensor("input", {1, 16, 9, 32}); |
| test->RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Width and height must be divisible by" |
| " block_size, at my_shuffle"); |
| } |
| { |
| // Height not divisible by block_size, should fail. |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<ops::SpaceToDepth>( |
| test->get_tf_type(), 3, "NCHW"); |
| test->AddTestTensor("input", {1, 16, 32, 9}); |
| test->RunValidationAndConversion(node_def, error::INVALID_ARGUMENT, |
| "Width and height must be divisible by" |
| " block_size, at my_shuffle"); |
| } |
| } |
| } |
| |
| for (auto p : params) { |
| test->Reset(); |
| NodeDef node_def = GetDepthSpaceShuffleNodeDef<OpType>( |
| test->get_tf_type(), p.block_size, p.data_format); |
| test->AddTestTensor("input", p.input_dims, p.input_value); |
| test->TestOpConverter("my_shuffle", node_def, p.expected_output_dims, |
| status, Status::OK(), |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertDepthToSpace) { |
| const std::vector<int> common_input = CreateVectorIota<int>(16); |
| std::vector<DepthSpaceShuffleTestParams> params = { |
| { |
| /*input_shape=*/{1, 4, 2, 2}, |
| /*input_value=*/common_input, |
| /*block_size=*/2, |
| /*data_format=*/"NCHW", |
| /*expected_output_dims=*/{1, 1, 4, 4}, |
| /*expected_output=*/ |
| {0, 4, 1, 5, 8, 12, 9, 13, 2, 6, 3, 7, 10, 14, 11, 15}, |
| }, |
| { |
| /*input_shape=*/{1, 2, 2, 4}, |
| /*input_value=*/common_input, |
| /*block_size=*/2, |
| /*data_format=*/"NHWC", |
| /*expected_output_dims=*/{1, 4, 4, 1}, |
| /*expected_output=*/ |
| {0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15}, |
| }, |
| { |
| /*input_shape=*/{1, 16, 1, 1}, |
| /*input_value=*/common_input, |
| /*block_size=*/4, |
| /*data_format=*/"NCHW", |
| /*expected_output_dims=*/{1, 1, 4, 4}, |
| /*expected_output=*/CreateVectorIota<int>(16), |
| }, |
| { |
| /*input_shape=*/{1, 2, 2, 8}, |
| /*input_value=*/CreateVectorIota<int>(32), |
| /*block_size=*/2, |
| /*data_format=*/"NHWC", |
| /*expected_output_dims=*/{1, 4, 4, 2}, |
| /*expected_output=*/{0, 1, 2, 3, 8, 9, 10, 11, 4, 5, 6, |
| 7, 12, 13, 14, 15, 16, 17, 18, 19, 24, 25, |
| 26, 27, 20, 21, 22, 23, 28, 29, 30, 31}, |
| }}; |
| |
| TestConvertDepthSpaceShuffle<ops::DepthToSpace>(this, params); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_INT32_Test, ConvertSpaceToDepth) { |
| const std::vector<int> common_input = CreateVectorIota<int>(16); |
| std::vector<DepthSpaceShuffleTestParams> params = { |
| { |
| /*input_shape=*/{1, 1, 4, 4}, |
| /*input_value=*/common_input, |
| /*block_size=*/2, |
| /*data_format=*/"NCHW", |
| /*expected_output_dims=*/{1, 4, 2, 2}, |
| /*expected_output=*/ |
| {0, 2, 8, 10, 1, 3, 9, 11, 4, 6, 12, 14, 5, 7, 13, 15}, |
| }, |
| { |
| /*input_shape=*/{1, 4, 4, 1}, |
| /*input_value=*/common_input, |
| /*block_size=*/2, |
| /*data_format=*/"NHWC", |
| /*expected_output_dims=*/{1, 2, 2, 4}, |
| /*expected_output=*/ |
| {0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15}, |
| }, |
| { |
| /*input_shape=*/{1, 1, 4, 4}, |
| /*input_value=*/common_input, |
| /*block_size=*/4, |
| /*data_format=*/"NCHW", |
| /*expected_output_dims=*/{1, 16, 1, 1}, |
| /*expected_output=*/CreateVectorIota<int>(16), |
| }, |
| { |
| /*input_shape=*/{1, 4, 4, 2}, |
| /*input_value=*/CreateVectorIota<int>(32), |
| /*block_size=*/2, |
| /*data_format=*/"NHWC", |
| /*expected_output_dims=*/{1, 2, 2, 8}, |
| /*expected_output=*/{0, 1, 2, 3, 8, 9, 10, 11, 4, 5, 6, |
| 7, 12, 13, 14, 15, 16, 17, 18, 19, 24, 25, |
| 26, 27, 20, 21, 22, 23, 28, 29, 30, 31}, |
| }, |
| }; |
| TestConvertDepthSpaceShuffle<ops::SpaceToDepth>(this, params); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertClipByValue) { |
| Scope s = Scope::NewRootScope(); |
| auto t = ops::Placeholder(s.WithOpName("t"), tf_type_); |
| auto clip_value_min = |
| ops::Placeholder(s.WithOpName("clip_value_min"), tf_type_); |
| auto clip_value_max = |
| ops::Placeholder(s.WithOpName("clip_value_max"), tf_type_); |
| auto clip = ops::ClipByValue(s.WithOpName("my_clip"), t, clip_value_min, |
| clip_value_max); |
| const NodeDef& node_def = clip.operation.node()->def(); |
| |
| nvinfer1::DataType trt_type_; |
| TF_ASSERT_OK(TfTypeToTrtType(tf_type_, &trt_type_)); |
| |
| { |
| // Input is a weight, should fail. |
| Reset(); |
| AddTestWeights("t", {1, 2, 3}, {1, 2, 3, 4, 5, 6}, tf_type_); |
| AddTestWeights("clip_value_min", {1}, {1}, tf_type_); |
| AddTestWeights("clip_value_max", {1}, {5}, tf_type_); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"t\" for ClipByValue must be a " |
| "tensor, at my_clip"); |
| } |
| { |
| // Clip min is a tensor, should fail. |
| Reset(); |
| AddTestTensor("t", {1, 2, 3}); |
| AddTestTensor("clip_value_min", {1}); |
| AddTestWeights("clip_value_max", {1}, {1}, tf_type_); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"clip_value_min\" for ClipByValue " |
| "must be a constant, at my_clip"); |
| } |
| { |
| // Clip max is a tensor, should fail. |
| Reset(); |
| AddTestTensor("t", {1, 2, 3}); |
| AddTestWeights("clip_value_min", {1}, {1}, tf_type_); |
| AddTestTensor("clip_value_max", {1}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"clip_value_max\" for ClipByValue " |
| "must be a constant, at my_clip"); |
| } |
| |
| struct TestParams { |
| std::vector<int> dims; |
| int clip_value_min; |
| int clip_value_max; |
| std::vector<float> expected_output; |
| }; |
| |
| const std::vector<float> common_input = CreateVectorIota<float>(6); |
| |
| std::vector<TestParams> params = {{ |
| /*dims=*/{6}, |
| /*clip_value_min=*/2, |
| /*clip_value_max=*/4, |
| /*expected_output=*/{2, 2, 2, 3, 4, 4}, |
| }, |
| { |
| /*dims=*/{1, 6}, |
| /*clip_value_min=*/2, |
| /*clip_value_max=*/4, |
| /*expected_output=*/{2, 2, 2, 3, 4, 4}, |
| }, |
| { |
| /*dims=*/{1, 2, 3}, |
| /*clip_value_min=*/2, |
| /*clip_value_max=*/4, |
| /*expected_output=*/{2, 2, 2, 3, 4, 4}, |
| }, |
| { |
| /*dims=*/{1, 2, 3, 1}, |
| /*clip_value_min=*/2, |
| /*clip_value_max=*/4, |
| /*expected_output=*/{2, 2, 2, 3, 4, 4}, |
| }, |
| { |
| /*dims=*/{1, 1, 3, 1, 2}, |
| /*clip_value_min=*/2, |
| /*clip_value_max=*/4, |
| /*expected_output=*/{2, 2, 2, 3, 4, 4}, |
| }, |
| { |
| /*dims=*/{1, 1, 3, 1, 2, 1}, |
| /*clip_value_min=*/2, |
| /*clip_value_max=*/4, |
| /*expected_output=*/{2, 2, 2, 3, 4, 4}, |
| }, |
| { |
| /*dims=*/{2, 1, 3}, |
| /*clip_value_min=*/-1, |
| /*clip_value_max=*/8, |
| /*expected_output=*/common_input, |
| }}; |
| |
| for (auto p : params) { |
| Reset(); |
| |
| AddTestTensor("t", p.dims, tf_type_, common_input); |
| AddTestWeights("clip_value_min", {1}, {p.clip_value_min}, tf_type_); |
| AddTestWeights("clip_value_max", {1}, {p.clip_value_max}, tf_type_); |
| |
| TestOpConverter("my_clip", node_def, p.dims, |
| /*expected_conversion_status=*/Status::OK(), |
| /*expected_runtime_status=*/Status::OK(), |
| /*matcher=*/ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| // Get the NodeDef for SquaredDifference. |
| NodeDef GetSquaredDifferenceNodeDef(DataType dtype) { |
| Scope s = Scope::NewRootScope(); |
| auto x = ops::Placeholder(s.WithOpName("x"), dtype); |
| auto y = ops::Placeholder(s.WithOpName("y"), dtype); |
| auto squared_diff = |
| ops::SquaredDifference(s.WithOpName("my_squared_diff"), x, y); |
| return squared_diff.operation.node()->def(); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertSquaredDifference) { |
| { |
| // Input is a weight, should fail. |
| Reset(); |
| NodeDef node_def = GetSquaredDifferenceNodeDef(tf_type_); |
| AddTestWeights<float>("x", {1, 2, 3}, {1, 2, 3, 4, 5, 6}); |
| AddTestTensor("y", {1, 1, 2, 3}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"x\" for SquaredDifference must be " |
| "a tensor, at my_squared_diff"); |
| } |
| |
| struct TestParams { |
| std::vector<int> dims_x; |
| std::vector<int> dims_y; |
| std::vector<float> value_x; |
| std::vector<float> value_y; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output; |
| Status status; |
| Status runtime_status; |
| }; |
| |
| const std::vector<float> common_input = CreateVectorIota<float>(6); |
| std::vector<TestParams> params = { |
| {/*dims_x=*/{1, 2, 3}, |
| /*dims_y=*/{1, 7, 5}, |
| /*value_x=*/common_input, |
| /*value_y=*/std::vector<float>(7 * 5, 0), |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/common_input, |
| trt_mode_ == TrtTestMode::kDynamicShape |
| ? Status::OK() |
| : errors::InvalidArgument("Infeasible broadcast scheme"), |
| errors::Internal( |
| "Binding index out of range. This can happen if profile is not set, " |
| "or the network is invalid for the current profile.")}, |
| { |
| /*dims_x=*/{1, 1, 2, 3}, |
| /*dims_y=*/{1, 1, 2, 3}, |
| /*value_x=*/common_input, |
| /*value_y=*/{0, -1, 3, 0, 10, -7}, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/{0, 4, 1, 9, 36, 144}, |
| }, |
| { |
| /*dims_x=*/{1, 1, 2, 3}, |
| /*dims_y=*/{1, 1, 1, 3}, |
| /*value_x=*/common_input, |
| /*value_y=*/{0, 1, 2}, |
| /*expected_output_dims=*/{1, 1, 2, 3}, |
| /*expected_output=*/{0, 0, 0, 9, 9, 9}, |
| }, |
| }; |
| |
| for (auto p : params) { |
| Reset(); |
| NodeDef node_def = GetSquaredDifferenceNodeDef(tf_type_); |
| AddTestTensor("x", p.dims_x, p.value_x); |
| AddTestTensor("y", p.dims_y, p.value_y); |
| TestOpConverter("my_squared_diff", node_def, p.expected_output_dims, |
| p.status, p.runtime_status, |
| ElementsAreArray(p.expected_output)); |
| } |
| } |
| |
| template <typename OpType> |
| NodeDef MakeResizeNodeDef(DataType dtype, bool align_corners) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), dtype); |
| auto size = ops::Placeholder(s.WithOpName("size"), DT_INT32); |
| auto attrs = typename OpType::Attrs().AlignCorners(align_corners); |
| auto resize = OpType(s.WithOpName("my_resize"), input, size, attrs); |
| return resize.operation.node()->def(); |
| } |
| |
| struct ResizeTestParams { |
| std::vector<int> input_dims; |
| std::vector<int> output_resize_dims; |
| std::vector<float> input_value; |
| bool size_as_tensor; |
| bool align_corners; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_nearest_output_values; |
| std::vector<float> expected_bilinear_output_values; |
| Status status; |
| }; |
| |
| template <typename OpType> |
| void TestConvertResize(ParameterizedOpConverterTestBase* test, |
| ResizeTestParams& p) { |
| test->Reset(); |
| // Create resize node. |
| NodeDef node_def = |
| MakeResizeNodeDef<OpType>(test->get_tf_type(), p.align_corners); |
| |
| test->AddTestTensor("input", p.input_dims, test->get_tf_type(), |
| p.input_value); |
| // Create output size. |
| if (p.size_as_tensor) { |
| std::vector<int32> size_dims{2}; |
| std::vector<int32> size_values{p.output_resize_dims}; |
| test->AddTestTensor("size", size_dims, DT_INT32, size_values, size_dims); |
| } else { |
| test->AddTestWeights("size", {2}, p.output_resize_dims, DT_INT32); |
| } |
| |
| std::vector<float> expected_out; |
| |
| if (node_def.op() == "ResizeBilinear") { |
| expected_out = p.expected_bilinear_output_values; |
| } else if (node_def.op() == "ResizeNearestNeighbor") { |
| expected_out = p.expected_nearest_output_values; |
| } else { |
| ASSERT_TRUE(false); |
| } |
| |
| test->TestOpConverter("my_resize", node_def, p.expected_output_dims, |
| /*expected_conversion_status=*/p.status, |
| /*expected_runtime_status=*/p.status, |
| /*matcher=*/ElementsAreArray(expected_out), |
| /*out_tf_types=*/{DT_FLOAT}); |
| } |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertResize) { |
| { |
| // First input is weight, should fail. |
| Reset(); |
| NodeDef node_def = MakeResizeNodeDef<ops::ResizeBilinear>(tf_type_, |
| /*align_corners=*/ |
| true); |
| AddTestWeights<float>("input", {1, 2}, {1, 2}); |
| AddTestWeights<int>("size", {1, 2}, {1, 2}); |
| RunValidationAndConversion( |
| node_def, error::UNIMPLEMENTED, |
| "The input \"input\" for ResizeBilinear must be a " |
| "tensor, at my_resize"); |
| } |
| |
| std::vector<ResizeTestParams> params{ |
| {/*input_dims=*/{1, 1, 2, 1}, // N, H, W, C |
| /*output_resize_dims=*/{2, 3}, // H_out, W_out |
| /*input_values=*/{2.0f, -1.0f}, |
| /*size_as_tensor=*/false, |
| /*align_corners=*/false, |
| /*expected_output_dims=*/{1, 2, 3, 1}, // N, H, W, C |
| /*expected_nearest_output_values=*/ |
| {2.0f, 2.0f, -1.0f, 2.0f, 2.0f, -1.0f}, |
| /*expected_bilinear_output_values=*/ |
| {2.0f, 0.f, -1.0f, 2.0f, 0.f, -1.0f}, |
| /*status=*/Status::OK()}, |
| {/*input_dims=*/{1, 1, 2, 1}, // N, H, W, C |
| /*output_resize_dims=*/{2, 3}, // H_out, W_out |
| /*input_values=*/{2.0f, -1.0f}, |
| /*size_as_tensor=*/false, |
| /*align_corners=*/true, |
| /*expected_output_dims=*/{1, 2, 3, 1}, // N, H, W, C |
| /*expected_nearest_output_values=*/ |
| {2.0f, 2.0f, -1.0f, 2.0f, 2.0f, -1.0f}, |
| /*expected_bilinear_output_values=*/ |
| {2.0f, 0.5f, -1.0f, 2.0f, 0.5f, -1.0f}, |
| /*status=*/Status::OK()}}; |
| |
| if (trt_mode_ != TrtTestMode::kImplicitBatch) { |
| // Size as a tensor is not supported in implicit batch mode. |
| params.push_back({/*input_dims=*/{1, 1, 2, 1}, // N, H, W, C |
| /*output_resize_dims=*/{2, 3}, // H_out, W_out |
| /*input_values=*/{2.0f, -1.0f}, |
| /*size_as_tensor=*/true, |
| /*align_corners=*/true, |
| /*expected_output_dims=*/{1, 2, 3, 1}, // N, H, W, C |
| /*expected_nearest_output_values=*/ |
| {2.0f, 2.0f, -1.0f, 2.0f, 2.0f, -1.0f}, |
| /*expected_bilinear_output_values=*/ |
| {2.0f, 0.5f, -1.0f, 2.0f, 0.5f, -1.0f}, |
| /*status=*/Status::OK()}); |
| } |
| |
| for (auto p : params) { |
| TestConvertResize<ops::ResizeNearestNeighbor>(this, p); |
| |
| // This use case is not supported as of TRT version 7.1 |
| #if IS_TRT_VERSION_GE(7, 1, 0, 0) |
| if (!p.align_corners) { |
| p.status = errors::InvalidArgument( |
| "Cannot Convert Bilinear Resize when align_corners=False"); |
| } |
| #endif |
| |
| TestConvertResize<ops::ResizeBilinear>(this, p); |
| } |
| } |
| |
| NodeDef MakePadNodeDef(std::string name, DataType dtype) { |
| Scope s = Scope::NewRootScope(); |
| auto input = ops::Placeholder(s.WithOpName("input"), dtype); |
| auto padding = ops::Placeholder(s.WithOpName("padding"), DT_INT32); |
| auto pad = ops::Pad(s.WithOpName(name), input, padding); |
| return pad.operation.node()->def(); |
| } |
| |
| struct PadTestParams { |
| std::vector<int> input_dims; |
| std::vector<int> pad_dims; |
| std::vector<int> pad_values; |
| std::vector<float> input_values; |
| std::vector<int> expected_output_dims; |
| std::vector<float> expected_output_values; |
| Status status; |
| }; |
| |
| TEST_P(OpConverter_FP32_FP16_Test, ConvertPad) { |
| { |
| // First input is weight, should fail. |
| Reset(); |
| NodeDef node_def = MakePadNodeDef("my_pad", tf_type_); |
| AddTestWeights("input", {1, 2}, {1, 2}, tf_type_); |
| AddTestWeights<int>("padding", {1, 2}, {1, 2}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"tensor\" for Pad must be a " |
| "tensor"); |
| } |
| { |
| // padding is a tensor, should fail. |
| Reset(); |
| NodeDef node_def = MakePadNodeDef("my_pad", tf_type_); |
| AddTestTensor("input", {1, 2}); |
| AddTestTensor("padding", {1, 2}); |
| RunValidationAndConversion(node_def, error::UNIMPLEMENTED, |
| "The input \"paddings\" for Pad must be a " |
| "constant"); |
| } |
| { |
| // Make sure that ranges are inferred across a Pad. |
| Reset(); |
| NodeDef node_def = MakePadNodeDef("my_pad", tf_type_); |
| AddTestTensor("input", {1, 1, 2, 1}); |
| AddTestWeights<int>("padding", {4, 2}, {0, 0, 1, 0, 0, 1, 0, 0}); |
| TRT_TensorOrWeights input; |
| TRT_TensorOrWeights output; |
| RunValidationAndConversion(node_def); |
| TF_EXPECT_OK(GetTensorOrWeights("input", &input)); |
| TF_EXPECT_OK(GetTensorOrWeights("my_pad", &output)); |
| ITensorProxyPtr input_tensor = input.tensor(); |
| converter_->ProvideQuantizationRange(&input_tensor, -5.0f, 5.0f); |
| auto ranges = quantization_ranges(); |
| EXPECT_EQ(5.0f, ranges[input.tensor()->trt_tensor()]); |
| } |
| |
| std::vector<PadTestParams> params{ |
| // 1 padding dim |
| { |
| /*input_dims=*/{1, 1, 3, 2}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 0, 0, 0, 1, 0, 0}, |
| /*input_values=*/{1, 2, 3, 4, 5, 6}, |
| /*expected_output_dims=*/{1, 1, 4, 2}, // N, H, W, C |
| /*expected_output_values=*/ |
| {1, 2, 3, 4, 5, 6, 0, 0}, |
| }, |
| { |
| /*input_dims=*/{1, 1, 3, 2}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 0, 0, 0, 0, 0, 1}, |
| /*input_values=*/{1, 2, 3, 4, 5, 6}, |
| /*expected_output_dims=*/{1, 1, 3, 3}, // N, H, W, C |
| /*expected_output_values=*/ |
| {1, 2, 0, 3, 4, 0, 5, 6, 0}, |
| }, |
| { |
| /*input_dims=*/{1, 1, 3, 2}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 1, 0, 0, 0, 0, 0}, |
| /*input_values=*/{1, 2, 3, 4, 5, 6}, |
| /*expected_output_dims=*/{1, 2, 3, 2}, // N, H, W, C |
| /*expected_output_values=*/ |
| {0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6}, |
| }, |
| // 2 padding dims |
| { |
| /*input_dims=*/{1, 1, 2, 1}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 1, 0, 0, 1, 0, 0}, |
| /*input_values=*/{2.0f, -1.0f}, |
| /*expected_output_dims=*/{1, 2, 3, 1}, // N, H, W, C |
| /*expected_output_values=*/ |
| {0.0, 0.0, 0.0, 2.0f, -1.0f, 0.0}, |
| }, |
| PadTestParams{ |
| /*input_dims=*/{1, 1, 2, 2}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 1, 0, 0, 1, 0, 0}, |
| /*input_values=*/{2, -1, 3., 4}, |
| /*expected_output_dims=*/{1, 2, 3, 2}, // N, H, W, C |
| /*expected_output_values=*/ |
| {0, 0, 0, 0, 0, 0, 2, -1, 3, 4, 0, 0}, |
| }, |
| PadTestParams{ |
| /*input_dims=*/{1, 1, 2, 1, 2}, // N, C, H, W, D |
| /*pad_dims=*/{5, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, |
| /*input_values=*/{2, -1, 3., 4}, |
| /*expected_output_dims=*/{1, 2, 3, 1, 2}, // N, H, W, C |
| /*expected_output_values=*/ |
| {0, 0, 0, 0, 0, 0, 2, -1, 3, 4, 0, 0}, |
| }, |
| PadTestParams{ |
| /*input_dims=*/{1, 1, 2, 1, 2}, // N, C, H, W, D |
| /*pad_dims=*/{5, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 0, 1, 0, 0, 1, 1, 0, 0}, |
| /*input_values=*/{2, -1, 3., 4}, |
| /*expected_output_dims=*/{1, 2, 2, 3, 2}, // N, H, W, C |
| /*expected_output_values=*/ |
| {0., 0., 2., -1., 0., 0., 0., 0., 3., 4., 0., 0., |
| 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0}, |
| }, |
| PadTestParams{ |
| /*input_dims=*/{1, 1, 2, 1}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {1, 0, 0, 0, 0, 1, 0, 0}, |
| /*input_values=*/{2.0f, -1.0f}, |
| /*expected_output_dims=*/{2, 1, 3, 1}, // N, H, W, C |
| /*expected_output_values=*/{0.0, 0.0, 0.0, 2.0f, -1.0f, 0.0}, |
| trt_mode_ == TrtTestMode::kImplicitBatch |
| ? errors::InvalidArgument("Padding layer does not support " |
| "padding on batch dimension") |
| : Status::OK()}, |
| PadTestParams{ |
| /*input_dims=*/{1, 1, 2, 1}, // N, H, W, C |
| /*pad_dims=*/{4, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 1, 0, 0, 1, 1, 1}, |
| /*input_values=*/{2.0f, -1.0f}, |
| /*expected_output_dims=*/{}, // N, H, W, C |
| /*expected_output_values=*/{}, |
| errors::InvalidArgument("Padding layer does not support padding on " |
| "> 2")}, |
| PadTestParams{ |
| /*input_dims=*/{1, 2, 2}, // N, H, W |
| /*pad_dims=*/{3, 2}, // #dims, {pad_before, pad_after} |
| /*pad_values*/ {0, 0, 1, 0, 0, 1}, |
| /*input_values=*/{2, -1, 3., 4}, |
| /*expected_output_dims=*/{1, 3, 3}, // N, H, W, C |
| /*expected_output_values=*/ |
| {0., 0., 0., 2., -1., 0., 3., 4., 0.}, |
| errors::InvalidArgument("Convertpad requires at least 4D input, at " |
| "my_pad")}}; |
| |
| for (auto p : params) { |
| Reset(); |
| // Create pad node. |
| NodeDef node_def = MakePadNodeDef("my_pad", tf_type_); |
| // Create input tensor. |
| AddTestTensor("input", p.input_dims, p.input_values); |
| // Create output size. |
| AddTestWeights<int32>("padding", p.pad_dims, p.pad_values); |
| TestOpConverter("my_pad", node_def, p.expected_output_dims, p.status, |
| p.status, ElementsAreArray(p.expected_output_values)); |
| } |
| } |
| } // namespace convert |
| } // namespace tensorrt |
| } // namespace tensorflow |
| |
| #endif // GOOGLE_CUDA && GOOGLE_TENSORRT |