------------------------------------
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.