Caffe2 - C++ API
A deep learning, cross platform ML framework
operator_schema.cc
1 #include "caffe2/core/operator_schema.h"
2 #include "caffe2/core/logging.h"
3 
4 namespace caffe2 {
5 
6 bool OpSchema::Verify(const OperatorDef& def) const {
7  // Check the number of inputs.
8  if (def.input_size() < min_input_ || def.input_size() > max_input_) {
9  LOG(ERROR) << "Input size " << def.input_size()
10  << " not in range [min=" << min_input_ << ", max="
11  << max_input_ << "].";
12  return false;
13  }
14  if (!num_inputs_allowed_(def.input_size())) {
15  LOG(ERROR) << "Input size " << def.input_size()
16  << " not in allowed input sizes.";
17  return false;
18  }
19  // Check the number of outputs.
20  if (def.output_size() < min_output_ || def.output_size() > max_output_) {
21  LOG(ERROR) << "Output size " << def.output_size()
22  << " not in range [min=" << min_output_ << ", max="
23  << max_output_ << "].";
24  return false;
25  }
26  if (!num_outputs_allowed_(def.output_size())) {
27  LOG(ERROR) << "Output size " << def.output_size()
28  << " not in allowed output sizes.";
29  return false;
30  }
31  if (!num_inputs_outputs_allowed_(def.input_size(), def.output_size())) {
32  LOG(ERROR) << "Combination of input size " << def.input_size()
33  << "and output size " << def.output_size() << " not in allowed.";
34  return false;
35  }
36  // If the number of outputs can be calculated, check if the number matches.
37  if (calculate_output_) {
38  int expected_nout = calculate_output_(def.input_size());
39  if (expected_nout != kCannotComputeNumOutputs &&
40  def.output_size() != expected_nout) {
41  LOG(ERROR) << "Output size " << def.output_size()
42  << " not matching expected output size, which is "
43  << expected_nout;
44  return false;
45  }
46  }
47 
48  // Check in-place settings.
49  for (int in_idx = 0; in_idx < def.input_size(); ++in_idx) {
50  for (int out_idx = 0; out_idx < def.output_size(); ++out_idx) {
51  // If an input is the same as an output but in-place is not opt-in
52  // either as allowed or enforced, we will fail the verification.
53  if (def.input(in_idx) == def.output(out_idx) &&
54  (!inplace_allowed_(in_idx, out_idx)
55  && !inplace_enforced_(in_idx, out_idx))) {
56  LOG(ERROR) << "Input index " << in_idx << " and output idx " << out_idx
57  << " (" << def.input(in_idx) << ")"
58  << " are set to be in-place but this is actually not "
59  << "supported by op " << def.type();
60  return false;
61  }
62  if (def.input(in_idx) != def.output(out_idx) &&
63  inplace_enforced_(in_idx, out_idx)) {
64  LOG(ERROR) << "Input index " << in_idx << " (" << def.input(in_idx) << ")"
65  << " and output idx " << out_idx
66  << " (" << def.output(in_idx) << ")"
67  << " are not in-place but should be as required by op "
68  << def.type();
69  return false;
70  }
71  }
72  }
73 
74  std::set<std::string> present_args{};
75  for (const auto& arg : def.arg()) {
76  present_args.insert(arg.name());
77  }
78 
79  for (const auto& arg : args()) {
80  if (arg.is_required() &&
81  present_args.find(arg.name()) == present_args.end()) {
82  LOG(ERROR) << "Argument '" << arg.name() << "' is required for Operator '"
83  << def.type() << "'.";
84  return false;
85  }
86  }
87 
88  // Phew. All verifications passed.
89  return true;
90 }
91 
92 OpSchema& OpSchema::NumInputs(int min, int max) {
93  min_input_ = min;
94  max_input_ = max;
95  return *this;
96 }
97 
99  return NumInputs(n, n);
100 }
101 
102 OpSchema& OpSchema::NumInputs(std::function<bool(int)> func) {
103  num_inputs_allowed_ = func;
104  return *this;
105 }
106 
107 OpSchema& OpSchema::NumInputs(set<int> allowed_input_nums) {
108  return NumInputs(
109  [allowed_input_nums](int n)->bool {
110  return allowed_input_nums.count(n);
111  });
112 }
113 
114 OpSchema& OpSchema::NumOutputs(int min, int max) {
115  min_output_ = min;
116  max_output_ = max;
117  return *this;
118 }
119 
121  return NumOutputs(n, n);
122 }
123 
124 OpSchema& OpSchema::NumOutputs(std::function<bool(int)> func) {
125  num_outputs_allowed_ = func;
126  return *this;
127 }
128 
129 OpSchema& OpSchema::NumOutputs(set<int> allowed_output_nums) {
130  return NumOutputs(
131  [allowed_output_nums](int n)->bool {
132  return allowed_output_nums.count(n);
133  });
134 }
135 
136 OpSchema& OpSchema::NumInputsOutputs(std::function<bool(int, int)> func) {
137  num_inputs_outputs_allowed_ = func;
138  return *this;
139 }
140 
141 OpSchema& OpSchema::OutputCalculator(std::function<int(int)> calc) {
142  calculate_output_ = calc;
143  return *this;
144 }
145 
147  return OutputCalculator([](int n)->int { return n; } );
148 }
149 
150 OpSchema& OpSchema::AllowInplace(std::function<bool(int, int)> inplace) {
151  inplace_allowed_ = inplace;
152  return *this;
153 }
154 
155 OpSchema& OpSchema::AllowInplace(set<std::pair<int, int>> inplace) {
156  return AllowInplace(
157  [inplace](int in, int out)->bool {
158  return inplace.count(std::make_pair(in, out));
159  });
160 }
161 
162 OpSchema& OpSchema::AllowOneToOneInplace() {
163  return AllowInplace([](int in, int out) { return in == out; });
164 }
165 
166 OpSchema& OpSchema::EnforceInplace(std::function<bool(int, int)> inplace) {
167  inplace_enforced_ = inplace;
168  return *this;
169 }
170 
171 OpSchema& OpSchema::EnforceInplace(set<std::pair<int, int>> inplace) {
172  return EnforceInplace(
173  [inplace](int in, int out)->bool {
174  return inplace.count(std::make_pair(in, out));
175  });
176 }
177 
178 OpSchema& OpSchema::EnforceOneToOneInplace() {
179  return EnforceInplace([](int in, int out) { return in == out; });
180 }
181 
182 OpSchema& OpSchema::Private() {
183  private_ = true;
184  return *this;
185 }
186 
187 OpSchema& OpSchema::InputsCanCrossDevices() {
188  inputs_can_cross_devices_ = true;
189  return *this;
190 }
191 
193  TensorInferenceFunctionType function) {
194  tensor_inference_function_ = function;
195  return *this;
196 }
197 
198 OpSchema::TensorInferenceFunctionType OpSchema::NeedsAllInputShapes(
199  TensorInferenceFunctionType f) {
200  return [f](const OperatorDef& def, const vector<TensorShape>& in) {
201  for (const auto& in_ts : in) {
202  if (in_ts.unknown_shape()) {
203  vector<TensorShape> out(def.output().size());
204  for (auto& out_ts : out) {
205  out_ts.set_unknown_shape(true);
206  }
207  return out;
208  }
209  }
210  return f(def, in);
211  };
212 }
213 
214 OpSchema& OpSchema::InheritOnnxSchema(const std::string& onnx_schema_name) {
215  onnx_schema_ = onnx_schema_name;
216  return *this;
217 }
218 
221  [](const OperatorDef&, const vector<TensorShape>& input_types) {
222  return vector<TensorShape>(input_types);
223  });
224 }
225 
226 OpSchema& OpSchema::IdenticalTypeAndShapeOfInput(int idx) {
228  [idx](const OperatorDef&, const vector<TensorShape>& input_types) {
229  vector<TensorShape> out(1);
230  out[0] = input_types[idx];
231  return out;
232  });
233 }
234 
235 OpSchema& OpSchema::IdenticalTypeAndShapeOfMultipleInputs(
236  const vector<int>& indices) {
238  [indices](const OperatorDef&, const vector<TensorShape>& input_types) {
239  vector<TensorShape> out(indices.size());
240  for (int i = 0; i < indices.size(); i++) {
241  out[i] = input_types[indices.at(i)];
242  }
243  return out;
244  });
245 }
246 
247 OpSchema& OpSchema::IdenticalTypeAndShapeOfInputDim(int idx, int dim) {
249  [idx, dim](const OperatorDef&, const vector<TensorShape>& input_types) {
250  vector<TensorShape> out(1);
251  out[0].add_dims(input_types[idx].dims(dim));
252  out[0].set_data_type(input_types[idx].data_type());
253  return out;
254  });
255 }
256 
257 OpSchema& OpSchema::ScalarType(::caffe2::TensorProto_DataType dt) {
259  [dt](const OperatorDef& def, const vector<TensorShape>& /*input_types*/) {
260  TensorShape shape;
261  shape.set_data_type(dt);
262  vector<TensorShape> out(def.output_size(), shape);
263  return out;
264  });
265 }
266 
268  cost_inference_function_ =
269  caffe2::make_unique<CostInferenceFunctionType>(function);
270  return *this;
271 }
272 
273 OpSchema& OpSchema::DeviceInferenceFunction(
274  DeviceInferenceFunctionType function) {
275  device_inference_function_ = function;
276  return *this;
277 }
278 
279 OpSchema& OpSchema::SetDoc(const string& doc) {
280  doc_ = doc;
281  return *this;
282 }
283 
284 OpSchema&
285 OpSchema::Arg(const char* name, const char* description, bool required) {
286  args_.push_back(Argument(name, description, required));
287  return *this;
288 }
289 
290 #define DEFINE_STANDARG_ARG(name, str) \
291  CAFFE2_API const char* OpSchema::Arg_##name = #str; \
292  CAFFE2_API OpSchema& OpSchema::Arg##name(const char* description) { \
293  return Arg(#str, description, true); \
294  }
295 
296 DEFINE_STANDARG_ARG(IsTest, is_test)
297 
298 #undef DEFINE_STANDARG_ARG
299 
300 OpSchema& OpSchema::Input(const int n, const char* name, const char* description) {
301  if (input_desc_.size() <= (unsigned)n) {
302  input_desc_.resize(n + 1);
303  }
304  input_desc_[n] = std::make_pair(name, description);
305  return *this;
306 }
307 
308 OpSchema& OpSchema::Output(const int n, const char* name, const char* description) {
309  if (output_desc_.size() <= (unsigned)n) {
310  output_desc_.resize(n + 1);
311  }
312  output_desc_[n] = std::make_pair(name, description);
313  return *this;
314 }
315 
316 OpSchema& OpSchema::FillUsing(std::function<void(OpSchema&)> populator) {
317  if (populator) {
318  populator(*this);
319  }
320  return *this;
321 }
322 
323 int OpSchema::CalculateOutput(int num_input) const {
324  if (min_output_ == max_output_) {
325  return min_output_;
326  } else if (calculate_output_) {
327  return calculate_output_(num_input);
328  } else {
329  return kCannotComputeNumOutputs;
330  }
331 }
332 
333 namespace {
334 void SparseLengthsFillerHelper(
335  const std::vector<std::vector<int64_t>>& shapes,
336  size_t value_index,
337  size_t length_index,
338  std::vector<TensorFiller>* fillers) {
339  CAFFE_ENFORCE_EQ(shapes[length_index].size(), 1);
340  // filler.h: SparseLengths->FixedSum will select FD_FIXEDSUM distribution
341  (*fillers)[length_index].SparseLengths(shapes[value_index].front());
342 }
343 
344 void SparseWeightsFillerHelper(
345  const std::vector<std::vector<int64_t>>& shapes,
346  size_t weight_index,
347  std::vector<TensorFiller>* fillers) {
348  (*fillers)[weight_index]
349  .Min(0)
350  .Max(shapes[weight_index].front())
351  .Dist(FD_UNIFORM);
352 }
353 
354 void SparseSegmentsFillerHelper(
355  const std::vector<std::vector<int64_t>>& shapes,
356  size_t value_index,
357  size_t segment_index,
358  std::vector<TensorFiller>* fillers) {
359  CAFFE_ENFORCE_EQ(shapes[segment_index].size(), 1);
360  // filler.h SparseSegments will select FD_UNIFORM or FD_SYNTHETIC distribution
361  (*fillers)[value_index]
362  .Min(0)
363  .Max(shapes[value_index].front() * 2)
364  .Dist(FD_UNIFORM);
365  (*fillers)[segment_index].SparseSegments(shapes[value_index].front() - 1);
366 }
367 } // namespace
368 
369 // The helper is build sparse input with values, keys, and lengths; e.g.:
370 // values = [1, 2, 3, 2, 4, 6, 7, 3, 6]
371 // keys = [0, 1, 4, 0, 1, 2, 5, 1, 2]
372 // \_____/ \________/ \__/
373 // lengths = [3, 4, 2]
374 OpSchema& OpSchema::ValueKeyLengthInputFillers(
375  size_t value_index,
376  size_t key_index,
377  size_t length_index) {
378  filler_supplier_ = [this, value_index, key_index, length_index](
379  const std::vector<std::vector<int64_t>>& shapes) {
380  auto fillers = SupplyDenseFillers(shapes);
381  // fill in the length (value_index is used to get the correct shape)
382  SparseLengthsFillerHelper(shapes, key_index, length_index, &fillers);
383  // fill in the keys (value_index is used to get the correct shape)
384  SparseSegmentsFillerHelper(shapes, value_index, key_index, &fillers);
385  return fillers;
386  };
387  return *this;
388 }
389 
390 // The helper is build sparse input with values, keys, and lengths; e.g.:
391 // values = [1, 2, 3, 2, 4, 6, 7, 3, 6]
392 // keys = [0, 1, 4, 0, 1, 2, 5, 1, 2]
393 // weights = [1, 1, 1, 0, 2, 2, 2, 1, 2]
394 // \_____/ \________/ \__/
395 // lengths = [3, 4, 2]
396 OpSchema& OpSchema::WeightedValueKeyLengthInputFillers(
397  size_t value_index,
398  size_t key_index,
399  size_t length_index,
400  size_t weight_index) {
401  filler_supplier_ = [this, value_index, key_index, length_index, weight_index](
402  const std::vector<std::vector<int64_t>>& shapes) {
403  auto fillers = SupplyDenseFillers(shapes);
404  // fill in the length (value_index is used to get the correct shape)
405  SparseLengthsFillerHelper(shapes, key_index, length_index, &fillers);
406  // fill in the keys (value_index is used to get the correct shape)
407  SparseSegmentsFillerHelper(shapes, value_index, key_index, &fillers);
408  // fill in the weights
409  SparseWeightsFillerHelper(shapes, weight_index, &fillers);
410  return fillers;
411  };
412  return *this;
413 }
414 
415 // The helper is build sparse input with values and lengths; e.g.:
416 // values = [1, 2, 3, 2, 4, 6, 7, 3, 6]
417 // \_____/ \________/ \__/
418 // lengths = [3, 4, 2]
419 OpSchema& OpSchema::ValueLengthInputFillers(
420  size_t value_index,
421  size_t length_index) {
422  filler_supplier_ = [this, value_index, length_index](
423  const std::vector<std::vector<int64_t>>& shapes) {
424  auto fillers = SupplyDenseFillers(shapes);
425  // fill in the length (value_index is used to get the correct shape)
426  SparseLengthsFillerHelper(shapes, value_index, length_index, &fillers);
427  return fillers;
428  };
429  return *this;
430 }
431 
432 OpSchema& OpSchema::DisallowInputFillers() {
433  filler_supplier_ =
434  [this](const std::vector<std::vector<int64_t>>& /* unused */) {
435  throw std::invalid_argument(type_ + " does not have input fillers");
436  return std::vector<TensorFiller>();
437  };
438  return *this;
439 }
440 
441 std::vector<TensorFiller> OpSchema::InputFillers(
442  const std::vector<std::vector<int64_t>>& shapes) const {
443  return filler_supplier_(shapes);
444 }
445 
446 std::vector<TensorFiller> OpSchema::SupplyDenseFillers(
447  const std::vector<std::vector<int64_t>>& shapes) {
448  std::vector<TensorFiller> fillers;
449  for (const auto& shape : shapes) {
450  fillers.emplace_back(shape);
451  }
452  return fillers;
453 }
454 
455 C10_EXPORT std::ostream& operator<<(std::ostream& out, const OpSchema& schema) {
456  if (!schema.args().empty()) {
457  out << "Arguments:" << std::endl;
458  for (const auto& arg : schema.args()) {
459  out << " " << arg.name() << " : " << arg.description() << std::endl;
460  }
461  }
462  if (schema.max_input_ > 0) {
463  out << "Inputs:" << std::endl;
464  if (!schema.input_desc_.empty()) {
465  for (size_t i = 0; i < schema.input_desc_.size(); ++i) {
466  const auto& p = schema.input_desc_[i];
467  out << " " << i << ", " << (p.first ? p.first : "(unnamed)") << " : "
468  << (p.second ? p.second : "(no doc)") << std::endl;
469  }
470  } else {
471  out << " (no explicit description available)" << std::endl;
472  }
473  }
474  if (schema.max_output_ > 0) {
475  out << "Outputs:" << std::endl;
476  if (!schema.output_desc_.empty()) {
477  for (size_t i = 0; i < schema.output_desc_.size(); ++i) {
478  const auto& p = schema.output_desc_[i];
479  out << " " << i << ", " << (p.first ? p.first : "(unnamed)") << " : "
480  << (p.second ? p.second : "(no doc)") << std::endl;
481  }
482  } else {
483  out << " (no explicit description available)" << std::endl;
484  }
485  }
486  out << std::endl;
487  if (schema.doc()) {
488  out << schema.doc();
489  } else {
490  out << "(no documentation yet)" << std::endl;
491  }
492  out << std::endl;
493  if (schema.line_) {
494  out << "Defined at " << schema.file_ << ":" << schema.line_ << std::endl;
495  }
496  return out;
497 }
498 
499 CaffeMap<string, OpSchema>& OpSchemaRegistry::map() {
500  static CaffeMap<string, OpSchema> map;
501  return map;
502 }
503 
504 } // namespace caffe2
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.
bool Verify(const OperatorDef &def) const
Verifies if an operator definition protobuf matches the pattern specified in the schema.
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.
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
Definition: blob.h:13
OpSchema & CostInferenceFunction(CostInferenceFunctionType function)
Register the Cost inference function.
OpSchema & InheritOnnxSchema()
Shortcut to InheritOnnxSchema(type_)
OpSchema & NumInputsOutputs(std::function< bool(int, int)> func)
Relationship between inputs and outputs is checked with a specified function.
static TensorInferenceFunctionType NeedsAllInputShapes(TensorInferenceFunctionType f)
A wrapper that makes an infer tensor function to return unknown shape for all outputs if any one of t...
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 ...