# Export IR Specification Export IR is an intermediate representation (IR) for the result of `torch.export`. To read more on the details of Export IR, please read this [document](https://pytorch.org/docs/main/export.ir_spec.html). The Exported IR is a specification that consists of the following parts: 1. A definition of computation graph model. 2. Set of operators allowed in the graph. A **dialect** is an Exported IR graph composed with the operations defined below, but with additional properties (such as restrictions on operator set or metadata) that are meant for a specific purpose. The EXIR dialects that currently exist are: * [ATen Dialect](#aten-dialect) * [Edge Dialect](#edge-dialect) * [Backend Dialect](#backend-dialect) These dialects represent stages that a captured program goes through from program capture to conversion into an executable format. For example, the ExecuTorch compilation process starts from a Python program capture into ATen Dialect, then ATen Dialect is converted to Edge Dialect, Edge to Backend, and finally to a binary format for execution. ## ATen Dialect ATen dialect will be used as the entry point of the ExecuTorch compilation pipeline. It is the first time an eager mode PyTorch program becomes an Exported IR graph. At this stage, functionalization is performed, removing any tensor aliases and mutations, and allowing for more flexible graph transformations to be made. Additionally, all tensors are converted to continuous format. The goal of this dialect is to capture users' programs as faithfully as possible (while remaining valid Exported IR). Registered custom operators that user has called in eager mode will preserve as-is in ATen dialect. However, we should refrain from adding custom ops in the graph via passes. For now, the function of ATen dialect is to further lower to Edge dialect. However, in the future we can see this one as the common integration point for other export use cases. ### ATen Dialect Properties An ATen dialect graph is a valid Export IR graph with the following additional properties: 1. All operators in `call_function` nodes are either ATen operators (in the `torch.ops.aten` namespace, higher order operators (like control flow operators), or a registered custom operator. A registered custom operator is an operator registered into the current PyTorch eager mode runtime, usually with `TORCH_LIBRARY` call (implies schema). Details for how to register a custom operator can be found [here](https://docs.google.com/document/d/1_W62p8WJOQQUzPsJYa7s701JXt0qf2OfLub2sbkHOaU/edit#heading=h.3rgxk3v387wl). 2. Every operator must also have a meta kernel. A meta kernel is a function that, given the shapes of the input tensors, can return the shape of output tensor. Details on how to write a meta kernel can be found [here](https://docs.google.com/document/d/1GgvOe7C8_NVOMLOCwDaYV1mXXyHMXY7ExoewHqooxrs/edit#heading=h.64r4npvq0w0). 3. Input value type must be “Pytree-able”. As a consequence, the output types are also Pytree-able because all operators output are pytree-able. 4. Ops of ATen dialect can choose to work Dynamic dtypes, implicit type promotions and implicit broadcasting of tensors. 5. All tensors memory formats are in `torch.contiguous_format`. ### ATen Operator Definition The operator set definition can be found [here](./ir-ops-set-definition.md). ## Edge Dialect This dialect is meant to introduce specializations that are useful for Edge devices but not necessarily for general (server) export. However, we still withhold specializing further to each different hardware. In other words, we don’t want to introduce any new hardware dependent concepts or data; besides those already present in users’ original python program. ### Edge Dialect Properties An Edge dialect graph is a valid Export IR graph with the following additional properties: 1. All operators in OpCall nodes are either from a predefined operator set, called **“Edge Operators”**, or a registered custom operator. An Edge operator is a ATen operator with dtype specialization. This allows users to register kernels that only work for certain dtypes to reduce binary size. 2. Input and output of the graph, and as well as to every node, cannot be Scalar. I.e. All scalar types (such as float, int) are converted to Tensor. ### Using the Edge Dialect The Edge dialect is represented with `exir.EdgeProgramManager` Python class in memory. This contains one or multiple `torch.export.ExportedProgram`s which contain the graph representation of a method. ```python import torch from executorch import exir class MyModule(torch.nn.Module): ... a = MyModule() tracing_inputs = (torch.rand(2, 2),) aten_dialect_program = torch.export.export(a, tracing_inputs) edge_dialect_program: exir.EdgeProgramManager = exir.to_edge(aten_dialect) print(edge_dialect_program.exported_program) ``` At this point, user defined graph transformation can be run through `edge_dialect_program.transform(pass)`. Order matters. Note: If the custom pass is touching `node.target`, be aware that all of the `node.target` at this stage are "Edge ops" (more details below) and not torch ops like in the ATen dialect. A tutorial on pass writing can be found [here](./compiler-custom-compiler-passes.md). After all these passes are executed, `to_edge()` will make sure the graph is still valid. ### Edge Operators As mentioned before, an edge operator is an ATen core operator with type specialization. This means an instance of the edge operator contains a set of dtype constraints, that describe all the tensor dtypes supported by both the ExecuTorch runtime and their ATen kernels. These dtype constraints are expressed in a DSL defined in [edge.yaml](https://github.com/pytorch/executorch/blob/main/exir/dialects/edge/edge.yaml). Here's an example of the dtype constraints: ``` - func: sigmoid namespace: edge inherits: aten::sigmoid type_alias: T0: [Bool, Byte, Char, Int, Long, Short] T1: [Double, Float] T2: [Float] type_constraint: - self: T0 __ret_0: T2 - self: T1 __ret_0: T1 ``` This is saying if `self` tensor is one of the type `Bool, Byte, Char, Int, Long, Short`, then the return tensor would be `Float`. If `self` is one of `Double, Float`, the return tensor will be the same dtype. After these dtype constraints are collected and documented in edge.yaml, EXIR consumes the file, and loads the constraints into EXIR Edge operators. This makes it convenient for developers to learn the supported dtypes of any argument in the Edge op schema. For example we can do: ```python from executorch.exir.dialects._ops import ops as exir_ops # import dialects ops sigmoid = exir_ops.edge.aten.sigmoid.default print(sigmoid._schema) # aten::sigmoid(Tensor self) -> Tensor self_arg = sigmoid._schema.arguments[0] _return = sigmoid._schema.returns[0] print(self_arg.allowed_types) # {torch.float32, torch.int8, torch.float64, torch.int16, torch.int32, torch.int64, torch.uint8, torch.bool} print(_return.allowed_types) # {torch.float32, torch.float64} ``` These constraints are helpful for someone who wants to write a custom kernel for this operator. Also inside EXIR, we offer a validator to check if the graph is still complying with these dtype constraints, after custom transformations. ### Op Set (WIP) Check out [edge.yaml](https://github.com/pytorch/executorch/blob/main/exir/dialects/edge/edge.yaml) for the complete list of operators having dtype constraints specified. We are gradually expanding this operator set and targeting to provide dtype constraints for all core ATen ops. ## Backend Dialect See this [doc](./compiler-backend-dialect.md)