Caffe2 - C++ API
A deep learning, cross platform ML framework
operator_schema.h
1 #ifndef CAFFE2_CORE_OPERATOR_SCHEMA_H_
2 #define CAFFE2_CORE_OPERATOR_SCHEMA_H_
3 
4 #include <climits>
5 #include <functional>
6 #include <initializer_list>
7 #include <ostream>
8 #include <set>
9 #include <vector>
10 #include <unordered_map>
11 
12 #include "caffe2/core/common.h"
13 #include "caffe2/core/logging.h"
14 #include "caffe2/core/registry.h"
15 #include "caffe2/proto/caffe2.pb.h"
16 
17 namespace caffe2 {
18 
19 // A const value returned by OpSchema::CalculateOutput() if the number of
20 // output cannot be determined.
21 constexpr int kCannotComputeNumOutputs = -1;
22 
38 class OpSchema {
39  public:
40  OpSchema() : file_("unknown"), line_(0) {}
41  OpSchema(const string& file, const int line) : file_(file), line_(line) {}
42 
46  inline const string& file() const {
47  return file_;
48  }
49 
53  inline int line() const {
54  return line_;
55  }
56 
60  inline const char* doc() const {
61  return doc_.empty() ? nullptr : doc_.c_str();
62  }
63 
68  bool Verify(const OperatorDef& def) const;
69 
70  // Functions to set the property of the operator schemas.
71  // Sets the number of inputs, either a fixed number or a min and a max.
72 
76  OpSchema& NumInputs(int n);
80  OpSchema& NumInputs(int min, int max);
84  OpSchema& NumInputs(set<int> allowed_input_nums);
88  OpSchema& NumInputs(std::function<bool(int)> func);
89 
90  // Sets the number of outputs, either a fixed number, a min and a max,
91  // or a function that takes in the input number and produces an output
92  // number. Use only one function in the set below.
96  OpSchema& NumOutputs(int n);
100  OpSchema& NumOutputs(int min, int max);
104  OpSchema& NumOutputs(set<int> allowed_output_nums);
108  OpSchema& NumOutputs(std::function<bool(int)> func);
109 
114  OpSchema& NumInputsOutputs(std::function<bool(int, int)> func);
115 
116  // Set the function that can calculate the number of output based on the
117  // number of input. Use only one function in the set below.
121  OpSchema& OutputCalculator(std::function<int(int)> calc);
126 
127  // Sets the rule to allow optional in-place operation.
128  OpSchema& AllowInplace(std::function<bool(int, int)> inplace);
129  OpSchema& AllowInplace(set<std::pair<int, int>> inplace);
130  OpSchema& AllowOneToOneInplace();
131  // Sets the rule to enforce in-place opeartion.
132  OpSchema& EnforceInplace(std::function<bool(int, int)> inplace);
133  OpSchema& EnforceInplace(set<std::pair<int, int>> inplace);
134  OpSchema& EnforceOneToOneInplace();
135 
136  // Functions to deal with type and shape inference. Basically, this registers
137  // a function that takes in an OperatorDef and a series of input type and
138  // shape specified by TensorProto objects (whose data fields are empty), and
139  // produces a series of output type and shape.
140  typedef std::function<
141  vector<TensorShape>(const OperatorDef&, const vector<TensorShape>&)>
142  TensorInferenceFunctionType;
143 
148  OpSchema& TensorInferenceFunction(TensorInferenceFunctionType function);
149 
153  OpSchema& InheritOnnxSchema(const std::string& onnx_schema_name);
154 
160  OpSchema& IdenticalTypeAndShapeOfInput(int idx);
161  OpSchema& IdenticalTypeAndShapeOfInputDim(int idx, int dim);
162  OpSchema& ScalarType(::caffe2::TensorProto_DataType dt);
163 
168  inline vector<TensorShape> InferTensor(
169  const OperatorDef& def,
170  const vector<TensorShape>& input_type_shape) const {
171  return tensor_inference_function_(def, input_type_shape);
172  }
173 
174  /*
175  * @brief A struct to store various cost information about
176  * an operator such as FLOPs, total memory use and parameters.
177  */
178  struct Cost {
179  uint64_t flops; // Floating point operations.
180  uint64_t bytes_moved; // Total memory used.
181  uint64_t params_bytes; // Memory footprint of parameters
182  };
188  typedef std::function<
189  struct Cost(const OperatorDef&, const vector<TensorShape>&)>
191 
196 
197 #if 0 // def _MSC_VER
198 
201  template <typename T,
202  typename = std::enable_if<
203  std::is_same<CostInferenceFunctionType&&, T>:value
204  >:type>
205  inline OpSchema& CostInferenceFunction(T func) {
206  // Note: This is here in order to resolve an MSVC compiler issue: it
207  // does not automatically convert a function pointer to a std::function,
208  // and needs an explicit conversion.
210  }
211 #endif // _MSC_VER
212 
213  bool HasCostInferenceFunction() const {
214  return !!cost_inference_function_;
215  }
216 
217  inline struct Cost InferCost(
218  const OperatorDef& def,
219  const vector<TensorShape>& input_tensor_shape) const {
220  CAFFE_ENFORCE(
221  cost_inference_function_, "Cost inference function not defined.");
222  return (*cost_inference_function_)(def, input_tensor_shape);
223  }
224 
225  // Functions to do documentation for the operator schema.
226  OpSchema& SetDoc(const string& doc);
227 
228  struct Argument {
229  Argument(const char* name, const char* description, bool required)
230  : name_{name}, description_{description}, required_{required} {}
231 
232  const char* name() const {
233  return name_;
234  }
235 
236  const char* description() const {
237  return description_;
238  }
239 
240  bool is_required() const {
241  return required_;
242  }
243 
244  private:
245  const char* name_;
246  const char* description_;
247  const bool required_;
248  };
249 
250  OpSchema&
251  Arg(const char* name, const char* description, bool required = false);
252 
253 #define DECLARE_STANDARD_ARG(name, str) \
254  CAFFE2_API static const char* Arg_##name; \
255  CAFFE2_API OpSchema& Arg##name(const char* description);
256 
257  DECLARE_STANDARD_ARG(IsTest, is_test)
258 
259 #undef DECLARE_STANDARD_ARG
260 
261  OpSchema& Input(const int n, const char* name, const char* description);
262  OpSchema& Output(const int n, const char* name, const char* description);
263  // Calls the passed function with `this` as an argument. Useful for
264  // adding docs for temlated/macro ops.
265  OpSchema& FillUsing(std::function<void(OpSchema&)> populator);
266 
267  // Remove from documentation
268  OpSchema& Private();
269 
270  // This op can pass data across devices
271  OpSchema& InputsCanCrossDevices();
272 
277  int CalculateOutput(int num_input) const;
278 
279  const std::string& onnx_schema() const {
280  return onnx_schema_;
281  }
282 
283  int min_input() const {
284  return min_input_;
285  }
286 
287  int max_input() const {
288  return max_input_;
289  }
290 
291  int min_output() const {
292  return min_output_;
293  }
294 
295  int max_output() const {
296  return max_output_;
297  }
298 
299  bool num_inputs_allowed(int x) const {
300  return num_inputs_allowed_(x);
301  }
302 
303  bool num_outputs_allowed(int x) const {
304  return num_outputs_allowed_(x);
305  }
306 
307  bool num_inputs_outputs_allowed(int x, int y) const {
308  return num_inputs_outputs_allowed_(x, y);
309  }
310 
311  int inf() const {
312  return std::numeric_limits<int>::max();
313  }
314 
315  friend std::ostream& operator<<(std::ostream& out, const OpSchema& schema);
316 
317  const std::vector<Argument>& args() const {
318  return args_;
319  }
320 
321  const std::vector<std::pair<const char*, const char*>>& input_desc() const {
322  return input_desc_;
323  }
324  const std::vector<std::pair<const char*, const char*>>& output_desc() const {
325  return output_desc_;
326  }
327  bool private_op() {
328  return private_;
329  }
330  bool inputs_can_cross_devices() const {
331  return inputs_can_cross_devices_;
332  }
333 
337  using DeviceInferenceFunctionType = std::function<
338  std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>(
339  const OperatorDef& def)>;
340 
341  OpSchema& DeviceInferenceFunction(DeviceInferenceFunctionType function);
342 
346  inline std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>
347  InferDevice(const OperatorDef& def) const {
348  return device_inference_function_(def);
349  }
350 
351  private:
352  string file_;
353  string doc_;
354  string onnx_schema_;
355  std::vector<Argument> args_{};
356  std::vector<std::pair<const char*, const char*>> input_desc_{};
357  std::vector<std::pair<const char*, const char*>> output_desc_{};
358  int line_ = 0;
359  int min_input_ = 0;
360  int max_input_ = std::numeric_limits<int>::max();
361  int min_output_ = 0;
362  int max_output_ = std::numeric_limits<int>::max();
363  bool private_ = false;
364  bool inputs_can_cross_devices_ = false;
365  std::function<bool(int)> num_inputs_allowed_ = [](int) { return true; };
366  std::function<bool(int)> num_outputs_allowed_ = [](int) { return true; };
367  std::function<bool(int, int)> num_inputs_outputs_allowed_ = [](int, int) {
368  return true;
369  };
370  std::function<int(int)> calculate_output_;
371  // In default, any in-place operation is neither allowed nor enforced.
372  std::function<bool(int, int)> inplace_allowed_ = [](int, int) {
373  return false;
374  };
375  std::function<bool(int, int)> inplace_enforced_ = [](int, int) {
376  return false;
377  };
378  TensorInferenceFunctionType tensor_inference_function_ =
379  [](const OperatorDef& def, const vector<TensorShape>&) {
380  vector<TensorShape> out;
381  for (int i = 0; i < def.output_size(); i++) {
382  TensorShape ts;
383  ts.set_unknown_shape(true);
384  out.push_back(ts);
385  }
386  return out;
387  };
388  std::unique_ptr<CostInferenceFunctionType> cost_inference_function_ = nullptr;
389  DeviceInferenceFunctionType device_inference_function_ =
390  [](const OperatorDef& def) {
391  auto op_device =
392  def.has_device_option() ? def.device_option() : DeviceOption();
393  vector<DeviceOption> in_dev(def.input_size(), op_device);
394  vector<DeviceOption> out_dev(def.output_size(), op_device);
395  return std::make_pair(in_dev, out_dev);
396  };
397 };
398 
403  public:
404  static OpSchema&
405  NewSchema(const string& key, const string& file, const int line) {
406  auto& m = map();
407  auto it = m.find(key);
408  if (it != m.end()) {
409  const auto& schema = it->second;
410  std::ios_base::Init init;
411  std::cerr << "Trying to register schema with name " << key
412  << " from file " << file << " line " << line
413  << ", but it is already registered from file " << schema.file()
414  << " line " << schema.line();
415  abort();
416  }
417  m.emplace(std::make_pair(key, OpSchema(file, line)));
418  return m[key];
419  }
420 
421  static const OpSchema* Schema(const string& key) {
422  auto& m = map();
423  auto it = m.find(key);
424  if (it != m.end()) {
425  return &it->second;
426  } else {
427  return nullptr;
428  }
429  }
430 
431  private:
432  // OpSchemaRegistry should not need to be instantiated.
433  OpSchemaRegistry() = delete;
434 
445  static CaffeMap<string, OpSchema>& map();
446 };
447 
448 // Helper function for creating simple tensorproto with dimension and type
449 template <typename T_I = int>
450 inline TensorShape CreateTensorShape(
451  vector<T_I> dims,
452  ::caffe2::TensorProto_DataType dt) {
453  TensorShape ts;
454  for (int d : dims) {
455  ts.add_dims(d);
456  }
457  ts.set_data_type(dt);
458  return ts;
459 }
460 
461 // Helper function
462 inline vector<TIndex> GetDimsVector(const TensorShape& shape) {
463  vector<TIndex> dims;
464  for (auto d : shape.dims()) {
465  dims.push_back(d);
466  }
467  return dims;
468 }
469 
470 // Helper function for infer op inputs and outputs device information.
471 inline std::pair<std::vector<DeviceOption>, std::vector<DeviceOption>>
472 InferOpInputOutputDevice(const OperatorDef& op) {
473  auto op_schema = OpSchemaRegistry::Schema(op.type());
474  CAFFE_ENFORCE(
475  op_schema, "Device inference failed. No schema for: ", op.type());
476  // TODO(wyiming) : add try catch here.
477  return op_schema->InferDevice(op);
478 }
479 
480 template <uint64_t OpsPerPoint>
481 OpSchema::Cost PointwiseCostInference(
482  const OperatorDef& /* unused */,
483  const vector<TensorShape>& inputs) {
484  struct OpSchema::Cost c;
485  const TensorShape X = inputs[0];
486  uint64_t size = 1;
487 
488  for (auto i = 0; i < X.dims().size(); ++i) {
489  size *= X.dims(i);
490  }
491 
492  c.flops = size * OpsPerPoint;
493  c.bytes_moved = size * sizeof(X.data_type());
494  return c;
495 }
496 
497 } // namespace caffe2
498 
499 #ifndef CAFFE2_NO_OPERATOR_SCHEMA
500 
501 #define OPERATOR_SCHEMA(name) \
502  void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name(){}; \
503  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(name) = \
504  &OpSchemaRegistry::NewSchema(#name, __FILE__, __LINE__)
505 #define OPERATOR_SCHEMA_STR(name) \
506  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(schema_registration) = \
507  &OpSchemaRegistry::NewSchema(name, __FILE__, __LINE__)
508 
509 #else // CAFFE2_NO_OPERATOR_SCHEMA
510 
511 #define OPERATOR_SCHEMA(name) \
512  void CAFFE2_PLEASE_ADD_OPERATOR_SCHEMA_FOR_##name(){}; \
513  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(name) = \
514  1 ? nullptr : &OpSchemaRegistry::NewSchema(#name, __FILE__, __LINE__)
515 #define OPERATOR_SCHEMA_STR(name) \
516  static OpSchema* CAFFE_ANONYMOUS_VARIABLE(schema_registration) = \
517  1 ? nullptr : &OpSchemaRegistry::NewSchema(name, __FILE__, __LINE__)
518 
519 #endif // CAFFE2_NO_OPERATOR_SCHEMA
520 
521 #endif // CAFFE2_CORE_OPERATOR_SCHEMA_H_
std::function< std::pair< std::vector< DeviceOption >, std::vector< DeviceOption >>(const OperatorDef &def)> DeviceInferenceFunctionType
Returns the required device location of inputs and outputs.
OpSchema & NumInputs(int n)
A single input.
A class to record the schema of an op.
vector< TensorShape > InferTensor(const OperatorDef &def, const vector< TensorShape > &input_type_shape) const
A function to allow one to infer the type and shape from the op schema.
bool Verify(const OperatorDef &def) const
Verifies if an operator definition protobuf matches the pattern specified in the schema.
A registry to hold all the operator schemas.
int line() const
Returns the line in file that the op schema is registered from.
const char * doc() const
Returns the docstring of the op schema.
OpSchema & OutputCalculator(std::function< int(int)> calc)
Set the output calculator to a user-defined function.
OpSchema & IdenticalTypeAndShape()
Sets the tensor inference function to produce the same output as the input.
OpSchema & SameNumberOfOutput()
Set the number of outputs to be the same as the number of inputs.
std::pair< std::vector< DeviceOption >, std::vector< DeviceOption > > InferDevice(const OperatorDef &def) const
Infer required device location of an op&#39;s inputs and outputs.
OpSchema & InheritOnnxSchema(const std::string &onnx_schema_name)
Sets the corresponding onnx schema name.
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
OpSchema & CostInferenceFunction(CostInferenceFunctionType function)
Register the Cost inference function.
OpSchema & NumInputsOutputs(std::function< bool(int, int)> func)
Relationship between inputs and outputs is checked with a specified function.
const string & file() const
Returns the file that the op schema is registered from.
OpSchema & TensorInferenceFunction(TensorInferenceFunctionType function)
Sets the tensor inference function, which is a std::function object defined in operator_schema.h.
int CalculateOutput(int num_input) const
A function to allow one to get the number of outputs based on the number of inputs, if this schema supports it.
OpSchema & NumOutputs(int n)
A single output.
std::function< struct Cost(const OperatorDef &, const vector< TensorShape > &)> CostInferenceFunctionType
Registers a function that takes in an OperatorDef and a series of input shapes and returns the total ...