Inferensys

Glossary

Canonicalization

Canonicalization is a compiler transformation that converts a computational graph into a standard, simplified form to eliminate redundancy and enable more effective subsequent optimizations.
Change management lead guiding AI transformation on laptop, transition roadmaps visible, executive workshop.
COMPUTE GRAPH OPTIMIZATION

What is Canonicalization?

A fundamental compiler transformation that standardizes a computational graph to enable reliable and effective optimization.

Canonicalization is a compiler transformation that converts a computational graph or expression into a standard, simplified form to eliminate redundancy and ensure consistency. This process rewrites semantically equivalent operations—such as mathematical identities or commutative operations—into a single, predetermined representation. By enforcing a canonical form, it makes subsequent optimization passes, like common subexpression elimination and constant folding, more effective and reliable, as they can match patterns against a predictable structure.

In machine learning compilers, canonicalization acts as a crucial preprocessing step. It simplifies the intermediate representation (IR) before more complex, target-specific optimizations like operator fusion or graph lowering. For example, it ensures (A + B) and (B + A) are treated identically, or that X * 1 is simplified to X. This reduces graph complexity, prevents optimizer confusion, and creates a stable foundation for generating efficient, deployable code across diverse hardware backends, from NPUs to mobile CPUs.

COMPUTE GRAPH OPTIMIZATION

Key Objectives of Canonicalization

Canonicalization is a compiler transformation that converts a computational graph or expression into a standard, simplified form to eliminate redundancy, making subsequent optimization passes and pattern matching more effective and reliable.

01

Eliminate Redundancy

The primary goal is to remove duplicate or unnecessary computations from the graph. This includes:

  • Algebraic simplification: Applying mathematical identities (e.g., x * 1 = x, x + 0 = x).
  • Constant folding: Pre-computing operations on constant tensors.
  • Common subexpression elimination (CSE): Identifying and caching identical subgraphs.

For example, an expression like (a + b) * 1 + 0 is canonicalized to simply a + b, removing the redundant multiplicative identity and additive zero operations.

02

Standardize Operator Forms

Canonicalization enforces a single, consistent representation for semantically equivalent operations. This is critical for pattern matching in later passes like operator fusion or hardware delegate selection.

Examples include:

  • Converting all convolution padding specifications to a single format.
  • Ensuring all reduction operations (sum, mean) use the same axis parameter representation.
  • Rewriting a transpose followed by a matrix multiplication as a single batched matmul with permuted indices.

Without this standardization, the compiler would need separate pattern rules for every syntactic variation of the same operation.

03

Simplify Control Flow

This objective transforms complex, nested conditional and loop structures into a normalized form. It involves:

  • Flattening nested conditionals into a sequence of simpler if-else blocks.
  • Canonicalizing loop bounds to use zero-based indexing and unit strides where possible.
  • Removing dead branches identified through static analysis and constant propagation.

This simplification is a prerequisite for advanced loop optimizations like loop tiling, unrolling, and vectorization, which require predictable, regular control flow patterns.

04

Enable Deterministic Optimization

By producing a predictable, canonical graph from potentially diverse input representations, this pass ensures that downstream optimizations are applied consistently and reliably. This is essential for:

  • Reproducible builds: The same model always compiles to the same efficient executable.
  • Regression testing: Performance changes can be traced to specific optimization modifications, not graph representation quirks.
  • Cross-framework compatibility: Models imported from PyTorch, TensorFlow, or ONNX are reduced to a common internal representation before hardware-specific optimizations.
05

Reduce Graph Complexity

Canonicalization actively reduces the number of nodes and edges in the computational graph, lowering the search space for subsequent NP-hard optimization problems like graph partitioning or optimal operator scheduling.

Techniques include:

  • Fusing chains of element-wise operations (e.g., ReLU -> Add -> Clip) into a single composite node.
  • Eliminating identity operations like a Reshape that does not change the tensor layout.
  • Propagating shape information via shape inference to remove dynamic guards.

A simpler graph allows the compiler's cost model to evaluate optimization choices more quickly and accurately.

06

Prepare for Lowering

