aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tensorflow/contrib/lite/kernels/conv.cc122
-rw-r--r--tensorflow/contrib/lite/kernels/conv_test.cc69
-rw-r--r--tensorflow/contrib/lite/kernels/test_util.cc11
-rw-r--r--tensorflow/contrib/lite/kernels/test_util.h52
4 files changed, 182 insertions, 72 deletions
diff --git a/tensorflow/contrib/lite/kernels/conv.cc b/tensorflow/contrib/lite/kernels/conv.cc
index 37f499a4d0..a5095e1e64 100644
--- a/tensorflow/contrib/lite/kernels/conv.cc
+++ b/tensorflow/contrib/lite/kernels/conv.cc
@@ -42,7 +42,7 @@ namespace conv {
enum KernelType {
kReference,
kGenericOptimized, // Neon-free
- kNeonOptimized,
+ kMultithreadOptimized,
};
struct OpData {
@@ -290,26 +290,33 @@ void EvalQuantized(TfLiteContext* context, TfLiteNode* node,
auto filter_offset = -filter->params.zero_point;
auto output_offset = output->params.zero_point;
- if (kernel_type == kReference) {
- reference_ops::Conv(
- GetTensorData<uint8_t>(input), GetTensorDims(input), input_offset,
- GetTensorData<uint8_t>(filter), GetTensorDims(filter), filter_offset,
- GetTensorData<int32_t>(bias), GetTensorDims(bias), params->stride_width,
- params->stride_height, data->padding.width, data->padding.height,
- output_offset, data->output_multiplier, data->output_shift,
- data->output_activation_min, data->output_activation_max,
- GetTensorData<uint8_t>(output), GetTensorDims(output),
- GetTensorData<uint8_t>(im2col), GetTensorDims(im2col), gemm_context);
- } else {
- optimized_ops::Conv(
- GetTensorData<uint8_t>(input), GetTensorDims(input), input_offset,
- GetTensorData<uint8_t>(filter), GetTensorDims(filter), filter_offset,
- GetTensorData<int32_t>(bias), GetTensorDims(bias), params->stride_width,
- params->stride_height, data->padding.width, data->padding.height,
- output_offset, data->output_multiplier, data->output_shift,
- data->output_activation_min, data->output_activation_max,
- GetTensorData<uint8_t>(output), GetTensorDims(output),
- GetTensorData<uint8_t>(im2col), GetTensorDims(im2col), gemm_context);
+ switch (kernel_type) {
+ case kReference:
+ reference_ops::Conv(
+ GetTensorData<uint8_t>(input), GetTensorDims(input), input_offset,
+ GetTensorData<uint8_t>(filter), GetTensorDims(filter), filter_offset,
+ GetTensorData<int32_t>(bias), GetTensorDims(bias),
+ params->stride_width, params->stride_height, data->padding.width,
+ data->padding.height, output_offset, data->output_multiplier,
+ data->output_shift, data->output_activation_min,
+ data->output_activation_max, GetTensorData<uint8_t>(output),
+ GetTensorDims(output), GetTensorData<uint8_t>(im2col),
+ GetTensorDims(im2col), gemm_context);
+ break;
+ case kGenericOptimized:
+ case kMultithreadOptimized:
+ // There is only one optimized implementation for Quantized Conv.
+ optimized_ops::Conv(
+ GetTensorData<uint8_t>(input), GetTensorDims(input), input_offset,
+ GetTensorData<uint8_t>(filter), GetTensorDims(filter), filter_offset,
+ GetTensorData<int32_t>(bias), GetTensorDims(bias),
+ params->stride_width, params->stride_height, data->padding.width,
+ data->padding.height, output_offset, data->output_multiplier,
+ data->output_shift, data->output_activation_min,
+ data->output_activation_max, GetTensorData<uint8_t>(output),
+ GetTensorDims(output), GetTensorData<uint8_t>(im2col),
+ GetTensorDims(im2col), gemm_context);
+ break;
}
}
@@ -322,31 +329,46 @@ void EvalFloat(TfLiteContext* context, TfLiteNode* node,
CalculateActivationRangeFloat(params->activation, &output_activation_min,
&output_activation_max);
- if (kernel_type == kReference) {
- reference_ops::Conv(GetTensorData<float>(input), GetTensorDims(input),
- GetTensorData<float>(filter), GetTensorDims(filter),
- GetTensorData<float>(bias), GetTensorDims(bias),
- params->stride_width, params->stride_height,
- data->padding.width, data->padding.height,
- output_activation_min, output_activation_max,
- GetTensorData<float>(output), GetTensorDims(output),
- GetTensorData<float>(im2col), GetTensorDims(im2col));
- } else {
- const float* filter_data;
- if (data->need_hwcn_weights) {
- filter_data = GetTensorData<float>(hwcn_weights);
- } else {
- filter_data = GetTensorData<float>(filter);
+ switch (kernel_type) {
+ case kReference: {
+ reference_ops::Conv(GetTensorData<float>(input), GetTensorDims(input),
+ GetTensorData<float>(filter), GetTensorDims(filter),
+ GetTensorData<float>(bias), GetTensorDims(bias),
+ params->stride_width, params->stride_height,
+ data->padding.width, data->padding.height,
+ output_activation_min, output_activation_max,
+ GetTensorData<float>(output), GetTensorDims(output),
+ GetTensorData<float>(im2col), GetTensorDims(im2col));
+ break;
+ }
+ case kGenericOptimized: {
+ optimized_ops::Conv(GetTensorData<float>(input), GetTensorDims(input),
+ GetTensorData<float>(filter), GetTensorDims(filter),
+ GetTensorData<float>(bias), GetTensorDims(bias),
+ params->stride_width, params->stride_height,
+ data->padding.width, data->padding.height,
+ output_activation_min, output_activation_max,
+ GetTensorData<float>(output), GetTensorDims(output),
+ GetTensorData<float>(im2col), GetTensorDims(im2col));
+ break;
+ }
+ case kMultithreadOptimized: {
+ const float* filter_data;
+ if (data->need_hwcn_weights) {
+ filter_data = GetTensorData<float>(hwcn_weights);
+ } else {
+ filter_data = GetTensorData<float>(filter);
+ }
+ multithreaded_ops::Conv(
+ GetTensorData<float>(input), GetTensorDims(input), filter_data,
+ GetTensorDims(filter), GetTensorData<float>(bias),
+ GetTensorDims(bias), params->stride_width, params->stride_height,
+ data->padding.width, data->padding.height, params->padding,
+ output_activation_min, output_activation_max,
+ GetTensorData<float>(output), GetTensorDims(output),
+ GetTensorData<float>(im2col), GetTensorDims(im2col));
+ break;
}
-
- multithreaded_ops::Conv(
- GetTensorData<float>(input), GetTensorDims(input), filter_data,
- GetTensorDims(filter), GetTensorData<float>(bias), GetTensorDims(bias),
- params->stride_width, params->stride_height, data->padding.width,
- data->padding.height, params->padding, output_activation_min,
- output_activation_max, GetTensorData<float>(output),
- GetTensorDims(output), GetTensorData<float>(im2col),
- GetTensorDims(im2col));
}
}
@@ -407,18 +429,14 @@ TfLiteRegistration* Register_CONVOLUTION_GENERIC_OPT() {
return &r;
}
-TfLiteRegistration* Register_CONVOLUTION_NEON_OPT() {
+TfLiteRegistration* Register_CONVOLUTION_MULTITHREADED_OPT() {
static TfLiteRegistration r = {conv::Init, conv::Free, conv::Prepare,
- conv::Eval<conv::kNeonOptimized>};
+ conv::Eval<conv::kMultithreadOptimized>};
return &r;
}
TfLiteRegistration* Register_CONV_2D() {
-#ifdef USE_NEON
- return Register_CONVOLUTION_NEON_OPT();
-#else
- return Register_CONVOLUTION_GENERIC_OPT();
-#endif
+ return Register_CONVOLUTION_MULTITHREADED_OPT();
}
} // namespace builtin
diff --git a/tensorflow/contrib/lite/kernels/conv_test.cc b/tensorflow/contrib/lite/kernels/conv_test.cc
index 1d0a81c313..461efffe39 100644
--- a/tensorflow/contrib/lite/kernels/conv_test.cc
+++ b/tensorflow/contrib/lite/kernels/conv_test.cc
@@ -21,6 +21,17 @@ limitations under the License.
#include "tensorflow/contrib/lite/model.h"
namespace tflite {
+
+namespace ops {
+namespace builtin {
+
+TfLiteRegistration* Register_CONVOLUTION_REF();
+TfLiteRegistration* Register_CONVOLUTION_GENERIC_OPT();
+TfLiteRegistration* Register_CONVOLUTION_MULTITHREADED_OPT();
+
+} // namespace builtin
+} // namespace ops
+
namespace {
using ::testing::ElementsAreArray;
@@ -30,9 +41,9 @@ class BaseConvolutionOpModel : public SingleOpModel {
// TODO(ahentz): Also test different activation types, bias, padding types,
// stride values.
BaseConvolutionOpModel(
- const TensorData& input, const TensorData& filter,
- const TensorData& output, int stride_width = 2, int stride_height = 2,
- enum Padding padding = Padding_VALID,
+ TfLiteRegistration* registration, const TensorData& input,
+ const TensorData& filter, const TensorData& output, int stride_width = 2,
+ int stride_height = 2, enum Padding padding = Padding_VALID,
enum ActivationFunctionType activation = ActivationFunctionType_NONE) {
input_ = AddInput(input);
filter_ = AddInput(filter);
@@ -62,6 +73,8 @@ class BaseConvolutionOpModel : public SingleOpModel {
stride_height, activation)
.Union());
+ resolver_ = absl::make_unique<SingleOpResolver>(BuiltinOperator_CONV_2D,
+ registration);
BuildInterpreter({GetShape(input_), GetShape(filter_), GetShape(bias_)});
}
@@ -83,12 +96,25 @@ class ConvolutionOpModel : public BaseConvolutionOpModel {
void SetInput(std::initializer_list<float> data) {
PopulateTensor(input_, data);
}
-
std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
};
-TEST(ConvolutionOpTest, SimpleTestFloat32) {
- ConvolutionOpModel m({TensorType_FLOAT32, {2, 2, 4, 1}},
+const auto kKernelMap = new std::map<string, TfLiteRegistration*>({
+ {"Reference", ops::builtin::Register_CONVOLUTION_REF()},
+ {"GenericOptimized", ops::builtin::Register_CONVOLUTION_GENERIC_OPT()},
+ {"MultithreadedOptimized",
+ ops::builtin::Register_CONVOLUTION_MULTITHREADED_OPT()},
+});
+
+class ConvolutionOpTest : public SingleOpTest {
+ protected:
+ const std::map<string, TfLiteRegistration*>& GetKernelMap() override {
+ return *kKernelMap;
+ }
+};
+
+TEST_P(ConvolutionOpTest, SimpleTestFloat32) {
+ ConvolutionOpModel m(GetRegistration(), {TensorType_FLOAT32, {2, 2, 4, 1}},
{TensorType_FLOAT32, {3, 2, 2, 1}},
{TensorType_FLOAT32, {}});
@@ -117,8 +143,8 @@ TEST(ConvolutionOpTest, SimpleTestFloat32) {
}));
}
-TEST(ConvolutionOpTest, SimpleTestFloat32WithAnisotropicStrides) {
- ConvolutionOpModel m({TensorType_FLOAT32, {1, 3, 6, 1}},
+TEST_P(ConvolutionOpTest, SimpleTestFloat32WithAnisotropicStrides) {
+ ConvolutionOpModel m(GetRegistration(), {TensorType_FLOAT32, {1, 3, 6, 1}},
{TensorType_FLOAT32, {1, 2, 2, 1}},
{TensorType_FLOAT32, {}},
/*stride_width=*/3, /*stride_height=*/1);
@@ -139,7 +165,7 @@ TEST(ConvolutionOpTest, SimpleTestFloat32WithAnisotropicStrides) {
}));
}
-TEST(ConvolutionOpTest, HandCalculatedFloat32) {
+TEST_P(ConvolutionOpTest, HandCalculatedFloat32) {
const int depth = 1;
const int image_width = 4;
const int image_height = 3;
@@ -150,6 +176,7 @@ TEST(ConvolutionOpTest, HandCalculatedFloat32) {
const int stride_height = 1;
const Padding padding = Padding_SAME;
ConvolutionOpModel m(
+ GetRegistration(),
{TensorType_FLOAT32,
{image_batch_count, image_height, image_width, depth}},
{TensorType_FLOAT32, {depth, filter_size, filter_size, filter_count}},
@@ -192,7 +219,7 @@ TEST(ConvolutionOpTest, HandCalculatedFloat32) {
178, 187, 234, 261, 121}));
}
-TEST(ConvolutionOpTest, HandCalculatedWithBiasFloat32) {
+TEST_P(ConvolutionOpTest, HandCalculatedWithBiasFloat32) {
const int depth = 1;
const int image_width = 4;
const int image_height = 3;
@@ -203,6 +230,7 @@ TEST(ConvolutionOpTest, HandCalculatedWithBiasFloat32) {
const int stride_height = 1;
const Padding padding = Padding_SAME;
ConvolutionOpModel m(
+ GetRegistration(),
{TensorType_FLOAT32,
{image_batch_count, image_height, image_width, depth}},
{TensorType_FLOAT32, {depth, filter_size, filter_size, filter_count}},
@@ -245,7 +273,7 @@ TEST(ConvolutionOpTest, HandCalculatedWithBiasFloat32) {
367, 188, 197, 244, 271, 131}));
}
-TEST(ConvolutionOpTest, HandCalculatedWithReluFloat32) {
+TEST_P(ConvolutionOpTest, HandCalculatedWithReluFloat32) {
const int depth = 1;
const int image_width = 4;
const int image_height = 3;
@@ -256,6 +284,7 @@ TEST(ConvolutionOpTest, HandCalculatedWithReluFloat32) {
const int stride_height = 1;
const Padding padding = Padding_SAME;
ConvolutionOpModel m(
+ GetRegistration(),
{TensorType_FLOAT32,
{image_batch_count, image_height, image_width, depth}},
{TensorType_FLOAT32, {depth, filter_size, filter_size, filter_count}},
@@ -300,7 +329,7 @@ TEST(ConvolutionOpTest, HandCalculatedWithReluFloat32) {
ElementsAreArray({0, 0, 0, 0, 35, 112, 157, 0, 0, 34, 61, 0}));
}
-TEST(ConvolutionOpTest, HandCalculatedValidFloat32) {
+TEST_P(ConvolutionOpTest, HandCalculatedValidFloat32) {
const int depth = 1;
const int image_width = 4;
const int image_height = 3;
@@ -311,6 +340,7 @@ TEST(ConvolutionOpTest, HandCalculatedValidFloat32) {
const int stride_height = 1;
const Padding padding = Padding_VALID;
ConvolutionOpModel m(
+ GetRegistration(),
{TensorType_FLOAT32,
{image_batch_count, image_height, image_width, depth}},
{TensorType_FLOAT32, {depth, filter_size, filter_size, filter_count}},
@@ -366,8 +396,9 @@ class QuantizedConvolutionOpModel : public BaseConvolutionOpModel {
// In this tests we set the input and output scales so that the results
// match exactly the 'non-quantized' version.
-TEST(ConvolutionOpTest, SimpleTestQuantized) {
- QuantizedConvolutionOpModel m({TensorType_UINT8, {2, 2, 4, 1}, -63.5, 64},
+TEST_P(ConvolutionOpTest, SimpleTestQuantized) {
+ QuantizedConvolutionOpModel m(GetRegistration(),
+ {TensorType_UINT8, {2, 2, 4, 1}, -63.5, 64},
{TensorType_UINT8, {3, 2, 2, 1}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128});
m.SetInput({
@@ -405,8 +436,9 @@ TEST(ConvolutionOpTest, SimpleTestQuantized) {
}));
}
-TEST(ConvolutionOpTest, SimpleTestQuantizedWithAnisotropicStrides) {
- QuantizedConvolutionOpModel m({TensorType_UINT8, {1, 3, 6, 1}, -63.5, 64},
+TEST_P(ConvolutionOpTest, SimpleTestQuantizedWithAnisotropicStrides) {
+ QuantizedConvolutionOpModel m(GetRegistration(),
+ {TensorType_UINT8, {1, 3, 6, 1}, -63.5, 64},
{TensorType_UINT8, {1, 2, 2, 1}, -63.5, 64},
{TensorType_UINT8, {}, -127, 128},
/*stride_width=*/3, /*stride_height=*/1);
@@ -430,6 +462,11 @@ TEST(ConvolutionOpTest, SimpleTestQuantizedWithAnisotropicStrides) {
167, 93, //
}));
}
+
+INSTANTIATE_TEST_CASE_P(
+ ConvolutionOpTest, ConvolutionOpTest,
+ ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap)));
+
} // namespace
} // namespace tflite
diff --git a/tensorflow/contrib/lite/kernels/test_util.cc b/tensorflow/contrib/lite/kernels/test_util.cc
index 3a58e7ec32..6f56aa6bf3 100644
--- a/tensorflow/contrib/lite/kernels/test_util.cc
+++ b/tensorflow/contrib/lite/kernels/test_util.cc
@@ -172,11 +172,14 @@ void SingleOpModel::BuildInterpreter(
auto* model = GetModel(builder_.GetBufferPointer());
- ops::builtin::BuiltinOpResolver builtins;
- for (const auto& reg : custom_registrations_) {
- builtins.AddCustom(reg.first.data(), reg.second());
+ if (!resolver_) {
+ auto resolver = new ops::builtin::BuiltinOpResolver();
+ for (const auto& reg : custom_registrations_) {
+ resolver->AddCustom(reg.first.data(), reg.second());
+ }
+ resolver_ = std::unique_ptr<OpResolver>(resolver);
}
- InterpreterBuilder(model, builtins)(&interpreter_);
+ InterpreterBuilder(model, *resolver_)(&interpreter_);
CHECK(interpreter_ != nullptr);
diff --git a/tensorflow/contrib/lite/kernels/test_util.h b/tensorflow/contrib/lite/kernels/test_util.h
index cc445299ff..7d476ba1ea 100644
--- a/tensorflow/contrib/lite/kernels/test_util.h
+++ b/tensorflow/contrib/lite/kernels/test_util.h
@@ -85,6 +85,23 @@ struct TensorData {
int32_t zero_point;
};
+class SingleOpResolver : public OpResolver {
+ public:
+ SingleOpResolver(const BuiltinOperator op, TfLiteRegistration* registration)
+ : op_(op), registration_(registration) {}
+ TfLiteRegistration* FindOp(BuiltinOperator op) const override {
+ if (op == op_) {
+ return registration_;
+ }
+ return nullptr;
+ }
+ TfLiteRegistration* FindOp(const char* op) const override { return nullptr; }
+
+ private:
+ const BuiltinOperator op_;
+ TfLiteRegistration* registration_;
+};
+
class SingleOpModel {
public:
SingleOpModel() {}
@@ -178,11 +195,16 @@ class SingleOpModel {
return result;
}
+ void SetResolver(std::unique_ptr<OpResolver> resolver) {
+ resolver_ = std::move(resolver);
+ }
+
protected:
int32_t GetTensorSize(int index) const;
flatbuffers::FlatBufferBuilder builder_;
std::unique_ptr<tflite::Interpreter> interpreter_;
+ std::unique_ptr<OpResolver> resolver_;
private:
int AddTensor(TensorData t, std::initializer_list<int> data);
@@ -197,6 +219,36 @@ class SingleOpModel {
std::map<string, std::function<TfLiteRegistration*()>> custom_registrations_;
};
+// Base class for single op unit tests.
+// The tests are parameterized to test multiple kernels for a single op.
+// The parameters are strings like "optimized" and "reference" to have better
+// readability in test reports.
+//
+// To use this class:
+// * Define a constant map from strings to TfLiteRegistration.
+// * Implement a test class that inherits SingleOpTest.
+// * Instantiate the test cases with SingleOpTest::GetKernelTags helper
+// function.
+// * Call GetRegistration to get the TfLiteRegistration to be used before
+// building the interpreter.
+class SingleOpTest : public ::testing::TestWithParam<string> {
+ public:
+ static std::vector<string> GetKernelTags(
+ const std::map<string, TfLiteRegistration*>& kernel_map) {
+ std::vector<string> tags;
+ for (auto it : kernel_map) {
+ tags.push_back(it.first);
+ }
+ return tags;
+ }
+
+ protected:
+ virtual const std::map<string, TfLiteRegistration*>& GetKernelMap() = 0;
+ TfLiteRegistration* GetRegistration() {
+ return GetKernelMap().at(GetParam());
+ }
+};
+
// Strings have a special implementation that is in test_util.cc
template <>
std::vector<string> SingleOpModel::ExtractVector(int index);