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