Inferensys

Glossary

Common Subexpression Elimination (CSE)

Common Subexpression Elimination (CSE) is a compiler optimization that identifies and eliminates redundant computations of identical expressions, replacing repeated calculations with a single computed value stored in a temporary variable.
Performance engineer optimizing AI latency on laptop, latency charts visible, technical optimization session.
COMPILER OPTIMIZATION

What is Common Subexpression Elimination (CSE)?

A fundamental compiler optimization technique for improving computational efficiency by removing redundant calculations.

Common Subexpression Elimination (CSE) is a compiler optimization that identifies and eliminates redundant computations of identical expressions within a program's scope, replacing repeated calculations with a single computed value stored in a temporary variable. This transformation reduces the total number of executed instructions and minimizes register pressure by reusing previously computed results, directly lowering execution time and power consumption. It is a critical data-flow analysis performed during the intermediate representation (IR) phase of compilation.

For NPU acceleration, CSE is applied to the computational graph of a neural network to fuse identical tensor operations, such as repeated normalization or activation functions applied to the same data. By eliminating these redundant subgraphs, the compiler reduces kernel launch overhead and decreases data movement between memory hierarchies. This optimization is foundational for techniques like kernel fusion and works in concert with constant propagation and dead code elimination to produce highly efficient, specialized binaries for target hardware.

COMPILER OPTIMIZATION

Key Benefits of CSE

Common Subexpression Elimination (CSE) is a foundational compiler optimization that directly enhances performance by removing redundant calculations. Its primary benefits are realized through reduced computational overhead and improved memory access patterns.

01

Reduced Computational Cost

The most direct benefit of CSE is the elimination of redundant arithmetic operations. By identifying identical expressions computed multiple times, the compiler computes the value once, stores it in a temporary register or variable, and reuses it. This directly reduces:

  • Instruction count in the generated code.
  • Execution cycles spent on floating-point or integer operations.
  • Power consumption, especially critical in embedded systems and NPUs where energy efficiency is paramount.

For example, in a loop computing a = x*y + z; b = x*y - z;, the subexpression x*y is calculated once, halving the required multiplications.

02

Improved Data Locality & Register Usage

CSE promotes better utilization of the processor's memory hierarchy. The result of a common subexpression is typically held in a fast storage location:

  • Register Allocation: The computed value is kept in a CPU or NPU register, providing near-zero latency access for subsequent uses.
  • Cache Efficiency: By reducing the number of distinct memory loads for the same data, it improves cache hit rates.
  • Reduced Register Pressure: While it uses a register for the temporary, it often saves more registers by eliminating the need to reload operands for the redundant calculation. This is crucial for NPUs with limited register files per core.
03

Enabler for Further Optimizations

CSE often creates opportunities for other compiler passes by simplifying the Intermediate Representation (IR).

  • Constant Folding & Propagation: If the common subexpression evaluates to a constant, CSE makes this constant obvious, allowing it to be propagated.
  • Dead Code Elimination: By consolidating computations, it can reveal calculations whose results are no longer needed.
  • Loop-Invariant Code Motion: CSE can help identify expressions that are invariant within a loop, which can then be hoisted outside the loop body. This synergistic effect creates a compounding performance benefit.
04

Critical for NPU & Accelerator Performance

In Neural Processing Unit compilation, CSE is vital for maximizing throughput and minimizing latency.

  • Kernel Fusion Context: When fusing kernels (e.g., convolution + bias + ReLU), CSE identifies redundant operations across the original kernel boundaries in the fused code.
  • Memory Bandwidth Reduction: NPUs are often memory-bandwidth constrained. By eliminating redundant loads of weight tensors or activation maps, CSE reduces traffic to high-bandwidth memory (HBM).
  • Tensor Core Utilization: For operations targeting specialized units like Tensor Cores, ensuring identical matrix blocks are not recomputed saves highly efficient compute cycles.
05

Trade-offs and Considerations