This final objective transforms the graph into a form that is ideal for the graph lowering process to hardware-specific instructions. It creates a clean separation between high-level, framework-agnostic optimizations and low-level, backend-specific code generation.

Key preparations include:

  • Type normalization: Ensuring all tensors have explicit, concrete data types (e.g., float32, int8).
  • Explicit broadcasting: Making implicit broadcast operations explicit with Expand or Tile nodes.
  • Layout annotation: Marking tensors with preferred memory layouts (e.g., NHWC or NCHW) for data layout optimization.

This creates a well-defined contract between the canonical intermediate representation (IR) and the backend compilers for GPUs, NPUs, or CPUs.

COMPUTE GRAPH OPTIMIZATION

How Canonicalization Works

Canonicalization is a foundational compiler pass that standardizes a computational graph to enable reliable and effective downstream optimizations.

Canonicalization is a compiler transformation that converts a computational graph or expression into a standard, simplified form to eliminate redundancy and ensure consistency. This process rewrites semantically equivalent operations—such as different forms of matrix multiplication or activation functions—into a single, canonical representation. By normalizing the graph, it makes subsequent optimization passes like operator fusion and constant folding more predictable and easier to implement, as pattern matching can target a reduced set of operator variants.

The transformation is applied early in the compilation pipeline, acting on the Intermediate Representation (IR). It systematically applies rewrite rules to collapse complex expressions, reorder commutative operations, and remove identity operations. This creates a directed acyclic graph (DAG) where structural noise is minimized. For inference frameworks targeting diverse hardware, a canonicalized graph provides a stable, hardware-agnostic foundation before graph lowering and hardware-aware optimizations, ensuring that performance gains are derived from true algorithmic improvements rather than incidental graph structure.

COMPUTE GRAPH OPTIMIZATION

Common Canonicalization Transformations

These are standard compiler passes applied to a neural network's computational graph to produce a normalized, simplified representation, enabling more reliable and effective downstream optimizations.

01

Algebraic Simplification

Applies mathematical identities to simplify expressions, reducing computational cost. This includes:

  • Constant folding: Evaluating operations with constant inputs (e.g., Add(5, 3)8).
  • Identity elimination: Removing operations that have no effect (e.g., Multiply(x, 1)x).
  • Zero propagation: Simplifying operations involving zeros (e.g., Add(x, 0)x).
  • Strength reduction: Replacing expensive operations with cheaper equivalents (e.g., Pow(x, 2)Multiply(x, x)).
02

Common Subexpression Elimination (CSE)

Identifies and eliminates redundant calculations of identical expressions within the graph. When the same computation appears multiple times with the same inputs, CSE:

  • Caches the result of the first computation.
  • Replaces all subsequent identical nodes with a reference to the cached result.
  • Crucially reduces compute and memory overhead in graphs with repeated patterns, such as those generated from high-level frameworks or after unrolling loops.
03

Dead Code Elimination (DCE)

Removes operations whose outputs do not contribute to the model's final output. The compiler performs a live variable analysis to trace data dependencies from output nodes backward. Any operation not on a path to a required output is deemed dead code and pruned. This eliminates:

  • Unused branches or layers from experimental architectures.
  • Debugging or profiling operations not needed for inference.
  • Intermediate calculations made redundant by other optimizations.
04

Node Normalization & Deduplication

Enforces a consistent, canonical form for all operations in the graph. This involves:

  • Operator canonicalization: Converting different operator representations that perform the same function into a single, standard form (e.g., normalizing all convolution padding specifications).
  • Attribute normalization: Ensuring operator attributes (like axis parameters) use a consistent indexing scheme (e.g., always 0-based).
  • Tensor layout canonicalization: Deciding on a default data format (e.g., NHWC or NCHW) and inserting explicit transpose operations where necessary, making subsequent data layout optimizations predictable.
05

Graph Flattening & Control Flow Lowering

Transforms high-level, structured control flow into a linearized, primitive representation. This is essential because many hardware backends and low-level IRs operate on basic blocks. The pass:

  • Lowers complex control constructs like tf.while_loop or torch.jit.script conditional branches into primitive Switch, Merge, and NextIteration nodes.
  • Flattens nested scopes or subgraph structures into a single, flat list of nodes.
  • Explicitly materializes iteration counts and loop conditions, making dataflow and memory planning analyzable by subsequent passes.
