onednn-src 0.1.13

Source of oneAPI Deep Neural Network Library (oneDNN)
Documentation
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
/*******************************************************************************
* Copyright 2021 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_UTILS_PM_NESTED_MATCHER_HPP
#define GRAPH_UTILS_PM_NESTED_MATCHER_HPP

#include <map>
#include <memory>
#include <utility>
#include <vector>
#include <unordered_map>

#include "graph/interface/op.hpp"
#include "graph/utils/pm/pbuilder.hpp"

namespace dnnl {
namespace impl {
namespace graph {
namespace utils {
namespace pm {

//
// Nested pattern matcher is greedy graph pattern matcher that is based
// on limited look ahead.
// Matching start with a seed graph op and pattern graph.
// Pattern graph serves as a map for guiding the matching process and
// while matching, individual graph ops will be paired up with a pb_op
// in the pattern graph. pb_op_t encodes which edges need to be matched
// (topological constraints) and what other properties a a paired graph
// op should possess(attribute constraints).
// The pattern graph itself is a nested graph where nesting happens
// at alternation and repetition nodes.
//
// Each pattern graph level has a match_context associated with it.
// A match_context holds the pattern graph in current context and
// the in/out port map used to map upper-level contexts.
//
// A "binding" is pre step for graph op to pb_op_t pairing. At any pattern
// level, some pattern nodes are not pb_op. binding is between a
// graph op and a pattern node (not necessarily a pb_op).
// Matching is driven by binding. If binding is not to a pb_op,
// resolve_node() will be called which invokes matching at the nesting
// pattern level. This process will invoke match_graph, match_alternation
// and match_repetition. If binding is to a pb_op, match_node() is called,
// which checks graph op attributes with the pb_op's decision_functions
// and bind neighboring ops and corresponding pattern nodes.
//
//
// Tells how a pattern node was brought in (reached)
// during matching process.
// BIND_IN: node reached by in port
// BIND_OUT: node reached by out port
// BIND_NONE: Start of matching.
//
enum node_bind_kind {
    BIND_IN,
    BIND_OUT,
    BIND_NONE,
};

//
// Bind a graph op and pattern node for matching
// pattern node may not be a pb_op. In that case, nested(recursive)
// matching happens.
//
class binding_t {
public:
    binding_t(node_bind_kind p_kind, op_t *p_op, size_t p_op_port,
            pb_node_t *p_node, size_t p_port);

    op_t *bind_op;
    pb_node_t *bind_node;
    node_bind_kind bind_kind;
    size_t bind_port;
    size_t bind_op_port;
    // hint op and hint_op_port are the previous binding info
    // used to handle the optional case
    op_t *hint_op = nullptr;
    size_t hint_op_port = 0;
};

// one input port can have multiple consumers
using graph_in_port_map
        = std::unordered_multimap<size_t, std::pair<op_t *, size_t>>;
// one output port corresponds to one producer
using graph_out_port_map
        = std::unordered_map<size_t, std::pair<op_t *, size_t>>;

//
// match context tracks a pattern graph match progress
// it consists of
// - pointer to parent match context
// - pointer to pattern graph tracked by this match context
// - a map of pattern graph input port to matched graph op and
// input port
// - a map of pattern graph output port to matched graph op and
// output port
//
class match_context_t {
public:
    // create a inherited context
    match_context_t(match_context_t *p_parent_ctx, pb_node_t *p_graph);
    match_context_t *get_parent_context() { return parent_ctx; }
    pb_graph_t *get_graph() { return graph_; }

    graph_in_port_map in_port_map;
    graph_out_port_map out_port_map;

protected:
    match_context_t *parent_ctx;
    // Can be a nullptr if context is a global for holding graph(s)
    pb_graph_t *graph_;
};

//
// match a pattern node by checking graph op's attributes with
// paired pattern node, and their inputs and outputs
//
bool match_node(const binding_t &b, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);

//
// pair pattern node input nodes (producers) with the paired op's
// input ops. If node has commutative inputs, different combination
// of input nodes and input ops are checked until one pair gets
// matched or all combination are failed
//
bool match_node_inputs(const binding_t &b, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
//
// pair pattern node output nodes (consumers) with the paired op's
// output ops. If node output has multiple consumers. different combination
// of output nodes and output ops are checked until one pair gets matched
// or all combination are failed
//
bool match_node_outputs(op_t *op, pb_node_t *node, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
//
// check if the matched graph causes cycles. Basically if one op in the
// matched graph has an input value produced by an external op, and the
// external op (or the external op's arbitrary producers) has an input
// value produced by another op in the matched graph, it causes a cycle,
// and the match process should not continue.
//
bool check_cyclic(
        op_t *op, const std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
//
// match a graph op's attributes using decision_functions of a pb_op_t node
//
bool match_node_attributes(op_t *op, pb_node_t *node);

//
// Trigger nested matching for non pb_op_t nodes
//
bool resolve_node(const binding_t &b, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);

//
// Match a graph
//
bool match_graph(const binding_t &b, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);

bool match_graph_helper(const binding_t &local_bind, match_context_t *ctx,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
//
// match an alternation
// iterates alternatives and apply match_graph until a matching one is found.
//
bool match_alternation(const binding_t &b, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);

//
// match a repetition including optional
// matches one iteration of repeating body at a time and matches edges
// across iterations of matched bodies.
//
bool match_repetition(const binding_t &b, match_context_t *context,
        std::unordered_map<op_t *, pb_op_t *> &matched_op_map);

//
// Based on op_depth, rearrange consumers in order of depth.
// Put consumer with larger depth the first place so that
// it is possible to meet more ops and match larger or more patterns.
//
std::vector<value_t::consumer_t> sort_op_consumers(
        std::shared_ptr<value_t> &op_out_value);

//
// Entry point of pattern matching.
// Find a match given a graph op (first_op) from an input graph
// and a pre-defined pattern.
//
bool match_pattern(op_t *first_op, const std::shared_ptr<pb_graph_t> &pattern,
        std::vector<op_t *> &fusion_ops);

//
// reorder the matched ops to make sure they are in topology order
//
inline std::vector<op_t *> reorder_matched_list(
        const std::unordered_map<op_t *, pb_op_t *> &matched_op_map);

// verify if all the in_ports of the graph have been filled
bool verify_global_in_map(match_context_t *ctx);
//
// fill the current match_context's in/out port map
// to pattern match_context. Useful for nested patterns
//
void fill_parent_io_map(match_context_t *local_ctx);

//
// Two utils classes for matching node outputs/inputs
//
class node_inputs_matcher_t {
public:
    node_inputs_matcher_t(const binding_t &b, match_context_t *ctx,
            std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
    std::unordered_map<op_t *, pb_op_t *> get_updated_op_map() const {
        return updated_op_map_;
    }
    //
    // If we have touched the upper boundary of the pattern
    // graph, it's time to terminate the recursion.
    // When in this case, the target node doesn't have inputs,
    // so we could confirm whether the recursion should be
    // terminated or not by checking the number of node inputs
    //
    bool check_recursion_termination();
    bool match_commutative_inputs();
    bool match_non_commutative_inputs();
    bool match_variadic_inputs();
    pb_node_t *get_node() const { return node_; }
    op_t *get_op() const { return op_; }

protected:
    op_t *op_;
    pb_node_t *node_;
    binding_t bind_;
    match_context_t *ctx_;
    std::unordered_map<op_t *, pb_op_t *> updated_op_map_;
    std::vector<std::pair<iport_t, producer_t>> node_inputs_;
    bool match_input_by_offset(
            const size_t &op_input_offset, const size_t &node_input_offset);
    bool support_optional_inputs(pb_node_t *n);
};

class node_outputs_matcher_t {
public:
    node_outputs_matcher_t(op_t *op, pb_node_t *node, match_context_t *ctx,
            std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
    std::unordered_map<op_t *, pb_op_t *> get_updated_op_map() const {
        return updated_op_map_;
    }
    pb_node_t *get_node() const { return node_; }
    op_t *get_op() const { return op_; }
    bool get_optional_case_status() const { return is_optional_case_; }
    //
    // If we have touched the lower boundary of the pattern
    // graph, it's time to terminate the recursion.
    // When in this case, the target node doesn't have outputs,
    // so we could confirm whether the recursion should be
    // terminated or not by checking the number of node outputs
    //
    bool check_recursion_termination();
    bool match_output();

protected:
    op_t *op_;
    pb_node_t *node_;
    match_context_t *ctx_;
    std::unordered_map<op_t *, pb_op_t *> updated_op_map_;
    std::pair<oport_t, consumers_t> current_node_output_;
    size_t current_node_oport_;
    std::vector<std::pair<oport_t, consumers_t>> node_outputs_;
    //
    // A flag variable to mark if the optional case is
    // exist or not
    //
    bool support_optional_;
    //
    // In optional case, we need to directly return the matching
    // result(success/fail) in advance. When in this time,
    // the matching process should be terminated, and here
    // the variable "is_optional_case_" is
    // used as a flag variable to help mark that.
    //
    bool is_optional_case_ = false;
    //
    // Match the op consumers one by one
    //
    bool match_op_consumers();
    //
    // When one op consumer couldn't find any corresponding
    // matched node consumer, there may exist three cases where
    // the matching process should still be successful:
    // 1. it's the allow_internal_inputs case
    // 2. it's the allow_external_outputs case
    // 3. it's the optional case
    // For all there three cases, we should check them before
    // we return a "false" matching result.
    // If the unmatching case hits any one of the three above,
    // the variable is_unmatching_exception_ will be true, which
    // means that the iteration of matching op consumers could
    // still forward.
    //
    bool op_consumer_unmatching_checking(op_t *out_op);
    bool support_optional_outputs(pb_node_t *n);
    bool check_internal_inputs(op_t *out_op);
    bool check_external_outputs();
    bool check_optional();
    //
    // If not all the consumers of node output are matched,
    // we should check the optional case
    //
    bool check_node_consumers(
            std::unordered_set<size_t> &node_oport_matched_cons);
};

//
// A utils class for repetition graph matching process
//
class repetition_matcher_t {
public:
    repetition_matcher_t(const binding_t &bind_arg, match_context_t *parent_ctx,
            std::unordered_map<op_t *, pb_op_t *> &matched_op_map);
    //
    // A while loop for matching each repetition block and
    // update the global ctx of repetition graph
    //
    size_t match_repetition_blocks();
    //
    // After matching the repetition blocks, we need to forward
    // the matching progress by changing the value of binding
    //
    bool post_repetition_matching(size_t num_rep, const binding_t &bind_arg);
    std::unordered_map<op_t *, pb_op_t *> get_updated_op_map() const {
        return updated_op_map_;
    }

protected:
    binding_t single_iter_bind_;
    match_context_t *parent_ctx_;
    std::unordered_map<op_t *, pb_op_t *> updated_op_map_;
    //
    // A temporary binding_t type instance for single matching iteration
    //
    repetition_t *rep_node_;
    port_map pmap_;
    size_t min_rep_;
    size_t max_rep_;
    bool forward_match_;
    //
    // The matching of repetition graph is a incremental process,
    // which means that the global context of repetition graph
    // could also be obtained incrementally. In our implementation,
    // the global context of repetition graph(rep_global_ctx_) is updated
    // by the local context(local_cached_ctx), whose i/o maps will be
    // filled when calling function match_graph() in each iteration
    //
    match_context_t rep_global_ctx_;
    //
    // After successfully matching repetition blocks, we need to
    // forward the matching process, if no repetition block is
    // matched, it also means that the current op is still
    // unmatched, so we should still take it into the next
    // matching process. Similarly, if any number of blocks are matched,
    // it means that the current op has been matched, and
    // what we should do is matching the next op.
    //
    bool match_current_op(const binding_t &bind_arg);
    bool match_next_op(const binding_t &bind_arg);
    //
    // update single_iter_bind_ for the next matching round
    //
    bool prepare_next_matching_round(const match_context_t &local_cached_ctx);
    //
    // After successfully matching one repetition block,
    // for BIND_OUT matching, we need to verify if the output op
    // of the block has current number of consumers.
    // Because for BIND_OUT matching, next matching round will
    // go to traverse the input op, so the output op remains
    // unchecked.
    //
    bool verify_current_matching_round(const match_context_t &local_cached_ctx,
            const std::unordered_map<op_t *, pb_op_t *> &local_op_map) const;
};

//
// fill the current match_context's in/out port map
//
void fill_local_in_map(match_context_t *local_ctx, pb_node_t *cur_node,
        op_t *cur_op, size_t cur_op_port);
void fill_local_out_map(match_context_t *local_ctx, pb_node_t *cur_node,
        op_t *cur_op, size_t cur_op_port);

} // namespace pm
} // namespace utils
} // namespace graph
} // namespace impl
} // namespace dnnl
#endif