annotate mlir/docs/QuickstartRewrites.md @ 164:fdfabb438fbf

...
author anatofuz
date Thu, 19 Mar 2020 17:02:53 +0900
parents 1d019706d866
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 # Quickstart tutorial to adding MLIR graph rewrite
anatofuz
parents:
diff changeset
2
anatofuz
parents:
diff changeset
3 This document will present a quickstart to adding graph rewrites. We shall start
anatofuz
parents:
diff changeset
4 by defining an operation, showing multiple ways to define the rewrite using
anatofuz
parents:
diff changeset
5 patterns, as well as defining the rewrite using a graph walker (note: using
anatofuz
parents:
diff changeset
6 patterns and the rewrite engine is preferred, showing the walker is for
anatofuz
parents:
diff changeset
7 demonstration purposes).
anatofuz
parents:
diff changeset
8
anatofuz
parents:
diff changeset
9 See [MLIR specification](LangRef.md) for more information about MLIR, the
anatofuz
parents:
diff changeset
10 structure of the IR, operations, etc. See
anatofuz
parents:
diff changeset
11 [Table-driven Operation Definition](OpDefinitions.md) and
anatofuz
parents:
diff changeset
12 [Declarative Rewrite Rule](DeclarativeRewrites.md) for the detailed explanation
anatofuz
parents:
diff changeset
13 of all available mechanisms for defining operations and rewrites in a
anatofuz
parents:
diff changeset
14 table-driven manner.
anatofuz
parents:
diff changeset
15
anatofuz
parents:
diff changeset
16 ## Adding operation
anatofuz
parents:
diff changeset
17
anatofuz
parents:
diff changeset
18 An operation in MLIR is specified using a definition in
anatofuz
parents:
diff changeset
19 [TableGen](https://llvm.org/docs/TableGen/LangIntro.html) file. TableGen is a
anatofuz
parents:
diff changeset
20 modeling tool to specify the ops and the C++ code to interact with these
anatofuz
parents:
diff changeset
21 operations are generated from. To define an operation one needs to specify:
anatofuz
parents:
diff changeset
22
anatofuz
parents:
diff changeset
23 * The operation name. This name is a unique identifier of the operation within
anatofuz
parents:
diff changeset
24 MLIR. Most operations are within a dialect, so for example one could have
anatofuz
parents:
diff changeset
25 `tfl.add` to represent the add operation in the TensorFlow Lite dialect.
anatofuz
parents:
diff changeset
26 Instead of repeating the dialect in the op definition, a base class for the
anatofuz
parents:
diff changeset
27 op dialect is commonly created that prepends the dialect namespace given an
anatofuz
parents:
diff changeset
28 op name.
anatofuz
parents:
diff changeset
29 * The traits of the operation. These allow you to specify traits of the
anatofuz
parents:
diff changeset
30 operation, such as whether it has side effects or whether it should be
anatofuz
parents:
diff changeset
31 verified that the operands and result types are the same. These are backed
anatofuz
parents:
diff changeset
32 by C++ traits that perform the verification.
anatofuz
parents:
diff changeset
33 * The arguments of the operation. These are the input operands (values at
anatofuz
parents:
diff changeset
34 runtime produced by other ops) and attributes (compile time known constant
anatofuz
parents:
diff changeset
35 values that affect the behavior of the op) that are the inputs of/define the
anatofuz
parents:
diff changeset
36 behavior of the operation. The input operands may be named, the attributes
anatofuz
parents:
diff changeset
37 must be named.
anatofuz
parents:
diff changeset
38 * The result(s) of the operation. These may again named or not.
anatofuz
parents:
diff changeset
39 * Documentation of the operation. This includes a one-line summary as well as
anatofuz
parents:
diff changeset
40 a longer human-readable description of the operation.
anatofuz
parents:
diff changeset
41 * Dialect specific information. Additional information could be added to the
anatofuz
parents:
diff changeset
42 operation definition that are only used by dialect specific drivers. These
anatofuz
parents:
diff changeset
43 are ignored by the main op and doc generators, but could be used in, say,
anatofuz
parents:
diff changeset
44 the translation from a dialect to another representation.
anatofuz
parents:
diff changeset
45
anatofuz
parents:
diff changeset
46 ```tablegen
anatofuz
parents:
diff changeset
47 def TFL_LeakyReluOp: TFL_Op<TFL_Dialect, "leaky_relu",
anatofuz
parents:
diff changeset
48 [NoSideEffect, SameValueType]>,
anatofuz
parents:
diff changeset
49 Results<(outs Tensor)> {
anatofuz
parents:
diff changeset
50 let arguments = (ins
anatofuz
parents:
diff changeset
51 F32Tensor:$x,
anatofuz
parents:
diff changeset
52 // Slope of the activation function at x < 0.
anatofuz
parents:
diff changeset
53 F32Attr:$alpha
anatofuz
parents:
diff changeset
54 );
anatofuz
parents:
diff changeset
55
anatofuz
parents:
diff changeset
56 let summary = "Leaky ReLU operator";
anatofuz
parents:
diff changeset
57 let description = [{
anatofuz
parents:
diff changeset
58 Element-wise Leaky ReLU operator
anatofuz
parents:
diff changeset
59 x -> x >= 0 ? x : (alpha * x)
anatofuz
parents:
diff changeset
60 }];
anatofuz
parents:
diff changeset
61
anatofuz
parents:
diff changeset
62 // TFLite specific attribute that is used when generating the output
anatofuz
parents:
diff changeset
63 // flatbuffer.
anatofuz
parents:
diff changeset
64 let hasOptions = 1;
anatofuz
parents:
diff changeset
65 }
anatofuz
parents:
diff changeset
66 ```
anatofuz
parents:
diff changeset
67
anatofuz
parents:
diff changeset
68 Note in the above the result types and inputs are specified in different ways,
anatofuz
parents:
diff changeset
69 one by way of trait and the other by way of let. It is possible to specify both
anatofuz
parents:
diff changeset
70 in either way.
anatofuz
parents:
diff changeset
71
anatofuz
parents:
diff changeset
72 <!-- TODO: Define a style convention. -->
anatofuz
parents:
diff changeset
73
anatofuz
parents:
diff changeset
74 Operations can also have custom parser, printer, builder, verifier, constant
anatofuz
parents:
diff changeset
75 folder, or canonicalizer. These require specifying additional C++ methods to
anatofuz
parents:
diff changeset
76 invoke for additional functionality. For example, if an operation is marked to
anatofuz
parents:
diff changeset
77 have a folder, the constant folder also needs to be added, e.g.,:
anatofuz
parents:
diff changeset
78
anatofuz
parents:
diff changeset
79 ```c++
anatofuz
parents:
diff changeset
80 OpFoldResult SpecificOp::fold(ArrayRef<Attribute> constOperands) {
anatofuz
parents:
diff changeset
81 if (unable_to_fold)
anatofuz
parents:
diff changeset
82 return {};
anatofuz
parents:
diff changeset
83 ....
anatofuz
parents:
diff changeset
84 return val;
anatofuz
parents:
diff changeset
85 }
anatofuz
parents:
diff changeset
86 ```
anatofuz
parents:
diff changeset
87
anatofuz
parents:
diff changeset
88 ## Adding patterns
anatofuz
parents:
diff changeset
89
anatofuz
parents:
diff changeset
90 There are multiple forms of graph rewrite that can be performed in MLIR. One of
anatofuz
parents:
diff changeset
91 the most common is DAG tile to DAG tile rewrite. Patterns provide a concise way
anatofuz
parents:
diff changeset
92 to express this transformation as a pair of source pattern to match and
anatofuz
parents:
diff changeset
93 resultant pattern. There are both the C++ classes to represent this
anatofuz
parents:
diff changeset
94 transformation, as well as the patterns in TableGen from which these can be
anatofuz
parents:
diff changeset
95 generated.
anatofuz
parents:
diff changeset
96
anatofuz
parents:
diff changeset
97 ### TableGen patterns
anatofuz
parents:
diff changeset
98
anatofuz
parents:
diff changeset
99 Let us continue with LeakyRelu. To map from TensorFlow's `LeakyRelu` to
anatofuz
parents:
diff changeset
100 TensorFlow Lite's `LeakyRelu`:
anatofuz
parents:
diff changeset
101
anatofuz
parents:
diff changeset
102 ```tablegen
anatofuz
parents:
diff changeset
103 def : Pat<(TF_LeakyReluOp $arg, F32Attr:$a), (TFL_LeakyReluOp $arg, $a)>
anatofuz
parents:
diff changeset
104 ```
anatofuz
parents:
diff changeset
105
anatofuz
parents:
diff changeset
106 The pattern is specified by instantiating a `Pat` with a source and result DAG.
anatofuz
parents:
diff changeset
107 The arguments in the source pattern is captured and can be used in the result
anatofuz
parents:
diff changeset
108 pattern. This is a simple pattern as we have a 1:1 mapping and the attribute
anatofuz
parents:
diff changeset
109 does not need to be transformed (e.g., both have a floating point attribute for
anatofuz
parents:
diff changeset
110 alpha). The names of the attributes specified in the pattern is for
anatofuz
parents:
diff changeset
111 matching/referencing and need not match the original attribute name in the op
anatofuz
parents:
diff changeset
112 definition but the order of arguments of the dags do need to match.
anatofuz
parents:
diff changeset
113
anatofuz
parents:
diff changeset
114 To specify a pattern, both the source and resultant ops need to be defined using
anatofuz
parents:
diff changeset
115 TableGen.
anatofuz
parents:
diff changeset
116
anatofuz
parents:
diff changeset
117 If this were a more advance pattern that the current framework could not express
anatofuz
parents:
diff changeset
118 as destination then one could use a general native code fallback method. This
anatofuz
parents:
diff changeset
119 consists of defining a pattern as well as adding a C++ function to perform the
anatofuz
parents:
diff changeset
120 replacement:
anatofuz
parents:
diff changeset
121
anatofuz
parents:
diff changeset
122 ```tablegen
anatofuz
parents:
diff changeset
123 def createTFLLeakyRelu : NativeCodeCall<
anatofuz
parents:
diff changeset
124 "createTFLLeakyRelu($_builder, $0.getDefiningOp(), $1, $2)">;
anatofuz
parents:
diff changeset
125
anatofuz
parents:
diff changeset
126 def : Pat<(TF_LeakyReluOp:$old_value, $arg, F32Attr:$a),
anatofuz
parents:
diff changeset
127 (createTFLLeakyRelu $old_value, $arg, $a)>;
anatofuz
parents:
diff changeset
128 ```
anatofuz
parents:
diff changeset
129
anatofuz
parents:
diff changeset
130 ```c++
anatofuz
parents:
diff changeset
131 static Value createTFLLeakyRelu(PatternRewriter &rewriter, Operation *op,
anatofuz
parents:
diff changeset
132 Value operand, Attribute attr) {
anatofuz
parents:
diff changeset
133 return rewriter.create<mlir::TFL::LeakyReluOp>(
anatofuz
parents:
diff changeset
134 op->getLoc(), operands[0].getType(), /*arg=*/operands[0],
anatofuz
parents:
diff changeset
135 /*alpha=*/attrs[0].cast<FloatAttr>());
anatofuz
parents:
diff changeset
136 }
anatofuz
parents:
diff changeset
137 ```
anatofuz
parents:
diff changeset
138
anatofuz
parents:
diff changeset
139 This allows for arbitrarily complex builders. Input pattern side one can express
anatofuz
parents:
diff changeset
140 multi-op patterns with constraints on input operands and attributes. But input
anatofuz
parents:
diff changeset
141 patterns cannot yet express constraints across multiple operands/attributes.
anatofuz
parents:
diff changeset
142
anatofuz
parents:
diff changeset
143 ### Register the pattern
anatofuz
parents:
diff changeset
144
anatofuz
parents:
diff changeset
145 The file containing the patterns need to be processed using `mlir-tblgen`
anatofuz
parents:
diff changeset
146 `-gen-rewriters` during compilation time. It can be invoked with the following
anatofuz
parents:
diff changeset
147 configuration in CMake:
anatofuz
parents:
diff changeset
148
anatofuz
parents:
diff changeset
149 ```cmake
anatofuz
parents:
diff changeset
150 set(LLVM_TARGET_DEFINITIONS <name-of-the-td-file>)
anatofuz
parents:
diff changeset
151 mlir_tablegen(<name-of-the-generated-inc-file> -gen-rewriters)
anatofuz
parents:
diff changeset
152 add_public_tablegen_target(<name-of-the-cmake-target>)
anatofuz
parents:
diff changeset
153 ```
anatofuz
parents:
diff changeset
154
anatofuz
parents:
diff changeset
155 Then you can `#include` the generated file in any C++ implementation file you
anatofuz
parents:
diff changeset
156 like. (You will also need to make sure the library depends on the CMake target
anatofuz
parents:
diff changeset
157 defined in the above.) The generated file will have a `populateWithGenerated(
anatofuz
parents:
diff changeset
158 MLIRContext *context, OwningRewritePatternList *patterns)` function that you can
anatofuz
parents:
diff changeset
159 use to collect all the generated patterns inside `patterns` and then use
anatofuz
parents:
diff changeset
160 `patterns` in any pass you would like.
anatofuz
parents:
diff changeset
161
anatofuz
parents:
diff changeset
162 ### C++ rewrite specification
anatofuz
parents:
diff changeset
163
anatofuz
parents:
diff changeset
164 In case patterns are not sufficient there is also the fully C++ way of
anatofuz
parents:
diff changeset
165 expressing a rewrite:
anatofuz
parents:
diff changeset
166
anatofuz
parents:
diff changeset
167 ```c++
anatofuz
parents:
diff changeset
168 /// Multi-step rewrite using "match" and "rewrite". This allows for separating
anatofuz
parents:
diff changeset
169 /// the concerns of matching and rewriting.
anatofuz
parents:
diff changeset
170 struct ConvertTFLeakyRelu : public RewritePattern {
anatofuz
parents:
diff changeset
171 ConvertTFLeakyRelu(MLIRContext *context)
anatofuz
parents:
diff changeset
172 : RewritePattern("tf.LeakyRelu", 1, context) {}
anatofuz
parents:
diff changeset
173
anatofuz
parents:
diff changeset
174 PatternMatchResult match(Operation *op) const override {
anatofuz
parents:
diff changeset
175 return matchSuccess();
anatofuz
parents:
diff changeset
176 }
anatofuz
parents:
diff changeset
177
anatofuz
parents:
diff changeset
178 void rewrite(Operation *op, PatternRewriter &rewriter) const override {
anatofuz
parents:
diff changeset
179 rewriter.replaceOpWithNewOp<TFL::LeakyReluOp>(
anatofuz
parents:
diff changeset
180 op, op->getResult(0).getType(), op->getOperand(0),
anatofuz
parents:
diff changeset
181 /*alpha=*/op->getAttrOfType<FloatAttr>("alpha"));
anatofuz
parents:
diff changeset
182 }
anatofuz
parents:
diff changeset
183 };
anatofuz
parents:
diff changeset
184
anatofuz
parents:
diff changeset
185 /// Single-step rewrite with "matchAndRewrite". This allows for performing the
anatofuz
parents:
diff changeset
186 /// rewrite immediately upon a successful match.
anatofuz
parents:
diff changeset
187 struct ConvertTFLeakyRelu : public RewritePattern {
anatofuz
parents:
diff changeset
188 ConvertTFLeakyRelu(MLIRContext *context)
anatofuz
parents:
diff changeset
189 : RewritePattern("tf.LeakyRelu", 1, context) {}
anatofuz
parents:
diff changeset
190
anatofuz
parents:
diff changeset
191 PatternMatchResult matchAndRewrite(Operation *op,
anatofuz
parents:
diff changeset
192 PatternRewriter &rewriter) const override {
anatofuz
parents:
diff changeset
193 rewriter.replaceOpWithNewOp<TFL::LeakyReluOp>(
anatofuz
parents:
diff changeset
194 op, op->getResult(0).getType(), op->getOperand(0),
anatofuz
parents:
diff changeset
195 /*alpha=*/op->getAttrOfType<FloatAttr>("alpha"));
anatofuz
parents:
diff changeset
196 return matchSuccess();
anatofuz
parents:
diff changeset
197 }
anatofuz
parents:
diff changeset
198 };
anatofuz
parents:
diff changeset
199 ```
anatofuz
parents:
diff changeset
200
anatofuz
parents:
diff changeset
201 In the C++ rewrite the static benefit of the rewrite pattern is specified at
anatofuz
parents:
diff changeset
202 construction. While in the pattern generator a simple heuristic is currently
anatofuz
parents:
diff changeset
203 employed based around the number of ops matched and replaced.
anatofuz
parents:
diff changeset
204
anatofuz
parents:
diff changeset
205 The above rule did not capture the matching operands/attributes, but in general
anatofuz
parents:
diff changeset
206 the `match` function in a multi-step rewrite may populate and return a
anatofuz
parents:
diff changeset
207 `PatternState` (or class derived from one) to pass information extracted during
anatofuz
parents:
diff changeset
208 matching to the rewrite. A single-step rewrite with the `matchAndRewrite`
anatofuz
parents:
diff changeset
209 function has the benefit of being able to directly use any values created when
anatofuz
parents:
diff changeset
210 matching; removing the need for `PatternState`.
anatofuz
parents:
diff changeset
211
anatofuz
parents:
diff changeset
212 ## Testing
anatofuz
parents:
diff changeset
213
anatofuz
parents:
diff changeset
214 MLIR uses [lit](https://llvm.org/docs/CommandGuide/lit.html) (LLVM Integrated
anatofuz
parents:
diff changeset
215 Testing) tool for performing testing. Testing is performed by way of creating
anatofuz
parents:
diff changeset
216 the input IR file, running a transformation and then verifying the output IR.
anatofuz
parents:
diff changeset
217 C++ unit tests are the exception, with the IR transformation serving as the core
anatofuz
parents:
diff changeset
218 testing mechanism. This results in fewer binaries that need to be built (and
anatofuz
parents:
diff changeset
219 linked) and forces to focus on the representation as an important piece.
anatofuz
parents:
diff changeset
220
anatofuz
parents:
diff changeset
221 For the legalization transform above we would have a test (probably as part of
anatofuz
parents:
diff changeset
222 the legalization pass test in TensorFlow Lite) such as:
anatofuz
parents:
diff changeset
223
anatofuz
parents:
diff changeset
224 ```mlir
anatofuz
parents:
diff changeset
225 // RUN: mlir-opt -tfl-legalize-tf %s | FileCheck %s
anatofuz
parents:
diff changeset
226
anatofuz
parents:
diff changeset
227 func @LeakyRelu(%arg0: tensor<1xf32>) -> tensor<1xf32> {
anatofuz
parents:
diff changeset
228 %2 = "tf.LeakyRelu"(%arg0) {alpha: 0.1} : (tensor<1xf32>) -> tensor<1xf32>
anatofuz
parents:
diff changeset
229 return %2: tensor<1xf32>
anatofuz
parents:
diff changeset
230
anatofuz
parents:
diff changeset
231 // CHECK-LABEL: LeakyRelu
anatofuz
parents:
diff changeset
232 // CHECK: %0 = "tfl.leaky_relu"(%arg0) {alpha: 1.000000e-01} : (tensor<1xf32>) -> tensor<1xf32>
anatofuz
parents:
diff changeset
233 }
anatofuz
parents:
diff changeset
234 ```
anatofuz
parents:
diff changeset
235
anatofuz
parents:
diff changeset
236 The RUN command at the top results in running the `mlir-opt` binary (which is
anatofuz
parents:
diff changeset
237 compiler writer tool to exercise different registered passes) to invoke the
anatofuz
parents:
diff changeset
238 optimization pass this transform was added as part of on the current file and to
anatofuz
parents:
diff changeset
239 verify its output using `FileCheck`. `FileCheck` is textual output verifier. In
anatofuz
parents:
diff changeset
240 particular it uses the CHECK expressions to verify the given output is produced.
anatofuz
parents:
diff changeset
241
anatofuz
parents:
diff changeset
242 There can be multiple RUN commands with different corresponding CHECK prefixes.
anatofuz
parents:
diff changeset
243 And in addition multiple independent tests separated by `// -----` and
anatofuz
parents:
diff changeset
244 `mlir-opt` invoked with `-split-input-file` flag. This is especially useful for
anatofuz
parents:
diff changeset
245 error testing.
anatofuz
parents:
diff changeset
246
anatofuz
parents:
diff changeset
247 This results in very simple, directed testing without need to work around
anatofuz
parents:
diff changeset
248 constant propagation or other, unrelated, optimization passes.
anatofuz
parents:
diff changeset
249
anatofuz
parents:
diff changeset
250 ## Adding optimization pass
anatofuz
parents:
diff changeset
251
anatofuz
parents:
diff changeset
252 Optimization passes that do not fit/are difficult to specify in the above
anatofuz
parents:
diff changeset
253 structure can be specified as general iterations across modules/functions. See
anatofuz
parents:
diff changeset
254 [Writing a Pass](WritingAPass.md) for a general overview and introduction to
anatofuz
parents:
diff changeset
255 optimization passes in MLIR.