Graph lowering is the process of transforming a high-level, abstract computational graph representation into a lower-level, hardware-specific representation through a series of legalization and conversion passes. This compiler-driven transformation is essential for executing neural networks on specialized accelerators like NPUs and GPUs, as it bridges the semantic gap between framework operators and native hardware instructions. The process involves decomposing complex operations into primitive kernels that the target hardware can execute natively.
Glossary
Graph Lowering

What is Graph Lowering?
Graph lowering is a foundational compiler technique in machine learning that systematically transforms a high-level computational graph into a hardware-executable form.
The lowering pipeline typically progresses through multiple intermediate representations (IRs), each with decreasing abstraction. A high-level graph from a framework like PyTorch or TensorFlow is first lowered to a mid-level IR (e.g., MLIR dialects) for hardware-agnostic optimizations, then to a low-level IR for target-specific optimizations, and finally to machine code or vendor-specific kernels. Key transformations during lowering include operator legalization, type legalization, and control flow flattening, ensuring the final graph conforms to the target's execution model and instruction set.
Core Characteristics of Graph Lowering
Graph lowering is the foundational compiler process that transforms abstract, high-level computational graphs into concrete, hardware-executable instructions through a series of deterministic, rule-based transformations.
Multi-Level Abstraction Lowering
Graph lowering operates across multiple levels of abstraction, systematically decomposing high-level operations. It begins with framework-specific operators (e.g., a PyTorch nn.Conv2d) and progresses through several Intermediate Representation (IR) dialects—such as linear algebra, loop nests, and vector operations—before reaching target-specific machine instructions. Each level applies legalization rules to convert operations into a form supported by the next, lower level. This layered approach enables hardware-agnostic high-level optimizations before committing to vendor-specific details.
Hardware-Specific Legalization
A core function of lowering is legalization, the process of replacing or decomposing operations unsupported by the target hardware with equivalent, legal sequences. For an NPU, this might involve:
- Replacing a high-level
Softmaxwith a manual decomposition usingExp,ReduceSum, andDiv. - Converting a 5D tensor operation into a series of lower-dimensional ops if the hardware only supports up to 4D.
- Mapping floating-point operations to fixed-point or block floating-point units. The legalizer uses a pattern-matching system to apply these hardware-specific rewriting rules.
Static Analysis and Shape Inference
Effective lowering depends on static shape inference, a compile-time analysis that propagates tensor dimension information throughout the graph. By resolving shapes before execution, the compiler can:
- Perform accurate memory planning, pre-allocating buffers of known size.
- Enable optimizations like constant folding and dead code elimination.
- Generate efficient, fixed-loop kernels without dynamic control flow overhead. This analysis transforms a dynamically-shaped graph from training frameworks into a static, predictable dataflow graph suitable for NPU execution.
Control Flow Linearization
NPUs and many accelerators excel at data-parallel computation but have limited support for complex control flow like loops and conditionals. A key lowering step is control flow flattening or linearization. This transforms structured control constructs into a dataflow representation using predicated execution or explicit computation graphs with conditional data edges. For example, a tf.while_loop may be unrolled a static number of times or lowered to a state machine where iterations are controlled by explicit data dependencies, eliminating branch instructions.
Integration with Optimization Passes
Lowering is not a single pass but a pipeline interleaved with optimization passes. Key optimizations performed during or just before lowering include:
- Operator Fusion: Merging adjacent nodes (e.g., Conv + BiasAdd + ReLU) into a single compound kernel.
- Layout Transformation: Converting tensor memory layouts (e.g., NCHW to NHWC) to match hardware access patterns.
- Common Subexpression Elimination (CSE): Removing duplicate calculations introduced during legalization.
- Constant Folding: Pre-computing operations on static weights. These optimizations are applied at the most appropriate abstraction level, often immediately after a legalization step creates new optimization opportunities.
Target-Specific Code Generation
The final stage of lowering is code generation, where the lowest-level IR is converted into executable code for the target. This involves:
- Instruction Selection: Mapping IR ops to specific NPU instructions (e.g., a MAC operation to a systolic array instruction).
- Register Allocation: Assigning virtual tensors to physical hardware registers or memory buffers.
- Scheduling: Determining the temporal order of instruction issuance to hide memory latency. This stage heavily utilizes the Vendor SDK and Intrinsic Mapping, emitting code that may be C/C++, assembly, or a proprietary binary format (e.g., TensorRT engine, CoreML mlmodel).
How Graph Lowering Works: The Step-by-Step Process
Graph lowering is the systematic transformation of a high-level computational graph into a hardware-executable form, a core process within NPU compilation.
Graph lowering is the compiler process of transforming a high-level, abstract computational graph into a lower-level, hardware-specific representation through a series of legalization and conversion passes. The goal is to bridge the semantic gap between a framework-defined model and the NPU's instruction set architecture (ISA), enabling efficient execution. This involves decomposing complex, generic operations into sequences of primitive operations that the target hardware natively supports.
The process typically follows a multi-stage lowering pipeline. First, a frontend converts a model from a framework like PyTorch into a common Intermediate Representation (IR). Subsequent passes then perform operator legalization, replacing unsupported ops with equivalent subgraphs. Finally, backend lowering maps the legalized graph to specific hardware instructions, registers, and memory addresses, producing an optimized binary for the NPU. This ensures the computational intent is preserved while maximizing hardware utilization.
Levels of Abstraction in a Lowering Pipeline
This table illustrates the progressive transformation of a computational graph from a high-level, framework-specific representation down to hardware-specific machine code, detailing the key characteristics and operations at each stage.
| Abstraction Level | Representation Form | Key Characteristics | Primary Compiler Operations |
|---|---|---|---|
Framework Graph (e.g., PyTorch, TensorFlow) | High-Level Operator Graph | Framework-specific operators (e.g., | Import from framework. Initial shape inference. High-level algebraic simplifications. |
Canonicalized Graph | Normalized Operator Graph | Syntactic variations eliminated. Operators lowered to a canonical set (e.g., decompose complex ops). Control flow may be represented explicitly. | Graph canonicalization. Operator decomposition. Static shape inference (where possible). |
Lowered Graph (Mid-Level IR) | Target-Agnostic Computational Graph | Hardware-agnostic, pure compute/dataflow. Often in Static Single Assignment (SSA) form. Explicit memory buffers. Loops and conditionals represented as primitive control flow nodes. | Common subexpression elimination. Dead code elimination. Memory planning. Loop-invariant code motion. Early fusion planning. |
Hardware-Specific Graph | Vendor/Architecture IR | Operations mapped to target hardware primitives (e.g., NPU tensor cores, DMA ops). Explicit memory hierarchy (global, shared, local). Vendor-specific intrinsics and data layouts appear. | Operator lowering/lowering to intrinsics. Layout transformations. Graph partitioning for multi-core. Instruction selection. |
Scheduled Kernel Graph | Scheduled Compute + Memory Ops | Explicit parallelism (workgroups, warps, wavefronts). Synchronization primitives inserted. Memory movement (load/store, DMA) explicitly scheduled relative to compute. | Loop tiling and unrolling. Parallelism mapping (thread/block assignment). Software pipelining. Insertion of barrier operations. |
Low-Level IR (e.g., LLVM IR, SPIR-V) | Linearized Instruction Stream | Register-based operations. Low-level control flow (branches, labels). Virtual or physical register allocation. Approaching general-purpose ISA level. | Register allocation. Peephole optimizations. Machine-specific instruction combining. Final instruction scheduling. |
Machine Code / Microcode | Binary Executable | Hardware-specific instructions (ISA). Absolute addresses. Configured hardware registers. May include proprietary microcode sequences for fixed-function units. | Binary assembly and linking. Address resolution. Generation of metadata and dispatch headers for the NPU runtime. |
Graph Lowering in Major Frameworks & Compilers
Graph lowering is the systematic process of transforming a high-level, abstract computational graph into a lower-level, hardware-specific representation. This section details how major frameworks and compilers implement this critical phase of the compilation pipeline.
TensorFlow / XLA: HLO to LLO
The XLA (Accelerated Linear Algebra) compiler within TensorFlow performs graph lowering through a defined sequence of Intermediate Representations (IRs). The process begins with the HLO (High-Level Optimizer) IR, which represents the graph after framework-level optimizations. XLA then lowers HLO through multiple passes:
- Target-specific legalization converts unsupported HLO ops to supported ones.
- Buffer assignment allocates memory for all tensors.
- Lowering to LLO (Low-Level Optimizer) IR, which is closer to machine code.
- Final lowering to LLVM IR for CPU/GPU or proprietary TPU IR for Google's hardware. This multi-stage approach allows for both hardware-agnostic and target-specific optimizations.
PyTorch / TorchInductor: Aten to Triton/OpenMP
PyTorch's TorchInductor compiler, used by torch.compile, lowers graphs via the Aten IR (PyTorch's core tensor operation library). The lowering path is determined by the target backend:
- For NVIDIA GPUs, it lowers Aten ops to Triton IR, a high-level Python-like language for GPU programming, which is then compiled to PTX.
- For CPUs, it lowers Aten to OpenMP-annotated C++ loops via the Cpp backend.
- The process involves operator decomposition, where complex Aten ops are broken down into simpler primitives, followed by fusion and scheduling on the target IR. This design prioritizes flexibility and performance on diverse hardware.
Apache TVM: Relay to TIR
TVM (Tensor Virtual Machine) employs a clear separation between computational and scheduling concerns. Graph lowering flows from Relay, a high-level functional IR for neural networks, down to TIR (Tensor IR).
- The Relay → TIR lowering involves legalization (mapping Relay operators to TIR primitives) and memory planning.
- TIR is a low-level, explicit loop-nest IR that represents detailed computation and hardware-aware schedules (e.g., loop tiling, vectorization).
- Finally, TIR is lowered to target code via code generation for backends like LLVM, CUDA, Vulkan, or vendor-specific NPU SDKs. This modularity is key to TVM's support for a wide array of accelerators.
MLIR: Dialect Lowering
MLIR (Multi-Level Intermediate Representation) formalizes graph lowering as dialect lowering. A computational graph enters MLIR in a high-level dialect (e.g., tosa, mhlo).
- Lowering is a series of conversion passes that translate operations from one dialect to another, progressively moving from abstract to concrete representations (e.g.,
mhlo→linalg→affine→llvm). - Each dialect owns a set of operations, types, and transformations. The pattern rewrite engine applies lowering patterns to convert ops.
- This allows hardware vendors to define their own dialects (e.g.,
npufor a specific NPU) and lowering paths, creating a customizable and interoperable compilation stack.
ONNX Runtime: Graph Transformations & EP
ONNX Runtime lowers ONNX model graphs for execution via a combination of graph transformations and Execution Provider (EP) interfaces.
- First, graph optimization passes (constant folding, node fusion) are applied on the ONNX graph.
- The optimized graph is then partitioned and assigned to hardware-specific EPs (e.g., CUDA EP, TensorRT EP, OpenVINO EP).
- Each EP is responsible for the final lowering of its subgraph. For example, the CUDA EP may convert ONNX nodes to CUDA kernel calls, while the TensorRT EP performs a more aggressive lowering and fusion to TensorRT's engine format. This architecture decouples high-level optimizations from vendor-specific lowering.
Vendor NPU SDKs (e.g., Qualcomm, NVIDIA)
Vendor SDKs for NPUs (like Qualcomm's AI Engine Direct or NVIDIA's TensorRT) perform the final, most hardware-specific stage of graph lowering.
- They ingest a framework-exported graph (e.g., ONNX, TensorFlow Lite) and apply vendor-optimized graph passes.
- This includes kernel selection (choosing the most efficient pre-written kernel for each op), memory allocation aligned with the NPU's memory hierarchy, and scheduling onto the NPU's cores.
- The output is a serialized engine or binary containing instructions and data tightly coupled to the NPU's microarchitecture. This stage is where abstract tensor operations become concrete hardware instructions.
Frequently Asked Questions
Graph lowering is a foundational compiler technique for deploying machine learning models. These questions address its core mechanisms, practical applications, and relationship to other optimization strategies.
Graph lowering is the systematic process of transforming a high-level, abstract computational graph representation into a lower-level, hardware-specific representation through a series of legalization and conversion passes. It works by applying a sequence of rewrite rules and lowering patterns that incrementally replace abstract operations with concrete implementations. For example, a single conv2d operation in a framework like PyTorch might be lowered through several intermediate representations (IRs): first to a set of linear algebra primitives (like matmul and bias_add), then to vendor-specific intrinsics (like NVIDIA's Tensor Cores or an NPU's matrix multiplication unit), and finally to scheduled machine instructions. Each step makes implicit hardware constraints explicit, enabling subsequent optimizations like kernel fusion and memory planning.
Enabling Efficiency, Speed & Accuracy
Intelligent Analysis, Decision & Execution
We build AI systems for teams that need search across company data, workflow automation across tools, or AI features inside products and internal software.
Talk to Us
Search across company data
Give teams answers from docs, tickets, runbooks, and product data with sources and permissions.
Useful when people spend too long searching or get different answers from different systems.

