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
/*******************************************************************************
* Copyright 2022 Intel Corporation
*
* Licensed 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 GRAPH_BACKEND_DNNL_SUBGRAPH_HPP
#define GRAPH_BACKEND_DNNL_SUBGRAPH_HPP
#include <algorithm>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include "graph/interface/c_types_map.hpp"
#include "graph/interface/graph.hpp"
#include "graph/interface/graph_attr.hpp"
#include "graph/interface/op.hpp"
#include "graph/interface/value.hpp"
#include "graph/utils/utils.hpp"
#include "graph/backend/dnnl/common.hpp"
#include "graph/backend/dnnl/fusion_info.hpp"
#include "graph/backend/dnnl/utils.hpp"
#include "dnnl.hpp"
namespace dnnl {
namespace impl {
namespace graph {
namespace dnnl_impl {
struct op_executable_t;
class subgraph_rewriter_t;
// The subgraph_t class is a subclass of graph_t, which is used as the only
// parameter of transformation passes. Each transformation pass will process the
// subgraph_t object, and after that, the content of subgraph_t object will be
// changed.
class subgraph_t : public graph_t {
friend class subgraph_rewriter_t;
private:
// Make this member private so that only the friend class
// subgraph_rewriter_t can change the subgraph structure
std::vector<op_ptr> &get_mutable_ops() {
return const_cast<std::vector<op_ptr> &>(get_ops());
}
public:
subgraph_t(const std::vector<op_ptr> &ops, const dnnl::engine &eng,
const graph::fpmath_t &fpm_mode, bool can_use_blocked_layout,
bool reset_layout);
subgraph_t(const std::vector<op_ptr> &ops, bool reset_layout = true);
// The inputs and outputs logical tensors given by users at compilation
// stage
std::vector<logical_tensor_t> ins_;
std::vector<logical_tensor_t> outs_;
// The engine that the subgraph is compiled for
const dnnl::engine *p_engine_;
bool can_use_blocked_layout_;
// The custom cache to store the created primitive desc
pd_cache_t pd_cache_;
// The vector to tell which op in the subgraph is constant and will only run
// once
std::vector<bool> is_constant_;
// The executable for each op in subgraph
std::vector<std::shared_ptr<op_executable_t>> execs_;
};
class subgraph_visualizer_t {
public:
subgraph_visualizer_t() = default;
subgraph_visualizer_t(size_t partition_id,
const std::function<std::string(const value_t *)> &mem_info_func
= {})
: enabled_(false)
, mem_info_func_(mem_info_func)
#ifndef DNNL_DISABLE_GRAPH_DUMP
, partition_id_(partition_id)
, index_(0)
#endif
{
MAYBE_UNUSED(partition_id);
// Set _DNNL_BACKEND_SUBGRAPH_DUMP=1 to enable dump subgraph
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
enabled_ = graph::utils::getenv_int_internal("BACKEND_SUBGRAPH_DUMP", 0)
> 0;
}
status_t run(const std::shared_ptr<subgraph_t> &sg,
const std::string &name_suffix, bool is_layout_sensitive,
bool is_memory_sensitive = false);
private:
bool enabled_ = false;
std::function<std::string(const value_t *)> mem_info_func_;
#ifndef DNNL_DISABLE_GRAPH_DUMP
size_t partition_id_;
size_t index_;
#endif
};
class subgraph_validator_t {
public:
subgraph_validator_t() = default;
status_t run(const std::shared_ptr<subgraph_t> &sg);
};
// This class provide some common used utils to do subgraph rewriting. Those
// utils use "lazy rewrite" policy, which only change the connections but not
// modify the op list in subgraph. To finalize the rewriting process and modify
// the op list, we must call run() method. This class is not thread safe, we
// must not rewrite the same subgraph in multiple threads.
class subgraph_rewriter_t {
public:
subgraph_rewriter_t(std::shared_ptr<subgraph_t> &sg) : subgraph_(sg) {}
// Finalize the rewriting, which actually insert/remove the op to/from
// subgraph op list
void run();
// Puts the op into the to_be_inserted_ops_ list
void to_insert(const std::shared_ptr<op_t> &op) {
to_be_inserted_ops_.emplace_back(op);
}
// Puts the op into the to_be_removed_ops_ list
void to_remove(const std::shared_ptr<op_t> &op) {
to_be_removed_ops_.emplace_back(op);
}
// Insert the inserted_op before the base_op, and put the inserted_op to the
// to_be_inserted_ops_ list
// in_val
// in_val \ | /
// \ | / \ |[j] /
// \ |[i] / --> inserted_op
// base_op |[k]
// |
// \ |[i] /
// base_op
void insert_op_before(const std::shared_ptr<op_t> &inserted_op,
const std::shared_ptr<op_t> &base_op, size_t i,
size_t j = std::numeric_limits<size_t>::max(),
size_t k = std::numeric_limits<size_t>::max());
// Insert the inserted_op after the base_op, and put the inserted_op to the
// to_be_inserted_ops_ list
// base_op
// base_op / |[i]
// / |[i] \ --> |
// / | \ \ |[j] /
// out_val inserted_op
// |[k]
// |
// out_val
void insert_op_after(const std::shared_ptr<op_t> &inserted_op,
const std::shared_ptr<op_t> &base_op, size_t i,
size_t j = std::numeric_limits<size_t>::max(),
size_t k = std::numeric_limits<size_t>::max());
// Fuse a op to its successor, and put the op to the to_be_removed list.
// The op must have only one successor and one input value
// in_val
// |
// op in_val
// | --> |
// successor successor
// | |
// out_val out_val
void fuse_op_to_successor(const std::shared_ptr<op_t> &op);
// Fuse a op to its predecessor, and put the op to the to_be_removed list.
// The unfused input values of op will be add to predecessor's inputs.
// in_val1 in_val1 in_val2
// | \ /
// predecessor in_val2 predecessor
// \ / --> |
// \[i] / |
// op out_val
// |
// out_val
void fuse_op_to_predecessor(const std::shared_ptr<op_t> &op, size_t i = 0);
// Replace the org_op with the new_op
void replace_op(const std::shared_ptr<op_t> &org_op,
const std::shared_ptr<op_t> &new_op);
// Swap neighboring single input ops, can be extend to support mimo per
// requirement
//
// in_val in_val
// | |
// producer consumer
// | --> |
// consumer producer
// | |
// out_val out_val
void swap_neighboring_si_ops(const std::shared_ptr<op_t> &producer,
const std::shared_ptr<op_t> &consumer);
void swap_neighboring_reshape_ops(const std::shared_ptr<op_t> &producer,
const std::shared_ptr<op_t> &consumer);
private:
bool is_to_be_removed(const std::shared_ptr<op_t> &op) const;
std::shared_ptr<subgraph_t> subgraph_;
std::vector<std::shared_ptr<op_t>> to_be_inserted_ops_;
std::vector<std::shared_ptr<op_t>> to_be_removed_ops_;
};
} // namespace dnnl_impl
} // namespace graph
} // namespace impl
} // namespace dnnl
#endif