Deep Learning Compilers

Raffi Khatchadourian

May 12, 2026

Where We Are in the Course

So far we have covered the classical compiler pipeline:

  1. Introduction.
  2. Lexical analysis (JFlex).
  3. Syntax analysis (CUP).
  4. Type checking (attribute grammars, type constraints).
  5. Intermediate code (ASTs, DAGs, three-address code).
  6. Control-flow analysis.
  7. Data-flow analysis.
  8. Compiler optimizations.

Today: an advanced topic that puts all of this to use in a new domain—deep learning.

Q: What does a “compiler” mean when the program is a neural network?

Two Lectures, One Class

This is a two-hour session, covering two related advanced topics.

Part 1 (This Deck)—Deep Learning Compilers

Part 2 (Next Deck)—LLMs in Compiler Construction

Why a New Kind of Compiler?

Modern DL workloads stress every assumption a classical compiler makes.

Q: Could you take a TensorFlow model, lower it to LLVM IR, and call it a day?

The Combinatorial Explosion

Frameworks (the “Language” Side)

  • TensorFlow.
  • PyTorch.
  • JAX.
  • ONNX (interchange format).
  • Keras, MXNet, PaddlePaddle, …

Hardware (the “Target” Side)

  • NVIDIA GPUs (CUDA).
  • AMD GPUs (ROCm/HIP).
  • Google TPUs.
  • Apple Silicon (Metal/ANE).
  • Mobile NPUs (Qualcomm, MediaTek).
  • Custom accelerators (Cerebras, Graphcore, Groq).

Q: M frameworks × N targets means M ⋅ N hand-tuned backends. How do compilers usually break this?

The DL Compiler Stack: A Picture

graph LR
  fw["Framework code\nTF/PyTorch/JAX"] --> high["High-level IR\ncomputation graph, ops, tensors, shapes"]
  high --> mid["Mid-level IR\nloops, tiles, memory layout"]
  mid --> low["Low-level IR\ntarget-specific: PTX, HIP, LLVM, Triton"]
  low --> mc["Machine code\nGPU/TPU/NPU/CPU"]

Low-level IR targets: PTX (NVIDIA’s GPU assembly-level IR), HIP (AMD’s CUDA-compatible runtime + IR), LLVM (the classical CPU/GPU backend), Triton (a Python-embedded GPU kernel DSL—details later).

Same lowering principle as a classical compiler. What’s new is the high level.

Imperative vs. Graph Execution

Imperative (Eager)

import torch

def f(x):
    y = torch.relu(x @ w1 + b1)
    z = y @ w2 + b2
    return z
  • Runs op-by-op, like normal Python.
  • Easy to debug (set a breakpoint!).
  • Dynamic: control flow can depend on runtime tensor values.
  • Slow: no cross-op optimization.

Graph (Deferred)

@torch.compile        # or @tf.function
def f(x):
    y = torch.relu(x @ w1 + b1)
    z = y @ w2 + b2
    return z
  • Captures a graph of ops first, executes later.
  • Compilable: fuse, optimize, schedule.
  • Deployable: serialize and run without Python.
  • Restrictive: side effects and dynamic Python don’t fit.

Why This Matters—and a Research Connection

Hybrid frameworks (TF, PyTorch 2.x) let developers opt in to graph execution per function: @tf.function, @torch.jit.script, @torch.compile.

But it is not free to use:

This is exactly the boundary our research investigates—when is it safe and beneficial to refactor an imperative DL program to graph execution? (Khatchadourian et al. 2023, 2025)

Two Worlds, One Bridge

The DL compiler sits on the graph side of the bridge. Our research sits on the imperative-to-graph side.

graph LR
  py["Python (eager)"] -->|"refactoring +\nstatic analysis"| g[Graph]
  g --> c["DL compiler (today)"]
  c --> k[Optimized kernels]

Q: Why can’t we just compile all Python automatically?

Inside the Bridge: A Static-Analysis Refactoring Tool

System architecture: PyDev (refactoring UI) and Ariadne (tensor + type analysis) sit on Eclipse, Jython 3, and WALA. (Khatchadourian et al. 2025.)
System architecture: PyDev (refactoring UI) and Ariadne (tensor + type analysis) sit on Eclipse, Jython 3, and WALA. (Khatchadourian et al. 2025.)

What Each Analysis Does

Built on WALA (T.J. Watson Libraries for Analysis, IBM); Ariadne is WALA’s Python/tensor frontend, providing the tensor and type analyses below.

  • Tensor analysis (Ariadne): track which Python values flow as tensors (vs. lists, dicts, scalars).
  • Side-effect analysis: identify operations that would not survive graph capture (mutating Python state, I/O, non-deterministic ops).
  • Preconditions: a safety contract per function—if all checks pass, refactoring is sound.

