Inferensys

Glossary

Loop Fusion

Loop fusion is a compiler optimization that combines two or more adjacent loops with the same iteration space into a single loop, reducing loop overhead and improving data locality by keeping intermediate results in faster memory hierarchies like registers or caches.
Stylish WeWork-like workspace with hot desks and document wall, professional searching through enterprise knowledge base on a mounted ultrawide display, warm industrial pendants overhead.
COMPILER OPTIMIZATION

What is Loop Fusion?

Loop fusion is a fundamental compiler optimization for high-performance computing and AI acceleration.

Loop fusion is a compiler optimization that combines two or more adjacent loops iterating over the same range into a single loop. This transformation reduces loop overhead—such as increment and branch instructions—and, more critically, improves data locality by keeping intermediate computation results in faster memory hierarchies like CPU registers or hardware accelerator caches. It is a core technique within loop nest optimization (LNO) for transforming compute-intensive kernels, particularly for neural network execution on NPUs and GPUs.

The primary performance benefit arises from minimizing costly accesses to global memory. By fusing loops, intermediate values produced by one operation can be consumed by the next within the same iteration without being written to and read from slow main memory. This is especially powerful when combined with kernel fusion to merge entire computational graphs. Effective fusion requires analyzing data dependencies to ensure the transformation is semantics-preserving and does not create anti-dependencies or exceed register pressure, which could lead to register spilling.

COMPILER OPTIMIZATION

Key Benefits of Loop Fusion

Loop fusion is a fundamental compiler transformation that merges adjacent loops with identical iteration spaces. Its primary benefits stem from reducing control overhead and improving data locality, which are critical for performance on modern hardware accelerators like NPUs and GPUs.

01

Reduced Loop Overhead

Loop fusion eliminates the prologue and epilogue instructions of the fused loops, along with the associated branch and increment operations for each iteration. This directly reduces the total number of executed instructions. For deep loops or loops within hot code paths, this reduction in control flow overhead can yield significant performance gains, especially on architectures where branch prediction misses are costly.

02

Improved Data Locality

This is the most significant benefit. By combining loops, intermediate results produced by the first loop can be consumed immediately by the second loop while the data is still in a fast memory hierarchy.

  • Register Locality: Values can be kept in processor registers across the fused operations, avoiding spills to slower memory.
  • Cache Locality: Data accessed by both loops may remain in the L1 or L2 cache, drastically reducing accesses to high-latency global memory (DRAM). Example: Fusing an element-wise multiplication loop with a subsequent summation loop keeps the multiplied value 'hot' for the addition.
03

Enhanced Parallelism & Vectorization

A single, larger fused loop often presents more uniform and predictable work for the compiler's auto-vectorizer and parallelization engines.

  • Vectorization: The combined loop body may expose longer sequences of contiguous, data-parallel operations amenable to SIMD or SIMT execution.
  • Parallel Loop Scheduling: Runtime systems (e.g., OpenMP, TBB) incur less overhead when scheduling one large parallel loop versus multiple smaller ones, improving core utilization. This reduces the ratio of parallel region management overhead to useful computation.
04

Memory Bandwidth Reduction

Fusion minimizes traffic between the processor and main memory. Without fusion, the output of the first loop is written to memory, only to be immediately read back as input for the second loop. This store/load redundancy consumes precious memory bandwidth. Fusion bypasses this by keeping the data on-chip. In memory-bound applications—where performance is limited by the speed of data movement—this bandwidth saving directly translates to higher execution speed and lower power consumption.

05

Enabling Further Optimizations

Loop fusion creates new opportunities for downstream compiler passes by creating a larger, unified code block for analysis.

  • Common Subexpression Elimination (CSE): Expressions computed in the first loop and used in the second can now be identified as common within the single loop body.
  • Dead Store Elimination: Temporary arrays that only served to pass data between loops may be eliminated entirely.
  • Constant Propagation & Folding: Constants can propagate more freely across the formerly separate loop boundaries. Fusion effectively lowers the abstraction barrier between sequential operations.
