Caffe2 - C++ API
A deep learning, cross platform ML framework
operator.cc
1 #include "caffe2/core/operator.h"
2 
3 #include <algorithm>
4 
5 #include "caffe2/core/logging.h"
6 #include "caffe2/core/net.h"
7 #include "caffe2/core/operator_gradient.h"
8 #include "caffe2/core/tensor.h"
9 #include "caffe2/core/workspace.h"
10 
11 #include "caffe2/proto/caffe2.pb.h"
12 #include "caffe2/utils/proto_utils.h"
13 #include "caffe2/utils/string_utils.h"
14 
15 namespace caffe2 {
16 
17 OperatorBase::OperatorBase(const OperatorDef& operator_def, Workspace* ws)
18  : operator_def_(operator_def), arg_helper_(operator_def_) {
19  for (const string& input_str : operator_def_.input()) {
20  auto* blob = ws->GetBlob(input_str);
21  CAFFE_ENFORCE(
22  blob != nullptr,
23  "op ",
24  operator_def_.type(),
25  ": Encountered a non-existing input blob: ",
26  input_str);
27  inputs_.push_back(blob);
28  }
29 
30  GetOperatorLogger()(operator_def_);
31 
32  for (const string& output_str : operator_def_.output()) {
33  outputs_.push_back(CHECK_NOTNULL(ws->CreateBlob(output_str)));
34  }
35 }
36 
37 namespace {
38 unique_ptr<OperatorBase> TryCreateOperator(
39  const string& key, const OperatorDef& operator_def, Workspace* ws) {
40  auto type = operator_def.device_option().device_type();
41  CAFFE_ENFORCE(
42  gDeviceTypeRegistry()->count(type),
43  "Device type ",
44  type,
45  " not registered.");
46  OperatorRegistry* registry = gDeviceTypeRegistry()->at(type);
47  VLOG(1) << "Creating operator with device type " << type;
48  try {
49  return registry->Create(key, operator_def, ws);
50  } catch (const UnsupportedOperatorFeature& err) {
51  VLOG(1) << "Operator " << operator_def.type()
52  << " does not support the requested feature. Msg: " << err.what()
53  << ". Proto is: " << ProtoDebugString(operator_def);
54  return nullptr;
55  }
56 }
57 } // namespace
58 
59 unique_ptr<OperatorBase> CreateOperator(
60  const OperatorDef& operator_def, Workspace* ws) {
61  // first, check with OpSchema if the operator is legal.
62  auto* schema = OpSchemaRegistry::Schema(operator_def.type());
63  if (schema) {
64  CAFFE_ENFORCE(
65  schema->Verify(operator_def),
66  "Operator def did not pass schema checking: ",
67  ProtoDebugString(operator_def));
68  } else {
69  // We would like to recommend every op to register its schema, so if there
70  // is not one, we print a LOG_ERROR. But we will still allow the operator
71  // to be constructed.
72  LOG(ERROR) << "Cannot find operator schema for "
73  << operator_def.type()
74  << ". Will skip schema checking.";
75  }
76 
77  // Second, if the user has provided an engine, try create that engine
78  if (operator_def.engine().size()) {
79  vector<string> engine_choices = split(',', operator_def.engine());
80  for (const string& engine : engine_choices) {
81  string key = operator_def.type() + "_ENGINE_" + engine;
82  VLOG(1) << "Trying to create operator " << operator_def.type()
83  << " with engine " << engine;
84  auto op = TryCreateOperator(key, operator_def, ws);
85  if (op) {
86  return op;
87  } else {
88  // If the above fails, we will just return the normal case with the
89  // default implementation.
90  VLOG(1) << "Operator with engine " << engine
91  << " is not available. Using default implementation.";
92  }
93  }
94  }
95 
96  // Lastly, if the engine does not work here, try using the default engine.
97  auto op = TryCreateOperator(operator_def.type(), operator_def, ws);
98  CAFFE_ENFORCE(
99  op,
100  "Cannot create operator of type '",
101  operator_def.type(),
102  "' on the device '",
103  DeviceTypeName(operator_def.device_option().device_type()),
104  "'. Verify that implementation for the corresponding device exist. It "
105  "might also happen if the binary is not linked with the operator "
106  "implementation code. If Python frontend is used it might happen if "
107  "dyndep.InitOpsLibrary call is missing. Operator def: ",
108  ProtoDebugString(operator_def));
109  return op;
110 }
111 
112 std::map<int32_t, OperatorRegistry*>* gDeviceTypeRegistry() {
113  static std::map<int32_t, OperatorRegistry*> g_device_type_registry;
114  return &g_device_type_registry;
115 }
116 
117 CAFFE_DEFINE_REGISTRY(
118  CPUOperatorRegistry,
119  OperatorBase,
120  const OperatorDef&,
121  Workspace*);
122 CAFFE_REGISTER_DEVICE_TYPE(DeviceType::CPU, CPUOperatorRegistry);
123 
124 CAFFE_DEFINE_REGISTRY(
125  CUDAOperatorRegistry,
126  OperatorBase,
127  const OperatorDef&,
128  Workspace*);
129 CAFFE_REGISTER_DEVICE_TYPE(DeviceType::CUDA, CUDAOperatorRegistry);
130 
131 CAFFE_DEFINE_REGISTRY(
132  GradientRegistry,
133  GradientMakerBase,
134  const OperatorDef&, const vector<GradientWrapper>&);
135 
137  const OperatorDef& def, const vector<GradientWrapper>& g_output) {
138  std::unique_ptr<GradientMakerBase> maker(
139  GradientRegistry()->Create(def.type(), def, g_output));
140  CAFFE_ENFORCE(maker,
141  "Gradient maker for operator ", def.type(), " not implemented.");
142  GradientOpsMeta meta = maker->Get();
143  // Copy device option, engine, and arguments if needed.
144  if (maker->CopyDeviceOption() && def.has_device_option()) {
145  for (OperatorDef& grad_def : meta.ops_) {
146  grad_def.mutable_device_option()->CopyFrom(def.device_option());
147  }
148  }
149  // Copy engine if needed.
150  if (maker->CopyEngine() && def.has_engine()) {
151  for (OperatorDef& grad_def : meta.ops_) {
152  grad_def.set_engine(def.engine());
153  }
154  }
155  // Copy arguments if needed.
156  if (maker->CopyArguments() && def.arg_size()) {
157  for (OperatorDef& grad_def : meta.ops_) {
158  for (auto& arg : def.arg()) {
159  grad_def.add_arg()->CopyFrom(arg);
160  }
161  }
162  }
163  // VLOG for debugging purposes.
164  for (const OperatorDef& grad_def : meta.ops_) {
165  VLOG(1) << "Gradient ops: " << ProtoDebugString(grad_def);
166  }
167  // Check if the gradient computation has returned the right size for the
168  // gradient vector.
169  CAFFE_ENFORCE_EQ(meta.g_input_.size(), def.input_size());
170  VLOG(1) << "Gradients:";
171  for (const GradientWrapper& grad : meta.g_input_) {
172  // The gradient should either be (1) not set, or (2) dense, or (3) sparse,
173  // but cannot be both dense and sparse.
174  if (!grad.IsDense() && !grad.IsSparse()) {
175  VLOG(1) << "\t [no gradient]";
176  } else if (grad.IsDense()) {
177  VLOG(1) << "\t [dense]" << grad.dense_;
178  } else {
179  CAFFE_ENFORCE(
180  grad.indices_.size() && grad.values_.size(),
181  "For sparse gradient, one should set both indices and values. "
182  "Currently we have: (" +
183  grad.indices_ + ", " + grad.values_ + ").");
184  VLOG(1) << "\t [sparse] " << grad.indices_ << ", " << grad.values_;
185  }
186  }
187  return meta;
188 }
189 
190 static TensorShapes InferBlobShapesAndTypes(
191  CaffeMap<string, TensorShape>& blob_desc,
192  const vector<std::unique_ptr<NetDef>>& nets) {
193  for (auto& defptr : nets) {
194  for (const OperatorDef& op : defptr.get()->op()) {
195  vector<TensorShape> input_desc;
196  for (const string& in : op.input()) {
197  auto inp_desc = blob_desc.find(in);
198  if (inp_desc == blob_desc.end()) {
199  CAFFE_THROW(
200  "Shape and type inference failed, could not find shape for ", in);
201  }
202  input_desc.push_back(inp_desc->second);
203  }
204  auto op_schema = OpSchemaRegistry::Schema(op.type());
205  if (op_schema == nullptr) {
206  CAFFE_THROW("Shape inference failed, since no schema for: ", op.type());
207  }
208 
209  std::vector<TensorShape> out = op_schema->InferTensor(op, input_desc);
210  if (op.is_gradient_op() && out.size()) {
211  // Special handling for gradient ops. We can assume gradients
212  // are of same size as the corresponding variables. This is bit
213  // ugly to base on string matching, but we don't have the connection
214  // between variable and its gradient specified
215 
216  CaffeMap<string, string> grads_to_params =
218 
219  for (int i = 0; i < out.size(); i++) {
220  if (out[i].unknown_shape()) {
221  std::string gradout = op.output(i);
222 
223  if (grads_to_params.find(gradout) != grads_to_params.end()) {
224  std::string var = grads_to_params[gradout];
225  if (blob_desc.find(var) != blob_desc.end()) {
226  out[i] = blob_desc[var];
227  }
228  }
229  }
230  }
231  }
232 
233  if (out.size() != op.output_size()) {
234  CAFFE_THROW(
235  "Invalid shape inference for operator ",
236  op.type(),
237  " Expected ",
238  op.output_size(),
239  " outputs, but got ",
240  out.size());
241  } else {
242  for (int i = 0; i < out.size(); i++) {
243  blob_desc[op.output(i)] = out[i];
244  }
245  }
246  }
247  }
248  TensorShapes tps;
249  for (auto kv : blob_desc) {
250  TensorShape& tp = kv.second;
251  TensorShape* tpnew = tps.add_shapes();
252  tpnew->CopyFrom(tp);
253  tpnew->set_name(kv.first);
254  }
255  return tps;
256 }
257 
258 TensorShapes InferBlobShapesAndTypesFromWorkspace(
259  Workspace* ws,
260  const vector<std::unique_ptr<NetDef>>& nets) {
261  CaffeMap<string, TensorShape> blob_desc;
262  // Populate shapes from workplace
263  const std::vector<string>& ws_blobs = ws->Blobs();
264  for (const auto& s : ws_blobs) {
265  Blob* b = ws->GetBlob(s);
266  TypeCall type_fun = GetTypeCallFunction(b->meta().id());
267  ShapeCall shape_fun = GetShapeCallFunction(b->meta().id());
268  TensorShape tp;
269 
270  if (type_fun) {
271  tp.set_data_type(TypeMetaToDataType(type_fun(b->GetRaw())));
272  }
273  if (shape_fun) {
274  bool _shares_data;
275  size_t _capacity;
276  auto shape = shape_fun(b->GetRaw(), _shares_data, _capacity);
277  for (auto d : shape) {
278  tp.add_dims(d);
279  }
280  } else {
281  tp.set_unknown_shape(true);
282  }
283  blob_desc[s] = tp;
284  }
285  return InferBlobShapesAndTypes(blob_desc, nets);
286 }
287 
288 TensorShapes InferBlobShapesAndTypesFromMap(
289  const CaffeMap<std::string, std::vector<TIndex>>& blob_dimensions,
290  const vector<std::unique_ptr<NetDef>>& nets) {
291  CaffeMap<string, TensorShape> blob_desc;
292  // Populate shapes from known blobs
293  for (const auto& blob : blob_dimensions) {
294  TensorShape tp;
295  for (auto d : blob.second) {
296  CAFFE_ENFORCE_GT(d, 0);
297  tp.add_dims(d);
298  }
299  blob_desc[blob.first] = tp;
300  }
301  return InferBlobShapesAndTypes(blob_desc, nets);
302 }
303 
304 } // namespace caffe2
const Blob * GetBlob(const string &name) const
Gets the blob with the given name as a const pointer.
Definition: workspace.cc:238
A struct that holds the gradient operators and related gradient maps.
static CaffeMap< string, string > MatchGradsToParams(const OperatorDef &op)
Returns map that returns the parameters that the gradients are for.
const CaffeTypeId & id() const
Returns the type id.
Definition: typeid.h:115
Workspace is a class that holds all the related objects created during runtime: (1) all blobs...
Definition: workspace.h:53
vector< string > Blobs() const
Return a list of blob names.
Definition: workspace.cc:203
Simple registry implementation in Caffe2 that uses static variables to register object creators durin...
Blob is a general container that hosts a typed pointer.
Definition: blob.h:25
GradientOpsMeta GetGradientForOp(const OperatorDef &def, const vector< GradientWrapper > &g_output)
Gets the GradientOpsMeta for the given operator def.
Definition: operator.cc:136
const TypeMeta & meta() const
Returns the meta info of the blob.
Definition: blob.h:61