This is classical static analysis, applied to a brand-new domain. Everything you learned about CFG and data-flow analysis maps directly—and the same ideas extend to SSA and pointer analysis, which we only previewed on the introductory overview slide.

Speculation: Living With Python’s Dynamism

Python is dynamically typed and reflective. Pure static analysis hits walls.

The Speculative-Analysis Trick

An unsound analysis with explicit assumptions can be more useful than a sound analysis that refuses to say anything.

This is a recurring theme in modern PL research: relax soundness, regain coverage, make the assumptions visible.

The Tool in Action

Refactoring preview: @tf.function injected before def call. (Khatchadourian et al. 2025.)
Refactoring preview: @tf.function injected before def call. (Khatchadourian et al. 2025.)

Real Eclipse plug-in. Real refactoring preview. Real @tf.function decorator inserted automatically once the analysis confirms preconditions hold.

Why TensorFlow and Not PyTorch?

A fair question: most of you write PyTorch. Why does this research target TensorFlow’s @tf.function?

The approach generalizes. PyTorch and JAX are next—and the retracing, graph-break, and side-effect patterns we study in TensorFlow recur in both. They are general DL-compiler problems, not TF-specific quirks.

Computation Graphs as the High-Level IR

A DL model is naturally a directed acyclic graph (DAG) of tensor ops.

  • Nodes: operators (matmul, conv, relu, softmax, …).
  • Edges: tensors (with shape, dtype, device).
  • Roots: inputs/parameters.
  • Leaves: outputs/loss.
graph LR
  x([x]) --> mm1[matmul]
  w1([w1]) --> mm1
  mm1 --> add1[add]
  b1([b1]) --> add1
  add1 --> relu[relu]
  relu --> mm2[matmul]
  w2([w2]) --> mm2
  mm2 --> add2[add]
  b2([b2]) --> add2
  add2 --> y([y])

Looks like a classical expression DAG—but the data flowing on edges is multi-dimensional.

Static Single Assignment, Tensor Edition

SSA (Static Single Assignment) is a classical compiler IR property where every value has a single defining op (we didn’t cover SSA explicitly this semester, but it’s a small idea):

Tensors get rich type information: shape, rank, dtype, layout, device—much richer than scalar SSA.

Shape and Type Information

A tensor’s type in a DL IR is much more than int or float.

Attribute Example
Rank 4 (NCHW image)
Shape [32, 3, 224, 224]
Dtype float16
Layout NHWC vs. NCHW
Device cuda:0
Sparsity dense/CSR/block-sparse

Layout codes: N = batch, C = channels, H = height, W = width—so NCHW orders memory as batch-major then channels, NHWC as batch-major then spatial. Different hardware prefers different orderings; rewriting between them is a real DL-compiler pass.

Q: How does this change what type checking and type inference mean?

Static vs. Dynamic Shapes

PyTorch 2.x uses symbolic shape tracking in its compiler so it can specialize without re-tracing every input (PyTorch Team 2023).

Why DL Compilers Handle Gradients

DL training is forward and backward. A DL compiler has to deal with both.

In PyTorch 2 this is what AOTAutograd does (the second box in the pipeline diagram coming up): it captures the backward pass ahead-of-time so the compiler sees the whole training step.

Inference-only compilers (TF Lite, TensorRT) skip backward and have a smaller job. Training compilers don’t get to skip it.

The Big Idea: Operator Fusion

The single most important DL-compiler optimization.

relu(x + b)          ----fuse---->     fused_add_relu(x, b)
   2 kernels                              1 kernel
   2 round trips through                  1 round trip
   GPU memory                             GPU memory

Q: Why does this matter much more for GPUs than for CPUs?

A Family of Fusion Patterns

This is the DL-compiler analogue of local algebraic simplification (the DAG-based CSE / algebraic-identity transforms from the local-optimizations part of the lecture) + loop fusion (Part 4 of the optimizations lecture, applied to scalar loops; here it operates on tensor ops).

Other Classical Optimizations, in DL Garb

Classical DL Compiler
Constant folding Fold weights+biases that depend only on consts
Dead-code elimination Drop ops whose outputs are unused
Common subexpression Share recomputed sub-graphs
Strength reduction Replace pow(x, 2) with x * x
Loop unrolling/tiling Tile tensor loops to fit cache/registers
Inlining Inline small functions into the graph

The taxonomy is familiar. The cost model is different.

DL-Specific Optimizations

The Search Problem: Autotuning

For a single op (say, matmul of two [1024, 1024] matrices) on a single GPU, the schedule space explodes:

10s of thousands of valid implementations.

Autotuning searches this space with cost models, evolutionary search, or learned heuristics (e.g., TVM Ansor (Zheng et al. 2020)).

Halide and the Algorithm/Schedule Split