06

Type and Shape Propagation

A foundational pass that annotates every node in the graph with the data type and dimensional shape of its output tensor. This is not just inference; it's a canonicalization step that:

  • Resolves polymorphic operators into their concrete, instantiated form based on input types.
  • Inserts explicit cast operations where type mismatches occur, eliminating implicit casting semantics of frontend frameworks.
  • Propagates static shape information as far as possible, which is critical for static memory planning and enabling aggressive optimizations like constant folding.
IMPLEMENTATION COMPARISON

Canonicalization in ML Frameworks & Compilers

A comparison of canonicalization strategies and their characteristics across major machine learning compilers and frameworks.

Canonicalization AspectPyTorch / TorchScriptTensorFlow / XLAApache TVMMLIR (Multi-Level IR)

Primary IR for Canonicalization

TorchScript IR (ATen ops)

XLA HLO (High-Level Ops)

Relay IR

Multiple Dialects (e.g., linalg, tosa, affine)

Canonical Form Goal

Stable, serializable graph for JIT

Optimized HLO for TPU/GPU compilation

Hardware-agnostic, portable graph

Progressive lowering between abstraction levels

Key Canonicalization Passes

Constant propagation, dead code elimination, common subexpression elimination

Algebraic simplification, constant folding, batch norm rewriting

Operator legalization, type inference, constant folding

Canonicalization patterns within each dialect, folding, CSE, DCE

Pattern Matching Engine

Graph-based, node substitution

HLO instruction pattern matcher

Relay pattern language / dataflow matcher

Declarative Rewrite Rules (DRR), pattern-driven rewrite system

Handles Dynamic Shapes

Limited (requires recompilation)

Hardware-Aware Canonicalization

Example Canonical Rule

fuse_add_relu -> composite op

conv + bias_add + relu -> __cudnn$convForward

batch_flatten -> reshape

addf(0.0, %x) -> %x (float identity)

Integration with Other Optimizations

Sequential pass manager

Fusion, layout optimization after canonical

Fusion, quantization folding after legalization

Canonicalization as a prerequisite for lowering between dialects

COMPUTE GRAPH OPTIMIZATION

Frequently Asked Questions

Canonicalization is a foundational compiler pass that standardizes a neural network's computational graph, enabling more effective and reliable downstream optimizations. These questions address its core mechanisms and practical applications.

Canonicalization is a compiler transformation that converts a computational graph or expression into a standard, simplified form to eliminate redundancy, making subsequent optimization passes and pattern matching more effective and reliable.

In practice, a neural network model defined in a high-level framework like PyTorch or TensorFlow can be represented in many syntactically different but semantically equivalent ways. For example, a sequence of operations like ReLU(Conv2D(x)) might be functionally identical to Conv2D_ReLU_Fused(x) after a fusion pass, but the compiler's pattern matchers need a predictable structure to recognize this opportunity. Canonicalization rewrites the graph into a normalized form by applying a set of deterministic rules, such as:

  • Sorting commutative operation inputs (e.g., Add(A, B) becomes Add(B, A) if B's node ID is lower).
  • Flattening nested associative operations (e.g., Add(Add(A, B), C) becomes Add(A, B, C)).
  • Replacing composite operators with their primitive constituents, or vice-versa, based on a target intermediate representation (IR).

This process is analogous to simplifying an algebraic expression to a standard form before solving it. It does not change the graph's mathematical function but drastically reduces the number of unique patterns the optimizer must handle, increasing the hit rate for transformations like constant folding, common subexpression elimination (CSE), and operator fusion.

Prasad Kumkar

About the author

Prasad Kumkar

CEO & MD, Inference Systems

Prasad Kumkar is the CEO & MD of Inference Systems and writes about AI systems architecture, LLM infrastructure, model serving, evaluation, and production deployment. Over 5+ years, he has worked across computer vision models, L5 autonomous vehicle systems, and LLM research, with a focus on taking complex AI ideas into real-world engineering systems.

His work and writing cover AI systems, large language models, AI agents, multimodal systems, autonomous systems, inference optimization, RAG, evaluation, and production AI engineering.