Caffe2 - C++ API
A deep learning, cross platform ML framework
elementwise_ops.h
1 #ifndef CAFFE2_OPERATORS_ELEMENTWISE_OPS_H_
2 #define CAFFE2_OPERATORS_ELEMENTWISE_OPS_H_
3 
4 #include <iterator>
5 #include <string>
6 #include <tuple>
7 #include <vector>
8 
9 #include "caffe2/core/common_omp.h"
10 #include "caffe2/core/context.h"
11 #include "caffe2/core/logging.h"
12 #include "caffe2/core/operator.h"
13 #include "caffe2/core/tensor.h"
14 #include "caffe2/operators/elementwise_ops_utils.h"
15 #include "caffe2/utils/eigen_utils.h"
16 #include "caffe2/utils/math.h"
17 
18 namespace caffe2 {
19 
20 using NumericTypes = TensorTypes<int32_t, int64_t, float, double>;
21 using IntTypes = TensorTypes<int32_t, int64_t>;
22 using BoolTypes = TensorTypes<bool>;
23 using IntBoolTypes = TensorTypes<int32_t, int64_t, bool>; // discrete types
24 
26  template <typename T>
27  using type = T;
28 };
29 
30 template <typename R>
31 struct FixedType {
32  template <typename T>
33  using type = R;
34 };
35 
36 template <
37  typename InputTypes,
38  class Context,
39  class Functor,
40  class OutputTypeMap = SameTypeAsInput>
41 class UnaryElementwiseWithArgsOp final : public Operator<Context> {
42  public:
43  USE_OPERATOR_CONTEXT_FUNCTIONS;
44 
45  template <class... Args>
46  explicit UnaryElementwiseWithArgsOp(Args&&... args)
47  : Operator<Context>(std::forward<Args>(args)...), functor_(*this) {}
48 
49  bool RunOnDevice() override {
50  return DispatchHelper<InputTypes>::call(this, Input(0));
51  }
52 
53  template <typename T>
54  bool DoRunWithType() {
55  const auto& X = Input(0);
56 
57  auto* Y = Output(
58  0, X.sizes(), at::dtype<typename OutputTypeMap::template type<T>>());
59  return functor_(
60  X.numel(),
61  X.template data<T>(),
62  Y->template mutable_data<typename OutputTypeMap::template type<T>>(),
63  &context_);
64  }
65 
66  private:
67  Functor functor_;
68 };
69 
70 // UnaryFunctorWithDefaultCtor is a functor that can be used as the functor of
71 // an UnaryElementwiseWithArgsOp. It simply forwards the operator() call into
72 // another functor that doesn't accept arguments in its constructor.
73 template <class Functor>
75  explicit UnaryFunctorWithDefaultCtor(OperatorBase& /* op */) {}
76 
77  template <typename TIn, typename TOut, class Context>
78  bool operator()(const int size, const TIn* X, TOut* Y, Context* context)
79  const {
80  return functor(size, X, Y, context);
81  }
82 
83  Functor functor{};
84 };
85 
86 // UnaryElementwiseOp is a wrapper around UnaryElementwiseWithArgsOp, with the
87 // difference that it takes a functor with default constructor, e.g. that does
88 // not need to take into consideration any arguments during operator creation.
89 template <
90  typename InputTypes,
91  class Context,
92  class Functor,
93  class OutputTypeMap = SameTypeAsInput>
95  InputTypes,
96  Context,
98  OutputTypeMap>;
99 
100 template <
101  typename InputTypes,
102  class Context,
103  class Functor,
104  class OutputTypeMap = SameTypeAsInput>
105 class BinaryElementwiseWithArgsOp final : public Operator<Context> {
106  public:
107  USE_OPERATOR_CONTEXT_FUNCTIONS;
108 
109  template <class... Args>
110  explicit BinaryElementwiseWithArgsOp(Args&&... args)
111  : Operator<Context>(std::forward<Args>(args)...),
112  OP_SINGLE_ARG(bool, "broadcast", legacy_broadcast_, false),
113  OP_SINGLE_ARG(int, "axis", axis_, -1),
114  OP_SINGLE_ARG(string, "axis_str", axis_str_, string("")),
115  OP_SINGLE_ARG(string, "order", order_, "NCHW"),
116  functor_(*this) {
117  if (legacy_broadcast_) {
118  if (axis_ != -1) {
119  // Get axis from an explicit axis argument.
120  CAFFE_ENFORCE_EQ(
121  axis_str_.size(),
122  0,
123  "Args axis and axis_str cannot be used simultaneously.");
124  } else if (axis_str_.size()) {
125  // Get the axis index semantically.
126  CAFFE_ENFORCE_EQ(
127  axis_str_.size(), 1, "Unsupported axis string", axis_str_);
128  const size_t semantic_axis_ = order_.find(axis_str_);
129  CAFFE_ENFORCE_NE(
130  semantic_axis_,
131  string::npos,
132  "Unrecognizable axis string ",
133  axis_str_,
134  " from order string ",
135  order_);
136  axis_ = semantic_axis_;
137  } else {
138  CAFFE_ENFORCE(
139  axis_ == -1 && axis_str_.empty(),
140  "Do not specify axis or axis_str if broadcast is not enabled.");
141  }
142  }
143  }
144 
145  bool RunOnDevice() override {
146  return DispatchHelper<InputTypes>::call(this, Input(0));
147  }
148 
149  template <typename T>
150  bool DoRunWithType() {
151  const auto& A = Input(0);
152  const auto& B = Input(1);
153 
154  const T* A_data = A.template data<T>();
155  const T* B_data = B.template data<T>();
156  std::vector<int> A_dims;
157  std::vector<int> B_dims;
158  std::vector<int64_t> C_dims;
159 
160  if (legacy_broadcast_) {
161  CAFFE_ENFORCE(
162  !IsInputOutputAlias(1, 0),
163  "In-place is allowed only with the first tensor when "
164  "legacy-broadcasting");
165  C_dims = A.sizes().vec();
166  if (B.numel() == 1) {
167  A_dims = {static_cast<int>(A.numel())};
168  B_dims = {1};
169  } else {
170  size_t pre, n, post;
171  std::tie(pre, n, post) =
172  elementwise_ops_utils::ComputeLegacyBroadcastSizes(A, B, axis_);
173  A_dims = {
174  static_cast<int>(pre), static_cast<int>(n), static_cast<int>(post)};
175  B_dims = {static_cast<int>(n), 1};
176  }
177  } else {
178  std::copy(
179  A.sizes().cbegin(), A.sizes().cend(), std::back_inserter(A_dims));
180  std::copy(
181  B.sizes().cbegin(), B.sizes().cend(), std::back_inserter(B_dims));
182  // TODO: change the types to vector<int64_t>
183  auto C_dims_int =
184  elementwise_ops_utils::ComputeBinaryBroadcastForwardDims(
185  A_dims, B_dims);
186  std::copy(
187  C_dims_int.cbegin(), C_dims_int.cend(), std::back_inserter(C_dims));
188  if (IsInputOutputAlias(0, 0)) {
189  CAFFE_ENFORCE_EQ(C_dims_int, A_dims);
190  } else if (IsInputOutputAlias(1, 0)) {
191  CAFFE_ENFORCE_EQ(C_dims_int, B_dims);
192  }
193  }
194 
195  auto* C = Output(
196  0, C_dims, at::dtype<typename OutputTypeMap::template type<T>>());
197  auto* C_data =
198  C->template mutable_data<typename OutputTypeMap::template type<T>>();
199  return functor_.Forward(A_dims, B_dims, A_data, B_data, C_data, &context_);
200  }
201 
202  private:
203  const bool legacy_broadcast_;
204  int axis_;
205  const std::string axis_str_;
206  const std::string order_;
207 
208  Functor functor_;
209 };
210 
211 template <
212  typename InputTypes,
213  class Context,
214  class Functor,
215  class OutputTypeMap = SameTypeAsInput,
216  class GradientTypeMap = SameTypeAsInput>
217 class BinaryElementwiseWithArgsGradientOp final : public Operator<Context> {
218  public:
219  USE_OPERATOR_CONTEXT_FUNCTIONS;
220 
221  template <class... Args>
222  explicit BinaryElementwiseWithArgsGradientOp(Args&&... args)
223  : Operator<Context>(std::forward<Args>(args)...),
224  OP_SINGLE_ARG(bool, "broadcast", legacy_broadcast_, false),
225  OP_SINGLE_ARG(int, "axis", axis_, -1),
226  OP_SINGLE_ARG(string, "axis_str", axis_str_, ""),
227  OP_SINGLE_ARG(string, "order", order_, "NCHW"),
228  functor_(*this) {
229  if (legacy_broadcast_) {
230  if (axis_ != -1) {
231  // Get axis from an explicit axis argument.
232  CAFFE_ENFORCE_EQ(
233  axis_str_.size(),
234  0,
235  "Args axis and axis_str cannot be used simultaneously.");
236  } else if (axis_str_.size()) {
237  // Get the axis index semantically.
238  CAFFE_ENFORCE_EQ(
239  axis_str_.size(), 1, "Unsupported axis string", axis_str_);
240  const size_t semantic_axis_ = order_.find(axis_str_);
241  CAFFE_ENFORCE_NE(
242  semantic_axis_,
243  string::npos,
244  "Unrecognizable axis string ",
245  axis_str_,
246  " from order string ",
247  order_);
248  axis_ = semantic_axis_;
249  } else {
250  CAFFE_ENFORCE(
251  axis_ == -1 && axis_str_.empty(),
252  "Do not specify axis or axis_str if broadcast is not enabled.");
253  }
254  }
255  }
256 
257  bool RunOnDevice() override {
258  return DispatchHelper<InputTypes>::call(this, Input(1));
259  }
260 
261  template <typename T>
262  bool DoRunWithType() {
263  const auto& dC = Input(0);
264  const auto& A = Input(1);
265  const auto& B = Input(2);
266 
267  vector<int> A_dims;
268  vector<int> B_dims;
269  if (legacy_broadcast_) {
270  if (B.numel() == 1) {
271  A_dims = {static_cast<int>(A.numel())};
272  B_dims = {1};
273  } else {
274  size_t pre, n, post;
275  std::tie(pre, n, post) =
276  elementwise_ops_utils::ComputeLegacyBroadcastSizes(A, B, axis_);
277  A_dims = {
278  static_cast<int>(pre), static_cast<int>(n), static_cast<int>(post)};
279  B_dims = {static_cast<int>(n), 1};
280  }
281  } else {
282  std::copy(
283  A.sizes().cbegin(), A.sizes().cend(), std::back_inserter(A_dims));
284  std::copy(
285  B.sizes().cbegin(), B.sizes().cend(), std::back_inserter(B_dims));
286  }
287  const typename OutputTypeMap::template type<T>* C_data = nullptr;
288  if (InputSize() == 4) {
289  const auto& C = Input(3);
290  C_data = C.template data<typename OutputTypeMap::template type<T>>();
291  }
292  const auto* dC_data =
293  dC.template data<typename GradientTypeMap::template type<T>>();
294  const T* A_data = A.template data<T>();
295  const T* B_data = B.template data<T>();
296  auto* dA = Output(
297  0, A.sizes(), at::dtype<typename GradientTypeMap::template type<T>>());
298  auto* dB = Output(
299  1, B.sizes(), at::dtype<typename GradientTypeMap::template type<T>>());
300  auto* dA_data =
301  dA->template mutable_data<typename GradientTypeMap::template type<T>>();
302  auto* dB_data =
303  dB->template mutable_data<typename GradientTypeMap::template type<T>>();
304  return functor_.Backward(
305  A_dims,
306  B_dims,
307  dC_data,
308  A_data,
309  B_data,
310  C_data,
311  dA_data,
312  dB_data,
313  &context_);
314  }
315 
316  private:
317  const bool legacy_broadcast_;
318  int axis_;
319  const std::string axis_str_;
320  const std::string order_;
321 
322  Functor functor_;
323 };
324 
325 template <class Functor>
327  explicit BinaryFunctorWithDefaultCtor(OperatorBase& /* op */) {}
328 
329  template <typename TIn, typename TOut, class Context>
330  bool Forward(
331  const std::vector<int>& A_dims,
332  const std::vector<int>& B_dims,
333  const TIn* A_data,
334  const TIn* B_data,
335  TOut* C_data,
336  Context* context) const {
337  return functor.Forward(A_dims, B_dims, A_data, B_data, C_data, context);
338  }
339 
340  template <typename TGrad, typename TIn, typename TOut, class Context>
341  bool Backward(
342  const std::vector<int>& A_dims,
343  const std::vector<int>& B_dims,
344  const TGrad* dC_data,
345  const TIn* A_data,
346  const TIn* B_data,
347  const TOut* C_data,
348  TGrad* dA_data,
349  TGrad* dB_data,
350  Context* context) const {
351  return functor.Backward(
352  A_dims,
353  B_dims,
354  dC_data,
355  A_data,
356  B_data,
357  C_data,
358  dA_data,
359  dB_data,
360  context);
361  }
362 
363  Functor functor{};
364 };
365 
366 // BinaryElementwiseOp is a wrapper around BinaryElementwiseWithArgsOp, with the
367 // difference that it takes a functor with default constructor, e.g. that does
368 // not need to take into consideration any arguments during operator creation.
369 template <
370  typename InputTypes,
371  class Context,
372  class Functor,
373  class TypeMap = SameTypeAsInput>
375  InputTypes,
376  Context,
378  TypeMap>;
379 
380 // BinaryElementwiseGradientOp is a wrapper around
381 // BinaryElementwiseGradientWithArgsOp, with the difference that it takes a
382 // functor with default constructor, e.g. that does not need to take into
383 // consideration any arguments during operator creation.
384 template <
385  typename InputTypes,
386  class Context,
387  class Functor,
388  class OutputTypeMap = SameTypeAsInput,
389  class GradientTypeMap = SameTypeAsInput>
391  InputTypes,
392  Context,
393  BinaryFunctorWithDefaultCtor<Functor>,
394  OutputTypeMap,
395  GradientTypeMap>;
396 
397 // Forward-only Unary Functors.
398 template <class Context>
399 struct NotFunctor {
400  bool operator()(const int N, const bool* X, bool* Y, Context* context) const {
401  math::Not(N, X, Y, context);
402  return true;
403  }
404 };
405 
406 template <class Context>
407 struct SignFunctor {
408  template <typename T>
409  bool operator()(const int N, const T* X, T* Y, Context* context) const {
410  math::Sign(N, X, Y, context);
411  return true;
412  }
413 };
414 
415 // Forward-only Binary Functors.
416 #define C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(FunctorName) \
417  template <class Context> \
418  struct FunctorName##Functor { \
419  template <typename TIn, typename TOut> \
420  bool Forward( \
421  const std::vector<int>& A_dims, \
422  const std::vector<int>& B_dims, \
423  const TIn* A, \
424  const TIn* B, \
425  TOut* C, \
426  Context* context) const { \
427  math::FunctorName( \
428  A_dims.size(), \
429  A_dims.data(), \
430  B_dims.size(), \
431  B_dims.data(), \
432  A, \
433  B, \
434  C, \
435  context); \
436  return true; \
437  } \
438  };
439 
440 // Compare functors.
441 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(EQ);
442 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(NE);
443 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(LT);
444 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(LE);
445 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(GT);
446 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(GE);
447 
448 // Logical functors.
449 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(And);
450 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(Or);
451 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(Xor);
452 
453 // Bitwise functors.
454 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(BitwiseAnd);
455 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(BitwiseOr);
456 C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR(BitwiseXor);
457 
458 #undef C10_DECLARE_FOWARD_ONLY_BINARY_FUNCTOR
459 
460 namespace SRLHelper {
461 
462 template <typename T>
463 void sum2one(const T* a, T* y, size_t n);
464 
465 template <typename T>
466 void RunWithBroadcastFront(const T* a, T* y, size_t pre, size_t n, CPUContext*);
467 
468 template <typename T>
469 void RunWithBroadcastBack(const T* a, T* y, size_t post, size_t n, CPUContext*);
470 
471 template <typename T>
472 void RunWithBroadcast2(
473  const T* a,
474  T* y,
475  size_t pre,
476  size_t n,
477  size_t post,
478  CPUContext*);
479 
480 } // namespace SRLHelper
481 
482 // Sum reduction operator that is used for computing the gradient in cases
483 // where the forward op is in broadcast mode.
484 template <class Context>
485 class SumReduceLikeOp final : public Operator<Context> {
486  public:
487  USE_OPERATOR_CONTEXT_FUNCTIONS;
488  template <class... Args>
489  explicit SumReduceLikeOp(Args&&... args)
490  : Operator<Context>(std::forward<Args>(args)...),
491  OP_SINGLE_ARG(int, "axis", axis_, -1),
492  OP_SINGLE_ARG(string, "axis_str", axis_str_, ""),
493  OP_SINGLE_ARG(string, "order", order_, "NCHW") {
494  if (axis_ != -1) {
495  // Get axis from an explicit axis argument.
496  CAFFE_ENFORCE_EQ(
497  axis_str_.size(),
498  0,
499  "Args axis and axis_str cannot be used simultaneously.");
500  } else if (axis_str_.size()) {
501  // Get the axis index semantically.
502  CAFFE_ENFORCE_EQ(
503  axis_str_.size(), 1, "Unsupported axis string", axis_str_);
504  size_t semantic_axis = order_.find(axis_str_);
505  CAFFE_ENFORCE_NE(
506  semantic_axis,
507  string::npos,
508  "Unrecognizable axis string ",
509  axis_str_,
510  " from order string ",
511  order_);
512  axis_ = semantic_axis;
513  }
514  }
515 
516  bool RunOnDevice() override {
517  return DispatchHelper<TensorTypes<float, double>>::call(this, Input(0));
518  }
519 
520  template <typename T>
521  bool DoRunWithType();
522 
523  private:
524  int axis_;
525  string axis_str_;
526  string order_;
527  Tensor ones_{Context::GetDeviceType()};
528  Tensor sum_buffer_{Context::GetDeviceType()};
529 };
530 
531 } // namespace caffe2
532 
533 #endif // CAFFE2_OPERATORS_ELEMENTWISE_OPS_H_
The CPU Context, representing the bare minimum of what a Context class in Caffe2 should implement...
Definition: context.h:40
Definition: static.cpp:52
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
Definition: blob.h:13
Definition: static.cpp:64
Definition: static.cpp:58