| /* 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 <vector> |
| |
| #include "tensorflow/core/common_runtime/dma_helper.h" |
| #include "tensorflow/core/common_runtime/kernel_benchmark_testlib.h" |
| #include "tensorflow/core/common_runtime/scoped_allocator.h" |
| #include "tensorflow/core/common_runtime/scoped_allocator_mgr.h" |
| #include "tensorflow/core/framework/fake_input.h" |
| #include "tensorflow/core/framework/node_def_builder.h" |
| #include "tensorflow/core/framework/op.h" |
| #include "tensorflow/core/framework/op_kernel.h" |
| #include "tensorflow/core/framework/tensor.h" |
| #include "tensorflow/core/framework/tensor_shape.h" |
| #include "tensorflow/core/framework/tensor_types.h" |
| #include "tensorflow/core/graph/graph.h" |
| #include "tensorflow/core/graph/node_builder.h" |
| #include "tensorflow/core/graph/testlib.h" |
| #include "tensorflow/core/kernels/ops_testutil.h" |
| #include "tensorflow/core/platform/test_benchmark.h" |
| #include "tensorflow/core/platform/types.h" |
| |
| namespace tensorflow { |
| |
| class ScopedAllocatorOpTest : public OpsTestBase { |
| protected: |
| void MakeOp(const TensorShape& shape, |
| const gtl::ArraySlice<TensorShape>& shapes, DataType dtype, |
| const string& name, int32 id, int32 expected_call_count) { |
| TF_EXPECT_OK(NodeDefBuilder("scoped_allocator_op", "_ScopedAllocator") |
| .Attr("T", dtype) |
| .Attr("shape", shape) |
| .Attr("shapes", shapes) |
| .Attr("sa_name", name) |
| .Attr("id", id) |
| .Attr("expected_call_count", expected_call_count) |
| .Finalize(node_def())); |
| TF_EXPECT_OK(InitOp()); |
| TF_ASSERT_OK(RunOpKernel()); |
| |
| // Allocate and Deallocate the tensors so that memory is not leaked |
| AllocatorAttributes attr; |
| Allocator* allocator; |
| for (size_t i = 0; i < shapes.size(); i++) { |
| attr.scope_id = id + i + 1; |
| allocator = device_->GetScopedAllocator(attr, context_->step_id()); |
| Tensor temp(allocator, dtype, shapes[i]); |
| } |
| } |
| }; |
| |
| TEST_F(ScopedAllocatorOpTest, Simple) { |
| MakeOp(TensorShape({8}), {TensorShape({8})}, DT_FLOAT, "test", 120, 1); |
| MakeOp(TensorShape({1024}), {TensorShape({32, 32})}, DT_DOUBLE, "test1", 130, |
| 1); |
| MakeOp(TensorShape({204}), |
| {TensorShape({64}), TensorShape({3, 3}), TensorShape({5, 5, 5})}, |
| DT_HALF, "test2", 140, 3); |
| MakeOp(TensorShape({1024}), {TensorShape({512}), TensorShape({64, 8})}, |
| DT_UINT32, "test3", 150, 2); |
| } |
| |
| // PrepOp is common to ConcatOp tests and SplitOpTests. |
| // It allocates a backing tensor that is large enough to hold all slices defined |
| // by fields, creates ScopedAllocatorInstances for each field, allocates the |
| // tensors, and assigns them as inputs to the op. |
| // We won't use the AddInput* suite of functions from ops_testutil.h because |
| // they allocate new tensors for each input. We need to mimic what a |
| // ScopedAllocator would do. |
| void PrepOp(DataType dtype, int32 id, |
| const std::vector<TensorShape>& fields_shapes, |
| std::vector<ScopedAllocator::Field>* fields, |
| Tensor** backing_tensor, Allocator* allocator, |
| ScopedAllocatorMgr* sam, const string& op_name, |
| std::vector<Tensor>* tensors, |
| gtl::InlinedVector<TensorValue, 4>* inputs, |
| const DataTypeVector& input_types) { |
| ScopedAllocatorMgr::PopulateFields(id, fields_shapes, dtype, fields); |
| // We don't simply allocate a tensor with shape as backing_tensor_shape, |
| // because we need to account for padding in the fields. We actually need a |
| // tensor of size at least (fields[-1].offset + fields[-1].bytes). |
| size_t num_bytes = fields->back().offset + fields->back().bytes; |
| int32_t num_elements = num_bytes / DataTypeSize(dtype); |
| CHECK_EQ(num_bytes % DataTypeSize(dtype), 0); |
| |
| *backing_tensor = new Tensor(allocator, dtype, {num_elements}); |
| int64 step_id = 10; |
| Status s = sam->AddScopedAllocator(**backing_tensor, step_id, id, |
| "sa_" + op_name + "_test", *fields, |
| fields_shapes.size()); |
| TF_ASSERT_OK(s); |
| |
| ScopedAllocatorContainer* sac = sam->GetContainer(step_id); |
| std::vector<ScopedAllocatorInstance*> sa_instances(fields_shapes.size(), |
| nullptr); |
| for (size_t i = 0; i < fields_shapes.size(); i++) { |
| sa_instances[i] = sac->GetInstance(id + i + 1); |
| tensors->push_back(Tensor(sa_instances[i], dtype, fields_shapes[i])); |
| } |
| // Now add the tensor as an input to ScopedAllocator<op_name>Op. |
| // Order matters here, so first add the backing tensor, then the slices. |
| inputs->reserve(1 + tensors->size()); |
| CHECK_GT(input_types.size(), inputs->size()); |
| CHECK_EQ(input_types[inputs->size()], dtype); |
| inputs->push_back({nullptr, *backing_tensor}); |
| for (size_t i = 0; i < tensors->size(); i++) { |
| CHECK_EQ(input_types[inputs->size()], dtype); |
| inputs->push_back({nullptr, &((*tensors)[i])}); |
| } |
| } |
| |
| class ScopedAllocatorConcatOpTest : public OpsTestBase { |
| protected: |
| void BuildNodeDef(const TensorShape& shape, DataType dtype, |
| const string& name, int32 id, int32 num_tensors) { |
| TF_EXPECT_OK( |
| NodeDefBuilder("scoped_allocator_concat_op", "_ScopedAllocatorConcat") |
| .Attr("shape", shape) |
| .Attr("T", dtype) |
| .Attr("N", num_tensors) |
| .Attr("sa_name", name) |
| .Attr("id", id) |
| .Input(FakeInput(dtype)) // backing tensor |
| .Input(FakeInput(num_tensors, dtype)) // list of tensors |
| .Finalize(node_def())); |
| shape_ = shape; |
| reshape_ = false; |
| } |
| |
| void BuildNodeDefWithReshape(const TensorShape& shape, DataType dtype, |
| bool reshape, const string& name, int32 id, |
| int32 num_tensors) { |
| TF_EXPECT_OK( |
| NodeDefBuilder("scoped_allocator_concat_op", "_ScopedAllocatorConcat") |
| .Attr("shape", shape) |
| .Attr("T", dtype) |
| .Attr("reshape", reshape) |
| .Attr("N", num_tensors) |
| .Attr("sa_name", name) |
| .Attr("id", id) |
| .Input(FakeInput(dtype)) // backing tensor |
| .Input(FakeInput(num_tensors, dtype)) // list of tensors |
| .Finalize(node_def())); |
| shape_ = shape; |
| reshape_ = reshape; |
| } |
| |
| void MakeOp(const TensorShape& shape, DataType dtype, bool reshape, |
| const string& name, int32 id, int32 num_tensors) { |
| BuildNodeDefWithReshape(shape, dtype, reshape, name, id, num_tensors); |
| TF_EXPECT_OK(InitOp()); |
| } |
| |
| void ExecOp(DataType dtype, int32 id, |
| const std::vector<TensorShape>& fields_shapes) { |
| Tensor* backing_tensor = nullptr; |
| std::vector<Tensor> tensors; |
| std::vector<ScopedAllocator::Field> fields; |
| PrepOp(dtype, id, fields_shapes, &fields, &backing_tensor, allocator(), |
| device_->GetScopedAllocatorMgr(), "concat", &tensors, &inputs_, |
| input_types_); |
| |
| TF_ASSERT_OK(RunOpKernel()); |
| |
| // Check input and output are same tensor. |
| const Tensor& input = context_->input(0); |
| OpOutputList output_list; |
| Status s = context_->output_list("output", &output_list); |
| TF_ASSERT_OK(s); |
| const Tensor& output = *(output_list[0]); |
| CHECK_EQ(DMAHelper::base(&input), DMAHelper::base(&output)); |
| CHECK_EQ(input.dtype(), output.dtype()); |
| CHECK_EQ(input.NumElements(), output.NumElements()); |
| if (reshape_) { |
| CHECK_EQ(shape_, output.shape()); |
| } else { |
| TensorShape expected_shape({input.NumElements()}); |
| CHECK_EQ(expected_shape, output.shape()); |
| } |
| |
| // Free the backing tensor which was allocated in PrepOp. |
| delete backing_tensor; |
| } |
| |
| private: |
| TensorShape shape_; |
| bool reshape_; |
| }; |
| |
| TEST_F(ScopedAllocatorConcatOpTest, Success1) { |
| MakeOp({32}, DT_FLOAT, false, "test", 120, 2); |
| ExecOp(DT_FLOAT, 120, {{16}, {16}}); |
| } |
| |
| TEST_F(ScopedAllocatorConcatOpTest, Success2) { |
| MakeOp({2, 2, 2}, DT_DOUBLE, false, "test", 120, 2); |
| ExecOp(DT_DOUBLE, 120, {{2, 2}, {2, 2}}); |
| } |
| |
| TEST_F(ScopedAllocatorConcatOpTest, Success3) { |
| MakeOp({3, 3, 3}, DT_HALF, false, "test", 120, 3); |
| ExecOp(DT_HALF, 120, {{3, 3}, {3, 3}, {3, 3}}); |
| } |
| |
| TEST_F(ScopedAllocatorConcatOpTest, Reshape) { |
| MakeOp({2, 2, 4}, DT_DOUBLE, true, "test", 120, 2); |
| |
| // The elements of the third parameter to ExecOp must be multiples of |
| // Allocator::kAllocatorAlignment in size. If they are not, the backing |
| // tensor allocated by PrepOp will have too many elements and reshaping |
| // will fail. |
| ExecOp(DT_DOUBLE, 120, {{2, 4}, {2, 4}}); |
| } |
| |
| TEST_F(ScopedAllocatorConcatOpTest, NoReshapeAttr) { |
| BuildNodeDef({3, 4, 4}, DT_HALF, "test", 120, 3); |
| TF_EXPECT_OK(InitOp()); |
| ExecOp(DT_HALF, 120, {{4, 4}, {4, 4}, {4, 4}}); |
| } |
| |
| TEST_F(ScopedAllocatorConcatOpTest, FailDtypeCheck) { |
| MakeOp({8}, DT_FLOAT, false, "test", 120, 2); |
| EXPECT_DEATH(ExecOp(DT_DOUBLE, 120, {{4}, {4}}), ""); |
| } |
| |
| TEST_F(ScopedAllocatorConcatOpTest, FailNumElementsCheck) { |
| MakeOp({32}, DT_FLOAT, false, "test", 120, 2); |
| AddInputFromArray<float>({8}, {0, 1, 2, 3, 4, 5, 6, 7}); |
| AddInputFromArray<float>({4}, {0, 1, 2, 3}); |
| AddInputFromArray<float>({4}, {4, 5, 6, 7}); |
| Status s = RunOpKernel(); |
| EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); |
| } |
| |
| // This test should fail because the backing tensor and the input tensors are |
| // unrelated, i.e. the inputs are not slices of the backing tensor. |
| TEST_F(ScopedAllocatorConcatOpTest, FailBounds) { |
| MakeOp({8}, DT_DOUBLE, false, "test", 120, 2); |
| AddInputFromArray<double>({8}, {0, 1, 2, 3, 4, 5, 6, 7}); |
| AddInputFromArray<double>({4}, {0, 1, 2, 3}); |
| AddInputFromArray<double>({4}, {4, 5, 6, 7}); |
| Status s = RunOpKernel(); |
| EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); |
| } |
| |
| class ScopedAllocatorSplitOpTest : public OpsTestBase { |
| protected: |
| void BuildNodeDef(const TensorShape& in_shape, DataType dtype, |
| const string& name, int32 id, int32 num_tensors, |
| const std::vector<TensorShape>& out_shapes) { |
| TF_EXPECT_OK( |
| NodeDefBuilder("scoped_allocator_split_op", "_ScopedAllocatorSplit") |
| .Attr("T", dtype) |
| .Attr("N", num_tensors) |
| .Attr("sa_name", name) |
| .Attr("id", id) |
| .Attr("shapes", out_shapes) |
| .Input(FakeInput(dtype)) // backing tensor and input |
| .Input( |
| FakeInput(num_tensors, dtype)) // list of subtensors to forward |
| .Finalize(node_def())); |
| } |
| |
| void MakeOp(const TensorShape& in_shape, DataType dtype, const string& name, |
| int32 id, int32 num_tensors, |
| const std::vector<TensorShape>& out_shapes) { |
| BuildNodeDef(in_shape, dtype, name, id, num_tensors, out_shapes); |
| TF_EXPECT_OK(InitOp()); |
| } |
| |
| // Similar to ConcatOpTest, we add inputs that are allocated from |
| // ScopedAllocator so that the memory lines up nicely. |
| void ExecOp(DataType dtype, int32 id, |
| const std::vector<TensorShape>& fields_shapes) { |
| Tensor* backing_tensor = nullptr; |
| std::vector<Tensor> tensors; |
| std::vector<ScopedAllocator::Field> fields; |
| PrepOp(dtype, id, fields_shapes, &fields, &backing_tensor, allocator(), |
| device_->GetScopedAllocatorMgr(), "split", &tensors, &inputs_, |
| input_types_); |
| |
| TF_ASSERT_OK(RunOpKernel()); |
| |
| // Check that outputs are slices of backing tensor. |
| const Tensor& input = context_->input(0); |
| const void* lower_limit = DMAHelper::base(&input); |
| const char* lower_limit_c = |
| static_cast<const char*>(lower_limit); // for pointer arithmetic |
| OpOutputList output_list; |
| Status s = context_->output_list("output", &output_list); |
| TF_ASSERT_OK(s); |
| for (int i = 0; i < output_list.size(); i++) { |
| const Tensor& output = *(output_list[i]); |
| const void* expected_base = |
| static_cast<const void*>(lower_limit_c + fields[i].offset); |
| CHECK_EQ(output.dtype(), input.dtype()); |
| CHECK_EQ(expected_base, DMAHelper::base(&output)); |
| CHECK_EQ(output.NumElements(), fields_shapes[i].num_elements()); |
| } |
| |
| // Free the backing tensor which was allocated in PrepOp. |
| delete backing_tensor; |
| } |
| }; |
| |
| TEST_F(ScopedAllocatorSplitOpTest, Success1) { |
| MakeOp({32}, DT_FLOAT, "test", 120, 2, {{16}, {16}}); |
| ExecOp(DT_FLOAT, 120, {{16}, {16}}); |
| } |
| |
| TEST_F(ScopedAllocatorSplitOpTest, Success2) { |
| MakeOp({2, 2, 2}, DT_DOUBLE, "test", 120, 2, {{2, 2}, {2, 2}}); |
| ExecOp(DT_DOUBLE, 120, {{2, 2}, {2, 2}}); |
| } |
| |
| TEST_F(ScopedAllocatorSplitOpTest, Success3) { |
| MakeOp({3, 3, 3}, DT_HALF, "test", 120, 3, {{3, 3}, {3, 3}, {3, 3}}); |
| ExecOp(DT_HALF, 120, {{3, 3}, {3, 3}, {3, 3}}); |
| } |
| |
| TEST_F(ScopedAllocatorSplitOpTest, FailNLessThan2) { |
| BuildNodeDef({4, 4}, DT_FLOAT, "test", 120, 1, {{4, 4}}); |
| Status s = InitOp(); |
| EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); |
| } |
| |
| TEST_F(ScopedAllocatorSplitOpTest, FailDtypeCheck) { |
| MakeOp({8}, DT_FLOAT, "test", 120, 2, {{4}, {4}}); |
| EXPECT_DEATH(ExecOp(DT_HALF, 120, {{4}, {4}}), ""); |
| } |
| |
| TEST_F(ScopedAllocatorSplitOpTest, FailBounds) { |
| MakeOp({8}, DT_DOUBLE, "test", 120, 2, {{4}, {4}}); |
| AddInputFromArray<double>({8}, {0, 1, 2, 3, 4, 5, 6, 7}); |
| AddInputFromArray<double>({4}, {0, 1, 2, 3}); |
| AddInputFromArray<double>({4}, {4, 5, 6, 7}); |
| Status s = RunOpKernel(); |
| EXPECT_EQ(s.code(), error::INVALID_ARGUMENT); |
| } |
| |
| } // end namespace tensorflow |