xref: /llvm-project/mlir/docs/Dialects/Linalg/OpDSL.md (revision 2b9edabe969e2f59f067ed7d49e2b0eca5411113)
1226f925cSTobias Gysi# Linalg OpDSL
2226f925cSTobias Gysi
3*2b9edabeSRenato Golin**_Warning: Linalg's OpDSL is currently being [deprecated](https://discourse.llvm.org/t/how-to-add-custom-linalg-named-ops-using-opdsl/83200/2),
4*2b9edabeSRenato Golinwith its operations slowly [being moved](https://github.com/llvm/llvm-project/pull/115319) into TableGen's ODS format.
5*2b9edabeSRenato GolinPlease refer to the [MLIR Restructuring discussion](https://discourse.llvm.org/t/rfc-mlir-project-charter-and-restructuring/82896)
6*2b9edabeSRenato Golinfor more in-depth information._**
7*2b9edabeSRenato Golin
8226f925cSTobias GysiPython based DSL for authoring Linalg op definitions and generating
9226f925cSTobias Gysi`linalg.generic` IR based on them for samples.
10226f925cSTobias Gysi
11226f925cSTobias GysiThe Linalg OpDSL is a high level DSL for constructing structured op definitions
12226f925cSTobias Gysiin a way that can be exported to built-in, named structured ops via
13226f925cSTobias Gysi[YAML-based definitions](_index.md/#yaml-gen) or used interactively to emit
14226f925cSTobias Gysicorresponding `linalg.generic` IR for the composition.
15226f925cSTobias Gysi
16226f925cSTobias Gysi## Basic usage
17226f925cSTobias Gysi
18226f925cSTobias GysiThe tool is bundled with the MLIR Python bindings. To use from the CMake build
19226f925cSTobias Gysitree, MLIR must be build with Python bindings enabled
208d59fc5fSThomas Preud'homme(`-DMLIR_ENABLE_BINDINGS_PYTHON=ON`). Then add the `python` directory in the
21226f925cSTobias Gysibuild tree to your `PYTHONPATH` environment variable (i.e. `export
22a543abc5STobias GysiPYTHONPATH=$PWD/build/tools/mlir/python_packages/mlir_core`). Optionally, use an
23a543abc5STobias Gysiinstalled MLIR package, if available, to avoid building.
24226f925cSTobias Gysi
25226f925cSTobias Gysi```shell
26226f925cSTobias Gysi# Dump the `core_named_ops.py` module as YAML.
27226f925cSTobias Gysipython -m mlir.dialects.linalg.opdsl.dump_oplib .ops.core_named_ops
28226f925cSTobias Gysi```
29226f925cSTobias Gysi
30a543abc5STobias GysiAlternatively, run the `$PWD/build/bin/update_core_linalg_named_ops.sh` script,
314cab4f43SFelix Schneiderwhich is available after building the `mlir-linalg-ods-yaml-gen` target. The tool
324cab4f43SFelix Schneideris meant for use during both development and runtime, but not as a build tool of
33a543abc5STobias Gysithe core compiler: in order to export static named op definitions to be built as
34a543abc5STobias Gysipart of the compiler, the corresponding Linalg dialect YAML file must be updated
35a543abc5STobias Gysiand reviewed. TODO: Develop a script to automate op updates to these files.
36226f925cSTobias Gysi
37226f925cSTobias Gysi## Language Guide
38226f925cSTobias Gysi
39226f925cSTobias GysiThe language presented here is loosely inspired from the
40226f925cSTobias Gysi[Tensor Comprehensions](https://arxiv.org/pdf/1802.04730.pdf) work, adapted to
41226f925cSTobias Gysirepresent linalg structured ops.
42226f925cSTobias Gysi
43226f925cSTobias GysiThis tool is new and rapidly evolving. For language examples, refer to the
44226f925cSTobias Gysibuilt-in ops in the `mlir.tools.linalg_opdsl.ops` package
45226f925cSTobias Gysi(`lib/Bindings/Python/mlir/tools/linalg_opdsl/ops` in the repository).
46226f925cSTobias Gysi
47226f925cSTobias GysiUsing a matmul as an example, we will decompose the language:
48226f925cSTobias Gysi
49226f925cSTobias Gysi```python
50226f925cSTobias GysiT1 = TV.T1
51226f925cSTobias GysiT2 = TV.T2
52226f925cSTobias Gysi
53226f925cSTobias Gysi@linalg_structured_op
54226f925cSTobias Gysidef matmul(A=TensorDef(T1, S.M, S.K),
55226f925cSTobias Gysi           B=TensorDef(T2, S.K, S.N),
56226f925cSTobias Gysi           C=TensorDef(U, S.M, S.N, output=True)):
57226f925cSTobias Gysi  """Performs a matrix multiplication of two 2D inputs.
58226f925cSTobias Gysi
59226f925cSTobias Gysi  Numeric casting is performed on the operands to the inner multiply, promoting
60226f925cSTobias Gysi  them to the same data type as the accumulator/output.
61226f925cSTobias Gysi  """
62226f925cSTobias Gysi  domain(D.m, D.n, D.k)
63d629645fSgysit  defines(Canonicalizer)
64226f925cSTobias Gysi  implements(ContractionOpInterface)
65e9085d0dSgysit  C[D.m, D.n] += TypeFn.cast_signed(
66e9085d0dSgysit      U, A[D.m, D.k]) * TypeFn.cast_signed(U, B[D.k, D.n])
67226f925cSTobias Gysi```
68226f925cSTobias Gysi
69226f925cSTobias GysiHere we have a simple type polymorphic contraction that takes arguments `A` and
70226f925cSTobias Gysi`B` and outputs `C`. Each is bound to a `TensorDef`, which specifies:
71226f925cSTobias Gysi
72226f925cSTobias Gysi*   The symbolic element type (`T1`, `T2`, `U` above).
73226f925cSTobias Gysi*   Symbolic shape expressions with symbols that are bound globally for the op (
74226f925cSTobias Gysi    note that in this simple example, the shape expressions are just symbol
75226f925cSTobias Gysi    references, but they are permitted to be a constrained set of affine
76226f925cSTobias Gysi    expressions).
77226f925cSTobias Gysi*   Usage (`output=True`).
78226f925cSTobias Gysi
79226f925cSTobias GysiThe docstring will be transferred to the op definition verbatim.
80226f925cSTobias Gysi
81226f925cSTobias GysiAn explicit iteration domain dimension order can be declared for the op via
82226f925cSTobias Gysi`domain(D.d0[, D.d1...])`.
83226f925cSTobias Gysi
84226f925cSTobias GysiSpecial identifying op interfaces can be declared for the op via
85226f925cSTobias Gysi`implements(interface1[, interface2...])`.
86226f925cSTobias Gysi
87d629645fSgysitExtra method definitions can be declared for the op via
88d629645fSgysit`defines(definition1[, definition2...])`.
89d629645fSgysit
90226f925cSTobias Gysi## Parameters
91226f925cSTobias Gysi
92226f925cSTobias GysiStructured operations take two types of runtime parameters namely scalars and
93226f925cSTobias Gysitensors. While scalars are inputs only, a tensor may be marked as an output.
94226f925cSTobias GysiAssignment expressions index the tensor parameters to access the individual
95226f925cSTobias Gysielements, while scalars can be accessed directly.
96226f925cSTobias Gysi
97226f925cSTobias GysiThe following example demonstrates the use of the two parameter types:
98226f925cSTobias Gysi
99226f925cSTobias Gysi```python
100226f925cSTobias Gysi@linalg_structured_op
101226f925cSTobias Gysidef copy_and_scale(val=ScalarDef(T),
102226f925cSTobias Gysi                   I=TensorDef(T, S.M, S.K),
103226f925cSTobias Gysi                   O=TensorDef(T, S.M, S.K, output=True)):
104226f925cSTobias Gysi  """Scale the input by the scalar value and store the result"""
105226f925cSTobias Gysi  O[D.m, D.n] = I[D.m, D.n] * val
106226f925cSTobias Gysi```
107226f925cSTobias Gysi
108226f925cSTobias GysiThe operation scales the input tensor `I` scales its elements by the value `val`
109226f925cSTobias Gysiand writes the result to the output tensor `out`. The scalar `val` is bound to a
110226f925cSTobias Gysi`ScalarDef`, which specifies the type of the scalar operand. The tensors are
111226f925cSTobias Gysibound to a `TensorDef` as demonstrated by the matmul example. All parameters
112226f925cSTobias Gysiappear in the parameter list of the operation:
113226f925cSTobias Gysi
114226f925cSTobias Gysi```python
115a3655de2Sgysitcopy_and_scale(val, in_tensor, outs=[out_tensor])
116226f925cSTobias Gysi```
117226f925cSTobias Gysi
118d50571abSgysit## Index Attributes
119226f925cSTobias Gysi
12024357fecSgysitIndex attributes are compile-time constant parameters only accessible in index
121226f925cSTobias Gysiexpressions. They can be used to parameterize the access pattern of a structured
122226f925cSTobias Gysioperation, for example, by setting its strides. They cannot take part in the
123226f925cSTobias Gysiactual computation.
124226f925cSTobias Gysi
12524357fecSgysitThe following example demonstrates the use of index attributes:
126226f925cSTobias Gysi
127226f925cSTobias Gysi```python
128226f925cSTobias Gysi@linalg_structured_op
129226f925cSTobias Gysidef strided_copy(I=TensorDef(T, S.IH, S.IW),
130226f925cSTobias Gysi                 O=TensorDef(T, S.OH, S.OW, output=True),
131d50571abSgysit                 strides=IndexAttrDef(S.SH, S.SW, default=[1, 1])):
132226f925cSTobias Gysi  """Copy a subset of the input tensor elements to the output tensor"""
133226f925cSTobias Gysi  O[D.oh, D.ow] = I[D.oh * S.SH, D.ow * S.SW]
134226f925cSTobias Gysi```
135226f925cSTobias Gysi
136226f925cSTobias GysiThe operation implements a strided copy from the input tensor `I` to the output
1372648e2d5Sgysittensor `O`. The `strides` attribute is bound to an `IndexAttrDef`. It defines
138226f925cSTobias Gysithe symbols `S.SH` and `S.SW`, which are used to index the input tensor `I`.
139226f925cSTobias GysiWhen instantiating the operation, the attribute is set using a named argument:
140226f925cSTobias Gysi
141226f925cSTobias Gysi```python
142226f925cSTobias Gysistrided_copy(in_tensor, outs=[out_tensor], strides=[1, 2])
143226f925cSTobias Gysi```
144226f925cSTobias Gysi
145226f925cSTobias GysiThe `strides` vector elements substitute the symbols `S.SH` and `S.SW` in the
146d50571abSgysitindex expressions of the operation instance. If no strides are provided the
147d50571abSgysit`default` vector elements are used instead.
148226f925cSTobias Gysi
14924357fecSgysitIndex attributes are currently limited to integer vectors and only accessible in
15024357fecSgysitindex expressions. An operation may have multiple attributes all of them placed
15124357fecSgysitat the end of the parameter list after the output tensors.
152226f925cSTobias Gysi
153226f925cSTobias Gysi## Shape-Only Tensors
154226f925cSTobias Gysi
155226f925cSTobias GysiStructured operations derive the iteration space given the sizes of the input
156226f925cSTobias Gysiand output tensors. Certain operations need shape-only tensors that are not
157226f925cSTobias Gysiaccessed and exist purely for the sake of specifying the iteration domain. An
158226f925cSTobias Gysiexample is the pooling operation that takes a shape-only tensor to define the
159226f925cSTobias Gysiiteration space of the reduction. As shape-only tensors have no uses, the
160226f925cSTobias Gysi`TensorDef` takes an additional optional `index_dims` parameter to map the shape
161226f925cSTobias Gysito index dimensions.
162226f925cSTobias Gysi
163226f925cSTobias GysiThe following example demonstrates the index dimension annotation:
164226f925cSTobias Gysi
165226f925cSTobias Gysi```python
166226f925cSTobias Gysi@linalg_structured_op
167226f925cSTobias Gysidef pooling_poly(
168226f925cSTobias Gysi    I=TensorDef(T1, S.N, S.H, S.W, S.C),
169226f925cSTobias Gysi    K=TensorDef(T2, S.KH, S.KW, index_dims=[D.kh, D.kw]),
170226f925cSTobias Gysi    O=TensorDef(U, S.N, S.OH, S.OW, S.C, output=True),
171d50571abSgysit    strides=IndexAttrDef(S.SH, S.SW, default=[1, 1]),
172d50571abSgysit    dilations=IndexAttrDef(S.DH, S.DW, default=[1, 1])):
173e9085d0dSgysit  O[D.n, D.oh, D.ow, D.c] += TypeFn.cast_signed(U,
17415757ea8Sgysit          I[D.n, D.oh * S.SH + D.kh * S.DH, D.ow * S.SW + D.kw * S.DW, D.c])
175226f925cSTobias Gysi```
176226f925cSTobias Gysi
177226f925cSTobias GysiThe pooling operation does not access the shape-only tensor `K`. Instead, the
178226f925cSTobias Gysishapes `S.KH` and `S.KW` specify the iteration domain for the reduction
179226f925cSTobias Gysidimensions `D.kh` and `D.kw`.
180226f925cSTobias Gysi
181226f925cSTobias Gysi## Assignments
182226f925cSTobias Gysi
183226f925cSTobias GysiThe bulk of language consists of assignment expressions of the form above. The
184226f925cSTobias Gysiiteration dimension order is determined lexically based on the order encountered
185226f925cSTobias Gysiin the expression (following operator precedence if math operators are used).
186226f925cSTobias GysiTODO: Introduce a directive to fix the dimension bindings.
187226f925cSTobias Gysi
188226f925cSTobias GysiReduction dimensions are inferred to be any dimensions on the RHS that are not
189226f925cSTobias Gysion the LHS.
190226f925cSTobias Gysi
191cd2776b0SgysitA number of unary and binary arithmetic functions are supported:
192226f925cSTobias Gysi
193cd2776b0Sgysit*   `BinaryFn.add(a, b)` (also via overloading the binary `+` operator)
194cd2776b0Sgysit*   `BinaryFn.mul(a, b)` (also via overloading the binary `*` operator)
195e9085d0dSgysit*   `BinaryFn.max_signed(a, b)`
196e9085d0dSgysit*   `BinaryFn.min_signed(a, b)`
197cd2776b0Sgysit*   `BinaryFn.sub(a, b)` (also via overloading the binary `-` operator)
198cd2776b0Sgysit*   `BinaryFn.max_unsigned(a, b)`
199cd2776b0Sgysit*   `BinaryFn.min_unsigned(a, b)`
200cd2776b0Sgysit*   `UnaryFn.exp(a)`
201cd2776b0Sgysit*   `UnaryFn.log(a)`
202cf05668cSgysit
203cf05668cSgysitAs the integer types are signless, signedness is implement by different
204cf05668cSgysitfunctions that treat integers as signed or unsigned values.
205226f925cSTobias Gysi
206e3b442b6SgysitA subset of the arithmetic functions are supported in reductions. These
207e3b442b6Sgysitreduction functions can appear as the outermost function on the RHS:
208226f925cSTobias Gysi
209226f925cSTobias Gysi*   `ReduceFn.add` (also overloading the inplace `+=` on a LHS)
210226f925cSTobias Gysi*   `ReduceFn.mul`
211e9085d0dSgysit*   `ReduceFn.max_signed`
212e9085d0dSgysit*   `ReduceFn.min_signed`
213e3b442b6Sgysit*   `ReduceFn.max_unsigned`
214e3b442b6Sgysit*   `ReduceFn.min_unsigned`
215e3b442b6Sgysit
216e3b442b6SgysitAs the integer types are signless, signedness is implement by different
217e3b442b6Sgysitfunctions that treat integers as signed or unsigned values.
218226f925cSTobias Gysi
21915757ea8SgysitAdditionally, type conversion functions cast an operand to a target type:
22015757ea8Sgysit
221e9085d0dSgysit*   `TypeFn.cast_signed(TypeVar, operand)`
22215757ea8Sgysit*   `TypeFn.cast_unsigned(TypeVar, operand)`
22315757ea8Sgysit
22415757ea8SgysitAs the integer types are signless, signedness is implement by different
225e9085d0dSgysitfunctions that treat integers as signed (`TypeFn.cast_signed`) or unsigned
22615757ea8Sgysit(`TypeFn.cast_unsigned`) values.
22715757ea8Sgysit
228226f925cSTobias GysiThere are also special forms:
229226f925cSTobias Gysi
23015757ea8Sgysit*   `const(value)` returns a constant value.
231226f925cSTobias Gysi*   `index(dim)` returns the iteration index in the given dimension `dim`.
232226f925cSTobias Gysi
23324357fecSgysit## Function Attributes
23424357fecSgysit
23524357fecSgysitFunction attributes are compile-time constant function parameters. They can be
23624357fecSgysitused to parameterize the computation performed by a structured operation, for
23724357fecSgysitexample, to support signed and unsigned computations.
23824357fecSgysit
23924357fecSgysitThe following example demonstrates the use of function attributes:
24024357fecSgysit
24124357fecSgysit```python
24224357fecSgysit@linalg_structured_op
24324357fecSgysitdef elemwise_binary(
24424357fecSgysit    lhs=TensorDef(T1),
24524357fecSgysit    rhs=TensorDef(T2),
24624357fecSgysit    O=TensorDef(U, output=True),
24724357fecSgysit    fun=BinaryFnAttrDef(default=BinaryFn.add),
248e9085d0dSgysit    cast=TypeFnAttrDef(default=TypeFn.cast_signed)):
24924357fecSgysit  O[None] = fun(cast(U, lhs[None]), cast(U, rhs[None]))
25024357fecSgysit```
25124357fecSgysit
25224357fecSgysitThe `fun` and `cast` function attributes by default are aliases for their
253e9085d0dSgysitdefault values `BinaryFn.add` and `TypeFn.cast_signed`, respectively. When
25424357fecSgysitinstantiating the operation, the function attributes may be set to other
25524357fecSgysitfunctions using optional named arguments:
25624357fecSgysit
25724357fecSgysit```python
25824357fecSgysitelemwise_binary(lhs, rhs, outs=[out_tensor],
25924357fecSgysit                fun=BinaryFn.mul, cast=TypeFn.cast_unsigned)
26024357fecSgysit```
26124357fecSgysit
26224357fecSgysitIn the example, the `fun` and `cast` arguments adapt the body of the operation
26324357fecSgysitto implement multiplication and unsigned casts instead of addition and signed
26424357fecSgysitcasts.
26524357fecSgysit
26624357fecSgysitOpDSL supports unary, binary, and type conversion function attributes. An
26724357fecSgysitoperation can take multiple attributes of different kinds placed at the end of
26824357fecSgysitthe parameter list.
26924357fecSgysit
270226f925cSTobias Gysi## Types
271226f925cSTobias Gysi
272226f925cSTobias GysiAll types in assignment expressions are late bound based on actual input and
273226f925cSTobias Gysioutput types of constructed ops. An exception are predefined types such as
274226f925cSTobias Gysi`I32`, `I64`, `F32`, and `F64`. These hardwired types enable intermediate
275226f925cSTobias Gysicomputations with a type that is independent of the input and output types. For
276226f925cSTobias Gysiexample, parts of floating point computation may require double precision
277226f925cSTobias Gysiarithmetic despite all inputs and outputs being single precision values.
278e9085d0dSgysitAssignment expressions with no `TypeFn.cast_signed` calls will generally require
27915757ea8Sgysituniform types throughout and will fail to verify if violated. The presence of a
280e9085d0dSgysit`TypeFn.cast_signed` or `TypeFn.cast_unsigned` allows for a limited form of
281e9085d0dSgysitnumeric type conversion between element types that can be derived from inputs
282e9085d0dSgysitand outputs (and in the future, attributes). `TypeFn.cast_signed` calls with a
283e9085d0dSgysit`TypeVar` first argument are emitted as `type_fn` primitives in the YAML
284e9085d0dSgysitdefinition.
285226f925cSTobias Gysi
286226f925cSTobias GysiCasting will perform `int<->float` and `index->int` type conversions and will
28715757ea8Sgysitperform any necessary extension or truncation within the type family. The
28815757ea8Sgysitinteger types themselves are signless and signedness is implemented by
289e9085d0dSgysitfunctions/operations. The `TypeFn.cast_signed` function treats all integers as
290e9085d0dSgysitsigned, while `TypeFn.cast_unsigned` treats them as unsigned.
29115757ea8Sgysit
29215757ea8SgysitThe following examples illustrate the lowering of signed and unsigned functions:
29315757ea8Sgysit
294e9085d0dSgysit*   cast_signed(I32 -> I64) -> `arith.ExtSIOp`
295e9085d0dSgysit*   cast_signed(F32 -> I32) -> `arith.FPToSIOp`
29615757ea8Sgysit*   cast_unsigned(I32 -> I64) -> `arith.ExtUIOp`
29715757ea8Sgysit*   cast_unsigned(F32 -> I32) -> `arith.FPToUIOp`
298e9085d0dSgysit*   max_signed -> `arith.MaxSIOp`
2995f4c89edSRageking8*   max_unsigned -> `arith.MaxUIOp`
300226f925cSTobias Gysi
301226f925cSTobias GysiNot all functions are applicable for all numeric types, and on mismatch, op
302226f925cSTobias Gysiverification will fail.
303a3655de2Sgysit
304a3655de2Sgysit## Pointwise Computations
305a3655de2Sgysit
306a3655de2SgysitPointwise computations are expressible in a rank polymorphic form that supports
307a3655de2Sgysitarbitrary ranked operands - all of them need to have the same rank - with a
308a3655de2Sgysitsingle operation definition.
309a3655de2Sgysit
310a3655de2SgysitAn example for a rank polymorphic operation is `fill`:
311a3655de2Sgysit
312a3655de2Sgysit```python
313a3655de2Sgysit@linalg_structured_op
314a3655de2Sgysitdef fill(value=ScalarDef(T1),
315a3655de2Sgysit         O=TensorDef(U, output=True)):
316e9085d0dSgysit  O[None] = TypeFn.cast_signed(U, value)
317a3655de2Sgysit```
318a3655de2Sgysit
319a3655de2SgysitThe operation sets the elements of the output tensor `O` to `value`. All
320a3655de2Sgysitoperands are either scalars or rank zero tensors that are accessed using the
321a3655de2Sgysitindex `None`. The operation thus performs a scalar computation that trivially
322a3655de2Sgysitextends to a multi-dimensional pointwise computation. As a result, we may use
323a3655de2Sgysit`fill` with arbitrary ranked output tensors:
324a3655de2Sgysit
325a3655de2Sgysit```python
32681ca5aa4SMatthias Springertensor_2d = tensor.EmptyOp([4, 8], f32)
32781ca5aa4SMatthias Springertensor_3d = tensor.EmptyOp([4, 8, 16], f32)
328a3655de2Sgysitfill(value, outs=[tensor_2d])
329a3655de2Sgysitfill(value, outs=[tensor_3d])
330a3655de2Sgysit```
331