06

Constraints and Trade-offs

Loop fusion is not always applicable or beneficial. Compilers must perform legality and profitability analysis.

  • Legality: Loops must have the same iteration space (start, stop, step) and no loop-carried dependencies that fusion would violate.
  • Register Pressure: Fusing large loop bodies can increase live variable counts, potentially causing register spilling, which harms performance.
  • Cache Behavior: If the fused loop's working set exceeds cache capacity, performance can degrade due to capacity misses. Thus, fusion is often applied as part of a broader loop nest optimization strategy.
COMPILER OPTIMIZATION

Loop Fusion vs. Loop Fission

A comparison of two complementary loop transformations used in NPU and GPU compilation to optimize for data locality, parallelism, and hardware resource utilization.

Feature / GoalLoop FusionLoop Fission (Loop Distribution)

Primary Objective

Improve data locality and reduce loop overhead

Improve parallelism and reduce register pressure

Transformation

Combines adjacent loops with the same iteration space into a single loop

Splits a single loop body into multiple separate loops

Effect on Intermediate Data

Keeps data in fast memory (registers/cache); reduces global memory traffic

May force intermediate data to slower memory; can increase memory traffic

Impact on Parallelism

Can reduce parallelism by creating a larger, more complex loop body

Can increase parallelism by creating smaller, independent loops for concurrent execution

Register Pressure

Increases (more live variables per iteration)

Decreases (fewer live variables per loop)

Applicability

Adjacent loops with identical trip counts and no loop-carried dependencies

Single loop with independent statements or separable sub-computations

Compiler Phase

Typically applied early to enable other locality optimizations

Often applied to enable fusion on specific sub-loops or to relieve resource constraints

Interaction with Tiling

Often performed before tiling to create larger tiles for better locality

Can be applied after tiling to distribute tile computations

Typical Performance Gain

10-30% reduction in memory-bound kernel runtime

5-20% improvement in compute-bound kernels via increased ILP/SIMD utilization

COMPILER OPTIMIZATION

Examples of Loop Fusion

Loop fusion combines adjacent loops with identical iteration spaces into a single loop, reducing overhead and improving data locality. Below are concrete examples illustrating its application and benefits in performance-critical code.

01

Element-Wise Array Operations

A classic example where separate loops performing independent calculations on the same arrays are fused.

Before Fusion:

c
for (int i = 0; i < N; i++) {
    C[i] = A[i] + B[i];
}
for (int i = 0; i < N; i++) {
    D[i] = C[i] * scale;
}

After Fusion:

c
for (int i = 0; i < N; i++) {
    float temp = A[i] + B[i];
    D[i] = temp * scale;
}
  • Key Benefit: The intermediate result C[i] is kept in a register (temp), eliminating a full write and read of array C to/from main memory or cache.
  • Performance Impact: Reduces memory bandwidth pressure and loop control overhead by 50% for these operations.
02

Reduction and Mapping

Fusing a map-like operation with a subsequent reduction (like a sum) is highly effective.

Before Fusion:

c
// Map: Compute squares
for (int i = 0; i < N; i++) {
    squares[i] = data[i] * data[i];
}
// Reduce: Sum the squares
float sum = 0.0f;
for (int i = 0; i < N; i++) {
    sum += squares[i];
}

After Fusion:

c
float sum = 0.0f;
for (int i = 0; i < N; i++) {
    sum += data[i] * data[i];
}
  • Key Benefit: Completely eliminates the allocation, writing, and reading of the temporary squares array.
  • Memory Savings: Transforms an O(N) memory operation into an O(1) operation, crucial for large N.
  • Common Use: Found in computations like variance, L2 norm, and dot products.
03

Multi-Stage Filter Kernels

In signal or image processing, consecutive filters are prime candidates for fusion.

Scenario: Applying a blur then an edge detection kernel to an image. Before Fusion: Two separate passes over the image.

  1. Loop over pixels to compute blurred image B.
  2. Loop over B to compute edge image E.

