TensorFlow Serving C++ API Documentation
batching_util.cc
1 /* Copyright 2017 Google Inc. 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 "tensorflow_serving/batching/batching_util.h"
17 
18 #include <map>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "tensorflow/core/framework/register_types.h"
24 #include "tensorflow/core/framework/tensor.h"
25 #include "tensorflow/core/framework/types.h"
26 #include "tensorflow/core/lib/core/errors.h"
27 #include "tensorflow/core/platform/env_time.h"
28 
29 namespace tensorflow {
30 namespace serving {
31 
32 // Padding for one dimension of a tensor.
33 // pad_before is a number of values to add before elements in one dimension,
34 // pad_after is number of objects to add after.
35 // NOTE: fields are named this way because of Eigen::TensorMap::pad method.
36 // It requires padding to be an array of elements that have fields
37 // "first" and "second".
38 struct OneDimPadding {
39  int64_t first; // pad before
40  int64_t second; // pad after
41 };
42 
43 // Constructs array of paddings, where:
44 // paddings[i].first - number of objects to add before elements in dimension i
45 // of given tensor,
46 // paddings[i].second - number of objects to add after elements in dimension i.
47 // This padding signature is used when performing internal padding with Eigen.
48 //
49 // When building paddings it is assumed that tensor needs to be padded
50 // after each dimension, so its shape matches max_dim_sizes,
51 // First entry of max_dim_sizes, which is maximum size of zeroth dimension,
52 // is ignored, because we don't perform padding in that dimension.
53 template <int num_dims>
54 Eigen::array<OneDimPadding, num_dims> CreatePadding(
55  Tensor tensor, absl::Span<const int> max_dim_sizes) {
56  Eigen::array<OneDimPadding, num_dims> padding;
57  for (unsigned int i = 0; i < max_dim_sizes.size(); ++i) {
58  if (i > 0 && max_dim_sizes[i] - tensor.dim_size(i) > 0) {
59  padding[i] = {0, max_dim_sizes[i] - tensor.dim_size(i)};
60  } else {
61  padding[i] = {0, 0};
62  }
63  }
64  return padding;
65 }
66 
67 // Functor, which performs padding of given input tensor
68 // using specified padding signature.
69 // For example, given tensor of shape [1, 2, 3] and padding signature
70 // [[0, 0], [0, 2], [2, 2]]
71 // functor produces padded_tensor of shape [1, 4, 7].
72 template <typename T, int num_dims>
73 struct PadTensor {
74  Status operator()(Tensor input,
75  const Eigen::array<OneDimPadding, num_dims>& padding,
76  Tensor* output) {
77  TensorShape output_shape;
78  for (int d = 0; d < num_dims; ++d) {
79  // Pad before existing elements.
80  const int32 before_d = padding[d].first;
81  // Pad after existing elements.
82  const int32 after_d = padding[d].second;
83  output_shape.AddDim(before_d + input.dim_size(d) + after_d);
84  }
85  if (output_shape.num_elements() == input.NumElements()) {
86  bool result = output->CopyFrom(input, output_shape);
87  if (!result) {
88  return errors::Internal("Couldn't create output.");
89  }
90  return absl::OkStatus();
91  }
92  if (input.NumElements() < 1) {
93  return errors::InvalidArgument(
94  "Got empty tensor in batch of non-empty tensors.");
95  }
96  *output = Tensor(input.dtype(), output_shape);
97  typename TTypes<T, num_dims>::Tensor inputs = input.tensor<T, num_dims>();
98  T pad_value(input.flat<T>()(0)); // using existing values in padding
99  output->tensor<T, num_dims>() = inputs.pad(padding, pad_value);
100  return absl::OkStatus();
101  }
102 };
103 
104 // Invokes padding procedure for specific tensor ranks.
105 // Only ranks from 1 to 6 are supported (like in PadOp).
106 template <typename T>
107 Status PadTensorOfSpecificType(const Tensor& tensor,
108  absl::Span<const int> max_dim_sizes,
109  Tensor* output_tensor) {
110  int num_dims = tensor.dims();
111  switch (num_dims) {
112  case 1: {
113  Eigen::array<OneDimPadding, 1> padding;
114  padding = CreatePadding<1>(tensor, max_dim_sizes);
115  PadTensor<T, 1> padding_functor = PadTensor<T, 1>();
116  return padding_functor(tensor, padding, output_tensor);
117  }
118  case 2: {
119  Eigen::array<OneDimPadding, 2> padding;
120  padding = CreatePadding<2>(tensor, max_dim_sizes);
121  PadTensor<T, 2> padding_functor = PadTensor<T, 2>();
122  return padding_functor(tensor, padding, output_tensor);
123  }
124  case 3: {
125  Eigen::array<OneDimPadding, 3> padding;
126  padding = CreatePadding<3>(tensor, max_dim_sizes);
127  PadTensor<T, 3> padding_functor = PadTensor<T, 3>();
128  return padding_functor(tensor, padding, output_tensor);
129  }
130  case 4: {
131  Eigen::array<OneDimPadding, 4> padding;
132  padding = CreatePadding<4>(tensor, max_dim_sizes);
133  PadTensor<T, 4> padding_functor = PadTensor<T, 4>();
134  return padding_functor(tensor, padding, output_tensor);
135  }
136  case 5: {
137  Eigen::array<OneDimPadding, 5> padding;
138  padding = CreatePadding<5>(tensor, max_dim_sizes);
139  PadTensor<T, 5> padding_functor = PadTensor<T, 5>();
140  return padding_functor(tensor, padding, output_tensor);
141  }
142  case 6: {
143  Eigen::array<OneDimPadding, 6> padding;
144  padding = CreatePadding<6>(tensor, max_dim_sizes);
145  PadTensor<T, 6> padding_functor = PadTensor<T, 6>();
146  return padding_functor(tensor, padding, output_tensor);
147  }
148  default:
149  // only ranks from 1 to 6 are supported
150  // (like in tensorflow/core/kernels/pad_op.cc)
151  return errors::InvalidArgument(
152  "Only tensors with rank from 1 to 6 can be padded.");
153  }
154 }
155 
156 std::map<string, std::vector<int>> CalculateMaxDimSizes(
157  const std::vector<std::vector<std::pair<string, Tensor>>>& batch) {
158  std::map<string, std::vector<int>> max_dim_sizes;
159  // Populate 'max_dim_sizes'
160  // init
161  const std::vector<std::pair<string, Tensor>>& task_inputs = batch[0];
162  for (const auto& entry : task_inputs) {
163  const string& tensor_name = entry.first;
164  const Tensor& tensor = entry.second;
165  max_dim_sizes[tensor_name] = std::vector<int>(tensor.dims(), 0);
166  }
167  // fill
168  for (int i = 0; i < batch.size(); ++i) {
169  const std::vector<std::pair<string, Tensor>>& task_inputs = batch[i];
170  for (const auto& entry : task_inputs) {
171  const string& tensor_name = entry.first;
172  const Tensor& tensor = entry.second;
173 
174  std::vector<int>& max_dim_sizes_for_one_input =
175  max_dim_sizes[tensor_name];
176  for (int j = 0; j < tensor.dims(); ++j) {
177  const int old_max_size = max_dim_sizes_for_one_input[j];
178  if (tensor.shape().dim_size(j) > old_max_size) {
179  max_dim_sizes_for_one_input[j] = tensor.shape().dim_size(j);
180  }
181  }
182  }
183  }
184  return max_dim_sizes;
185 }
186 
187 Status AddPadding(const Tensor& tensor, absl::Span<const int> max_dim_sizes,
188  Tensor* padded_tensor) {
189  const DataType input_dtype = tensor.dtype();
190  Status padding_status;
191 #define CASE(type) \
192  case DataTypeToEnum<type>::value: { \
193  padding_status = \
194  PadTensorOfSpecificType<type>(tensor, max_dim_sizes, padded_tensor); \
195  break; \
196  }
197  switch (input_dtype) {
198  TF_CALL_ALL_TYPES(CASE);
199  TF_CALL_QUANTIZED_TYPES(CASE);
200  // quantized types macro doesn't include these types
201  TF_CALL_quint16(CASE);
202  TF_CALL_qint16(CASE);
203  default:
204  padding_status = errors::InvalidArgument("Unsupported type");
205  }
206 #undef CASE
207  return padding_status;
208 }
209 
210 int RoundToLowestAllowedBatchSize(absl::Span<const int> allowed_batch_sizes,
211  int batch_size) {
212  if (allowed_batch_sizes.empty()) {
213  return batch_size;
214  }
215  for (int allowed_size : allowed_batch_sizes) {
216  if (allowed_size >= batch_size) {
217  return allowed_size;
218  }
219  }
220  // `allowed_batch_sizes` is guaranteed to be not empty and sorted in
221  // ascending order.
222  LOG(WARNING) << "Input batch size " << batch_size
223  << " is greater than largest allowed size "
224  << *allowed_batch_sizes.rbegin()
225  << " ignoring allowed sizes constraint.";
226  return batch_size;
227 }
228 
229 bool AreShapesEqualExceptZeroDim(const TensorShape& shape1,
230  const TensorShape& shape2) {
231  if (shape1.dims() != shape2.dims()) {
232  return false;
233  }
234  for (int i = 1; i < shape1.dims(); ++i) {
235  if (shape1.dim_size(i) != shape2.dim_size(i)) {
236  return false;
237  }
238  }
239  return true;
240 }
241 
242 } // namespace serving
243 } // namespace tensorflow