This repository has been archived by the owner on Nov 17, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
/
Copy pathsubgraph_property.h
616 lines (556 loc) · 22.4 KB
/
subgraph_property.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef MXNET_OPERATOR_SUBGRAPH_SUBGRAPH_PROPERTY_H_
#define MXNET_OPERATOR_SUBGRAPH_SUBGRAPH_PROPERTY_H_
#include <dmlc/base.h>
#include <dmlc/thread_local.h>
#include <mxnet/graph_attr_types.h>
#include <mxnet/op_attr_types.h>
#include <nnvm/node.h>
#include <unordered_map>
#include <vector>
#include <string>
#include <utility>
namespace mxnet {
namespace op {
struct BiDirectedNode;
using BiDirectedNodePtr = std::shared_ptr<BiDirectedNode>;
/*!
* \brief Node of the undirected graph which replicates the network structures
* of the computational graph. It is used to ease the graph traversal for finding
* subgraphs.
*/
struct BiDirectedNode {
static BiDirectedNodePtr Create() {
return std::make_shared<BiDirectedNode>();
}
BiDirectedNode() : label(-1), node(nullptr) {}
/*! subgraph label */
int label;
/*! the original node in the computational graph it references*/
nnvm::Node* node;
/*!
* \brief output nodes of the current node
* key is node ptr and value is an array of indices standing for the entry indices
* in key->inputs whose source is the current node.
*/
std::unordered_map<nnvm::Node*, std::vector<size_t>> outputs;
}; // struct BiDirectedNode
struct NodeAttr {
DispatchMode dispatch_mode;
ShapeVector ishape;
std::vector<int> itype;
};
/*!
* This provides criteria for the graph partitioning algorithm to select
* nodes to subgraphs.
* The algorithm first sorts all the nodes in topological order, and then
* loops through the sorted nodes and tries to find a subgraph starting
* from each node (we call it a seed node) that satisfies the following two conditions:
* 1. The node has not been selected before.
* 2. The function Select is called on the node and returns true.
*
* Expanding from this seed node, we do BFS to traverse the graph.
* During the traversal, we call SelectInput and SelectOutput to determine
* if a neighboring node of the current node should be selected as a candidate for the subgraph.
* The search continues when a new node is selected as a candidate, and terminates when no more
* qualified nodes are found. When the search ends, all of the candidate nodes will
* be passed to the function Filter to finalize the subgraph. The filtering gives
* developers the last opportunity to drop off some of the candidate nodes.
* By default, Filter returns all nodes as the subgraph nodes.
* If the pre-selected subgraph becomes disconnected because some
* nodes are filtered out in the Filter function, the algorithm will automatically convert
* the rest of the nodes to multiple valid subgraphs based upon their connectivity.
*/
class SubgraphSelector {
public:
virtual ~SubgraphSelector() {}
/*!
* \brief Determines if to search for other nodes to form a subgraph from the seed_node.
*/
virtual bool Select(const nnvm::Node& seed_node) {
LOG(FATAL) << "No Select is implemented.";
return false;
}
virtual bool Select(const nnvm::Node& seed_node, const std::shared_ptr<NodeAttr>& node_attr) {
return Select(seed_node);
}
/*!
* \brief Determines if to select input_node when traverse to the cur_node.
* \param cur_node the node for determining whether its input_node should be selected
* \param input_node the input node of the cur_node
* \return true if input_node is selected
*/
virtual bool SelectInput(const nnvm::Node& cur_node, const nnvm::Node& input_node) {
LOG(FATAL) << "No SelectInput is implemented.";
return false;
}
virtual bool SelectInput(const nnvm::Node& cur_node,
const nnvm::Node& input_node,
const std::shared_ptr<NodeAttr>& input_node_attr) {
return SelectInput(cur_node, input_node);
}
/*!
* \brief Determines if to select output_node when traverse to the cur_node.
* \param cur_node the node for determining whether its output_node should be selected
* \param output_node the output node of the cur_node
* \return true if output_node is selected
*/
virtual bool SelectOutput(const nnvm::Node& cur_node, const nnvm::Node& output_node) {
LOG(FATAL) << "No SelectOutput is implemented.";
return false;
}
virtual bool SelectOutput(const nnvm::Node& cur_node,
const nnvm::Node& output_node,
const std::shared_ptr<NodeAttr>& output_node_attr) {
return SelectOutput(cur_node, output_node);
}
/*!
* \brief Post processes pre-selected subgraph nodes. Return a list of nodes that
* users want to keep in subgraph(s).
* \param candidates re-selected subgraph nodes to filt
* \return a list of nodes to keep
*/
virtual std::vector<nnvm::Node*> Filter(const std::vector<nnvm::Node*>& candidates) {
return candidates;
}
/*!
* \brief Reset the state of selector for SelectInput.
* Note: the state should reset to Select() is successful.
*/
virtual void Reset() {}
};
using SubgraphSelectorPtr = std::shared_ptr<SubgraphSelector>;
class SubgraphSelectorV2 {
public:
virtual ~SubgraphSelectorV2() {}
/*!
* \brief Determines if to search for other nodes to form a subgraph from the seed_node.
*/
virtual bool Select(const BiDirectedNode& seed_node) {
LOG(FATAL) << "No Select is implemented.";
return false;
}
virtual bool Select(const BiDirectedNode& seed_node, const std::shared_ptr<NodeAttr>& node_attr) {
return Select(seed_node);
}
/*!
* \brief Determines if to select input_node when traverse to the cur_node.
* \param cur_node the node for determining whether its input_node should be selected
* \param input_node the input node of the cur_node
* \return true if input_node is selected
*/
virtual bool SelectInput(const BiDirectedNode& cur_node, const BiDirectedNode& input_node) {
LOG(FATAL) << "No SelectInput is implemented.";
return false;
}
virtual bool SelectInput(const BiDirectedNode& cur_node,
const BiDirectedNode& input_node,
const std::shared_ptr<NodeAttr>& input_node_attr) {
return SelectInput(cur_node, input_node);
}
/*!
* \brief Determines if to select output_node when traverse to the cur_node.
* \param cur_node the node for determining whether its output_node should be selected
* \param output_node the output node of the cur_node
* \return true if output_node is selected
*/
virtual bool SelectOutput(const BiDirectedNode& cur_node, const BiDirectedNode& output_node) {
LOG(FATAL) << "No SelectOutput is implemented.";
return false;
}
virtual bool SelectOutput(const BiDirectedNode& cur_node,
const BiDirectedNode& output_node,
const std::shared_ptr<NodeAttr>& output_node_attr) {
return SelectOutput(cur_node, output_node);
}
/*!
* \brief Post processes pre-selected subgraph nodes. Return a list of nodes that
* users want to keep in subgraph(s).
* \param candidates re-selected subgraph nodes to filter
* \return a list of nodes to keep
*/
virtual std::vector<BiDirectedNode*> Filter(const std::vector<BiDirectedNode*>& candidates) {
return candidates;
}
/*!
* \brief Reset the state of selector for SelectInput.
* Note: the state should reset to Select() is successful.
*/
virtual void Reset() {}
};
using SubgraphSelectorV2Ptr = std::shared_ptr<SubgraphSelectorV2>;
class SubgraphSelectorV2Bridge : public SubgraphSelectorV2 {
public:
explicit SubgraphSelectorV2Bridge(SubgraphSelectorPtr ptr) : ss_ptr_(ptr) {}
virtual ~SubgraphSelectorV2Bridge() {}
bool Select(const BiDirectedNode& seed_node,
const std::shared_ptr<NodeAttr>& node_attr) override {
return ss_ptr_->Select(*seed_node.node, node_attr);
}
bool SelectInput(const BiDirectedNode& cur_node,
const BiDirectedNode& input_node,
const std::shared_ptr<NodeAttr>& node_attr) override {
return ss_ptr_->SelectInput(*cur_node.node, *input_node.node, node_attr);
}
bool SelectOutput(const BiDirectedNode& cur_node,
const BiDirectedNode& output_node,
const std::shared_ptr<NodeAttr>& node_attr) override {
return ss_ptr_->SelectOutput(*cur_node.node, *output_node.node, node_attr);
}
std::vector<BiDirectedNode*> Filter(const std::vector<BiDirectedNode*>& candidates) override {
std::unordered_map<nnvm::Node*, BiDirectedNode*> node_2_snode_map;
std::vector<nnvm::Node*> n_candidates;
for (auto i : candidates) {
node_2_snode_map[i->node] = i;
n_candidates.push_back(i->node);
}
auto n_ret = ss_ptr_->Filter(n_candidates);
std::vector<BiDirectedNode*> ret;
for (auto i : n_ret)
ret.push_back(node_2_snode_map[i]);
return ret;
}
void Reset() override {
ss_ptr_->Reset();
}
const SubgraphSelectorPtr& GetV1ptr() const {
return ss_ptr_;
}
private:
SubgraphSelectorPtr ss_ptr_;
};
/*!
* \brief This provides a set of properties for partitioning a graph into subgraphs,
* reconstructing a new graph from the subgraphs and creating a subgraph
* operator to execute the subgraph.
*/
class SubgraphProperty {
public:
virtual ~SubgraphProperty() {}
/*! \brief Property type */
enum SgPropertyType {
kCreate,
kAdjust,
};
explicit SubgraphProperty(SgPropertyType type = kCreate) : type_(type), dedup_subgraph(true) {}
/*!
* \brief The criteria of selecting the subgraph nodes.
*/
virtual SubgraphSelectorPtr CreateSubgraphSelector() const {
LOG(FATAL) << "No CreateSubgraphSelector is implemented for this SubgraphProperty.";
return nullptr;
}
virtual void PrePartition(const nnvm::Graph& g,
const std::unordered_map<std::string, std::string>& options_map) {
if (options_map.count("dedup_subgraph") > 0 &&
options_map.at("dedup_subgraph").compare("True") == 0) {
dedup_subgraph = true;
} else {
dedup_subgraph = false;
}
}
virtual void PostPartition(const nnvm::Graph& g) {}
virtual SubgraphSelectorV2Ptr CreateSubgraphSelectorV2() const {
auto v1_ptr = CreateSubgraphSelector();
return std::make_shared<SubgraphSelectorV2Bridge>(v1_ptr);
}
/*!
* \brief Create an nnvm node for a given subgraph. Here users can customize how to
* execute the operators in the subgraph.
* \param sym the symbol to create subgraph node
* \param subgraph_id subgraph id
*/
virtual nnvm::ObjectPtr CreateSubgraphNode(const nnvm::Symbol& sym,
const int subgraph_id = 0) const {
CHECK_EQ(GetPropertyType(), kCreate);
LOG(FATAL) << "Not implement CreateSubgraphNode() for this subgraph property.";
return nullptr;
}
/*!
* \brief Create an nnvm node for a given subgraph. Here users can customize how to
* execute the operators in the subgraph.
* \param sym the symbol to create subgraph node
* \param subgraph_selector the selector used for creating this subgraph
* \param subgraph_id subgraph id
*/
virtual nnvm::ObjectPtr CreateSubgraphNode(const nnvm::Symbol& sym,
const SubgraphSelectorPtr& subgraph_selector,
const int subgraph_id = 0) const {
return CreateSubgraphNode(sym, subgraph_id);
}
/*!
* \brief Create an nnvm node for a given subgraph. Here users can customize how to
* execute the operators in the subgraph.
* \param sym the symbol to create subgraph node
* \param subgraph_selector The selector used for selecting this node set
* \param subgraph_id subgraph id
*/
virtual nnvm::ObjectPtr CreateSubgraphNode(const nnvm::Symbol& sym,
const SubgraphSelectorV2Ptr& subgraph_selector,
const int subgraph_id = 0) const {
CHECK_EQ(GetPropertyType(), kCreate);
const auto bridge = static_cast<SubgraphSelectorV2Bridge*>(subgraph_selector.get());
return CreateSubgraphNode(sym, bridge->GetV1ptr(), subgraph_id);
}
/*!
* \brief Adjust nnvm nodes from a given subgraph. No new node is created, but adjust
* selected nodes' attributes. This can be used to implement peephole optimization.
* Here users can customize how to adjust the operators in the subgraph.
* \param subgraph_nodes the subgraph nodes to adjust
* \param subgraph_selector The selector used for selecting this node set.
* \param subgraph_id subgraph id
*/
virtual void AdjustSubgraphNode(const std::vector<nnvm::Node*>& subgraph_nodes,
const SubgraphSelectorV2Ptr& subgraph_selector,
const int subgraph_id = 0) const {
CHECK_EQ(GetPropertyType(), kAdjust);
LOG(FATAL) << "Not implement AdjustSubgraphNode() for this subgraph property.";
}
/*!
* \brief Connect subgraph internal output with external output entries.
* By default, each output entry will connect to an unique internal output.
* \param subgraph_node the subgraph node to connect output
* \param output_entries external output entries depending on this subgraph node
*/
virtual void ConnectSubgraphOutputs(const nnvm::ObjectPtr subgraph_node,
std::vector<nnvm::NodeEntry*>* output_entries) const {
// Collapse output_entries pointing to same NodeEntry
// Outputs are ordered, duplicates are neighbors
nnvm::NodeEntryEqual node_equal;
nnvm::NodeEntry prevNodeEntry;
uint32_t idx = 0;
for (size_t i = 0; i < output_entries->size(); ++i) {
if (dedup_subgraph) {
// increment the output idx for each unique output of the subgraph
if (i != 0 && !node_equal(prevNodeEntry, *output_entries->at(i)))
idx++;
prevNodeEntry = *output_entries->at(i); // make a copy so we can compare before modifying
// change output entry to point to subgraph instead of original node
*output_entries->at(i) = nnvm::NodeEntry{subgraph_node, idx, 0};
} else {
*output_entries->at(i) = nnvm::NodeEntry{subgraph_node, static_cast<uint32_t>(i), 0};
}
}
}
/*!
* \brief Connect subgraph internal input with external input entries.
* By default, each input entry will connect in top sorted order.
* \param subgraph_node the subgraph node to connect input
* \param input_entries input entries inside subgraph
* \param orig_input_entries input entries outside subgraph
*/
virtual void ConnectSubgraphInputs(const nnvm::ObjectPtr subgraph_node,
std::vector<nnvm::NodeEntry*>* input_entries,
std::vector<nnvm::NodeEntry>* orig_input_entries) const {
subgraph_node->inputs = *orig_input_entries;
}
/*!
* \brief Initialize subgraph internal inputs with external input entries.
* Called before CreateSubgraphNode, optional
* \param input_entries input entries inside subgraph
* \param orig_input_entries input entries outside subgraph
*/
virtual void InitSubgraphInputs(std::vector<nnvm::NodeEntry*>* input_entries,
std::vector<nnvm::NodeEntry>* orig_input_entries) const {}
/*!
* \brief Set an attr with name in the attr map.
*/
template <typename T>
SubgraphProperty& SetAttr(const std::string& name, const T& value) {
attrs_[name] = std::make_shared<dmlc::any>(value);
return *this;
}
/*!
* \brief Get the attr with the name.
*/
template <typename T>
const T& GetAttr(const std::string& name) const {
auto it = attrs_.find(name);
CHECK(it != attrs_.end()) << "Cannot find attribute " << name << " in SubgraphProperty";
return nnvm::get<T>(*it->second);
}
/*!
* \brief Check if the attr exists.
*/
bool HasAttr(const std::string& name) const {
auto it = attrs_.find(name);
return it != attrs_.end();
}
/*!
* \brief Remove attr if the attr exists.
*/
void RemoveAttr(const std::string& name) {
auto it = attrs_.find(name);
if (it != attrs_.end()) {
attrs_.erase(it);
}
}
/*!
* \brief Get the property type.
*/
SgPropertyType GetPropertyType() const {
return type_;
}
protected:
SgPropertyType type_;
std::unordered_map<std::string, std::shared_ptr<nnvm::any>> attrs_;
bool dedup_subgraph;
};
using SubgraphPropertyPtr = std::shared_ptr<SubgraphProperty>;
class SubgraphPropertyEntry {
public:
explicit SubgraphPropertyEntry(std::shared_ptr<SubgraphProperty> entry) : entry_(entry) {}
template <typename T>
SubgraphPropertyEntry set_attr(const std::string& name, const T value) const {
if (entry_)
entry_->SetAttr<T>(name, value);
return *this;
}
private:
std::shared_ptr<SubgraphProperty> entry_;
};
class SubgraphBackend {
public:
explicit SubgraphBackend(std::string name) : name_(name) {}
/*!
* \brief Set an attr with name in the attr map.
*/
template <typename T>
SubgraphBackend& SetAttr(const std::string& name, const T& value) {
attrs_[name] = std::make_shared<dmlc::any>(value);
return *this;
}
/*!
* \brief Get the attr with the name.
*/
template <typename T>
const T& GetAttr(const std::string& name) const {
auto it = attrs_.find(name);
CHECK(it != attrs_.end()) << "Cannot find attribute " << name << " in SubgraphProperty";
return nnvm::get<T>(*it->second);
}
/*!
* \brief Check if the attr exists.
*/
bool HasAttr(const std::string& name) const {
auto it = attrs_.find(name);
return it != attrs_.end();
}
/*!
* \brief Remove attr if the attr exists.
*/
void RemoveAttr(const std::string& name) {
auto it = attrs_.find(name);
if (it != attrs_.end()) {
attrs_.erase(it);
}
}
SubgraphPropertyPtr RegisterSubgraphProperty(SubgraphPropertyPtr prop) {
if (prop) {
prop_ptr_.push_back(prop);
return prop_ptr_.back();
}
return prop;
}
const std::string& GetName() const {
return name_;
}
const std::vector<SubgraphPropertyPtr>& GetSubgraphProperties() const {
return prop_ptr_;
}
private:
const std::string name_;
std::unordered_map<std::string, std::shared_ptr<nnvm::any>> attrs_;
std::vector<SubgraphPropertyPtr> prop_ptr_;
};
using SubgraphBackendPtr = std::shared_ptr<SubgraphBackend>;
class SubgraphBackendEntry {
public:
explicit SubgraphBackendEntry(SubgraphBackendPtr entry) : entry_(entry) {}
template <typename T>
SubgraphBackendEntry set_attr(const std::string& name, const T value) const {
entry_->SetAttr<T>(name, value);
return *this;
}
private:
SubgraphBackendPtr entry_;
};
class SubgraphBackendRegistry {
typedef SubgraphPropertyPtr (*SubgraphPropertyCreateFn)(void);
public:
static SubgraphBackendRegistry* Get() {
static SubgraphBackendRegistry inst;
return &inst;
}
SubgraphBackendPtr& GetSubgraphBackend(const std::string& name) {
auto it = backend_map_.find(name);
CHECK(it != backend_map_.end())
<< "SubgraphProperty " << name << " is not found in SubgraphBackendRegistry";
return it->second;
}
SubgraphBackendEntry __REGISTER_BACKEND__(const std::string& name) {
auto it = backend_map_.find(name);
CHECK(it == backend_map_.end()) << "Subgraph backend " << name << " is already registered";
backend_map_[name] = std::make_shared<SubgraphBackend>(name);
return SubgraphBackendEntry(backend_map_[name]);
}
SubgraphPropertyEntry __REGISTER_PROPERTY__(const std::string& name,
SubgraphPropertyCreateFn fn) {
auto it = backend_map_.find(name);
CHECK(it != backend_map_.end())
<< "Subgraph backend " << name << " is not found in SubgraphBackendRegistry";
auto prop = it->second->RegisterSubgraphProperty(fn());
return SubgraphPropertyEntry(prop);
}
SubgraphPropertyEntry __REGISTER_CUSTOM_PROPERTY__(const std::string& name,
SubgraphPropertyPtr cprop) {
auto it = backend_map_.find(name);
CHECK(it != backend_map_.end())
<< "Subgraph backend " << name << " is not found in SubgraphBackendRegistry";
auto prop = it->second->RegisterSubgraphProperty(cprop);
return SubgraphPropertyEntry(prop);
}
SubgraphBackendRegistry() = default;
SubgraphBackendRegistry(const SubgraphBackendRegistry&) = delete;
SubgraphBackendRegistry(SubgraphBackendRegistry&&) = delete;
SubgraphBackendRegistry& operator=(const SubgraphBackendRegistry&) = delete;
std::unordered_map<std::string, SubgraphBackendPtr> backend_map_;
};
/*!
* This op name set is for setting the names of operators that should be grouped into
* subgraphs. In practice, every backend accelerator should have a predefined name set.
* This set is only used for the testing purpose.
* key: property name, value: op name set
*/
typedef dmlc::ThreadLocalStore<std::unordered_map<std::string, std::unordered_set<std::string>>>
SubgraphPropertyOpNameSet;
#define DECLARE_PROPERTY_EX(NAME, SubgraphPropertyType, X) \
static const DMLC_ATTRIBUTE_UNUSED auto __make_##SubgraphPropertyType##_##Name##_##X##__
#define DECLARE_PROPERTY(NAME, SubgraphPropertyType, X) \
DECLARE_PROPERTY_EX(NAME, SubgraphPropertyType, X)
#define MXNET_REGISTER_SUBGRAPH_PROPERTY(Name, SubgraphPropertyType) \
DECLARE_PROPERTY(Name, SubgraphPropertyType, __LINE__) = \
SubgraphBackendRegistry::Get()->__REGISTER_PROPERTY__(#Name, &SubgraphPropertyType::Create)
#define DECLARE_BACKEND(Name) static const DMLC_ATTRIBUTE_UNUSED auto __make_##Name##__
#define MXNET_REGISTER_SUBGRAPH_BACKEND(Name) \
DECLARE_BACKEND(Name) = SubgraphBackendRegistry::Get()->__REGISTER_BACKEND__(#Name)
} // namespace op
} // namespace mxnet
#endif // MXNET_OPERATOR_SUBGRAPH_SUBGRAPH_PROPERTY_H_