Operators ========= .. contents:: :depth: 2 :local: Introduction ------------ An **Operator** defines the abstract computational logic for specific mathematical or logical operations. Operators provide the interface that enables Aidge to understand what computation needs to be performed, while backends provide the actual hardware-specific implementations. **Key characteristics:** - **Operation type**: for example, an Add operator performs element-wise addition, a Conv operator performs a convolution. - **Inputs and outputs**: each operator specify how many inputs it expects, their data types (e.g., float, int), and their categories (e.g., data input, parameter input like weights or biases). It also defines what kind of output (or outputs) it produces. - **Attributes**: many operators have configurable parameters beyond their inputs, such as the kernel size for a convolution, the stride, padding, or whether to enable specific features. These are called attributes. Input categories ~~~~~~~~~~~~~~~~ Operator input categories convey semantic information about the role of each input in the operator. - :class:`aidge_core.InputCategory.Data` / :class:`aidge_core.InputCategory.OptionalData`: differentiable tensor inputs representing activations or other runtime computational values. - :class:`aidge_core.InputCategory.Param` / :class:`aidge_core.InputCategory.OptionalParam`: differentiable tensor inputs representing persistent learnable model parameters (typically weights or biases). **These inputs are automatically materialized during graph construction by creating and connecting a dedicated Producer node by default.** Additionally, the created Producer automatically gets an attribute *Param* set to true. - :class:`aidge_core.InputCategory.Attr` / :class:`aidge_core.InputCategory.OptionalAttr`: non-differentiable inputs representing structural or control information, such as dimensions, axes, shapes, lengths, masks, indices, or token ids. **Attr inputs never participate in gradient propagation.** These inputs may alternatively be specified as constant operator attributes and are therefore generally optional. When both an input and an operator attribute are specified, the input value takes precedence. .. list-table:: :header-rows: 1 * - Kind - Example - Differentiable - Dynamic at runtime * - :class:`aidge_core.InputCategory.Data` - activation tensor - yes - yes * - :class:`aidge_core.InputCategory.Param` - weights - yes - yes * - :class:`aidge_core.InputCategory.Attr` - axis, kernel size - no - usually no Attributes and properties ~~~~~~~~~~~~~~~~~~~~~~~~~ Operators expose two categories of metadata: - **Attributes** are predefined instance variables declared by the operator definition. Their values are initialized during operator construction and may be modified throughout the operator's lifetime, subject to operator-specific constraints. As mentioned above, when a corresponding :class:`aidge_core.InputCategory.Attr` input is defined for an attribute and connected to a tensor or node, the input takes precedence and the attribute value is automatically updated during dimension forwarding. Operator attributes are stored in a *StaticAttributes* object, whose schema is fixed at compile time. Note that in Aidge, **Parameters** are reserved for learnable parameters, typically weights and bias tensors. - **Properties** are static class variables shared by all instances of an operator type. They describe intrinsic characteristics, capabilities, constraints, or default behaviors of that operator class. Properties are stored in a *DynamicAttributes* object. Additionally, nodes may define their own **annotations**. Unlike operator attributes, node annotations can be introduced dynamically at runtime and are specific to individual node instances. Node annotations are also stored in a *DynamicAttributes* object. .. note:: The names *StaticAttributes* and *DynamicAttributes* refer to the storage model rather than the semantic role of the metadata. *StaticAttributes* stores a compile-time fixed tuple of predefined fields, whereas *DynamicAttributes* stores a mutable key-value map. .. list-table:: :header-rows: 1 * - Concept - Scope - Storage - Mutable - Runtime-extensible * - Operator attribute - :class:`aidge_core.Operator` instance - ``StaticAttributes`` - Yes - No * - Operator property - :class:`aidge_core.Operator` class - ``DynamicAttributes`` - Typically no - Yes * - Node annotation - :class:`aidge_core.Node` instance - ``DynamicAttributes`` - Yes - Yes Operator base class ------------------- :class:`aidge_core.Operator` is Aidge's base class for describing a mathematical operator. It does not make any assumption on the data coding. .. tab-set:: :sync-group: language .. tab-item:: Python :sync: python .. autoclass:: aidge_core.Operator :members: :inherited-members: .. tab-item:: C++ :sync: cpp .. doxygenclass:: Aidge::Operator OperatorTensor base class ------------------------- :class:`aidge_core.OperatorTensor` derives from the :class:`aidge_core.Operator` base class and is the base class for any tensor-based operator. .. tab-set:: :sync-group: language .. tab-item:: Python :sync: python .. autoclass:: aidge_core.OperatorTensor :members: :inherited-members: .. tab-item:: C++ :sync: cpp .. doxygenclass:: Aidge::OperatorTensor Generic Operator ---------------- A generic tensor-based operator can be used to model any kind of mathematical operator that takes a defined number of inputs, produces a defined number of outputs and can have some attributes. It is possible to provide a function that produces the output tensors size w.r.t. the inputs size. It has a default consumer-producer model (require and consume all inputs full tensors and produces output full tensors). This is the default operator used for unsupported ONNX operators when loading an ONNX model. While it obviously cannot be executed, a generic operator has still some usefulness: - It allows loading any graph even with unknown operators. It is possible to identify exactly all the missing operator types and their position in the graph; - It can be searched and manipulated with graph matching, allowing for example to replace it with alternative operators; - It can be scheduled and included in the graph static scheduling; - 🚧 A custom implementation may be provided in the future, even in pure Python, for rapid integration and prototyping. .. tab-set:: :sync-group: language .. tab-item:: Python :sync: python .. autofunction:: aidge_core.GenericOperator .. tab-item:: C++ :sync: cpp .. doxygenfunction:: Aidge::GenericOperator(const std::string& type, IOIndex_t nbData, IOIndex_t nbParam, IOIndex_t nbOut, const std::string& name = "") .. doxygenfunction:: Aidge::GenericOperator(const std::string& type, std::shared_ptr op, const std::string& name = "") Meta Operator ------------- A meta-operator (or composite operator) is internally built from a sub-graph. .. tab-set:: :sync-group: language .. tab-item:: Python :sync: python .. autofunction:: aidge_core.meta_operator .. tab-item:: C++ :sync: cpp .. doxygenfunction:: Aidge::MetaOperator Building a new meta-operator is simple: .. code-block:: c++ auto graph = Sequential({ Pad<2>(padding_dims, (!name.empty()) ? name + "_pad" : ""), MaxPooling<2>(kernel_dims, (!name.empty()) ? name + "_maxpooling" : "", stride_dims, ceil_mode) }); return MetaOperator("PaddedMaxPooling2D", graph, name); You can use the :class:`aidge_core.expand_metaops` to flatten the meta-operators in a graph.