------------------------------------ Intermediate Representation in Aidge ------------------------------------ At the heart of Aidge's power is its **innovative Intermediate Representation (IR)**, which transforms your deep neural networks into a highly flexible and powerful graph. This adaptable, interactive representation empowers precise control and optimization of your AI models. You can play with the Aidge IR with the :doc:`Aidge IR tutorial<../Tutorial/aidge_ir>`. Why a graph-based intermediate representation? ============================================== The graph intermediate representation is the foundation upon which Aidge enables advanced model manipulation and optimization. In Aidge, the computing graph is **always explicit**, ensuring users know exactly which operators are effectively used. The notions of graph (a set of nodes), mathematical operator and actual implementation are separated: a node contains an operator, which itself points to an implementation. Furthermore, the graph only defines a topology, without any assumption on execution priority. For insights into graph structure, delve into the Aidge Core Module. The graph provides **step-by-step editing and adaptation** of your model, offering **granular control** that is crucial for - **Applying optimization methods**: specialized nodes, such as cast or scaling nodes, can be easily inserted directly into the graph to prepare the model for specific optimizations such quantization, pruning or compression. - **Leveraging hardware capabilities**: the graph can be transformed to take full advantage of the underlying hardware's unique architecture and features, leading to more efficient execution. Key Features of Aidge's intermediate representation =================================================== Aidge's intermediate representation is designed for both power and ease of use, offering several key features: Hierarchical ~~~~~~~~~~~~ A graph can be hierarchical (to any depth), thanks to **meta-operators**. This means you can view your model at different levels of abstraction. Complex operations can be collapsed into single meta-operators for a high-level overview, or expanded to reveal their detailed underlying structure, providing flexibility for analysis and optimization. - Expand meta-operator(s): .. code-block:: python # Expand only level-1 meta operators aidge_core.expand_metaops(model) # Expand all meta operators recursively aidge_core.expand_metaops(model, recursive=True) # Expand a single meta operator aidge_core.expand_metaop(model.get_node("conv5")) - Fuse to meta-operator: .. code-block:: python # Fuse using graph matching aidge_core.fuse_to_metaops(model, "Conv2D#<-Pad?", "PaddedConv2D") .. image:: /source/_static/aidge_metaop.png .. raw:: html

See an example of `Meta-Operator `__ in this tutorial. Generic ~~~~~~~ It is possible to specify any type of operator, even without a corresponding implementation, thanks to **generic operators**. No need to write a single line of code to handle custom operators at the graph level. The following example shows how to replace ``ReLU`` operators with generic ``Swish`` operators. .. code-block:: python # Replace ReLU with a Swish generic operator model = aidge_onnx.load_onnx("lenet.onnx") matches = aidge_core.SinglePassGraphMatching(model).match("ReLU") for match in matches: node_ReLU = match.graph.root_node() # Create a generic op. of type Swish node_swish = aidge_core.GenericOperator("Swish", [aidge_core.InputCategory.Data], 1) # Optional: add attributes to the generic op. node_swish.get_operator().attr.beta = [1.0] * node_ReLU.get_operator().get_input(0).dims()[1] # Optional: specifies how its output dimensions are computed w.r.t. its inputs node_swish.get_operator().set_forward_dims(lambda x: x) # Usage example: replace ReLU with the Swish generic op. aidge_core.GraphView.replace(set([node_ReLU]), set([node_swish])) This tutorial offers a practical look at how `Generic Operators `__ are used. Graph Matching ~~~~~~~~~~~~~~ Aidge introduces a simple and efficient **Domain Specific Language (DSL)** for graph matching, sometimes called “graph regex”. This powerful feature allows you to write complex textual queries to find specific nodes or sets of nodes based on their types, attributes, and relationships. This is particularly useful to implement sophisticated pattern-matching heuristics with no effort. .. code-block:: python matches = aidge_core.SinglePassGraphMatching(model).match("(Pad#?->Conv#|Deconv#->Pad#?)->ReLU#?->Add#1?->ReLU#?->MaxPool#?->ReLU#?;.->Add#1?") for match in matches: aidge_core.GraphView.replace(match, MyCustomIPOperator()) You can define your own node test function as well: .. code-block:: python gm = aidge_core.SinglePassGraphMatching(model) gm.add_node_lambda("test", lambda node: ...) matches = gm.match("Conv->test") Discover how to use `graph matching `__ with a tutorial. Automatic Adaptation ~~~~~~~~~~~~~~~~~~~~ Aidge can automatically transform the graph to fit the constraints of the targeted hardware, primarily through techniques like **tiling**. Tiling breaks down large operations into smaller, manageable chunks that better fit the memory of edge devices. Importantly, Aidge performs tiling at the same graph IR level, simply by expanding the compute graph with tiled operators, without making assumptions about the underlying programming paradigm. Users retain the flexibility to choose the appropriate tiling granularity based on the operators used. The tiled operator implementation may be a C kernel, a call to a specific hardware accelerator or HLS-generated operator, or none of that, if the new tiled graph is just made to be exported back in ONNX, ready to be fed to another tool. An example of tiling a ``MatMul`` operator: .. code-block:: python aidge_core.matmul_tiling(matMul, [16, 16]) For a ``Conv2D`` operator, horizontal tiling can be performed as follows: .. code-block:: python aidge_core.get_conv_horizontal_tiling(conv, axis=1, nb_slices=5) Learn more about `tiling `__ with the tutorial.