blob: 634f9ba8876ea0b542fd995e32599686bd201cc5 [file] [log] [blame]
/* 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