Automate internal workflows
Use AI to route work, draft outputs, trigger actions, and keep approvals and logs in place.
Useful when repetitive work moves across multiple tools and teams.

Add AI to products and internal tools
Build assistants, guided actions, or decision support into the software your team or customers already use.
Useful when AI needs to be part of the product, not a separate tool.
Related Terms
Graph lowering is a core phase within the broader graph compilation pipeline. These related terms represent the specific compiler techniques and intermediate representations that enable the transformation from a high-level graph to efficient hardware-specific code.
Intermediate Representation (IR)
An Intermediate Representation is a compiler's internal data structure or code used to represent a program between its source form and its target machine code. It is the central data structure upon which graph lowering and all other transformations operate.
- Purpose: Enables machine-independent analysis and optimization.
- Levels: A compilation pipeline often uses multiple IRs, transitioning from high-level (e.g., framework graphs) to mid-level (e.g., operator lists) to low-level (e.g., LLVM IR, vendor-specific IR).
- Example: MLIR is a modern, extensible framework for defining domain-specific IRs, crucial for AI/ML compilation.
Graph Canonicalization
Graph canonicalization is a compiler transformation that rewrites a computational graph into a standard, simplified form. It is a critical preparatory step before lowering to eliminate syntactic variations and ensure predictable optimization.
- Function: Converts semantically equivalent operations into a single, canonical form (e.g., rewriting
Sub(x, y)asAdd(x, Neg(y))if that's the canonical form for subtraction). - Benefit: Simplifies pattern matching for subsequent passes like fusion, constant folding, and lowering itself.
- Process: Involves algebraic simplification, operator normalization, and dead argument elimination.
Operator Legalization
Operator legalization is a specific lowering step that converts high-level or framework-specific operators into a set of primitive operations supported by the target hardware or lower-level IR.
- Mechanism: A pattern-matching and rewriting process. For example, a
Softmaxoperator may be legalized into a sequence ofExp,ReduceSum, andDivoperations. - Target: The output is a graph consisting only of operations in the target instruction set or a vendor's supported kernel library.
- Challenge: Must preserve numerical semantics while mapping to available hardware primitives.
MLIR (Multi-Level Intermediate Representation)
MLIR is a flexible, extensible compiler infrastructure that provides a modular system of interoperable intermediate representations ('dialects'). It is fundamentally designed to facilitate sophisticated multi-level lowering for domains like machine learning.
- Core Concept: Different dialects represent different levels of abstraction (e.g.,
tensordialect for high-level ops,linalgfor loop-based computation,llvmfor low-level machine ops). - Lowering Path: Graph lowering in MLIR is the process of progressively translating operations from a higher-level dialect to a lower-level one through dialect conversion passes.
- Use Case: Enables co-design of hardware and compilers by allowing custom, hardware-specific dialects.
Instruction Selection
Instruction selection is the compiler backend phase that maps the low-level operations from a legalized IR to specific sequences of machine instructions available on the target hardware. It is the final step of lowering to machine code.
- Process: Uses pattern matching (often via tree parsing) to find the most efficient instruction sequence for each IR operation or subgraph.
- Considerations: Must account for instruction costs, available registers, and side effects.
- Example: Selecting between a fused multiply-add (FMA) instruction or separate MUL and ADD instructions based on the target NPU's capabilities.
Control Flow Flattening
Control flow flattening is a lowering transformation that converts structured control flow (like loops, conditionals) into a simpler, goto-based or dataflow representation. This is often necessary for hardware with limited or explicit control flow mechanisms.
- Purpose: Eases lowering to hardware that lacks complex control flow primitives, such as many NPUs which prefer static, data-parallel execution.
- Technique: Transforms
forandifstatements into a graph of basic blocks connected by explicit branch operations. - Trade-off: Simplifies hardware mapping but can obscure high-level structure for later optimizations.

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.
Partnered with leading AI, data, and software stack.
How We Work
Custom AI workflows for your Business
One-fit-all AI don't work for modern businesses. At Inferensys, we aim to understand your business & custom requirements; which we use to define most efficient agentic workflows, the data, and the tools for your business.
01
Review the use case
We understand the task, the users, and where AI can actually help.
Read more02
Pick the right approach
We define what needs search, automation, or product integration.
Read more03
Build the first useful version
We implement the part that proves the value first.
Read more04
Improve from there
We add the checks and visibility needed to keep it useful.
Read moreThe first call is a practical review of your use case and the right next step.
Talk to Us