While highly beneficial, CSE requires careful implementation to avoid negative impacts.

  • Register Pressure vs. Compute Trade-off: Storing a result consumes a register. Aggressive CSE can cause register spilling if too many temporaries are live simultaneously, hurting performance more than the saved computation.
  • Precision in Floating-Point: Reusing a computed floating-point value is not strictly equivalent to recomputing it due to non-associative operations and fused multiply-add (FMA) instructions. Compilers must respect language semantics (e.g., -ffp-contract in C/C++).
  • Scope Analysis: Effective CSE must correctly analyze the scope and lifetime of expressions, ensuring the computed value is still valid (i.e., its operands haven't been modified) at the point of reuse.
06

Relationship to Other Optimizations

CSE interacts closely with several sibling optimizations in the kernel fusion and optimization group.

  • Loop-Invariant Code Motion: Often works before CSE, moving invariant expressions out of loops, after which CSE can eliminate duplicates of those hoisted expressions.
  • Constant Propagation: Feeds into CSE; once a variable is known to be constant, expressions using it become candidates for elimination.
  • Kernel Fusion: Creates a larger code region where CSE can find redundancies that were previously hidden across separate kernel boundaries.
  • Dead Code Elimination: Runs after CSE to clean up any original computations that were made redundant by the elimination process.
COMPARISON

CSE vs. Related Compiler Optimizations

This table distinguishes Common Subexpression Elimination from other key compiler optimizations, highlighting their primary objectives, scopes, and typical application contexts within NPU compilation.

Optimization / FeatureCommon Subexpression Elimination (CSE)Constant PropagationDead Code Elimination (DCE)Loop-Invariant Code Motion (LICM)

Primary Objective

Eliminate redundant computation of identical expressions

Replace variables with known constant values

Remove code that does not affect program output

Move computations outside loops if their result is loop-invariant

Scope of Analysis

Local (basic block) and Global (across blocks)

Local and Global data-flow analysis

Local and Global data-flow analysis

Primarily loop nests

Key Mechanism

Identifies identical expression trees; stores result in a temporary variable

Tracks constant assignments and substitutes values

Identifies unused variables and unreachable code

Analyzes data dependencies to hoist invariant statements

Impact on Memory

May increase register pressure due to temporary variables

Typically reduces memory/register usage

Reduces memory footprint and instruction count

Reduces redundant computations within loops; may increase register use outside

Interaction with CSE

N/A (Core technique)

Enables CSE by revealing more expressions as identical

Can be enabled by CSE (computations become dead after elimination)

Often performed before CSE to expose more subexpressions

Typical NPU Benefit

Reduces FLOPs and power consumption for repeated tensor operations

Enables further optimizations (folding, CSE) and simplifies instructions

Reduces kernel binary size and wasted compute cycles

Reduces loop body execution time, improving parallelism efficiency

Applicable IR Phase

Mid-level IR (after parsing, before low-level machine code gen)

Mid-level IR (often before or alongside CSE)

Multiple phases (mid-level and low-level IR)

Mid-level IR, during loop nest optimization (LNO)

Example Transformation

t = a * b; x = t + c; y = t + d;

const int N=256; -> Use literal 256

x = y * z; (if x never used) -> Remove line

for(i=0;i<N;i++) { x = y * z; a[i] = x + i; } -> x = y * z; for(i=0;i<N;i++) { a[i] = x + i; }

COMPILER OPTIMIZATION

Examples of CSE in AI/ML Compilation

Common Subexpression Elimination (CSE) is a foundational compiler optimization that identifies and removes redundant calculations. In AI/ML compilation, it is applied at multiple levels of abstraction to reduce computational overhead and improve execution efficiency on hardware accelerators like NPUs and GPUs.

01

Computational Graph Level

At the highest level, CSE operates on the computational graph representation of a neural network. The compiler analyzes the Directed Acyclic Graph (DAG) to find identical subgraphs or tensor operations.

  • Example: In a transformer block, the same layer normalization operation might be computed separately for queries, keys, and values before attention. CSE identifies this and computes the normalization once, reusing the result.
  • Impact: Eliminates redundant forward passes of identical sub-networks, directly reducing FLOPs in the execution graph. This is critical in models with repeated structures, such as Vision Transformer (ViT) patches or recurrent cell unrolling.
02

Kernel Fusion Context

CSE is a prerequisite and enabler for effective kernel fusion. Before fusing multiple operations into a single kernel, the compiler eliminates common subexpressions within the fusion candidate set.

  • Process: When fusing a sequence like Conv2D → BiasAdd → ReLU, the compiler first checks for repeated calculations of the convolution's input padding or stride offsets. These intermediate address calculations are hoisted and computed once.
  • Benefit: Reduces register pressure and instruction count within the fused kernel, allowing more resources for the core computation. This prevents the fused kernel from becoming bloated with duplicate address arithmetic.
03

Loop Nest Transformations

Within a single computational kernel, CSE is applied during loop nest optimization. The compiler's scalar evolution analysis identifies invariant expressions inside loops.

  • Classic Example: In a matrix multiplication kernel C[i][j] += A[i][k] * B[k][j], the address calculation for A[i][k] is invariant with respect to the inner j loop. CSE moves this calculation to an outer loop.
  • AI/ML Specific: In a depthwise convolution, the same filter weight is used across all spatial positions for a given channel. CSE loads the weight into a register once per channel, rather than for each pixel calculation.
  • Interaction: Works synergistically with loop-invariant code motion (LICM) and strength reduction.
04

Address Calculation & Memory Access

A major source of redundancy in optimized kernels is address arithmetic for multi-dimensional tensor accesses. CSE is aggressively used to compute base pointers and offsets once.

  • Scenario: Accessing a 4D tensor [N, C, H, W] at position [n, c, h, w] requires computing a linear index: ((n * C + c) * H + h) * W + w. If this tensor is accessed multiple times in a loop with the same n, c, h indices but varying w, the partial calculation ((n * C + c) * H + h) * W is a common subexpression.
  • Hardware Impact: Eliminating these redundant calculations reduces integer ALU pressure, freeing units for the core floating-point or matrix operations (e.g., Tensor Core instructions).
05

Constant Folding & Propagation Synergy

CSE works in tandem with constant propagation and constant folding. After constants are propagated, new opportunities for CSE often emerge.

  • Workflow:
    1. Constant Propagation: Identifies that a model hyperparameter (e.g., epsilon=1e-5 in LayerNorm) is a compile-time constant.
    2. Constant Folding: Pre-computes expressions involving only constants (e.g., 1.0 / sqrt(variance + 1e-5)).
    3. CSE: If this folded constant expression appears in multiple normalization layers, it is computed once and reused.
  • Result: Moves work from runtime to compile-time, generating a more efficient binary. This is especially valuable for deploying static graphs where model parameters are fixed.
06

Challenges in Dynamic Graphs & JIT

Applying CSE in Just-In-Time (JIT) compilation for dynamic graphs (e.g., PyTorch eager mode, control flow) presents unique challenges.

  • Problem: The full computation graph is not known statically. Redundant expressions may only become apparent at runtime based on input-dependent control flow.
  • Solution: Advanced JIT compilers (like TorchInductor or XLA) use tracing to capture a concrete graph from a sample execution. CSE is then applied to this traced graph.
  • Limitation: If the trace is not representative, CSE might incorrectly eliminate an expression that is not truly redundant for all inputs. This requires careful guard insertion to ensure correctness.
  • Trade-off: Balances optimization aggressiveness with the overhead of guard checks and re-compilation.
COMMON SUBEXPRESSION ELIMINATION

Frequently Asked Questions

Common subexpression elimination (CSE) is a fundamental compiler optimization crucial for performance on NPUs and other accelerators. This FAQ addresses its core mechanisms, benefits, and role within the broader kernel fusion and optimization landscape.

Common subexpression elimination (CSE) is a compiler optimization that identifies and eliminates redundant computations of identical expressions within a program's scope. It works by analyzing the intermediate representation (IR) or computational graph to find expressions that are computed more than once with the same operands. When a match is found, the compiler computes the expression once, stores the result in a temporary variable, and replaces all subsequent occurrences of that expression with a reference to the temporary. This process reduces the total number of arithmetic operations and can improve data locality by keeping intermediate results in faster memory hierarchies like registers.

For example, in the code snippet c = (a + b) * d; e = (a + b) / f;, the subexpression (a + b) is computed twice. After CSE, it becomes temp = a + b; c = temp * d; e = temp / f;, eliminating one addition operation.

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.