1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include <vector>
17
18 #include "tensorflow/core/common_runtime/dma_helper.h"
19 #include "tensorflow/core/common_runtime/kernel_benchmark_testlib.h"
20 #include "tensorflow/core/common_runtime/scoped_allocator.h"
21 #include "tensorflow/core/common_runtime/scoped_allocator_mgr.h"
22 #include "tensorflow/core/framework/fake_input.h"
23 #include "tensorflow/core/framework/node_def_builder.h"
24 #include "tensorflow/core/framework/op.h"
25 #include "tensorflow/core/framework/op_kernel.h"
26 #include "tensorflow/core/framework/tensor.h"
27 #include "tensorflow/core/framework/tensor_shape.h"
28 #include "tensorflow/core/framework/tensor_types.h"
29 #include "tensorflow/core/graph/graph.h"
30 #include "tensorflow/core/graph/node_builder.h"
31 #include "tensorflow/core/graph/testlib.h"
32 #include "tensorflow/core/kernels/ops_testutil.h"
33 #include "tensorflow/core/lib/core/status_test_util.h"
34 #include "tensorflow/core/platform/test_benchmark.h"
35 #include "tensorflow/core/platform/types.h"
36
37 namespace tensorflow {
38
39 class ScopedAllocatorOpTest : public OpsTestBase {
40 protected:
MakeOp(const TensorShape & shape,const gtl::ArraySlice<TensorShape> shapes,DataType dtype,const string & name,int32_t id,int32_t expected_call_count)41 void MakeOp(const TensorShape& shape,
42 const gtl::ArraySlice<TensorShape> shapes, DataType dtype,
43 const string& name, int32_t id, int32_t expected_call_count) {
44 TF_EXPECT_OK(NodeDefBuilder("scoped_allocator_op", "_ScopedAllocator")
45 .Attr("T", dtype)
46 .Attr("shape", shape)
47 .Attr("shapes", shapes)
48 .Attr("sa_name", name)
49 .Attr("id", id)
50 .Attr("expected_call_count", expected_call_count)
51 .Finalize(node_def()));
52 TF_EXPECT_OK(InitOp());
53 TF_ASSERT_OK(RunOpKernel());
54
55 // Allocate and Deallocate the tensors so that memory is not leaked
56 AllocatorAttributes attr;
57 Allocator* allocator;
58 for (size_t i = 0; i < shapes.size(); i++) {
59 attr.scope_id = id + i + 1;
60 allocator = device_->GetScopedAllocator(attr, context_->step_id());
61 Tensor temp(allocator, dtype, shapes[i]);
62 }
63 }
64 };
65
TEST_F(ScopedAllocatorOpTest,Simple)66 TEST_F(ScopedAllocatorOpTest, Simple) {
67 MakeOp(TensorShape({8}), {TensorShape({8})}, DT_FLOAT, "test", 120, 1);
68 MakeOp(TensorShape({1024}), {TensorShape({32, 32})}, DT_DOUBLE, "test1", 130,
69 1);
70 MakeOp(TensorShape({204}),
71 {TensorShape({64}), TensorShape({3, 3}), TensorShape({5, 5, 5})},
72 DT_HALF, "test2", 140, 3);
73 MakeOp(TensorShape({1024}), {TensorShape({512}), TensorShape({64, 8})},
74 DT_UINT32, "test3", 150, 2);
75 }
76
77 // PrepOp is common to ConcatOp tests and SplitOpTests.
78 // It allocates a backing tensor that is large enough to hold all slices defined
79 // by fields, creates ScopedAllocatorInstances for each field, allocates the
80 // tensors, and assigns them as inputs to the op.
81 // We won't use the AddInput* suite of functions from ops_testutil.h because
82 // they allocate new tensors for each input. We need to mimic what a
83 // ScopedAllocator would do.
PrepOp(DataType dtype,int32_t 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)84 void PrepOp(DataType dtype, int32_t id,
85 const std::vector<TensorShape>& fields_shapes,
86 std::vector<ScopedAllocator::Field>* fields,
87 Tensor** backing_tensor, Allocator* allocator,
88 ScopedAllocatorMgr* sam, const string& op_name,
89 std::vector<Tensor>* tensors,
90 gtl::InlinedVector<TensorValue, 4>* inputs,
91 const DataTypeVector& input_types) {
92 ScopedAllocatorMgr::PopulateFields(id, fields_shapes, dtype, fields);
93 // We don't simply allocate a tensor with shape as backing_tensor_shape,
94 // because we need to account for padding in the fields. We actually need a
95 // tensor of size at least (fields[-1].offset + fields[-1].bytes_allocated).
96 size_t num_bytes = fields->back().offset + fields->back().bytes_allocated;
97 int32_t num_elements = num_bytes / DataTypeSize(dtype);
98 CHECK_EQ(num_bytes % DataTypeSize(dtype), 0);
99
100 *backing_tensor = new Tensor(allocator, dtype, {num_elements});
101 int64_t step_id = 10;
102 Status s = sam->AddScopedAllocator(**backing_tensor, step_id, id,
103 "sa_" + op_name + "_test", *fields,
104 fields_shapes.size());
105 TF_ASSERT_OK(s);
106
107 ScopedAllocatorContainer* sac = sam->GetContainer(step_id);
108 std::vector<ScopedAllocatorInstance*> sa_instances(fields_shapes.size(),
109 nullptr);
110 for (size_t i = 0; i < fields_shapes.size(); i++) {
111 sa_instances[i] = sac->GetInstance(id + i + 1);
112 tensors->push_back(Tensor(sa_instances[i], dtype, fields_shapes[i]));
113 }
114 // Now add the tensor as an input to ScopedAllocator<op_name>Op.
115 // Order matters here, so first add the backing tensor, then the slices.
116 inputs->reserve(1 + tensors->size());
117 CHECK_GT(input_types.size(), inputs->size());
118 CHECK_EQ(input_types[inputs->size()], dtype);
119 inputs->push_back({nullptr, *backing_tensor});
120 for (size_t i = 0; i < tensors->size(); i++) {
121 CHECK_EQ(input_types[inputs->size()], dtype);
122 inputs->push_back({nullptr, &((*tensors)[i])});
123 }
124 }
125
126 class ScopedAllocatorConcatOpTest : public OpsTestBase {
127 protected:
BuildNodeDef(const TensorShape & shape,DataType dtype,const string & name,int32_t id,int32_t num_tensors)128 void BuildNodeDef(const TensorShape& shape, DataType dtype,
129 const string& name, int32_t id, int32_t num_tensors) {
130 TF_EXPECT_OK(
131 NodeDefBuilder("scoped_allocator_concat_op", "_ScopedAllocatorConcat")
132 .Attr("shape", shape)
133 .Attr("T", dtype)
134 .Attr("N", num_tensors)
135 .Attr("sa_name", name)
136 .Attr("id", id)
137 .Input(FakeInput(dtype)) // backing tensor
138 .Input(FakeInput(num_tensors, dtype)) // list of tensors
139 .Finalize(node_def()));
140 shape_ = shape;
141 reshape_ = false;
142 }
143
BuildNodeDefWithReshape(const TensorShape & shape,DataType dtype,bool reshape,const string & name,int32_t id,int32_t num_tensors)144 void BuildNodeDefWithReshape(const TensorShape& shape, DataType dtype,
145 bool reshape, const string& name, int32_t id,
146 int32_t num_tensors) {
147 TF_EXPECT_OK(
148 NodeDefBuilder("scoped_allocator_concat_op", "_ScopedAllocatorConcat")
149 .Attr("shape", shape)
150 .Attr("T", dtype)
151 .Attr("reshape", reshape)
152 .Attr("N", num_tensors)
153 .Attr("sa_name", name)
154 .Attr("id", id)
155 .Input(FakeInput(dtype)) // backing tensor
156 .Input(FakeInput(num_tensors, dtype)) // list of tensors
157 .Finalize(node_def()));
158 shape_ = shape;
159 reshape_ = reshape;
160 }
161
MakeOp(const TensorShape & shape,DataType dtype,bool reshape,const string & name,int32_t id,int32_t num_tensors)162 void MakeOp(const TensorShape& shape, DataType dtype, bool reshape,
163 const string& name, int32_t id, int32_t num_tensors) {
164 BuildNodeDefWithReshape(shape, dtype, reshape, name, id, num_tensors);
165 TF_EXPECT_OK(InitOp());
166 }
167
ExecOp(DataType dtype,int32_t id,const std::vector<TensorShape> & fields_shapes)168 void ExecOp(DataType dtype, int32_t id,
169 const std::vector<TensorShape>& fields_shapes) {
170 Tensor* backing_tensor = nullptr;
171 std::vector<Tensor> tensors;
172 std::vector<ScopedAllocator::Field> fields;
173 PrepOp(dtype, id, fields_shapes, &fields, &backing_tensor, allocator(),
174 device_->GetScopedAllocatorMgr(), "concat", &tensors, &inputs_,
175 input_types_);
176
177 TF_ASSERT_OK(RunOpKernel());
178
179 // Check input and output are same tensor.
180 const Tensor& input = context_->input(0);
181 OpOutputList output_list;
182 Status s = context_->output_list("output", &output_list);
183 TF_ASSERT_OK(s);
184 const Tensor& output = *(output_list[0]);
185 CHECK_EQ(DMAHelper::base(&input), DMAHelper::base(&output));
186 CHECK_EQ(input.dtype(), output.dtype());
187 CHECK_EQ(input.NumElements(), output.NumElements());
188 if (reshape_) {
189 CHECK_EQ(shape_, output.shape());
190 } else {
191 TensorShape expected_shape({input.NumElements()});
192 CHECK_EQ(expected_shape, output.shape());
193 }
194
195 // Free the backing tensor which was allocated in PrepOp.
196 delete backing_tensor;
197 }
198
199 private:
200 TensorShape shape_;
201 bool reshape_;
202 };
203
TEST_F(ScopedAllocatorConcatOpTest,Success1)204 TEST_F(ScopedAllocatorConcatOpTest, Success1) {
205 MakeOp({32}, DT_FLOAT, false, "test", 120, 2);
206 ExecOp(DT_FLOAT, 120, {{16}, {16}});
207 }
208
TEST_F(ScopedAllocatorConcatOpTest,Success2)209 TEST_F(ScopedAllocatorConcatOpTest, Success2) {
210 MakeOp({2, 2, 2}, DT_DOUBLE, false, "test", 120, 2);
211 ExecOp(DT_DOUBLE, 120, {{2, 2}, {2, 2}});
212 }
213
TEST_F(ScopedAllocatorConcatOpTest,Success3)214 TEST_F(ScopedAllocatorConcatOpTest, Success3) {
215 MakeOp({3, 3, 3}, DT_HALF, false, "test", 120, 3);
216 ExecOp(DT_HALF, 120, {{3, 3}, {3, 3}, {3, 3}});
217 }
218
TEST_F(ScopedAllocatorConcatOpTest,Reshape)219 TEST_F(ScopedAllocatorConcatOpTest, Reshape) {
220 MakeOp({2, 2, 4}, DT_DOUBLE, true, "test", 120, 2);
221
222 // The elements of the third parameter to ExecOp must be multiples of
223 // Allocator::kAllocatorAlignment in size. If they are not, the backing
224 // tensor allocated by PrepOp will have too many elements and reshaping
225 // will fail.
226 ExecOp(DT_DOUBLE, 120, {{2, 4}, {2, 4}});
227 }
228
TEST_F(ScopedAllocatorConcatOpTest,NoReshapeAttr)229 TEST_F(ScopedAllocatorConcatOpTest, NoReshapeAttr) {
230 BuildNodeDef({3, 4, 4}, DT_HALF, "test", 120, 3);
231 TF_EXPECT_OK(InitOp());
232 ExecOp(DT_HALF, 120, {{4, 4}, {4, 4}, {4, 4}});
233 }
234
TEST_F(ScopedAllocatorConcatOpTest,FailDtypeCheck)235 TEST_F(ScopedAllocatorConcatOpTest, FailDtypeCheck) {
236 MakeOp({8}, DT_FLOAT, false, "test", 120, 2);
237 EXPECT_DEATH(ExecOp(DT_DOUBLE, 120, {{4}, {4}}), "");
238 }
239
TEST_F(ScopedAllocatorConcatOpTest,FailNumElementsCheck)240 TEST_F(ScopedAllocatorConcatOpTest, FailNumElementsCheck) {
241 MakeOp({32}, DT_FLOAT, false, "test", 120, 2);
242 AddInputFromArray<float>({8}, {0, 1, 2, 3, 4, 5, 6, 7});
243 AddInputFromArray<float>({4}, {0, 1, 2, 3});
244 AddInputFromArray<float>({4}, {4, 5, 6, 7});
245 Status s = RunOpKernel();
246 EXPECT_EQ(s.code(), error::INVALID_ARGUMENT);
247 }
248
249 // This test should fail because the backing tensor and the input tensors are
250 // unrelated, i.e. the inputs are not slices of the backing tensor.
TEST_F(ScopedAllocatorConcatOpTest,FailBounds)251 TEST_F(ScopedAllocatorConcatOpTest, FailBounds) {
252 MakeOp({8}, DT_DOUBLE, false, "test", 120, 2);
253 AddInputFromArray<double>({8}, {0, 1, 2, 3, 4, 5, 6, 7});
254 AddInputFromArray<double>({4}, {0, 1, 2, 3});
255 AddInputFromArray<double>({4}, {4, 5, 6, 7});
256 Status s = RunOpKernel();
257 EXPECT_EQ(s.code(), error::INVALID_ARGUMENT);
258 }
259
260 class ScopedAllocatorSplitOpTest : public OpsTestBase {
261 protected:
BuildNodeDef(const TensorShape & in_shape,DataType dtype,const string & name,int32_t id,int32_t num_tensors,const std::vector<TensorShape> & out_shapes)262 void BuildNodeDef(const TensorShape& in_shape, DataType dtype,
263 const string& name, int32_t id, int32_t num_tensors,
264 const std::vector<TensorShape>& out_shapes) {
265 TF_EXPECT_OK(
266 NodeDefBuilder("scoped_allocator_split_op", "_ScopedAllocatorSplit")
267 .Attr("T", dtype)
268 .Attr("N", num_tensors)
269 .Attr("sa_name", name)
270 .Attr("id", id)
271 .Attr("shapes", out_shapes)
272 .Input(FakeInput(dtype)) // backing tensor and input
273 .Input(
274 FakeInput(num_tensors, dtype)) // list of subtensors to forward
275 .Finalize(node_def()));
276 }
277
MakeOp(const TensorShape & in_shape,DataType dtype,const string & name,int32_t id,int32_t num_tensors,const std::vector<TensorShape> & out_shapes)278 void MakeOp(const TensorShape& in_shape, DataType dtype, const string& name,
279 int32_t id, int32_t num_tensors,
280 const std::vector<TensorShape>& out_shapes) {
281 BuildNodeDef(in_shape, dtype, name, id, num_tensors, out_shapes);
282 TF_EXPECT_OK(InitOp());
283 }
284
285 // Similar to ConcatOpTest, we add inputs that are allocated from
286 // ScopedAllocator so that the memory lines up nicely.
ExecOp(DataType dtype,int32_t id,const std::vector<TensorShape> & fields_shapes)287 void ExecOp(DataType dtype, int32_t id,
288 const std::vector<TensorShape>& fields_shapes) {
289 Tensor* backing_tensor = nullptr;
290 std::vector<Tensor> tensors;
291 std::vector<ScopedAllocator::Field> fields;
292 PrepOp(dtype, id, fields_shapes, &fields, &backing_tensor, allocator(),
293 device_->GetScopedAllocatorMgr(), "split", &tensors, &inputs_,
294 input_types_);
295
296 TF_ASSERT_OK(RunOpKernel());
297
298 // Check that outputs are slices of backing tensor.
299 const Tensor& input = context_->input(0);
300 const void* lower_limit = DMAHelper::base(&input);
301 const char* lower_limit_c =
302 static_cast<const char*>(lower_limit); // for pointer arithmetic
303 OpOutputList output_list;
304 Status s = context_->output_list("output", &output_list);
305 TF_ASSERT_OK(s);
306 for (int i = 0; i < output_list.size(); i++) {
307 const Tensor& output = *(output_list[i]);
308 const void* expected_base =
309 static_cast<const void*>(lower_limit_c + fields[i].offset);
310 CHECK_EQ(output.dtype(), input.dtype());
311 CHECK_EQ(expected_base, DMAHelper::base(&output));
312 CHECK_EQ(output.NumElements(), fields_shapes[i].num_elements());
313 }
314
315 // Free the backing tensor which was allocated in PrepOp.
316 delete backing_tensor;
317 }
318 };
319
TEST_F(ScopedAllocatorSplitOpTest,Success1)320 TEST_F(ScopedAllocatorSplitOpTest, Success1) {
321 MakeOp({32}, DT_FLOAT, "test", 120, 2, {{16}, {16}});
322 ExecOp(DT_FLOAT, 120, {{16}, {16}});
323 }
324
TEST_F(ScopedAllocatorSplitOpTest,Success2)325 TEST_F(ScopedAllocatorSplitOpTest, Success2) {
326 MakeOp({2, 2, 2}, DT_DOUBLE, "test", 120, 2, {{2, 2}, {2, 2}});
327 ExecOp(DT_DOUBLE, 120, {{2, 2}, {2, 2}});
328 }
329
TEST_F(ScopedAllocatorSplitOpTest,Success3)330 TEST_F(ScopedAllocatorSplitOpTest, Success3) {
331 MakeOp({3, 3, 3}, DT_HALF, "test", 120, 3, {{3, 3}, {3, 3}, {3, 3}});
332 ExecOp(DT_HALF, 120, {{3, 3}, {3, 3}, {3, 3}});
333 }
334
TEST_F(ScopedAllocatorSplitOpTest,FailNLessThan2)335 TEST_F(ScopedAllocatorSplitOpTest, FailNLessThan2) {
336 BuildNodeDef({4, 4}, DT_FLOAT, "test", 120, 1, {{4, 4}});
337 Status s = InitOp();
338 EXPECT_EQ(s.code(), error::INVALID_ARGUMENT);
339 }
340
TEST_F(ScopedAllocatorSplitOpTest,FailDtypeCheck)341 TEST_F(ScopedAllocatorSplitOpTest, FailDtypeCheck) {
342 MakeOp({8}, DT_FLOAT, "test", 120, 2, {{4}, {4}});
343 EXPECT_DEATH(ExecOp(DT_HALF, 120, {{4}, {4}}), "");
344 }
345
TEST_F(ScopedAllocatorSplitOpTest,FailBounds)346 TEST_F(ScopedAllocatorSplitOpTest, FailBounds) {
347 MakeOp({8}, DT_DOUBLE, "test", 120, 2, {{4}, {4}});
348 AddInputFromArray<double>({8}, {0, 1, 2, 3, 4, 5, 6, 7});
349 AddInputFromArray<double>({4}, {0, 1, 2, 3});
350 AddInputFromArray<double>({4}, {4, 5, 6, 7});
351 Status s = RunOpKernel();
352 EXPECT_EQ(s.code(), error::INVALID_ARGUMENT);
353 }
354
355 } // end namespace tensorflow
356