Caffe2 - C++ API
A deep learning, cross platform ML framework
conv_pool_op_base.h
1 
17 #ifndef CAFFE2_OPERATORS_CONV_POOL_OP_BASE_H_
18 #define CAFFE2_OPERATORS_CONV_POOL_OP_BASE_H_
19 
20 #include <vector>
21 
22 #include "caffe2/core/context.h"
23 #include "caffe2/core/logging.h"
24 #include "caffe2/core/operator.h"
25 #include "caffe2/proto/caffe2_legacy.pb.h"
26 #include "caffe2/utils/math.h"
27 
28 // This macro is here just to allow us to experiment with padding values that
29 // determines, when we have an odd number of pads, which side gets the one
30 // additional pad value, the head side, or the tail side. Setting it to false
31 // will enable the TensorFlow behavior, and setting it to true will enable
32 // a behavior more consistent with Caffe and CuDNN.
33 // This only affects the case when you set legacy pad to VALID or SAME. The
34 // behavior inherits from the early designs of Google's CNN implementation,
35 // where padding values are implicitly calculated instead of explicitly
36 // specified. This is still the case with TensorFlow. Many frameworks have
37 // followed a slightly different approach of explicitly giving padding values,
38 // in which case the value of this constant value does not matter.
39 const bool CAFFE2_PAD_HEAD_MORE = false;
40 
41 namespace caffe2 {
42 
43 template <class Context>
44 class ConvPoolOpBase : public Operator<Context> {
45  public:
46  USE_OPERATOR_CONTEXT_FUNCTIONS;
47  ConvPoolOpBase(const OperatorDef& operator_def, Workspace* ws)
48  : Operator<Context>(operator_def, ws),
49  legacy_pad_(
50  static_cast<LegacyPadding>(OperatorBase::GetSingleArgument<int>(
51  "legacy_pad",
52  LegacyPadding::NOTSET))),
53  global_pooling_(
54  OperatorBase::GetSingleArgument<int>("global_pooling", 0)),
55  kernel_(OperatorBase::GetRepeatedArgument<int>("kernels")),
56  dilation_(OperatorBase::GetRepeatedArgument<int>("dilations")),
57  stride_(OperatorBase::GetRepeatedArgument<int>("strides")),
58  pads_(OperatorBase::GetRepeatedArgument<int>("pads")),
59  group_(OperatorBase::GetSingleArgument<int>("group", 1)),
60  order_(StringToStorageOrder(
61  OperatorBase::GetSingleArgument<string>("order", "NCHW"))),
62  shared_buffer_(
63  OperatorBase::GetSingleArgument<int>("shared_buffer", 0)),
64  ws_(ws) {
65  // For the padding, they should either be the legacy padding strategy
66  // (VALID or SAME), or an explicit, non-negative value.
67  if (legacy_pad_ == LegacyPadding::VALID ||
68  legacy_pad_ == LegacyPadding::SAME) {
69  CAFFE_ENFORCE(
71  "If you use legacy padding VALID or SAME, you should not specify "
72  "any specific padding values.");
73  }
74 
75  // Get old arguments values.
76  if (OperatorBase::HasArgument("kernel")) {
77  kernel_.resize(2, OperatorBase::GetSingleArgument<int>("kernel", 0));
78  } else if (
79  OperatorBase::HasArgument("kernel_h") &&
80  OperatorBase::HasArgument("kernel_w")) {
81  kernel_.push_back(OperatorBase::GetSingleArgument<int>("kernel_h", 0));
82  kernel_.push_back(OperatorBase::GetSingleArgument<int>("kernel_w", 0));
83  }
84 
85  if (OperatorBase::HasArgument("stride")) {
86  stride_.resize(2, OperatorBase::GetSingleArgument<int>("stride", 0));
87  } else if (
88  OperatorBase::HasArgument("stride_h") &&
89  OperatorBase::HasArgument("stride_w")) {
90  stride_.push_back(OperatorBase::GetSingleArgument<int>("stride_h", 0));
91  stride_.push_back(OperatorBase::GetSingleArgument<int>("stride_w", 0));
92  }
93 
94  if (OperatorBase::HasArgument("dilation")) {
95  dilation_.resize(2, OperatorBase::GetSingleArgument<int>("dilation", 0));
96  } else if (
97  OperatorBase::HasArgument("dilation_h") &&
98  OperatorBase::HasArgument("dilation_w")) {
99  dilation_.push_back(
100  OperatorBase::GetSingleArgument<int>("dilation_h", 0));
101  dilation_.push_back(
102  OperatorBase::GetSingleArgument<int>("dilation_w", 0));
103  }
104 
105  if (OperatorBase::HasArgument("pad")) {
106  CAFFE_ENFORCE(
107  legacy_pad_ != LegacyPadding::VALID &&
108  legacy_pad_ != LegacyPadding::SAME,
109  "If you use legacy padding VALID or SAME, you should not specify "
110  "any specific padding values.");
111  pads_.resize(4, OperatorBase::GetSingleArgument<int>("pad", 0));
112  } else if (
113  OperatorBase::HasArgument("pad_t") &&
114  OperatorBase::HasArgument("pad_l") &&
115  OperatorBase::HasArgument("pad_b") &&
116  OperatorBase::HasArgument("pad_r")) {
117  CAFFE_ENFORCE(
118  legacy_pad_ != LegacyPadding::VALID &&
119  legacy_pad_ != LegacyPadding::SAME,
120  "If you use legacy padding VALID or SAME, you should not specify "
121  "any specific padding values.");
122  pads_.push_back(OperatorBase::GetSingleArgument<int>("pad_t", 0));
123  pads_.push_back(OperatorBase::GetSingleArgument<int>("pad_l", 0));
124  pads_.push_back(OperatorBase::GetSingleArgument<int>("pad_b", 0));
125  pads_.push_back(OperatorBase::GetSingleArgument<int>("pad_r", 0));
126  }
127 
128  // Fill default values.
129  if (kernel_.size() == 0) {
130  kernel_.assign({0, 0});
131  }
132 
133  if (stride_.size() == 0) {
134  stride_.resize(kernel_.size(), 1);
135  }
136 
137  if (pads_.size() == 0) {
138  pads_.resize(kernel_.size() * 2, 0);
139  }
140 
141  if (dilation_.size() == 0) {
142  dilation_.resize(kernel_.size(), 1);
143  }
144 
145  CAFFE_ENFORCE_EQ(stride_.size(), kernel_.size());
146  CAFFE_ENFORCE_EQ(dilation_.size(), kernel_.size());
147 
148  if (legacy_pad_ != LegacyPadding::VALID &&
149  legacy_pad_ != LegacyPadding::SAME) {
150  CAFFE_ENFORCE_EQ(pads_.size(), 2 * kernel_.size());
151  }
152 
153  if (global_pooling_) {
154  for (int dim = 0; dim < kernel_.size(); ++dim) {
155  CAFFE_ENFORCE(
156  pads_[2 * dim] == 0 && pads_[2 * dim + 1] == 0 &&
157  dilation_[dim] == 1 && stride_[dim] == 1,
158  "If global_pooling is set pad, dilation and stride shouldn't be set.");
159  }
160  }
161 
162  AllocateAndCopy(kernel_, kernel_device_);
163  AllocateAndCopy(stride_, stride_device_);
164  AllocateAndCopy(dilation_, dilation_device_);
165  AllocateAndCopy(pads_, pads_device_);
166 
167  // Check kernel only if we are doing conv or pooling. The reason is that a
168  // few other ops, like PadImage, are also using this base class. We really
169  // need to clean this up.
170  if (operator_def.name().find("Conv") == 0 ||
171  operator_def.name().find("Pool") != std::string::npos) {
172  for (int dim = 0; dim < kernel_.size(); ++dim) {
173  CAFFE_ENFORCE_GE(pads_[dim], 0);
174  CAFFE_ENFORCE_GE(pads_[kernel_.size() + dim], 0);
175  CAFFE_ENFORCE(
176  kernel_[dim],
177  "If you are doing convolution or pooling, you will need to set "
178  "explicitly the kernel size.");
179  }
180  }
181 
182  for (int dim = 0; dim < kernel_.size(); ++dim) {
183  CAFFE_ENFORCE_GE(kernel_[dim], 0);
184  CAFFE_ENFORCE_GE(dilation_[dim], 0);
185  CAFFE_ENFORCE_GE(stride_[dim], 0);
186  }
187 
188  if (group_ != 1) {
189  for (int dim = 0; dim < kernel_.size(); ++dim) {
190  CAFFE_ENFORCE_EQ(
191  dilation_[dim],
192  1,
193  "When group is used, dilation should not be set at the same time.");
194  }
195  }
196  }
197 
198  // Returns the input image dimensions for the current storage order type.
199  vector<int> GetDims(const Tensor<Context>& input) {
200  vector<int> dims;
201  switch (order_) {
202  case StorageOrder::NCHW:
203  dims.assign(input.dims().begin() + 2, input.dims().end());
204  break;
205  case StorageOrder::NHWC:
206  dims.assign(input.dims().begin() + 1, input.dims().end() - 1);
207  break;
208  default:
209  CAFFE_THROW("Unknown storage order : ", order_);
210  }
211  return dims;
212  }
213 
214  // Returns the size of the input image for the current storage type.
215  int GetDimsSize(const Tensor<Context>& input) {
216  int size = 0;
217  switch (order_) {
218  case StorageOrder::NCHW:
219  size = std::accumulate(
220  input.dims().begin() + 2,
221  input.dims().end(),
222  1,
223  std::multiplies<int>());
224  break;
225  case StorageOrder::NHWC:
226  size = std::accumulate(
227  input.dims().begin() + 1,
228  input.dims().end() - 1,
229  1,
230  std::multiplies<int>());
231  break;
232  default:
233  CAFFE_THROW("Unknown storage order : ", order_);
234  }
235  return size;
236  }
237 
238  // Sets the output size. The output channel is manually provided since
239  // it may not be identical to the input channels.
240  // This function can be used in the forward functions to obtain the output
241  // sizes.
242  // Note(jiayq): the templatization of this function is mainly to help
243  // implementations that do not use first-class Tensor objects, such as the
244  // MKL operator. One can still call this function with dummy
245  // Tensor<CPUContext> objects in order to obtain the sizes.
246  template <typename AlternativeContext>
247  void SetOutputSize(
248  const Tensor<AlternativeContext>& input,
250  int output_channel) {
251  CAFFE_ENFORCE(input.size() > 0);
252  vector<int> output_dims;
253  int N = input.dim32(0);
254  bool channel_first;
255  InferOutputSize(
256  input.dims(),
257  output_channel,
258  order_,
259  global_pooling_,
260  legacy_pad_,
261  N,
262  kernel_,
263  output_dims,
264  dilation_,
265  stride_,
266  pads_,
267  channel_first);
268 
269  if (channel_first) {
270  output_dims.insert(output_dims.begin(), {N, output_channel});
271  } else {
272  output_dims.insert(output_dims.begin(), N);
273  output_dims.push_back(output_channel);
274  }
275  output->Resize(output_dims);
276  }
277 
278  // Helper function that is also called from OperatorSchema. Modified
279  // kernel parameters and output output_dims and channel_first.
280  static inline void InferOutputSize(
281  vector<TIndex> input_dims,
282  int /*output_channel*/,
283  StorageOrder order,
284  bool global_pooling,
285  LegacyPadding legacy_pad,
286  int /*N*/,
287  vector<int>& kernel,
288  vector<int>& output_dims,
289  const vector<int>& dilation,
290  const vector<int>& stride,
291  vector<int>& pads,
292  bool& channel_first) {
293  channel_first = false; // initialized to suppress compiler warning.
294  vector<TIndex> dims;
295  switch (order) {
296  case StorageOrder::NHWC:
297  channel_first = false;
298  dims.assign(input_dims.begin() + 1, input_dims.end() - 1);
299  break;
300  case StorageOrder::NCHW:
301  // Old Caffe order.
302  channel_first = true;
303  dims.assign(input_dims.begin() + 2, input_dims.end());
304  break;
305  default:
306  CAFFE_THROW("Unknown Storage order: ", order);
307  }
308 
309  if (global_pooling) {
310  kernel.assign(dims.begin(), dims.end());
311  output_dims.assign(dims.size(), 1);
312  } else {
313  for (int dim = 0; dim < dims.size(); ++dim) {
314  int dim_size = 0;
315  ComputeSizeAndPad(
316  dims[dim],
317  stride[dim],
318  kernel[dim],
319  dilation[dim],
320  legacy_pad,
321  &pads[dim],
322  &pads[dims.size() + dim],
323  &dim_size);
324  output_dims.push_back(dim_size);
325  }
326  }
327  }
328 
329  // ComputePads could be used in backward functions to figure out the padding
330  // values for the given input.
331  void ComputePads(const vector<int>& dims) {
332  if (global_pooling_) {
333  kernel_ = dims;
334  } else if (legacy_pad_ != LegacyPadding::NOTSET) {
335  int output_unused;
336  for (int dim = 0; dim < dims.size(); ++dim) {
337  ComputeSizeAndPad(
338  dims[dim],
339  stride_[dim],
340  kernel_[dim],
341  dilation_[dim],
342  legacy_pad_,
343  &pads_[dim],
344  &pads_[dims.size() + dim],
345  &output_unused);
346  }
347  }
348  }
349 
350  void SetDeviceTensor(const std::vector<int>& data, Tensor<Context>* tensor) {
351  if (tensor->size() != data.size()) {
352  tensor->Resize(data.size());
353  context_.template Copy<int, CPUContext, Context>(
354  data.size(), data.data(), tensor->template mutable_data<int>());
355  }
356  }
357 
358  template <typename T>
359  void SetBiasMultiplier(const int size, Tensor<Context>* bias_multiplier_) {
360  if (bias_multiplier_->size() != size) {
361  // If the helper bias multiplier is not image size, reshape and fill it
362  // with one.
363  bias_multiplier_->Resize(std::vector<TIndex>{size});
364  math::Set<T, Context>(
365  size,
366  static_cast<T>(1),
367  bias_multiplier_->template mutable_data<T>(),
368  &context_);
369  }
370  }
371 
372  bool RunOnDevice() override {
373  if (!global_pooling_) {
374  for (int dim = 0; dim < kernel_.size(); ++dim) {
375  CAFFE_ENFORCE_GT(kernel_[dim], 0);
376  }
377  }
378  switch (order_) {
379  case StorageOrder::NHWC:
380  // VLOG(2) << "Running NHWC";
381  return RunOnDeviceWithOrderNHWC();
382  case StorageOrder::NCHW:
383  // VLOG(2) << "Running NCHW";
384  return RunOnDeviceWithOrderNCHW();
385  default:
386  CAFFE_THROW("Unknown Storage order: ", order_);
387  }
388  }
389 
390  // The actual function that does the computation, if the different
391  // storage order leads to different implementations.
392  virtual bool RunOnDeviceWithOrderNHWC() {
393  CAFFE_NOT_IMPLEMENTED;
394  }
395  virtual bool RunOnDeviceWithOrderNCHW() {
396  CAFFE_NOT_IMPLEMENTED;
397  }
398 
399  static struct OpSchema::Cost CostInferenceForConv(
400  const OperatorDef& def,
401  const vector<TensorShape>& inputs) {
402  struct OpSchema::Cost c;
403  const TensorShape X = inputs[0];
404  const TensorShape W = inputs[1];
405  const TensorShape Y = TensorInferenceForConv(def, inputs)[0];
406  ArgumentHelper helper(def);
407  const auto order =
408  StringToStorageOrder(helper.GetSingleArgument<string>("order", "NCHW"));
409 
410  unsigned long long N;
411  unsigned long long Y_t = 1;
412  unsigned long long Y_h;
413  unsigned long long Y_w;
414  unsigned long long kernel_t = 1;
415  unsigned long long kernel_h;
416  unsigned long long kernel_w;
417  unsigned long long in_channels;
418  unsigned long long out_channels;
419 
420  N = X.dims(0);
421  if (X.dims_size() == 5) {
422  // 3D convolution
423  CAFFE_ENFORCE_EQ(order, StorageOrder::NCHW, "Conv3D only supports NCHW");
424  Y_t = Y.dims(2);
425  Y_h = Y.dims(3);
426  Y_w = Y.dims(4);
427  kernel_t = W.dims(2);
428  kernel_h = W.dims(3);
429  kernel_w = W.dims(4);
430  in_channels = W.dims(1);
431  out_channels = W.dims(0);
432  } else {
433  // 2D convolution
434  CAFFE_ENFORCE_EQ(X.dims_size(), 4, "Conv2D should have 4D input tensor");
435  if (order == StorageOrder::NHWC) {
436  Y_h = Y.dims(1);
437  Y_w = Y.dims(2);
438  kernel_h = W.dims(1);
439  kernel_w = W.dims(2);
440  in_channels = W.dims(3);
441  out_channels = W.dims(0);
442  } else {
443  Y_h = Y.dims(2);
444  Y_w = Y.dims(3);
445  kernel_h = W.dims(2);
446  kernel_w = W.dims(3);
447  in_channels = W.dims(1);
448  out_channels = W.dims(0);
449  }
450  }
451  // grouping is NOT properly handled yet
452  c.flops = N * Y_t * Y_h * Y_w * kernel_t * kernel_w * kernel_h *
453  in_channels * out_channels * 2;
454  c.bytes_moved = N * out_channels * Y_t * Y_h * Y_w * sizeof(float);
455  c.params_bytes = out_channels * in_channels * kernel_t * kernel_h *
456  kernel_w * sizeof(float);
457  return c;
458  }
459 
460  static vector<TensorShape> TensorInferenceForSchema(
461  const OperatorDef& def,
462  const vector<TensorShape>& in,
463  int output_channel) {
464  ArgumentHelper helper(def);
465  CAFFE_ENFORCE_GT(in.size(), 0);
466  CAFFE_ENFORCE_GT(in[0].dims_size(), 0);
467  int N = in[0].dims(0);
468  bool channel_first;
469  vector<int> pads = helper.GetRepeatedArgument<int>("pads");
470  vector<int> kernel = helper.GetRepeatedArgument<int>("kernels");
471  vector<int> strides = helper.GetRepeatedArgument<int>("strides");
472  vector<int> dilations = helper.GetRepeatedArgument<int>("dilation");
473  if (helper.HasArgument("pad")) {
474  pads.resize(4, helper.GetSingleArgument<int>("pad", 0));
475  } else if (
476  helper.HasArgument("pad_t") && helper.HasArgument("pad_l") &&
477  helper.HasArgument("pad_b") && helper.HasArgument("pad_r")) {
478  pads.push_back(helper.GetSingleArgument<int>("pad_t", 0));
479  pads.push_back(helper.GetSingleArgument<int>("pad_l", 0));
480  pads.push_back(helper.GetSingleArgument<int>("pad_b", 0));
481  pads.push_back(helper.GetSingleArgument<int>("pad_r", 0));
482  }
483 
484  if (helper.HasArgument("kernel")) {
485  kernel.resize(2, helper.GetSingleArgument<int>("kernel", 1));
486  } else if (
487  helper.HasArgument("kernel_h") && helper.HasArgument("kernel_w")) {
488  kernel.push_back(helper.GetSingleArgument<int>("kernel_h", 1));
489  kernel.push_back(helper.GetSingleArgument<int>("kernel_w", 1));
490  }
491 
492  if (helper.HasArgument("stride")) {
493  strides.resize(2, helper.GetSingleArgument<int>("stride", 1));
494  } else if (
495  helper.HasArgument("stride_h") && helper.HasArgument("stride_w")) {
496  strides.push_back(helper.GetSingleArgument<int>("stride_h", 1));
497  strides.push_back(helper.GetSingleArgument<int>("stride_w", 1));
498  }
499 
500  if (helper.HasArgument("dilation")) {
501  strides.resize(2, helper.GetSingleArgument<int>("dilation", 1));
502  } else if (
503  helper.HasArgument("dilation_h") && helper.HasArgument("dilation_w")) {
504  strides.push_back(helper.GetSingleArgument<int>("dilation_h", 1));
505  strides.push_back(helper.GetSingleArgument<int>("dilation_w", 1));
506  }
507 
508  auto check_and_set_default_value =
509  [](vector<int>& vec, int size, int value) {
510  if (vec.size() == 0) {
511  vec.resize(size, value);
512  }
513  };
514 
515  check_and_set_default_value(kernel, 2, 1);
516  check_and_set_default_value(strides, kernel.size(), 1);
517  check_and_set_default_value(pads, kernel.size() * 2, 0);
518  check_and_set_default_value(dilations, kernel.size(), 1);
519 
520  vector<int> output_dims;
522  GetDimsVector(in[0]),
523  output_channel,
524  StringToStorageOrder(helper.GetSingleArgument<string>("order", "NCHW")),
525  helper.GetSingleArgument<int>("global_pooling", 0),
526  static_cast<LegacyPadding>(
527  helper.GetSingleArgument<int>("legacy_pad", LegacyPadding::NOTSET)),
528  N,
529  kernel,
530  output_dims,
531  dilations,
532  strides,
533  pads,
534  channel_first);
535  vector<TensorShape> out(1);
536  if (channel_first) {
537  output_dims.insert(output_dims.begin(), {N, output_channel});
538  } else {
539  output_dims.push_back(output_channel);
540  output_dims.insert(output_dims.begin(), N);
541  }
542 
543  out[0] = CreateTensorShape(output_dims, TensorProto::FLOAT);
544  return out;
545  }
546 
547  static vector<TensorShape> TensorInferenceForConv(
548  const OperatorDef& def,
549  const vector<TensorShape>& in) {
550  return TensorInferenceForSchema(def, in, in[1].dims(0));
551  }
552 
553  static vector<TensorShape> TensorInferenceForPool(
554  const OperatorDef& def,
555  const vector<TensorShape>& in) {
556  ArgumentHelper helper(def);
557  auto order =
558  StringToStorageOrder(helper.GetSingleArgument<string>("order", "NCHW"));
559  int num_channels =
560  (order == StorageOrder::NCHW ? in[0].dims(1) : in[0].dims(3));
561  return TensorInferenceForSchema(def, in, num_channels);
562  }
563 
564  virtual ~ConvPoolOpBase() {}
565 
566  protected:
567  LegacyPadding legacy_pad_;
568  bool global_pooling_;
569  vector<int> kernel_;
570  vector<int> dilation_;
571  vector<int> stride_;
572  vector<int> pads_;
573 
574  // We need the above parameters to be available for the devices.
575  Tensor<Context> kernel_device_;
576  Tensor<Context> dilation_device_;
577  Tensor<Context> stride_device_;
578  Tensor<Context> pads_device_;
579 
580  int group_;
581  StorageOrder order_;
582  bool shared_buffer_;
583  Workspace* ws_;
584 
585  static inline void ComputeSizeAndPad(
586  const int in_size,
587  const int stride,
588  const int kernel,
589  const int dilation,
590  LegacyPadding legacy_pad,
591  int* pad_head,
592  int* pad_tail,
593  int* out_size) {
594  const int dkernel = dilation * (kernel - 1) + 1;
595  switch (legacy_pad) {
596  case LegacyPadding::NOTSET:
597  // We will just use the direct padding head and tail values, but we
598  // will verify that they are non-negative.
599  CAFFE_ENFORCE_GE(in_size + *pad_head + *pad_tail, dkernel);
600  *out_size = static_cast<int>(
601  static_cast<float>(in_size + *pad_head + *pad_tail - dkernel) /
602  stride +
603  1);
604  break;
605  case LegacyPadding::VALID:
606  *pad_head = 0;
607  *pad_tail = 0;
608  *out_size = (in_size - dkernel) / stride + 1;
609  break;
610  case LegacyPadding::SAME: {
611  CAFFE_ENFORCE(
612  1 == dilation, "Dilation not supported for legacy padding.");
613  int legacy_target_size = (in_size + stride - 1) / stride;
614  int pad_needed = (legacy_target_size - 1) * stride + kernel - in_size;
615  if (CAFFE2_PAD_HEAD_MORE) {
616  *pad_head = (pad_needed + 1) / 2;
617  } else {
618  *pad_head = pad_needed / 2;
619  }
620  *pad_tail = pad_needed - *pad_head;
621  *out_size = (in_size + pad_needed - dkernel) / stride + 1;
622  break;
623  }
624  case LegacyPadding::CAFFE_LEGACY_POOLING:
625  // This is in order to adapt Caffe's pooling padding case. In this case,
626  // we will only use pad_head and will compute pad_tail to match the
627  // old caffe pooling strategy. Also see caffe2_legacy.proto for more
628  // details.
629  CAFFE_ENFORCE_GE(*pad_head, 0);
630  // Here, notice that caffe casts UP while caffe2 casts DOWN for the
631  // output size computation.
632  *out_size = std::ceil(
633  static_cast<float>(in_size + *pad_head * 2 - kernel) / stride + 1);
634  // If we have padding, caffe also ensures that the last pooling starts
635  // strictly inside the image (instead of at the padding); otherwise clip
636  // the last.
637  if (*pad_head > 0 && (*out_size - 1) * stride >= in_size + *pad_head) {
638  --*out_size;
639  }
640  // Now, compare the output size with the standard Caffe2 output size.
641  // The
642  // caffe2 standard output size should always be no larger than the
643  // output
644  // size of caffe.
645  int standard_out_size = static_cast<int>(
646  static_cast<float>(in_size + *pad_head * 2 - kernel) / stride + 1);
647  CAFFE_ENFORCE_GE(
648  *out_size,
649  standard_out_size,
650  "This should never happen. If this happens, double check the logic "
651  "above.");
652  if (*out_size > standard_out_size) {
653  LOG(WARNING)
654  << "You are hitting a case where Caffe's legacy padding calculation "
655  "is hit. This leads to inefficient and sometimes incorrect "
656  "results. We are keeping this behavior for backward compatibility"
657  ", but you are strongly recommended to move away from it.";
658  }
659  *pad_tail = *pad_head + stride * (*out_size - standard_out_size);
660  break;
661  }
662  }
663 
664  // Accessors for 2D conv params.
665 
666  inline int pad_t() const {
667  return pads_[0];
668  }
669 
670  inline int pad_l() const {
671  return pads_[1];
672  }
673 
674  inline int pad_b() const {
675  return pads_[2];
676  }
677 
678  inline int pad_r() const {
679  return pads_[3];
680  }
681 
682  inline int kernel_h() const {
683  return kernel_[0];
684  }
685 
686  inline int kernel_w() const {
687  return kernel_[1];
688  }
689 
690  inline int stride_h() const {
691  return stride_[0];
692  }
693 
694  inline int stride_w() const {
695  return stride_[1];
696  }
697 
698  inline int dilation_h() const {
699  return dilation_[0];
700  }
701 
702  inline int dilation_w() const {
703  return dilation_[1];
704  }
705 
706  private:
707  inline void AllocateAndCopy(const vector<int>& vec, Tensor<Context>& tensor) {
708  tensor.Resize(vec.size());
709  context_.template Copy<int, CPUContext, Context>(
710  vec.size(), vec.data(), tensor.template mutable_data<int>());
711  }
712 
713 #define USE_CONV_POOL_BASE_FUNCTIONS(Context) \
714  USE_OPERATOR_FUNCTIONS(Context); \
715  using ConvPoolOpBase<Context>::pads_; \
716  using ConvPoolOpBase<Context>::pads_device_; \
717  using ConvPoolOpBase<Context>::pad_t; \
718  using ConvPoolOpBase<Context>::pad_l; \
719  using ConvPoolOpBase<Context>::pad_b; \
720  using ConvPoolOpBase<Context>::pad_r; \
721  using ConvPoolOpBase<Context>::legacy_pad_; \
722  using ConvPoolOpBase<Context>::global_pooling_; \
723  using ConvPoolOpBase<Context>::kernel_; \
724  using ConvPoolOpBase<Context>::kernel_device_; \
725  using ConvPoolOpBase<Context>::kernel_h; \
726  using ConvPoolOpBase<Context>::kernel_w; \
727  using ConvPoolOpBase<Context>::dilation_; \
728  using ConvPoolOpBase<Context>::dilation_device_; \
729  using ConvPoolOpBase<Context>::dilation_h; \
730  using ConvPoolOpBase<Context>::dilation_w; \
731  using ConvPoolOpBase<Context>::stride_; \
732  using ConvPoolOpBase<Context>::stride_device_; \
733  using ConvPoolOpBase<Context>::stride_h; \
734  using ConvPoolOpBase<Context>::stride_w; \
735  using ConvPoolOpBase<Context>::group_; \
736  using ConvPoolOpBase<Context>::order_; \
737  using ConvPoolOpBase<Context>::shared_buffer_; \
738  using ConvPoolOpBase<Context>::GetDims; \
739  using ConvPoolOpBase<Context>::GetDimsSize; \
740  using ConvPoolOpBase<Context>::SetDeviceTensor; \
741  using ConvPoolOpBase<Context>::ws_
742 };
743 
744 } // namespace caffe2
745 
746 #endif // CAFFE2_OPERATORS_CONV_POOL_OP_BASE_H_
Tensor is the basic class in Caffe2 that stores a contiguous memory with its shape information...
Definition: tensor.h:109
int dim32(const int i) const
Returns the i-th dimension of the tensor in int.
Definition: tensor.h:673
TIndex size() const
Returns the size (i.e.
Definition: tensor.h:609
A helper class to index into arguments.
Definition: proto_utils.h:198
const vector< TIndex > & dims() const
Returns the dimensions of the tensor as a vector.
Definition: tensor.h:627
Workspace is a class that holds all the related objects created during runtime: (1) all blobs...
Definition: workspace.h:63
void Resize(Ts...dim_source)
Resizes a tensor.
Definition: tensor.h:304
Copyright (c) 2016-present, Facebook, Inc.
bool HasArgument(const string &name) const
Checks if the operator has an argument of the given name.
Definition: operator.h:52