After Fusion: A single pass computing edges directly from original pixels.

  • Mechanism: The fused loop's body expands to compute the local blur for a pixel and immediately apply the edge detector to that intermediate value.
  • Benefit: The intermediate blurred image B never exists in full in memory. Data stays in cache/registers.
  • Challenge: Increases register pressure as more live variables are needed simultaneously. May conflict with other optimizations like tiling.
04

Fusion in Machine Learning Kernels

A foundational optimization in ML compilers for layers like activation functions.

Common Pattern: Linear layer followed by a non-linear activation (e.g., ReLU). Before Fusion (Two Kernels):

  1. GEMM or MatMul kernel: Z = X * W + B
  2. ReLU kernel: A = max(Z, 0)

After Fusion (One Fused Kernel):

  • A single kernel computes A = max(X * W + B, 0).
  • Impact: Eliminates the global memory round-trip for the large tensor Z.
  • Extended to Operation Fusion: This is often called operator fusion or kernel fusion, where loops are fused at the granularity of tensor operators (convolution, bias add, activation).
  • Framework Support: Performed automatically by compilers like XLA, TVM, and MLIR.
05

Loop Fusion vs. Loop Fission

Understanding when not to fuse is critical. Loop fission (distribution) is the inverse transformation.

When Fusion May Hurt Performance:

  • Excessive Register Pressure: Fusing large loop bodies may require more live variables than physical registers, causing register spilling to slower memory.
  • Reduced Parallelism: A single fused loop may offer less independent work for parallelization compared to distributing work across multiple loops.
  • Impedes Other Optimizations: A fused loop may have complex access patterns that prevent effective vectorization or tiling.

Compiler Heuristic: Modern compilers analyze trade-offs using cost models. They may:

  • Fuse loops with low arithmetic intensity to improve data locality.
  • Apply fission to loops with high register pressure to enable better instruction-level parallelism (ILP).
  • Use profile-guided optimization (PGO) data to make informed decisions.
06

Constraints for Legal Fusion

Not all adjacent loops can be fused. The compiler must prove the transformation is safe and semantics-preserving.

Key Requirements:

  1. Identical Iteration Space: Loops must have the same bounds and stride. for(i=0;i<N;i++) can fuse with for(i=0;i<N;i++) but not with for(i=0;i<M;i++).
  2. No Loop-Carried Dependencies: The first loop must not write to a variable read in the second loop's later iterations (a true dependency).
    • Fusible: A[i]=...; B[i]=A[i]; (flow dependency within same iteration).
    • Not Fusible: A[i]=...; B[i]=A[i-1]; (dependency crosses iterations).
  3. No Reverse Dependencies: The second loop must not write to a variable read by the first loop.

Compiler Analysis: Uses dependence analysis to construct a dependence graph. Loops are fused only if no violating dependencies exist between them.

LOOP FUSION

Frequently Asked Questions

Loop fusion is a foundational compiler optimization for high-performance computing and neural processing unit (NPU) acceleration. These questions address its core mechanics, benefits, and practical applications for engineers.

Loop fusion is a compiler optimization that combines two or more adjacent loops iterating over the same range into a single loop. It works by merging their loop bodies, thereby executing the computations from the original separate loops within a single iteration. This transformation reduces the total number of loop control instructions (increment, compare, branch) and, critically, improves data locality by keeping intermediate results in faster memory hierarchies like registers or caches between the fused operations.

For example, consider two loops that first compute an element-wise operation and then a reduction:

c
// Original: Two separate loops
for (int i = 0; i < N; i++) {
    temp[i] = A[i] * B[i]; // Kernel 1
}
for (int i = 0; i < N; i++) {
    sum += temp[i];        // Kernel 2
}

// After Loop Fusion: One loop
for (int i = 0; i < N; i++) {
    float t = A[i] * B[i]; // Fused computation
    sum += t;              // Immediate consumption
}

The fused version eliminates the need to write and read the entire temp array from main memory, keeping the intermediate value t in a register.

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.