What Is MLIR and Why Does It Exist? Chris Lattner created MLIR (Multi-Level Intermediate Representation) in 2018 at Google to solve the problem of fragmented compiler infrastructure across different hardware targets and programming models. MLIR, released publicly in 2019 under the LLVM umbrella, provides a common way to represent and transform code, reducing the need to build separate compilers for each new chip, language, or ML framework. The project lives inside the LLVM monorepo to leverage existing battle-tested building blocks. If you've never written a compiler, the word "MLIR" probably looks like alphabet soup. This article is for you. By the end you'll understand, in plain language, what problem MLIR solves and why it had to exist at all. Let's start with the origin story — because where something comes from tells you almost everything about what it's for. The story of MLIR starts in 2018 at Google. Chris Lattner, one of the most influential figures in compiler engineering, set out to solve a problem that had been bothering the industry for years — there was no common way to represent and transform code across different hardware targets and programming models. MLIR was his answer, and it went public in 2019 under the LLVM umbrella. Imagine you work on TensorFlow, Google's machine learning library. Your job is to take a model someone wrote in Python and make it run fast — on a laptop CPU, on a phone, on a GPU, and on Google's custom TPU chips. To do that, the model has to be translated, step by step, into instructions each piece of hardware understands. That translation-and-optimization process is, fundamentally, a compiler . The trouble was that there wasn't one compiler. There were many. One team built a tool to optimize graphs. Another built a separate tool to target TPUs. Another for mobile. Another for a specific hardware accelerator. Each tool had its own way of representing the program internally, its own bugs, its own optimization tricks that couldn't be shared with the others. The ecosystem was siloed — a pile of separate, half-overlapping compilers all reinventing the same wheels. And this wasn't unique to Google. Across the industry, the same pattern kept repeating: a new chip, a new language, or a new ML framework would appear, and someone would sit down to build yet another compiler from scratch to support it. Everybody was paying the same enormous bill, over and over. Chris Lattner moved to Google in 2017 to lead the TensorFlow infrastructure team, walked straight into that fragmentation mess, and built MLIR to fix it. MLIR stands for Multi-Level Intermediate Representation . Hold onto that name — every word in it is doing real work, and we'll unpack it as we go. The official paper describes the goals directly: reduce software fragmentation, improve compilation for the wild variety of modern hardware, dramatically lower the cost of building domain-specific compilers, and help existing compilers connect to one another. A small but telling detail:MLIR doesn't live in its own separate project. It was addedinsidethe LLVM monorepo llvm-project in a folder literally called mlir/ . Why? Because LLVM already had two decades of battle-tested, reusable building blocks — data structures, error handling, a testing framework — and Lattner knew that codebase better than anyone alive. Starting from zero would have meant rebuilding all of that. Sitting inside the monorepo, MLIR could borrow it on day one. Before we get to the machine-learning payoff, we need a shared mental model of what a compiler actually does . Let's build that with the simplest possible program. When you compile a program, your code goes on a journey through several stages: Source code → Frontend parsing → AST a tree of your program → IR intermediate representation → Optimization passes run in a loop → Lowering toward the machine → Backend per-CPU details → Code generation actual machine code Don't worry about memorizing it. The three ideas that matter are: Let's trace a single expression — x = 1 + 2 — through all three. For instance, when you run a .py file, the very first thing CPython does is break raw text into tokens — the smallest meaningful chunks of the language. python import tokenize, io source = "x = 1 + 2" tokens = tokenize.generate tokens io.StringIO source .readline for tok in tokens: print tok Output: TokenInfo type=1 NAME , string='x', ... TokenInfo type=54 OP , string='=', ... TokenInfo type=2 NUMBER , string='1', ... TokenInfo type=54 OP , string='+', ... TokenInfo type=2 NUMBER , string='2', ... So x = 1 + 2 stops being an opaque string and becomes a flat list of typed pieces. The tokenizer doesn't care about meaning yet — it just answers: "what kind of thing is this character sequence?" Next, the parser takes that flat list of tokens and builds an AST Abstract Syntax Tree — a nested structure that captures the grammar of your program. python import ast tree = ast.parse "x = 1 + 2" print ast.dump tree, indent=2 Output: Module body= Assign targets= Name id='x' , value=BinOp left=Constant value=1 , op=Add , right=Constant value=2 The flat sequence 1 + 2 became a BinOp node with an Add operator and two children. The structure of the expression is now explicit in the shape of the tree — not buried in the order of characters. This tree is what gets handed off to the next stage. The compiler never looks at your source text again. Next, compile takes the AST and produces bytecode — CPython's IR. The optimizer runs between the two, applying any transformations it can find. Here it applied constant folding : since both operands are literals, 1 + 2 can be solved at compile time. The runtime never sees the addition at all. python import ast, dis source = "x = 1 + 2" tree = ast.parse source Stage 1 — AST code = compile source, "