• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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