A foundational idea (Ragan-Kelley et al., MIT/Adobe) (Ragan-Kelley et al. 2013):

This decoupling is the conceptual root of TVM and many modern DL compilers.

A Tour: Major DL Compilers

We will walk through five systems:

  1. TVM (Apache).
  2. XLA (Google).
  3. MLIR (LLVM project)—we’ll spend the most time here.
  4. TorchInductor (PyTorch 2).
  5. IREE/Glow/TensorRT/ONNX Runtime (briefly).

Each one makes different tradeoffs in IR design, generality, and target focus.

TVM

  • Open-source, originally from the University of Washington (Tianqi Chen et al.) (Chen et al. 2018).
  • Multi-stage IR: Relay (graph) TIR (loop-level) target code.
  • Strong on autotuning (AutoTVM, Ansor, MetaSchedule).
  • Targets CPUs, GPUs, mobile, FPGAs.
  • Ingests ONNX, TF, PyTorch.

Why It Matters

  • Showed that autotuning could match or beat hand-tuned vendor libraries.
  • Big influence on every later DL compiler.

XLA

Q: How is HLO similar to and different from a traditional three-address-code IR?

Why MLIR Exists

MLIR is arguably the most influential compiler infrastructure project of the past decade.

Q: What pattern from this course (and from LLVM) does this remind you of?

Dialects: The Unit of Extensibility

MLIR is infrastructure for building IRs, not a single IR.

A dialect is a namespaced collection of operations, types, and attributes. You define dialects to fit your domain; multiple dialects coexist in one program.

func.func @add_relu(%a: tensor<8x8xf32>, %b: tensor<8x8xf32>)
    -> tensor<8x8xf32> {
  %sum  = arith.addf %a, %b : tensor<8x8xf32>
  %zero = arith.constant dense<0.0> : tensor<8x8xf32>
  %out  = arith.maximumf %sum, %zero : tensor<8x8xf32>
  return %out : tensor<8x8xf32>
}

Three dialects on one slide: func, arith, plus the tensor type system.

Progressive Lowering

The defining workflow of an MLIR-based compiler.

graph LR
  tosa["tosa\n(NN ops)"] -->|legalize| linalg[linalg]
  linalg -->|tile/fuse| scfvec["scf + vector"]
  scfvec -->|lower| llvmgpu["llvm + nvgpu"]
  llvmgpu -->|LLVM backend| target["PTX/object code"]

Dialects in this chain: tosa (Tensor Operator Set Architecture—high-level NN ops), linalg (generic linear-algebra ops over tensors), scf (structured control flow: for, if, while), vector (SIMD-style vector ops), nvgpu (NVIDIA-GPU-specific ops above raw PTX), llvm (the LLVM IR dialect, the final stop before the LLVM backend).

Compare with the single-IR design (e.g., LLVM IR): MLIR generalizes this to a family of IRs.

MLIR’s Reach Today

  • TensorFlow: TF graphs MLIR XLA HLO TPU/GPU.
  • JAX: traces to stablehlo (an MLIR dialect).
  • PyTorch: torch-mlir exposes PyTorch through MLIR.
  • IREE: full ML inference stack built end-to-end on MLIR.
  • CIRCT: hardware design (chip RTL) on MLIR.
  • Mojo: Modular’s Python-superset language, MLIR-native.

One infrastructure, many domains. The LLVM playbook applied a level higher.

IREE: an MLIR-based end-to-end ML compiler. (Courtesy iree.dev.)
IREE: an MLIR-based end-to-end ML compiler. (Courtesy iree.dev.)

TorchInductor (PyTorch 2.x)

PyTorch’s default backend behind torch.compile (PyTorch Team 2023).

Frontend: TorchDynamo

  • Hooks CPython’s frame-evaluation API (PEP 523).
  • Symbolically interprets bytecode.
  • Captures an FX graph (from torch.fx, PyTorch’s symbolic-trace IR)—a Python-level graph of torch ops, still introspectable from Python.
  • Falls back to eager on “graph breaks” (e.g., unsupported Python).

Backend: TorchInductor

  • A PyTorch-native compiler: takes TorchDynamo’s FX graph and emits low-level kernels.
  • IR is pythonic and define-by-run—built incrementally as code is traced.
  • Lowers to Triton (GPU) or C++/OpenMP (CPU).
  • Aggressive op fusion. Real-world reports: 30–80% inference speedups on common models.

Notice the graph-break mechanism—it concedes that not all imperative code can be compiled. (Recall the connection to safe refactoring.)

The PT2 Compilation Pipeline

The PyTorch 2.x stack: TorchDynamo captures an FX graph, AOTAutograd adds the backward pass, PrimTorch decomposes ops, and TorchInductor lowers to Triton (GPU) or C++/OpenMP (CPU). (Courtesy pytorch.org.)
The PyTorch 2.x stack: TorchDynamo captures an FX graph, AOTAutograd adds the backward pass, PrimTorch decomposes ops, and TorchInductor lowers to Triton (GPU) or C++/OpenMP (CPU). (Courtesy pytorch.org.)

