diff options
27 files changed, 986 insertions, 174 deletions
diff --git a/tensorflow/contrib/lite/builtin_op_data.h b/tensorflow/contrib/lite/builtin_op_data.h index 4910c89eae..35cf43dd32 100644 --- a/tensorflow/contrib/lite/builtin_op_data.h +++ b/tensorflow/contrib/lite/builtin_op_data.h @@ -162,6 +162,9 @@ typedef struct { } TfLitePadParams; typedef struct { +} TfLitePadV2Params; + +typedef struct { // TODO(ahentz): We can't have dynamic data in this struct, at least not yet. // For now we will fix the maximum possible number of dimensions. int shape[8]; diff --git a/tensorflow/contrib/lite/builtin_ops.h b/tensorflow/contrib/lite/builtin_ops.h index 962a7a8970..d66b72843a 100644 --- a/tensorflow/contrib/lite/builtin_ops.h +++ b/tensorflow/contrib/lite/builtin_ops.h @@ -85,6 +85,7 @@ typedef enum { kTfLiteBuiltinMinimum = 57, kTfLiteBuiltinLess = 58, kTfLiteBuiltinNeg = 59, + kTfLiteBuiltinPadv2 = 60, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h index 4776726972..e2a1a6996d 100644 --- a/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/optimized/optimized_ops.h @@ -5851,10 +5851,26 @@ inline void BatchToSpaceND(const T* input_data, const Dims<4>& input_dims, } template <typename T> -inline void Pad(const T* input_data, const Dims<4>& input_dims, - const std::vector<int>& left_paddings, - const std::vector<int>& right_paddings, T* output_data, - const Dims<4>& output_dims, const int32_t pad_value) { +void TypedMemset(void* ptr, T value, size_t num) { + // Optimization for common cases where memset() will suffice. + if (value == 0 || std::is_same<T, uint8_t>::value) { + memset(ptr, value, num * sizeof(T)); + } else { + // Default implementation for cases where memset() will not preserve the + // bytes, e.g., typically when sizeof(T) > sizeof(uint8_t). + char* pos = static_cast<char*>(ptr); + for (size_t i = 0; i < num; ++i) { + memcpy(pos, &value, sizeof(T)); + pos = pos + sizeof(T); + } + } +} + +template <typename T> +inline void PadV2(const T* input_data, const Dims<4>& input_dims, + const std::vector<int>& left_paddings, + const std::vector<int>& right_paddings, T* output_data, + const Dims<4>& output_dims, const T pad_value) { gemmlowp::ScopedProfilingLabel label("Pad"); TFLITE_DCHECK_EQ(left_paddings.size(), 4); TFLITE_DCHECK_EQ(right_paddings.size(), 4); @@ -5877,27 +5893,28 @@ inline void Pad(const T* input_data, const Dims<4>& input_dims, const int input_depth = ArraySize(input_dims, 0); if (left_b_padding != 0) { - memset(output_data, pad_value, - left_b_padding * output_height * output_width * output_depth * - sizeof(T)); + TypedMemset<T>( + output_data, pad_value, + left_b_padding * output_height * output_width * output_depth); } for (int out_b = left_b_padding; out_b < output_batch - right_b_padding; ++out_b) { if (left_h_padding != 0) { - memset(output_data + Offset(output_dims, 0, 0, 0, out_b), pad_value, - left_h_padding * output_width * output_depth * sizeof(T)); + TypedMemset<T>(output_data + Offset(output_dims, 0, 0, 0, out_b), + pad_value, left_h_padding * output_width * output_depth); } for (int out_h = left_h_padding; out_h < output_height - right_h_padding; ++out_h) { if (left_w_padding != 0) { - memset(output_data + Offset(output_dims, 0, 0, out_h, out_b), pad_value, - left_w_padding * output_depth * sizeof(T)); + TypedMemset<T>(output_data + Offset(output_dims, 0, 0, out_h, out_b), + pad_value, left_w_padding * output_depth); } for (int out_w = left_w_padding; out_w < output_width - right_w_padding; ++out_w) { if (left_d_padding != 0) { - memset(output_data + Offset(output_dims, 0, out_w, out_h, out_b), - pad_value, left_d_padding * sizeof(T)); + TypedMemset<T>( + output_data + Offset(output_dims, 0, out_w, out_h, out_b), + pad_value, left_d_padding); } T* out = output_data + @@ -5908,35 +5925,46 @@ inline void Pad(const T* input_data, const Dims<4>& input_dims, memcpy(out, in, input_depth * sizeof(T)); if (right_d_padding != 0) { - memset( + TypedMemset<T>( output_data + Offset(output_dims, output_depth - right_d_padding, out_w, out_h, out_b), - pad_value, right_d_padding * sizeof(T)); + pad_value, right_d_padding); } } if (right_w_padding != 0) { - memset( + TypedMemset<T>( output_data + Offset(output_dims, 0, output_width - right_w_padding, out_h, out_b), - pad_value, right_w_padding * output_depth * sizeof(T)); + pad_value, right_w_padding * output_depth); } } if (right_h_padding != 0) { - memset(output_data + Offset(output_dims, 0, 0, - output_height - right_h_padding, out_b), - pad_value, - right_h_padding * output_width * output_depth * sizeof(T)); + TypedMemset<T>( + output_data + + Offset(output_dims, 0, 0, output_height - right_h_padding, out_b), + pad_value, right_h_padding * output_width * output_depth); } } if (right_b_padding != 0) { - memset(output_data + - Offset(output_dims, 0, 0, 0, output_batch - right_b_padding), - 0, - right_b_padding * output_height * output_width * output_depth * - sizeof(T)); + TypedMemset<T>( + output_data + + Offset(output_dims, 0, 0, 0, output_batch - right_b_padding), + pad_value, + right_b_padding * output_height * output_width * output_depth); } } +// Legacy Pad() method that casts an int32_t to T before padding. +template <typename T> +inline void Pad(const T* input_data, const Dims<4>& input_dims, + const std::vector<int>& left_paddings, + const std::vector<int>& right_paddings, T* output_data, + const Dims<4>& output_dims, const int32_t pad_value) { + const T converted_pad_value = static_cast<T>(pad_value); + PadV2<T>(input_data, input_dims, left_paddings, right_paddings, output_data, + output_dims, converted_pad_value); +} + template <typename T> inline void Pad(const T* input_data, const Dims<4>& input_dims, const std::vector<int>& left_paddings, diff --git a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h index c6ed614593..05e6ca8e7e 100644 --- a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h @@ -3158,10 +3158,10 @@ inline void BatchToSpaceND(const T* input_data, const Dims<4>& input_dims, } template <typename T> -inline void Pad(const T* input_data, const Dims<4>& input_dims, - const std::vector<int>& left_paddings, - const std::vector<int>& right_paddings, T* output_data, - const Dims<4>& output_dims, const int32_t pad_value) { +inline void PadV2(const T* input_data, const Dims<4>& input_dims, + const std::vector<int>& left_paddings, + const std::vector<int>& right_paddings, T* output_data, + const Dims<4>& output_dims, const T pad_value) { TFLITE_DCHECK_EQ(left_paddings.size(), 4); TFLITE_DCHECK_EQ(right_paddings.size(), 4); @@ -3194,7 +3194,7 @@ inline void Pad(const T* input_data, const Dims<4>& input_dims, out_w >= output_width - right_w_padding || out_d < left_d_padding || out_d >= output_depth - right_d_padding) { - *out_ptr++ = static_cast<T>(pad_value); + *out_ptr++ = pad_value; } else { *out_ptr++ = *in_ptr++; } @@ -3204,6 +3204,17 @@ inline void Pad(const T* input_data, const Dims<4>& input_dims, } } +// Legacy Pad() method that casts an int32_t to T before padding. +template <typename T> +inline void Pad(const T* input_data, const Dims<4>& input_dims, + const std::vector<int>& left_paddings, + const std::vector<int>& right_paddings, T* output_data, + const Dims<4>& output_dims, const int32_t pad_value) { + const T converted_pad_value = static_cast<T>(pad_value); + PadV2<T>(input_data, input_dims, left_paddings, right_paddings, output_data, + output_dims, converted_pad_value); +} + template <typename T> inline void Pad(const T* input_data, const Dims<4>& input_dims, const std::vector<int>& left_paddings, diff --git a/tensorflow/contrib/lite/kernels/pad.cc b/tensorflow/contrib/lite/kernels/pad.cc index 4f9449a225..9e1e4658e9 100644 --- a/tensorflow/contrib/lite/kernels/pad.cc +++ b/tensorflow/contrib/lite/kernels/pad.cc @@ -37,9 +37,15 @@ struct PadContext { PadContext(TfLiteContext* context, TfLiteNode* node) { input = GetInput(context, node, 0); paddings = GetInput(context, node, 1); + if (NumInputs(node) == 3) { + constant_values = GetOptionalInputTensor(context, node, 2); + } else { + constant_values = nullptr; + } output = GetOutput(context, node, 0); dims = NumDimensions(input); } + TfLiteTensor* constant_values; TfLiteTensor* input; TfLiteTensor* paddings; TfLiteTensor* output; @@ -76,11 +82,15 @@ TfLiteStatus ResizeOutputTensor(TfLiteContext* context, } TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { - TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); + TF_LITE_ENSURE(context, NumInputs(node) == 2 || NumInputs(node) == 3); TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); PadContext op_context(context, node); TF_LITE_ENSURE_EQ(context, op_context.input->type, op_context.output->type); + if (op_context.constant_values != nullptr) { + TF_LITE_ENSURE_EQ(context, op_context.input->type, + op_context.constant_values->type); + } // TODO(nupurgarg): Our current implementations rely on the inputs being 4D. TF_LITE_ENSURE_EQ(context, op_context.dims, 4); @@ -98,6 +108,11 @@ template <KernelType kernel_type> TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { PadContext op_context(context, node); + if (op_context.constant_values != nullptr) { + // Ensure that constant_values is a scalar. + TF_LITE_ENSURE_EQ(context, NumElements(op_context.constant_values), 1); + } + // Resize the output tensor if the output tensor is dynamic. if (IsDynamicTensor(op_context.output)) { TF_LITE_ENSURE_OK(context, ResizeOutputTensor(context, &op_context)); @@ -119,48 +134,70 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { after_padding.push_back(paddings_data[idx * 2 + 1]); } -#define TF_LITE_PAD(type, scalar, pad_value) \ - type::Pad(GetTensorData<scalar>(op_context.input), \ - GetTensorDims(op_context.input), before_padding, after_padding, \ - GetTensorData<scalar>(op_context.output), \ - GetTensorDims(op_context.output), pad_value) +#define TF_LITE_PAD(type, scalar, pad_value) \ + type::PadV2(GetTensorData<scalar>(op_context.input), \ + GetTensorDims(op_context.input), before_padding, after_padding, \ + GetTensorData<scalar>(op_context.output), \ + GetTensorDims(op_context.output), pad_value) switch (op_context.input->type) { - case kTfLiteFloat32: + case kTfLiteFloat32: { + float pad_value = op_context.constant_values == nullptr + ? 0.f + : *GetTensorData<float>(op_context.constant_values); if (kernel_type == kReference) { - TF_LITE_PAD(reference_ops, float, 0); + TF_LITE_PAD(reference_ops, float, pad_value); } else if (kernel_type == kGenericOptimized) { - TF_LITE_PAD(optimized_ops, float, 0); + TF_LITE_PAD(optimized_ops, float, pad_value); + } + } break; + case kTfLiteUInt8: { + uint8_t pad_value; + if (op_context.constant_values == nullptr) { + // Quantized Pad requires that 0 is represented in the quantized + // range. + TF_LITE_ENSURE(context, op_context.output->params.zero_point >= + std::numeric_limits<uint8_t>::min()); + TF_LITE_ENSURE(context, op_context.output->params.zero_point <= + std::numeric_limits<uint8_t>::max()); + pad_value = static_cast<uint8_t>(op_context.output->params.zero_point); + } else { + // Quantized Pad requires that 'constant_values' is represented in the + // same quantized range as the input and output tensors. + TF_LITE_ENSURE_EQ(context, op_context.output->params.zero_point, + op_context.constant_values->params.zero_point); + TF_LITE_ENSURE_EQ(context, op_context.output->params.scale, + op_context.constant_values->params.scale); + pad_value = *GetTensorData<uint8_t>(op_context.constant_values); } - break; - case kTfLiteUInt8: - // Quantized Pad requires that 0 is represented in the quantized range. - TF_LITE_ENSURE(context, op_context.output->params.zero_point >= - std::numeric_limits<uint8_t>::min()); - TF_LITE_ENSURE(context, op_context.output->params.zero_point <= - std::numeric_limits<uint8_t>::max()); if (kernel_type == kReference) { - TF_LITE_PAD(reference_ops, uint8_t, - op_context.output->params.zero_point); + TF_LITE_PAD(reference_ops, uint8_t, pad_value); } else if (kernel_type == kGenericOptimized) { - TF_LITE_PAD(optimized_ops, uint8_t, - op_context.output->params.zero_point); + TF_LITE_PAD(optimized_ops, uint8_t, pad_value); } - break; - case kTfLiteInt32: + } break; + case kTfLiteInt32: { + int32_t pad_value = + op_context.constant_values == nullptr + ? 0 + : *GetTensorData<int32_t>(op_context.constant_values); if (kernel_type == kReference) { - TF_LITE_PAD(reference_ops, int32_t, 0); + TF_LITE_PAD(reference_ops, int32_t, pad_value); } else if (kernel_type == kGenericOptimized) { - TF_LITE_PAD(optimized_ops, int32_t, 0); + TF_LITE_PAD(optimized_ops, int32_t, pad_value); } - break; - case kTfLiteInt64: + } break; + case kTfLiteInt64: { + int64_t pad_value = + op_context.constant_values == nullptr + ? 0L + : *GetTensorData<int64_t>(op_context.constant_values); if (kernel_type == kReference) { - TF_LITE_PAD(reference_ops, int64_t, 0); + TF_LITE_PAD(reference_ops, int64_t, pad_value); } else if (kernel_type == kGenericOptimized) { - TF_LITE_PAD(optimized_ops, int64_t, 0); + TF_LITE_PAD(optimized_ops, int64_t, pad_value); } - break; + } break; default: context->ReportError(context, "Type is currently not supported by Pad."); return kTfLiteError; @@ -185,6 +222,21 @@ TfLiteRegistration* Register_PAD_GENERIC_OPT() { TfLiteRegistration* Register_PAD() { return Register_PAD_GENERIC_OPT(); } +// Also register Pad as PadV2. +TfLiteRegistration* Register_PADV2_REF() { + static TfLiteRegistration r = {nullptr, nullptr, pad::Prepare, + pad::Eval<pad::kReference>}; + return &r; +} + +TfLiteRegistration* Register_PADV2_GENERIC_OPT() { + static TfLiteRegistration r = {nullptr, nullptr, pad::Prepare, + pad::Eval<pad::kGenericOptimized>}; + return &r; +} + +TfLiteRegistration* Register_PADV2() { return Register_PADV2_GENERIC_OPT(); } + } // namespace builtin } // namespace ops } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/pad_test.cc b/tensorflow/contrib/lite/kernels/pad_test.cc index c06237e572..f8b9064fbb 100644 --- a/tensorflow/contrib/lite/kernels/pad_test.cc +++ b/tensorflow/contrib/lite/kernels/pad_test.cc @@ -24,21 +24,26 @@ namespace { using ::testing::ElementsAreArray; using ::testing::Matcher; +template <typename T> class PadOpModel : public SingleOpModel { public: - void SetInput(std::initializer_list<float> data) { - PopulateTensor<float>(input_, data); + void SetInput(std::initializer_list<T> data) { + PopulateTensor<T>(input_, data); } void SetQuantizedInput(std::initializer_list<float> data) { QuantizeAndPopulate<uint8_t>(input_, data); } + void SetQuantizedPadValue(float data) { + QuantizeAndPopulate<uint8_t>(constant_values_, {data}); + } + void SetPaddings(std::initializer_list<int> paddings) { PopulateTensor<int>(paddings_, paddings); } - std::vector<float> GetOutput() { return ExtractVector<float>(output_); } + std::vector<T> GetOutput() { return ExtractVector<T>(output_); } std::vector<int> GetOutputShape() { return GetTensorShape(output_); } std::vector<float> GetDequantizedOutput() { @@ -50,6 +55,59 @@ class PadOpModel : public SingleOpModel { int input_; int output_; int paddings_; + int constant_values_; +}; + +namespace { + +// Returns the corresponding TensorType given the type T. +template <typename T> +TensorType GetTensorType() { + if (std::is_same<T, float>::value) return TensorType_FLOAT32; + if (std::is_same<T, int32_t>::value) return TensorType_INT32; + if (std::is_same<T, uint8_t>::value) return TensorType_UINT8; + return TensorType_MIN; // default value +} + +} // namespace + +// Tests case where paddings is a const tensor. Type T is the dtype. +template <typename T> +class PadV2OpConstModel : public PadOpModel<T> { + public: + PadV2OpConstModel(const TensorData& input, + std::initializer_list<int> paddings_shape, + std::initializer_list<int> paddings, T constant_values, + const TensorData& output) { + this->input_ = this->AddInput(input); + this->paddings_ = + this->AddConstInput(TensorType_INT32, paddings, paddings_shape); + this->constant_values_ = + this->AddConstInput(GetTensorType<T>(), {constant_values}, {1}); + + this->output_ = this->AddOutput(output); + + this->SetBuiltinOp(BuiltinOperator_PADV2, BuiltinOptions_PadV2Options, + CreatePadV2Options(this->builder_).Union()); + this->BuildInterpreter({input.shape}); + } + + PadV2OpConstModel(const TensorData& input, + std::initializer_list<int> paddings_shape, + std::initializer_list<int> paddings, + const TensorData& constant_values, + const TensorData& output) { + this->input_ = this->AddInput(input); + this->paddings_ = + this->AddConstInput(TensorType_INT32, paddings, paddings_shape); + this->constant_values_ = this->AddInput(constant_values); + + this->output_ = this->AddOutput(output); + + this->SetBuiltinOp(BuiltinOperator_PADV2, BuiltinOptions_PadV2Options, + CreatePadV2Options(this->builder_).Union()); + this->BuildInterpreter({input.shape}); + } }; // Tests case where paddings is a const tensor. @@ -58,7 +116,7 @@ class PadOpModel : public SingleOpModel { // PadOpDynamicModel m(input_shape, paddings_shape, paddings_data); // m.SetInput(input_data); // m.Invoke(); -class PadOpConstModel : public PadOpModel { +class PadOpConstModel : public PadOpModel<float> { public: PadOpConstModel(const TensorData& input, std::initializer_list<int> paddings_shape, @@ -66,6 +124,7 @@ class PadOpConstModel : public PadOpModel { const TensorData& output) { input_ = AddInput(input); paddings_ = AddConstInput(TensorType_INT32, paddings, paddings_shape); + constant_values_ = AddNullInput(); output_ = AddOutput(output); SetBuiltinOp(BuiltinOperator_PAD, BuiltinOptions_PadOptions, @@ -75,19 +134,52 @@ class PadOpConstModel : public PadOpModel { }; // Test case where paddings is a non-const tensor. +template <typename T> +class PadV2OpDynamicModel : public PadOpModel<T> { + public: + PadV2OpDynamicModel(const TensorData& input, + std::initializer_list<int> paddings_shape, + T constant_values, const TensorData& output) { + this->input_ = this->AddInput(input); + this->paddings_ = this->AddInput(TensorType_INT32); + this->constant_values_ = + this->AddConstInput(GetTensorType<T>(), {constant_values}, {1}); + this->output_ = this->AddOutput(output); + + this->SetBuiltinOp(BuiltinOperator_PADV2, BuiltinOptions_PadV2Options, + CreatePadV2Options(this->builder_).Union()); + this->BuildInterpreter({input.shape, paddings_shape}); + } + PadV2OpDynamicModel(const TensorData& input, + std::initializer_list<int> paddings_shape, + const TensorData& constant_values, + const TensorData& output) { + this->input_ = this->AddInput(input); + this->paddings_ = this->AddInput(TensorType_INT32); + this->constant_values_ = this->AddInput(constant_values); + this->output_ = this->AddOutput(output); + + this->SetBuiltinOp(BuiltinOperator_PADV2, BuiltinOptions_PadV2Options, + CreatePadV2Options(this->builder_).Union()); + this->BuildInterpreter({input.shape, paddings_shape}); + } +}; + +// Test case where paddings is a non-const tensor. // // Example usage is as follows: // PadOpDynamicModel m(input_shape, paddings_shape); // m.SetInput(input_data); // m.SetPaddings(paddings_data); // m.Invoke(); -class PadOpDynamicModel : public PadOpModel { +class PadOpDynamicModel : public PadOpModel<float> { public: PadOpDynamicModel(const TensorData& input, std::initializer_list<int> paddings_shape, const TensorData& output) { input_ = AddInput(input); paddings_ = AddInput(TensorType_INT32); + constant_values_ = AddNullInput(); output_ = AddOutput(output); SetBuiltinOp(BuiltinOperator_PAD, BuiltinOptions_PadOptions, @@ -237,6 +329,272 @@ TEST_F(QuantizedPadOpTest, AdvancedDynamicTest) { EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); } +TEST(PadV2OpTest, TooManyDimensions) { + EXPECT_DEATH(PadV2OpConstModel<float>( + {TensorType_FLOAT32, {1, 2, 3, 4, 5, 6, 7, 8, 9}}, {9, 2}, + {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, 0.0, + {TensorType_FLOAT32}), + "dims != 4"); +} + +TEST(PadV2OpTest, UnequalDimensions) { + EXPECT_DEATH( + PadV2OpConstModel<float>({TensorType_FLOAT32, {1, 1, 2, 1}}, {3, 2}, + {1, 1, 2, 2, 3, 3}, 0.0, {TensorType_FLOAT32}), + "3 != 4"); +} + +TEST(PadV2OpTest, InvalidPadValue) { + EXPECT_DEATH(PadV2OpConstModel<float>({TensorType_FLOAT32, {1, 1, 2, 1}}, + {4, 2}, {0, 0, 1, -1, 2, -1, 0, 0}, 0.0, + {TensorType_FLOAT32}), + "Pad value has to be greater than equal to 0."); +} + +TEST(PadV2OpTest, SimpleConstTest) { + // Padding is represented as four 2-D lists representing above padding and + // below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}). + PadV2OpConstModel<float> m({TensorType_FLOAT32, {1, 2, 2, 1}}, {4, 2}, + {0, 0, 1, 1, 1, 1, 0, 0}, 0.0, + {TensorType_FLOAT32}); + m.SetInput({1, 2, 3, 4}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({0, 0, 0, 0, 0, 1, 2, 0, 0, 3, 4, + 0, 0, 0, 0, 0})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST(PadV2OpTest, SimpleConstFloat32ValuedTest) { + // Padding is represented as four 2-D lists representing above padding and + // below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}). + PadV2OpConstModel<float> m({TensorType_FLOAT32, {1, 2, 2, 1}}, {4, 2}, + {0, 0, 1, 1, 1, 1, 0, 0}, 5, {TensorType_FLOAT32}); + m.SetInput({1, 2, 3, 4}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({5, 5, 5, 5, 5, 1, 2, 5, 5, 3, 4, + 5, 5, 5, 5, 5})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST(PadV2OpTest, Simple4DConstFloat32ValuedTest) { + // Padding is represented as four 2-D lists representing above padding and + // below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}). + PadV2OpConstModel<float> m({TensorType_FLOAT32, {1, 1, 2, 1}}, {4, 2}, + {0, 1, 0, 0, 0, 0, 0, 1}, 5, {TensorType_FLOAT32}); + m.SetInput({3, 3}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({3, 5, 3, 5, 5, 5, 5, 5})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 2, 2})); +} + +TEST(PadV2OpTest, SimpleConstInt32ValuedTest) { + // Padding is represented as four 2-D lists representing above padding and + // below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}). + PadV2OpConstModel<int32_t> m({TensorType_INT32, {1, 2, 2, 1}}, {4, 2}, + {0, 0, 1, 1, 1, 1, 0, 0}, 5, {TensorType_INT32}); + m.SetInput({1, 2, 3, 4}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({5, 5, 5, 5, 5, 1, 2, 5, 5, 3, 4, + 5, 5, 5, 5, 5})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST(PadV2OpTest, SimpleDynamicTest) { + PadV2OpDynamicModel<float> m({TensorType_FLOAT32, {1, 2, 2, 1}}, {4, 2}, 0.0, + {TensorType_FLOAT32}); + m.SetInput({1, 2, 3, 4}); + m.SetPaddings({0, 0, 1, 1, 1, 1, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({0, 0, 0, 0, 0, 1, 2, 0, 0, 3, 4, + 0, 0, 0, 0, 0})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST(PadV2OpTest, SimpleDynamicValuedTest) { + PadV2OpDynamicModel<float> m({TensorType_FLOAT32, {1, 2, 2, 1}}, {4, 2}, 5, + {TensorType_FLOAT32}); + m.SetInput({1, 2, 3, 4}); + m.SetPaddings({0, 0, 1, 1, 1, 1, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), ElementsAreArray({5, 5, 5, 5, 5, 1, 2, 5, 5, 3, 4, + 5, 5, 5, 5, 5})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST(PadV2OpTest, AdvancedConstTest) { + PadV2OpConstModel<float> m({TensorType_FLOAT32, {1, 2, 3, 1}}, {4, 2}, + {0, 0, 0, 2, 1, 3, 0, 0}, 0, {TensorType_FLOAT32}); + m.SetInput({1, 2, 3, 4, 5, 6}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), + ElementsAreArray({0, 1, 2, 3, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); +} + +TEST(PadV2OpTest, AdvancedDynamicTest) { + PadV2OpDynamicModel<float> m({TensorType_FLOAT32, {1, 2, 3, 1}}, {4, 2}, 0, + {TensorType_FLOAT32}); + m.SetInput({1, 2, 3, 4, 5, 6}); + m.SetPaddings({0, 0, 0, 2, 1, 3, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetOutput(), + ElementsAreArray({0, 1, 2, 3, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); +} + +class QuantizedPadV2OpTest : public ::testing::Test { + protected: + std::vector<Matcher<float>> DequantizedArrayNear( + const std::vector<float>& values, const float min, const float max) { + const float quantization_tolerance = (max - min) / 255.0; + return ArrayFloatNear(values, quantization_tolerance); + } +}; + +TEST_F(QuantizedPadV2OpTest, ZeroNotInQuantizationRange) { + // The test_util and actual quantization code currently ensure that the range + // must include zero, but if that ever changes, this test will catch it. + EXPECT_DEATH( + PadV2OpConstModel<float> m({TensorType_UINT8, {1, 2, 2, 1}, 1.0, 2.0}, + {4, 2}, {0, 0, 1, 1, 1, 1, 0, 0}, 0, + {TensorType_UINT8, {}, 1.0, 2.0}), + ".*Check failed: f_min <= 0.*"); +} + +TEST_F(QuantizedPadV2OpTest, SimpleConstTest) { + // Padding is represented as four 2-D lists representing above padding and + // below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}). + PadV2OpConstModel<uint8_t> m({TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0}, + {4, 2}, {0, 0, 1, 1, 1, 1, 0, 0}, + {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7}); + m.SetQuantizedPadValue(0); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {0, 0, 0, 0, 0, -0.8, 0.2, 0, 0, 0.9, 0.7, 0, 0, 0, 0, 0}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST_F(QuantizedPadV2OpTest, SimpleDynamicTest) { + PadV2OpDynamicModel<uint8_t> m({TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0}, + {4, 2}, {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7}); + m.SetQuantizedPadValue(0); + m.SetPaddings({0, 0, 1, 1, 1, 1, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {0, 0, 0, 0, 0, -0.8, 0.2, 0, 0, 0.9, 0.7, 0, 0, 0, 0, 0}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST_F(QuantizedPadV2OpTest, AdvancedConstTest) { + PadV2OpConstModel<uint8_t> m({TensorType_UINT8, {1, 2, 3, 1}, -1.0, 1.0}, + {4, 2}, {0, 0, 0, 2, 1, 3, 0, 0}, + {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7, 0.1, -0.3}); + m.SetQuantizedPadValue(0); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {0, -0.8, 0.2, 0.9, 0, 0, 0, 0, 0.7, 0.1, -0.3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); +} + +TEST_F(QuantizedPadV2OpTest, AdvancedDynamicTest) { + PadV2OpDynamicModel<uint8_t> m({TensorType_UINT8, {1, 2, 3, 1}, -1.0, 1.0}, + {4, 2}, {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7, 0.1, -0.3}); + m.SetQuantizedPadValue(0); + m.SetPaddings({0, 0, 0, 2, 1, 3, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {0, -0.8, 0.2, 0.9, 0, 0, 0, 0, 0.7, 0.1, -0.3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); +} + +TEST_F(QuantizedPadV2OpTest, SimpleConstValuedTest) { + // Padding is represented as four 2-D lists representing above padding and + // below padding (i.e. {{0, 0}, {1, 1}, {1, 1}, {0, 0}}). + PadV2OpConstModel<uint8_t> m({TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0}, + {4, 2}, {0, 0, 1, 1, 1, 1, 0, 0}, + {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7}); + m.SetQuantizedPadValue(-0.5); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {-0.5, -0.5, -0.5, -0.5, -0.5, -0.8, 0.2, -0.5, -0.5, 0.9, + 0.7, -0.5, -0.5, -0.5, -0.5, -0.5}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST_F(QuantizedPadV2OpTest, SimpleDynamicValuedTest) { + PadV2OpDynamicModel<uint8_t> m({TensorType_UINT8, {1, 2, 2, 1}, -1.0, 1.0}, + {4, 2}, {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7}); + m.SetQuantizedPadValue(-0.5); + m.SetPaddings({0, 0, 1, 1, 1, 1, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {-0.5, -0.5, -0.5, -0.5, -0.5, -0.8, 0.2, -0.5, -0.5, 0.9, + 0.7, -0.5, -0.5, -0.5, -0.5, -0.5}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); +} + +TEST_F(QuantizedPadV2OpTest, AdvancedConstValuedTest) { + PadV2OpConstModel<uint8_t> m({TensorType_UINT8, {1, 2, 3, 1}, -1.0, 1.0}, + {4, 2}, {0, 0, 0, 2, 1, 3, 0, 0}, + {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7, 0.1, -0.3}); + m.SetQuantizedPadValue(-0.5); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {-0.5, -0.8, 0.2, 0.9, -0.5, -0.5, -0.5, -0.5, 0.7, 0.1, + -0.3, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); +} + +TEST_F(QuantizedPadV2OpTest, AdvancedDynamicValuedTest) { + PadV2OpDynamicModel<uint8_t> m({TensorType_UINT8, {1, 2, 3, 1}, -1.0, 1.0}, + {4, 2}, {TensorType_UINT8, {1}, -1.0, 1.0}, + {TensorType_UINT8, {}, -1.0, 1.0}); + m.SetQuantizedInput({-0.8, 0.2, 0.9, 0.7, 0.1, -0.3}); + m.SetQuantizedPadValue(-0.5); + m.SetPaddings({0, 0, 0, 2, 1, 3, 0, 0}); + m.Invoke(); + EXPECT_THAT(m.GetDequantizedOutput(), + ElementsAreArray(DequantizedArrayNear( + {-0.5, -0.8, 0.2, 0.9, -0.5, -0.5, -0.5, -0.5, 0.7, 0.1, + -0.3, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5}, + -1.0, 1.0))); + EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1})); +} + } // namespace } // namespace tflite diff --git a/tensorflow/contrib/lite/kernels/register.cc b/tensorflow/contrib/lite/kernels/register.cc index 29ea718a96..a6ea874546 100644 --- a/tensorflow/contrib/lite/kernels/register.cc +++ b/tensorflow/contrib/lite/kernels/register.cc @@ -60,6 +60,7 @@ TfLiteRegistration* Register_LSTM(); TfLiteRegistration* Register_BIDIRECTIONAL_SEQUENCE_LSTM(); TfLiteRegistration* Register_UNIDIRECTIONAL_SEQUENCE_LSTM(); TfLiteRegistration* Register_PAD(); +TfLiteRegistration* Register_PADV2(); TfLiteRegistration* Register_RESHAPE(); TfLiteRegistration* Register_RESIZE_BILINEAR(); TfLiteRegistration* Register_SKIP_GRAM(); @@ -121,6 +122,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM, Register_UNIDIRECTIONAL_SEQUENCE_LSTM()); AddBuiltin(BuiltinOperator_PAD, Register_PAD()); + AddBuiltin(BuiltinOperator_PADV2, Register_PADV2()); AddBuiltin(BuiltinOperator_RESHAPE, Register_RESHAPE()); AddBuiltin(BuiltinOperator_RESIZE_BILINEAR, Register_RESIZE_BILINEAR()); AddBuiltin(BuiltinOperator_SKIP_GRAM, Register_SKIP_GRAM()); diff --git a/tensorflow/contrib/lite/kernels/test_util.cc b/tensorflow/contrib/lite/kernels/test_util.cc index 0bb28b50b2..5a6c85e97e 100644 --- a/tensorflow/contrib/lite/kernels/test_util.cc +++ b/tensorflow/contrib/lite/kernels/test_util.cc @@ -22,23 +22,6 @@ namespace tflite { using ::testing::FloatNear; using ::testing::Matcher; -namespace { -template <typename T> -std::pair<float, int32_t> QuantizationParams(float f_min, float f_max) { - // These are required by many quantized operations. - CHECK_LE(f_min, 0); - CHECK_GE(f_max, 0); - T q_min = std::numeric_limits<T>::min(); - T q_max = std::numeric_limits<T>::max(); - float range = q_max - q_min; - float scale = (f_max - f_min) / range; - int32_t zero_point = std::min( - q_max, - std::max(q_min, static_cast<T>(std::round(q_min - f_min / scale)))); - return {scale, zero_point}; -} -} // namespace - std::vector<Matcher<float>> ArrayFloatNear(const std::vector<float>& values, float max_abs_error) { std::vector<Matcher<float>> matchers; @@ -49,69 +32,8 @@ std::vector<Matcher<float>> ArrayFloatNear(const std::vector<float>& values, return matchers; } -int SingleOpModel::AddTensor(TensorData t, std::initializer_list<int> data) { - int id = tensors_.size(); - - // This is slightly different depending on whether we are adding a - // quantized or a regular tensor. - bool is_quantized = (t.min != 0 || t.max != 0 || t.scale != 0); - - flatbuffers::Offset<QuantizationParameters> q_params = 0; - - if (is_quantized) { - if (t.min != 0 || t.max != 0) { - if (t.type == TensorType_UINT8) { - std::tie(t.scale, t.zero_point) = - QuantizationParams<uint8_t>(t.min, t.max); - } else if (t.type == TensorType_INT32) { - std::tie(t.scale, t.zero_point) = - QuantizationParams<int32_t>(t.min, t.max); - } else { - LOG(FATAL) << "No support for the requested quantized type"; - } - t.min = 0; - t.max = 0; - } - - q_params = CreateQuantizationParameters( - builder_, /*min=*/0, /*max=*/0, builder_.CreateVector<float>({t.scale}), - builder_.CreateVector<int64_t>({t.zero_point})); - } - - int buffer_id = 0; - if (data.size()) { - // Initialize buffers list with empty buffer to allow for non-const tensors. - if (buffers_.empty()) { - buffers_.push_back(CreateBuffer(builder_, builder_.CreateVector({}))); - } - - // Add data as a Buffer to buffers list. - buffer_id = buffers_.size(); - auto data_buffer = - builder_.CreateVector(reinterpret_cast<const uint8_t*>(data.begin()), - sizeof(int) * data.size()); - buffers_.push_back(CreateBuffer(builder_, data_buffer)); - } - - tensors_.push_back(CreateTensor(builder_, builder_.CreateVector<int>(t.shape), - t.type, /*buffer=*/buffer_id, - /*name=*/0, q_params)); - - tensor_data_[id] = t; - - return id; -} - int SingleOpModel::AddInput(const TensorData& t) { - int id = AddTensor(t, {}); - inputs_.push_back(id); - return id; -} - -int SingleOpModel::AddConstInput(TensorType type, - std::initializer_list<int> data, - std::initializer_list<int> shape) { - int id = AddTensor(TensorData{type, shape}, data); + int id = AddTensor<float>(t, {}); inputs_.push_back(id); return id; } @@ -123,7 +45,7 @@ int SingleOpModel::AddNullInput() { } int SingleOpModel::AddOutput(const TensorData& t) { - int id = AddTensor(t, {}); + int id = AddTensor<float>(t, {}); outputs_.push_back(id); return id; } diff --git a/tensorflow/contrib/lite/kernels/test_util.h b/tensorflow/contrib/lite/kernels/test_util.h index 6fb6fe27eb..6a9fdf1112 100644 --- a/tensorflow/contrib/lite/kernels/test_util.h +++ b/tensorflow/contrib/lite/kernels/test_util.h @@ -116,9 +116,14 @@ class SingleOpModel { int AddInput(TensorType type) { return AddInput(TensorData{type}); } int AddInput(const TensorData& t); - // Add a Tensor containing const data and return the tensor id. - int AddConstInput(TensorType type, std::initializer_list<int> data, - std::initializer_list<int> shape); + // Templated version of AddConstInput(). + template <typename T> + int AddConstInput(TensorType type, std::initializer_list<T> data, + std::initializer_list<int> shape) { + int id = AddTensor(TensorData{type, shape}, data); + inputs_.push_back(id); + return id; + } // Add a null input tensor (optional input) and return kOptionalTensor. int AddNullInput(); @@ -224,7 +229,79 @@ class SingleOpModel { std::unique_ptr<OpResolver> resolver_; private: - int AddTensor(TensorData t, std::initializer_list<int> data); + // TODO(gavinbelson): sync this method with + // //tensorflow/contrib/lite/kernels/internal/quantization_util.h?l=31 + template <typename T> + std::pair<float, int32_t> QuantizationParams(float f_min, float f_max) { + // These are required by many quantized operations. + CHECK_LE(f_min, 0); + CHECK_GE(f_max, 0); + T q_min = std::numeric_limits<T>::min(); + T q_max = std::numeric_limits<T>::max(); + float range = q_max - q_min; + float scale = (f_max - f_min) / range; + int32_t zero_point = std::min( + q_max, + std::max(q_min, static_cast<T>(std::round(q_min - f_min / scale)))); + return {scale, zero_point}; + } + + template <typename T> + int AddTensor(TensorData t, std::initializer_list<T> data) { + int id = tensors_.size(); + + // This is slightly different depending on whether we are adding a + // quantized or a regular tensor. + bool is_quantized = (t.min != 0 || t.max != 0 || t.scale != 0); + + flatbuffers::Offset<QuantizationParameters> q_params = 0; + + if (is_quantized) { + if (t.min != 0 || t.max != 0) { + if (t.type == TensorType_UINT8) { + std::tie(t.scale, t.zero_point) = + QuantizationParams<uint8_t>(t.min, t.max); + } else if (t.type == TensorType_INT32) { + std::tie(t.scale, t.zero_point) = + QuantizationParams<int32_t>(t.min, t.max); + } else { + LOG(FATAL) << "No support for the requested quantized type"; + } + t.min = 0; + t.max = 0; + } + + q_params = CreateQuantizationParameters( + builder_, /*min=*/0, /*max=*/0, + builder_.CreateVector<float>({t.scale}), + builder_.CreateVector<int64_t>({t.zero_point})); + } + + int buffer_id = 0; + if (data.size()) { + // Initialize buffers list with empty buffer to allow for non-const + // tensors. + if (buffers_.empty()) { + buffers_.push_back(CreateBuffer(builder_, builder_.CreateVector({}))); + } + + // Add data as a Buffer to buffers list. + buffer_id = buffers_.size(); + auto data_buffer = + builder_.CreateVector(reinterpret_cast<const uint8_t*>(data.begin()), + sizeof(T) * data.size()); + buffers_.push_back(CreateBuffer(builder_, data_buffer)); + } + + tensors_.push_back(CreateTensor(builder_, + builder_.CreateVector<int>(t.shape), t.type, + /*buffer=*/buffer_id, + /*name=*/0, q_params)); + + tensor_data_[id] = t; + + return id; + } std::map<int, TensorData> tensor_data_; std::vector<int32_t> inputs_; diff --git a/tensorflow/contrib/lite/model.cc b/tensorflow/contrib/lite/model.cc index 590f042e21..6253570fa2 100644 --- a/tensorflow/contrib/lite/model.cc +++ b/tensorflow/contrib/lite/model.cc @@ -569,6 +569,9 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type, case BuiltinOperator_PAD: { break; } + case BuiltinOperator_PADV2: { + break; + } case BuiltinOperator_RESHAPE: { auto* params = MallocPOD<TfLiteReshapeParams>(); if (auto* schema_params = op->builtin_options_as_ReshapeOptions()) { diff --git a/tensorflow/contrib/lite/nnapi_delegate.cc b/tensorflow/contrib/lite/nnapi_delegate.cc index 6eac18c4f5..b4c46917bf 100644 --- a/tensorflow/contrib/lite/nnapi_delegate.cc +++ b/tensorflow/contrib/lite/nnapi_delegate.cc @@ -347,6 +347,7 @@ void AddOpsAndParams(tflite::Interpreter* interpreter, case tflite::BuiltinOperator_L2_NORMALIZATION: case tflite::BuiltinOperator_LOCAL_RESPONSE_NORMALIZATION: case tflite::BuiltinOperator_PAD: + case tflite::BuiltinOperator_PADV2: case tflite::BuiltinOperator_RESIZE_BILINEAR: case tflite::BuiltinOperator_CALL: case tflite::BuiltinOperator_SKIP_GRAM: diff --git a/tensorflow/contrib/lite/schema/schema.fbs b/tensorflow/contrib/lite/schema/schema.fbs index 265b1dd3fe..84ff3b16bd 100644 --- a/tensorflow/contrib/lite/schema/schema.fbs +++ b/tensorflow/contrib/lite/schema/schema.fbs @@ -137,6 +137,7 @@ enum BuiltinOperator : byte { MINIMUM = 57, LESS = 58, NEG = 59, + PADV2 = 60, } // Options for the builtin operators. @@ -163,6 +164,7 @@ union BuiltinOptions { EmbeddingLookupSparseOptions, MulOptions, PadOptions, + PadV2Options, GatherOptions, BatchToSpaceNDOptions, SpaceToBatchNDOptions, @@ -316,6 +318,9 @@ table CallOptions { table PadOptions { } +table PadV2Options { +} + table ReshapeOptions { new_shape:[int]; } diff --git a/tensorflow/contrib/lite/schema/schema_generated.h b/tensorflow/contrib/lite/schema/schema_generated.h index c172f77aa9..8855e4ad58 100755 --- a/tensorflow/contrib/lite/schema/schema_generated.h +++ b/tensorflow/contrib/lite/schema/schema_generated.h @@ -88,6 +88,9 @@ struct CallOptionsT; struct PadOptions; struct PadOptionsT; +struct PadV2Options; +struct PadV2OptionsT; + struct ReshapeOptions; struct ReshapeOptionsT; @@ -276,11 +279,12 @@ enum BuiltinOperator { BuiltinOperator_MINIMUM = 57, BuiltinOperator_LESS = 58, BuiltinOperator_NEG = 59, + BuiltinOperator_PADV2 = 60, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_NEG + BuiltinOperator_MAX = BuiltinOperator_PADV2 }; -inline BuiltinOperator (&EnumValuesBuiltinOperator())[59] { +inline BuiltinOperator (&EnumValuesBuiltinOperator())[60] { static BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -340,7 +344,8 @@ inline BuiltinOperator (&EnumValuesBuiltinOperator())[59] { BuiltinOperator_ARG_MAX, BuiltinOperator_MINIMUM, BuiltinOperator_LESS, - BuiltinOperator_NEG + BuiltinOperator_NEG, + BuiltinOperator_PADV2 }; return values; } @@ -407,6 +412,7 @@ inline const char **EnumNamesBuiltinOperator() { "MINIMUM", "LESS", "NEG", + "PADV2", nullptr }; return names; @@ -441,31 +447,32 @@ enum BuiltinOptions { BuiltinOptions_EmbeddingLookupSparseOptions = 20, BuiltinOptions_MulOptions = 21, BuiltinOptions_PadOptions = 22, - BuiltinOptions_GatherOptions = 23, - BuiltinOptions_BatchToSpaceNDOptions = 24, - BuiltinOptions_SpaceToBatchNDOptions = 25, - BuiltinOptions_TransposeOptions = 26, - BuiltinOptions_MeanOptions = 27, - BuiltinOptions_SubOptions = 28, - BuiltinOptions_DivOptions = 29, - BuiltinOptions_SqueezeOptions = 30, - BuiltinOptions_SequenceRNNOptions = 31, - BuiltinOptions_StridedSliceOptions = 32, - BuiltinOptions_ExpOptions = 33, - BuiltinOptions_TopKV2Options = 34, - BuiltinOptions_SplitOptions = 35, - BuiltinOptions_LogSoftmaxOptions = 36, - BuiltinOptions_CastOptions = 37, - BuiltinOptions_DequantizeOptions = 38, - BuiltinOptions_MaximumMinimumOptions = 39, - BuiltinOptions_ArgMaxOptions = 40, - BuiltinOptions_LessOptions = 41, - BuiltinOptions_NegOptions = 42, + BuiltinOptions_PadV2Options = 23, + BuiltinOptions_GatherOptions = 24, + BuiltinOptions_BatchToSpaceNDOptions = 25, + BuiltinOptions_SpaceToBatchNDOptions = 26, + BuiltinOptions_TransposeOptions = 27, + BuiltinOptions_MeanOptions = 28, + BuiltinOptions_SubOptions = 29, + BuiltinOptions_DivOptions = 30, + BuiltinOptions_SqueezeOptions = 31, + BuiltinOptions_SequenceRNNOptions = 32, + BuiltinOptions_StridedSliceOptions = 33, + BuiltinOptions_ExpOptions = 34, + BuiltinOptions_TopKV2Options = 35, + BuiltinOptions_SplitOptions = 36, + BuiltinOptions_LogSoftmaxOptions = 37, + BuiltinOptions_CastOptions = 38, + BuiltinOptions_DequantizeOptions = 39, + BuiltinOptions_MaximumMinimumOptions = 40, + BuiltinOptions_ArgMaxOptions = 41, + BuiltinOptions_LessOptions = 42, + BuiltinOptions_NegOptions = 43, BuiltinOptions_MIN = BuiltinOptions_NONE, BuiltinOptions_MAX = BuiltinOptions_NegOptions }; -inline BuiltinOptions (&EnumValuesBuiltinOptions())[43] { +inline BuiltinOptions (&EnumValuesBuiltinOptions())[44] { static BuiltinOptions values[] = { BuiltinOptions_NONE, BuiltinOptions_Conv2DOptions, @@ -490,6 +497,7 @@ inline BuiltinOptions (&EnumValuesBuiltinOptions())[43] { BuiltinOptions_EmbeddingLookupSparseOptions, BuiltinOptions_MulOptions, BuiltinOptions_PadOptions, + BuiltinOptions_PadV2Options, BuiltinOptions_GatherOptions, BuiltinOptions_BatchToSpaceNDOptions, BuiltinOptions_SpaceToBatchNDOptions, @@ -539,6 +547,7 @@ inline const char **EnumNamesBuiltinOptions() { "EmbeddingLookupSparseOptions", "MulOptions", "PadOptions", + "PadV2Options", "GatherOptions", "BatchToSpaceNDOptions", "SpaceToBatchNDOptions", @@ -661,6 +670,10 @@ template<> struct BuiltinOptionsTraits<PadOptions> { static const BuiltinOptions enum_value = BuiltinOptions_PadOptions; }; +template<> struct BuiltinOptionsTraits<PadV2Options> { + static const BuiltinOptions enum_value = BuiltinOptions_PadV2Options; +}; + template<> struct BuiltinOptionsTraits<GatherOptions> { static const BuiltinOptions enum_value = BuiltinOptions_GatherOptions; }; @@ -948,6 +961,14 @@ struct BuiltinOptionsUnion { return type == BuiltinOptions_PadOptions ? reinterpret_cast<const PadOptionsT *>(value) : nullptr; } + PadV2OptionsT *AsPadV2Options() { + return type == BuiltinOptions_PadV2Options ? + reinterpret_cast<PadV2OptionsT *>(value) : nullptr; + } + const PadV2OptionsT *AsPadV2Options() const { + return type == BuiltinOptions_PadV2Options ? + reinterpret_cast<const PadV2OptionsT *>(value) : nullptr; + } GatherOptionsT *AsGatherOptions() { return type == BuiltinOptions_GatherOptions ? reinterpret_cast<GatherOptionsT *>(value) : nullptr; @@ -2873,6 +2894,46 @@ inline flatbuffers::Offset<PadOptions> CreatePadOptions( flatbuffers::Offset<PadOptions> CreatePadOptions(flatbuffers::FlatBufferBuilder &_fbb, const PadOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct PadV2OptionsT : public flatbuffers::NativeTable { + typedef PadV2Options TableType; + PadV2OptionsT() { + } +}; + +struct PadV2Options FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef PadV2OptionsT NativeTableType; + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + verifier.EndTable(); + } + PadV2OptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(PadV2OptionsT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset<PadV2Options> Pack(flatbuffers::FlatBufferBuilder &_fbb, const PadV2OptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct PadV2OptionsBuilder { + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + explicit PadV2OptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + PadV2OptionsBuilder &operator=(const PadV2OptionsBuilder &); + flatbuffers::Offset<PadV2Options> Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset<PadV2Options>(end); + return o; + } +}; + +inline flatbuffers::Offset<PadV2Options> CreatePadV2Options( + flatbuffers::FlatBufferBuilder &_fbb) { + PadV2OptionsBuilder builder_(_fbb); + return builder_.Finish(); +} + +flatbuffers::Offset<PadV2Options> CreatePadV2Options(flatbuffers::FlatBufferBuilder &_fbb, const PadV2OptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct ReshapeOptionsT : public flatbuffers::NativeTable { typedef ReshapeOptions TableType; std::vector<int32_t> new_shape; @@ -4258,6 +4319,9 @@ struct Operator FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const PadOptions *builtin_options_as_PadOptions() const { return builtin_options_type() == BuiltinOptions_PadOptions ? static_cast<const PadOptions *>(builtin_options()) : nullptr; } + const PadV2Options *builtin_options_as_PadV2Options() const { + return builtin_options_type() == BuiltinOptions_PadV2Options ? static_cast<const PadV2Options *>(builtin_options()) : nullptr; + } const GatherOptions *builtin_options_as_GatherOptions() const { return builtin_options_type() == BuiltinOptions_GatherOptions ? static_cast<const GatherOptions *>(builtin_options()) : nullptr; } @@ -4432,6 +4496,10 @@ template<> inline const PadOptions *Operator::builtin_options_as<PadOptions>() c return builtin_options_as_PadOptions(); } +template<> inline const PadV2Options *Operator::builtin_options_as<PadV2Options>() const { + return builtin_options_as_PadV2Options(); +} + template<> inline const GatherOptions *Operator::builtin_options_as<GatherOptions>() const { return builtin_options_as_GatherOptions(); } @@ -5572,6 +5640,29 @@ inline flatbuffers::Offset<PadOptions> CreatePadOptions(flatbuffers::FlatBufferB _fbb); } +inline PadV2OptionsT *PadV2Options::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = new PadV2OptionsT(); + UnPackTo(_o, _resolver); + return _o; +} + +inline void PadV2Options::UnPackTo(PadV2OptionsT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; +} + +inline flatbuffers::Offset<PadV2Options> PadV2Options::Pack(flatbuffers::FlatBufferBuilder &_fbb, const PadV2OptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreatePadV2Options(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset<PadV2Options> CreatePadV2Options(flatbuffers::FlatBufferBuilder &_fbb, const PadV2OptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const PadV2OptionsT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + return tflite::CreatePadV2Options( + _fbb); +} + inline ReshapeOptionsT *ReshapeOptions::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = new ReshapeOptionsT(); UnPackTo(_o, _resolver); @@ -6432,6 +6523,10 @@ inline bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast<const PadOptions *>(obj); return verifier.VerifyTable(ptr); } + case BuiltinOptions_PadV2Options: { + auto ptr = reinterpret_cast<const PadV2Options *>(obj); + return verifier.VerifyTable(ptr); + } case BuiltinOptions_GatherOptions: { auto ptr = reinterpret_cast<const GatherOptions *>(obj); return verifier.VerifyTable(ptr); @@ -6618,6 +6713,10 @@ inline void *BuiltinOptionsUnion::UnPack(const void *obj, BuiltinOptions type, c auto ptr = reinterpret_cast<const PadOptions *>(obj); return ptr->UnPack(resolver); } + case BuiltinOptions_PadV2Options: { + auto ptr = reinterpret_cast<const PadV2Options *>(obj); + return ptr->UnPack(resolver); + } case BuiltinOptions_GatherOptions: { auto ptr = reinterpret_cast<const GatherOptions *>(obj); return ptr->UnPack(resolver); @@ -6792,6 +6891,10 @@ inline flatbuffers::Offset<void> BuiltinOptionsUnion::Pack(flatbuffers::FlatBuff auto ptr = reinterpret_cast<const PadOptionsT *>(value); return CreatePadOptions(_fbb, ptr, _rehasher).Union(); } + case BuiltinOptions_PadV2Options: { + auto ptr = reinterpret_cast<const PadV2OptionsT *>(value); + return CreatePadV2Options(_fbb, ptr, _rehasher).Union(); + } case BuiltinOptions_GatherOptions: { auto ptr = reinterpret_cast<const GatherOptionsT *>(value); return CreateGatherOptions(_fbb, ptr, _rehasher).Union(); @@ -6966,6 +7069,10 @@ inline BuiltinOptionsUnion::BuiltinOptionsUnion(const BuiltinOptionsUnion &u) FL value = new PadOptionsT(*reinterpret_cast<PadOptionsT *>(u.value)); break; } + case BuiltinOptions_PadV2Options: { + value = new PadV2OptionsT(*reinterpret_cast<PadV2OptionsT *>(u.value)); + break; + } case BuiltinOptions_GatherOptions: { value = new GatherOptionsT(*reinterpret_cast<GatherOptionsT *>(u.value)); break; @@ -7163,6 +7270,11 @@ inline void BuiltinOptionsUnion::Reset() { delete ptr; break; } + case BuiltinOptions_PadV2Options: { + auto ptr = reinterpret_cast<PadV2OptionsT *>(value); + delete ptr; + break; + } case BuiltinOptions_GatherOptions: { auto ptr = reinterpret_cast<GatherOptionsT *>(value); delete ptr; diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD index 211de63d58..ca1390fdeb 100644 --- a/tensorflow/contrib/lite/testing/BUILD +++ b/tensorflow/contrib/lite/testing/BUILD @@ -45,6 +45,7 @@ gen_zipped_test_files( "mul.zip", "neg.zip", "pad.zip", + "padv2.zip", "relu.zip", "relu1.zip", "relu6.zip", diff --git a/tensorflow/contrib/lite/testing/generate_examples.py b/tensorflow/contrib/lite/testing/generate_examples.py index 7e892769bf..6fe0f491d0 100644 --- a/tensorflow/contrib/lite/testing/generate_examples.py +++ b/tensorflow/contrib/lite/testing/generate_examples.py @@ -1391,6 +1391,60 @@ def make_pad_tests(zip_path): make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) +def make_padv2_tests(zip_path): + """Make a set of tests to do padv2.""" + + # TODO(nupurgarg): Add test for tf.uint8. + test_parameters = [ + { + "dtype": [tf.int32, tf.int64, tf.float32], + "input_shape": [[1, 1, 2, 1], [2, 1, 1, 1]], + "paddings": [[[0, 0], [0, 1], [2, 3], [0, 0]], [[0, 1], [0, 0], + [0, 0], [2, 3]]], + "constant_paddings": [True, False], + "constant_values": [0, 2], + }, + # Non-4D use case. + { + "dtype": [tf.int32, tf.int64, tf.float32], + "input_shape": [[1, 2], [0, 1, 2]], + "paddings": [[[0, 1], [2, 3]]], + "constant_paddings": [True, False], + "constant_values": [0, 2], + }, + ] + + def build_graph(parameters): + """Build a pad graph given `parameters`.""" + input_tensor = tf.placeholder( + dtype=parameters["dtype"], + name="input", + shape=parameters["input_shape"]) + + # Get paddings as either a placeholder or constants. + if parameters["constant_paddings"]: + paddings = parameters["paddings"] + input_tensors = [input_tensor] + else: + shape = [len(parameters["paddings"]), 2] + paddings = tf.placeholder(dtype=tf.int32, name="padding", shape=shape) + input_tensors = [input_tensor, paddings] + + out = tf.pad(input_tensor, paddings=paddings, + constant_values=parameters["constant_values"]) + return input_tensors, [out] + + def build_inputs(parameters, sess, inputs, outputs): + values = [ + create_tensor_data(parameters["dtype"], parameters["input_shape"]) + ] + if not parameters["constant_paddings"]: + values.append(np.array(parameters["paddings"])) + return values, sess.run(outputs, feed_dict=dict(zip(inputs, values))) + + make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs) + + def make_reshape_tests(zip_path): """Make a set of tests to do reshape.""" diff --git a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc index 0673a3bb46..96681952c9 100644 --- a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc +++ b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc @@ -54,9 +54,11 @@ std::map<string, string> kBrokenTests = { {R"(^\/div.*int32)", "68808744"}, {R"(^\/sub.*int32)", "68808744"}, - // Pad only supports 4D tensors. + // Pad and PadV2 only supports 4D tensors. {R"(^\/pad.*,input_shape=\[.,.\],paddings=\[\[.,.\],\[.,.\]\])", "70527055"}, + {R"(^\/padv2.*,input_shape=\[.,.\],paddings=\[\[.,.\],\[.,.\]\])", + "70527055"}, // L2Norm only supports tensors with 4D or fewer. {R"(^\/l2normdim=.*,epsilon=.*,input_shape=\[.,.,.,.,.*\])", "67963684"}, @@ -268,6 +270,7 @@ INSTANTIATE_TESTS(minimum) INSTANTIATE_TESTS(mul) INSTANTIATE_TESTS(neg) INSTANTIATE_TESTS(pad) +INSTANTIATE_TESTS(padv2) // INSTANTIATE_TESTS(prelu) INSTANTIATE_TESTS(relu) INSTANTIATE_TESTS(relu1) diff --git a/tensorflow/contrib/lite/toco/BUILD b/tensorflow/contrib/lite/toco/BUILD index ce0a74724a..01ce0d9db2 100644 --- a/tensorflow/contrib/lite/toco/BUILD +++ b/tensorflow/contrib/lite/toco/BUILD @@ -280,6 +280,7 @@ cc_library( "graph_transformations/resolve_mean_attributes.cc", "graph_transformations/resolve_multiply_by_zero.cc", "graph_transformations/resolve_pad_attributes.cc", + "graph_transformations/resolve_padv2_attributes.cc", "graph_transformations/resolve_reorder_axes.cc", "graph_transformations/resolve_reshape_attributes.cc", "graph_transformations/resolve_slice_attributes.cc", diff --git a/tensorflow/contrib/lite/toco/export_tensorflow.cc b/tensorflow/contrib/lite/toco/export_tensorflow.cc index 99ccfaea64..9e899cf977 100644 --- a/tensorflow/contrib/lite/toco/export_tensorflow.cc +++ b/tensorflow/contrib/lite/toco/export_tensorflow.cc @@ -1492,6 +1492,37 @@ void ConvertPadOperator(const Model& model, const PadOperator& src_op, shape->add_dim()->set_size(2); } +void ConvertPadV2Operator(const Model& model, const PadV2Operator& src_op, + GraphDef* tensorflow_graph) { + auto* new_op = tensorflow_graph->add_node(); + new_op->set_op("PadV2"); + new_op->set_name(src_op.outputs[0]); + CHECK_EQ(src_op.inputs.size(), 2); + *new_op->add_input() = src_op.inputs[0]; + *new_op->add_input() = src_op.inputs[1]; + *new_op->add_input() = src_op.inputs[2]; + + const auto params_type = GetTensorFlowDataType(model, src_op.inputs[0]); + (*new_op->mutable_attr())["T"].set_type(params_type); + + // Create the params tensor. + auto* params_op = tensorflow_graph->add_node(); + params_op->set_op("Const"); + params_op->set_name(src_op.inputs[1]); + (*params_op->mutable_attr())["dtype"].set_type(DT_INT32); + auto* tensor = (*params_op->mutable_attr())["value"].mutable_tensor(); + tensor->set_dtype(DT_INT32); + + CHECK_EQ(src_op.left_padding.size(), src_op.right_padding.size()); + for (int i = 0; i < src_op.left_padding.size(); ++i) { + tensor->add_int_val(src_op.left_padding[i]); + tensor->add_int_val(src_op.right_padding[i]); + } + auto* shape = tensor->mutable_tensor_shape(); + shape->add_dim()->set_size(src_op.left_padding.size()); + shape->add_dim()->set_size(2); +} + void CreateSliceInput(const string& input_name, const std::vector<int>& values, GraphDef* tensorflow_graph) { auto* params_op = tensorflow_graph->add_node(); @@ -1795,6 +1826,9 @@ void ConvertOperator(const Model& model, const Operator& src_op, } else if (src_op.type == OperatorType::kPad) { ConvertPadOperator(model, static_cast<const PadOperator&>(src_op), tensorflow_graph); + } else if (src_op.type == OperatorType::kPadV2) { + ConvertPadV2Operator(model, static_cast<const PadV2Operator&>(src_op), + tensorflow_graph); } else if (src_op.type == OperatorType::kStridedSlice) { ConvertStridedSliceOperator( model, static_cast<const StridedSliceOperator&>(src_op), diff --git a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h index 72ffd51db4..4e3ea72182 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h +++ b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h @@ -174,6 +174,7 @@ DECLARE_GRAPH_TRANSFORMATION(UnrollBatchMatMul) DECLARE_GRAPH_TRANSFORMATION(ResolveSpaceToBatchNDAttributes) DECLARE_GRAPH_TRANSFORMATION(ResolveBatchToSpaceNDAttributes) DECLARE_GRAPH_TRANSFORMATION(ResolvePadAttributes) +DECLARE_GRAPH_TRANSFORMATION(ResolvePadV2Attributes) DECLARE_GRAPH_TRANSFORMATION(ResolveStridedSliceAttributes) DECLARE_GRAPH_TRANSFORMATION(ResolveSliceAttributes) DECLARE_GRAPH_TRANSFORMATION(ResolveMeanAttributes) diff --git a/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc index b02b02c5be..9b0e232132 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc +++ b/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc @@ -1146,6 +1146,32 @@ void ProcessPadOperator(Model* model, PadOperator* op) { output_array.copy_shape(output_shape); } +void ProcessPadV2Operator(Model* model, PadV2Operator* op) { + CHECK_EQ(op->inputs.size(), 3); + CHECK_EQ(op->outputs.size(), 1); + + const auto& input_array = model->GetArray(op->inputs[0]); + + // Yield until input dims have been resolved. + if (!input_array.has_shape()) return; + + if (op->left_padding.empty()) return; + CHECK_EQ(op->left_padding.size(), op->right_padding.size()); + + auto& output_array = model->GetArray(op->outputs[0]); + if (output_array.has_shape()) return; + + Shape output_shape = input_array.shape(); + std::vector<int>& dims = *output_shape.mutable_dims(); + CHECK_EQ(op->left_padding.size(), dims.size()); + + for (int i = 0; i < op->left_padding.size(); ++i) { + dims[i] += op->left_padding[i] + op->right_padding[i]; + } + + output_array.copy_shape(output_shape); +} + void ProcessRankOperator(Model* model, RankOperator* op) { CHECK_GE(op->inputs.size(), 1); CHECK_EQ(op->outputs.size(), 1); @@ -1628,6 +1654,9 @@ bool PropagateFixedSizes::Run(Model* model, std::size_t op_index) { case OperatorType::kPad: ProcessPadOperator(model, static_cast<PadOperator*>(op)); break; + case OperatorType::kPadV2: + ProcessPadV2Operator(model, static_cast<PadV2Operator*>(op)); + break; case OperatorType::kStridedSlice: ProcessStridedSliceOperator(model, static_cast<StridedSliceOperator*>(op)); diff --git a/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc b/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc index 347302c7a5..58e214b76b 100644 --- a/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc +++ b/tensorflow/contrib/lite/toco/graph_transformations/quantize.cc @@ -48,6 +48,7 @@ bool SupportsQuantization(const Operator& op) { type == OperatorType::kLogSoftmax || type == OperatorType::kTensorFlowSplit || type == OperatorType::kSub || type == OperatorType::kSqueeze || type == OperatorType::kPad || + type == OperatorType::kPadV2 || type == OperatorType::kTensorFlowReshape || type == OperatorType::kTanh || type == OperatorType::kMul || type == OperatorType::kSpaceToDepth || diff --git a/tensorflow/contrib/lite/toco/graph_transformations/resolve_padv2_attributes.cc b/tensorflow/contrib/lite/toco/graph_transformations/resolve_padv2_attributes.cc new file mode 100644 index 0000000000..ebb023e342 --- /dev/null +++ b/tensorflow/contrib/lite/toco/graph_transformations/resolve_padv2_attributes.cc @@ -0,0 +1,55 @@ +/* Copyright 2017 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 <memory> +#include <string> +#include <unordered_map> +#include <vector> + +#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h" +#include "tensorflow/contrib/lite/toco/model.h" +#include "tensorflow/contrib/lite/toco/tooling_util.h" +#include "tensorflow/core/platform/logging.h" + +namespace toco { + +bool ResolvePadV2Attributes::Run(Model* model, std::size_t op_index) { + const auto pad_it = model->operators.begin() + op_index; + auto* pad_op = pad_it->get(); + if (pad_op->type != OperatorType::kPadV2) return false; + + auto* op = static_cast<PadV2Operator*>(pad_op); + if (!op->left_padding.empty()) return false; + + CHECK_EQ(op->inputs.size(), 3); + if (!IsConstantParameterArray(*model, op->inputs[1])) return false; + + const auto& array = model->GetArray(op->inputs[1]); + if (!array.has_shape()) return false; + + const std::vector<int>& dims = array.shape().dims(); + CHECK_EQ(dims.size(), 2); + + std::vector<int> buffer = array.GetBuffer<ArrayDataType::kInt32>().data; + + for (int i = 0; i < dims[0]; ++i) { + op->left_padding.push_back(buffer[i * 2]); + op->right_padding.push_back(buffer[i * 2 + 1]); + } + + // TODO(dkalenichenko): Delete the extra input? + + return true; +} +} // namespace toco diff --git a/tensorflow/contrib/lite/toco/import_tensorflow.cc b/tensorflow/contrib/lite/toco/import_tensorflow.cc index 453ff29b0d..532fcdd808 100644 --- a/tensorflow/contrib/lite/toco/import_tensorflow.cc +++ b/tensorflow/contrib/lite/toco/import_tensorflow.cc @@ -925,6 +925,19 @@ void ConvertPadOperator(const NodeDef& node, model->operators.emplace_back(op); } +void ConvertPadV2Operator(const NodeDef& node, + const TensorFlowImportFlags& tf_import_flags, + Model* model) { + CHECK_EQ(node.op(), "PadV2"); + CheckInputsCount(node, tf_import_flags, 3); + auto* op = new PadV2Operator; + op->inputs.push_back(node.input(0)); + op->inputs.push_back(node.input(1)); + op->inputs.push_back(node.input(2)); + op->outputs.push_back(node.name()); + model->operators.emplace_back(op); +} + void ConvertShapeOperator(const NodeDef& node, const TensorFlowImportFlags& tf_import_flags, Model* model) { @@ -2169,6 +2182,8 @@ Status ImportTensorFlowNode(const tensorflow::NodeDef& node, ConvertMergeOperator(node, tf_import_flags, model); } else if (node.op() == "Pad") { ConvertPadOperator(node, tf_import_flags, model); + } else if (node.op() == "PadV2") { + ConvertPadV2Operator(node, tf_import_flags, model); } else if (node.op() == "StridedSlice") { ConvertStridedSliceOperator(node, tf_import_flags, model); } else if (node.op() == "Shape") { diff --git a/tensorflow/contrib/lite/toco/model.h b/tensorflow/contrib/lite/toco/model.h index 482cc71d8b..7ee7841511 100644 --- a/tensorflow/contrib/lite/toco/model.h +++ b/tensorflow/contrib/lite/toco/model.h @@ -82,6 +82,7 @@ enum class OperatorType { kStack, kBatchToSpaceND, kPad, + kPadV2, kStridedSlice, kSlice, kSqueeze, @@ -825,6 +826,29 @@ struct PadOperator : Operator { std::vector<int> right_padding; }; +// PaddingV2 operator. Pads a tensor with the given constant value. +// +// Inputs: +// inputs[0]: required: the input array +// inputs[1]: required: the padding array +// inputs[2]: required: the scalar constant_values +// +// This operation pads input according to the paddings and constant_values you +// specify. paddings is an integer tensor with shape [Dn, 2], where n is the +// rank of input. For each dimension D of input, paddings[D, 0] indicates how +// many padding values to add before the contents of input in that dimension, +// and paddings[D, 1] indicates how many padding values to add after the +// contents of input in that dimension. constant_values is a scalar tensor of +// the same type as input that indicates the value to use for padding input. +// +// TensorFlow equivalent: PadV2 +struct PadV2Operator : Operator { + PadV2Operator() : Operator(OperatorType::kPadV2) {} + + std::vector<int> left_padding; + std::vector<int> right_padding; +}; + // Strided slice operator. // // Inputs: diff --git a/tensorflow/contrib/lite/toco/tflite/operator.cc b/tensorflow/contrib/lite/toco/tflite/operator.cc index e18ae805c0..df784a2a76 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator.cc @@ -465,6 +465,21 @@ class Pad : public BuiltinOperator<PadOperator, ::tflite::PadOptions, TocoOperator* op) const override {} }; +class PadV2 : public BuiltinOperator<PadV2Operator, ::tflite::PadV2Options, + ::tflite::BuiltinOptions_PadV2Options> { + public: + using BuiltinOperator::BuiltinOperator; + + flatbuffers::Offset<TfLiteOptions> WriteOptions( + const TocoOperator& op, + flatbuffers::FlatBufferBuilder* builder) const override { + return ::tflite::CreatePadV2Options(*builder); + } + + void ReadOptions(const TfLiteOptions& options, + TocoOperator* op) const override {} +}; + class Reshape : public BuiltinOperator<TensorFlowReshapeOperator, ::tflite::ReshapeOptions, @@ -832,6 +847,8 @@ std::vector<std::unique_ptr<BaseOperator>> BuildOperatorList() { OperatorType::kMaxPool)); ops.emplace_back(new Mul(::tflite::BuiltinOperator_MUL, OperatorType::kMul)); ops.emplace_back(new Pad(::tflite::BuiltinOperator_PAD, OperatorType::kPad)); + ops.emplace_back( + new PadV2(::tflite::BuiltinOperator_PADV2, OperatorType::kPadV2)); ops.emplace_back(new Reshape(::tflite::BuiltinOperator_RESHAPE, OperatorType::kTensorFlowReshape)); ops.emplace_back( diff --git a/tensorflow/contrib/lite/toco/toco_tooling.cc b/tensorflow/contrib/lite/toco/toco_tooling.cc index 6973b22c5a..58c99051bd 100644 --- a/tensorflow/contrib/lite/toco/toco_tooling.cc +++ b/tensorflow/contrib/lite/toco/toco_tooling.cc @@ -106,6 +106,7 @@ void MakeGeneralGraphTransformationsSet( transformations->Add(new ResolveSpaceToBatchNDAttributes); transformations->Add(new ResolveBatchToSpaceNDAttributes); transformations->Add(new ResolvePadAttributes); + transformations->Add(new ResolvePadV2Attributes); transformations->Add(new ResolveStridedSliceAttributes); transformations->Add(new ResolveSliceAttributes); transformations->Add(new ResolveMeanAttributes); diff --git a/tensorflow/contrib/lite/toco/tooling_util.cc b/tensorflow/contrib/lite/toco/tooling_util.cc index 341d45e753..f82bb33535 100644 --- a/tensorflow/contrib/lite/toco/tooling_util.cc +++ b/tensorflow/contrib/lite/toco/tooling_util.cc @@ -356,6 +356,7 @@ const char* OperatorTypeName(OperatorType type) { HANDLE_OPERATORTYPENAME_CASE(TensorFlowMinimum) HANDLE_OPERATORTYPENAME_CASE(Neg) HANDLE_OPERATORTYPENAME_CASE(Pad) + HANDLE_OPERATORTYPENAME_CASE(PadV2) HANDLE_OPERATORTYPENAME_CASE(StridedSlice) HANDLE_OPERATORTYPENAME_CASE(Stack) HANDLE_OPERATORTYPENAME_CASE(Range) |