Triton: The Modern GPU Kernel DSL

@triton.jit
def add_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK: tl.constexpr):
    pid = tl.program_id(0)
    offs = pid * BLOCK + tl.arange(0, BLOCK)
    mask = offs < n
    tl.store(out_ptr + offs, tl.load(x_ptr + offs, mask) + tl.load(y_ptr + offs, mask), mask)

Other Notable Systems

A Concrete Comparison

System High IR Mid/Low IR Strength
TVM Relay TIR Autotuning, breadth
XLA HLO (was LLO; now MLIR) TPU codegen
MLIR-based many dialects many dialects Infrastructure
TorchInductor FX graph Inductor IR + Triton PyTorch UX
IREE StableHLO LinAlg/Vector On-device deployment
TensorRT Internal Internal NVIDIA peak performance

What Still Goes Wrong

Even with great compilers, real DL programs fight the toolchain. Each bullet below is an active research direction.

These are exactly the obstacles our refactoring research targets (Khatchadourian et al. 2023, 2025).

Class Discussion

Pick a DL system you have used (PyTorch, TensorFlow, JAX, …) and answer:

  1. Which graph-capture mechanism does it use?
  2. Which compiler backend does it use by default today?
  3. Have you ever hit a graph break/tf.function retracing issue?

Now: which of those failures are a programming language problem, and which are a compiler engineering problem?

Tying It Back to the Course

Most of what we covered earlier shows up here:

Plus one piece we didn’t cover this semester (machine code generation), now visible everywhere: codegen to PTX, HIP, LLVM, Triton, MLIR.

A DL compiler is a classical compiler—with a richer high-level IR and a much more demanding cost model.

Take-Home Points

  1. DL compilers exist because M frameworks × N targets is intractable by hand.
  2. The high-level IR is a typed computation graph.
  3. Operator fusion is the defining optimization, motivated by memory-bound GPUs.
  4. MLIR is the dominant infrastructure, built around dialects and progressive lowering.
  5. The hardest open problem isn’t “make it fast”—it’s “make it safe to compile in the first place”.
  6. That last point connects this entire course to active research.

Reading

The Dragon Book does not (yet) cover this material. Use these instead.

Required (Pick One)

Connecting to Research (Optional)

References

Chen, Tianqi, Thierry Moreau, Ziheng Jiang, Lianmin Zheng, Eddie Yan, Meghan Cowan, Haichen Shen, et al. 2018. TVM: An Automated End-to-End Optimizing Compiler for Deep Learning.” In 13th USENIX Symposium on Operating Systems Design and Implementation (OSDI).
Khatchadourian, Raffi, Tatiana Castro Vélez, Mehdi Bagherzadeh, Nan Jia, and Anita Raja. 2023. “Towards Safe Automated Refactoring of Imperative Deep Learning Programs to Graph Execution.” In 2023 38th IEEE/ACM International Conference on Automated Software Engineering (ASE), 1800–1802. https://doi.org/10.1109/ASE56229.2023.00187.
———. 2025. “Speculative Automated Refactoring of Imperative Deep Learning Programs to Graph Execution.” In 2025 40th IEEE/ACM International Conference on Automated Software Engineering (ASE), 752–64. https://doi.org/10.1109/ASE63991.2025.00068.
Lattner, Chris, Mehdi Amini, Uday Bondhugula, Albert Cohen, Andy Davis, Jacques Pienaar, River Riddle, Tatiana Shpeisman, Nicolas Vasilache, and Oleksandr Zinenko. 2020. MLIR: A Compiler Infrastructure for the End of Moore’s Law.” https://arxiv.org/abs/2002.11054.
PyTorch Team. 2023. PyTorch 2.x: torch.compile and Symbolic Shapes.” https://pytorch.org/get-started/pytorch-2-x/.
Ragan-Kelley, Jonathan, Connelly Barnes, Andrew Adams, Sylvain Paris, Frédo Durand, and Saman Amarasinghe. 2013. Halide: A Language and Compiler for Optimizing Parallelism, Locality, and Recomputation in Image Processing Pipelines.” In Proceedings of the 34th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI).
Zheng, Lianmin, Chengfan Jia, Minmin Sun, Zhao Wu, Cody Hao Yu, Ameer Haj-Ali, Yida Wang, et al. 2020. Ansor: Generating High-Performance Tensor Programs for Deep Learning.” In 14th USENIX Symposium on Operating Systems Design and Implementation (OSDI).

Up Next

After the break: Part 2—LLMs in Compiler Construction.

What if the compiler itself is partly a neural network?