AI Engineer World's Fair 2025
Luminal - Search-Based Deep Learning Compilers - Joe Fioti
About this talk
Luminal creator Joe Fioti explains how a small set of primitive tensor operations and static computation graphs can represent modern machine-learning models while avoiding the complexity of conventional framework stacks. He describes compiling those graphs directly to CUDA, searching candidate transformations to discover fast kernels instead of relying on handwritten optimization rules, handling bounded transformer dynamism, and exploiting whole-workload visibility for buffer reuse.
Chapters
- 0:02Introducing Luminal and simplification through search
- 1:10Framework complexity and minimal primitive operations
- 4:48Bounded model dynamism and compact computation graphs
- 7:23Replacing conventional ML stacks with direct CUDA compilation
- 11:59Search-generated kernels and whole-graph memory optimization
- 24:13Inference workloads and closing invitation
Talk transcript
- 0:02
Hey everyone, I'm Joe. I'm one of the creators of Luminal, and so today in this talk I'm gonna explain what it is, how it works, and why we really think that it's the future of ML libraries.
- 0:14
So the title of the talk is Radical Simplificat- Simplification Through Search, and that really is the theme of Luminal. We are far simpler than most other ML libraries, and yet we are not taking a hit on performance or capability because we are using, uh, compilers and more specifically, search.
- 0:35
So, deep learning fundamentally is very simple. It's simple linear algebra. And so basically all it really is, is, is, you know, scalars, vectors, matrices, and tensors, and then a couple core ops, a couple core operations.
- 0:52
So we have addition, some multiplies, matmuls, some element-wise ops in between, and that's, that's mostly it. But if deep learning is so simple, why aren't the libraries simple? So the machine learning software ecosystem is very, very complicated.
- 1:10
Um, PyTorch is one of the most famous libraries out there. It has over twelve hundred operations, uh, and has over fifteen different data types. It runs on a ton of different kinds of devices, uh, CPU, CUDA, AMD, TPUs, uh, some NPUs out there.
- 1:28
And the problem with all of this is that complexity does not scale just by adding these together. You don't have to have the number of operations plus the number of data types plus the number of, uh, supported devices.
- 1:42
It's actually multiplicative. So the number-- the complexity scales with ops times data types times number of devices. And so once you raise any of those, you wanna support a new op or a new data type or a new device, your complex- complexity starts exploding.
- 2:02
So what that... what's happened to PyTorch is it's now over like three million lines of code. Uh, TensorFlow is even much worse than this. So it's extremely, extremely complicated pieces of software, and this hurts because there's a lot more bugs obviously if you have a ton more code, but it's also just very hard for people to ever
- 2:21
extend or use or build anything inside of.
- 2:26
So what we do is we take the approach of looking top-down at machine learning and then saying fundamentally, "What are the minimum amount of things we need in order to make ML models run?"
- 2:38
So deep learning, again, like I said, is linear algebra. Linear algebra boils down to simple ops. So what if we just built these very complicated models out of like Lego blocks of very simple operations?
- 2:51
So what we do is we have, uh, twelve operations that are very, very, very simple. Um, and so we have exp2, log2, sine, and reciprocal, and square root. Those are our unary operations.
- 3:04
We have, uh, our binary operations: addition, multiplication, modulo, and less than. And then we have our reductions. So we have sum reduce and max reduce. And with those, just those operations, you can support all of the big models out there, all of the commercially relevant models that everybody cares about.
- 3:21
So you can do language models, vision language models, uh, CNNs, RNNs. You can do, um, uh, diffusion models, all these other very, very popular models.
- 3:34
Is that really it? I mean, that's not a lot of ops. [clears throat] Is that really all it takes to, uh, to represent all of this? Well, it's a lot of these other operations that you would have expected to see on the list are really just,
- 3:47
uh, uh, formable, usable by combining different operations on this list. So subtraction is really simple. It's just addition and multiplication with a negative one. Division is really simple because [clears throat] it's just multiplication and a reciprocal on B.
- 4:04
Uh, matmuls are really simple if you have the ability to manipulate the shape metadata of your tensor. So you can basically just do a broadcast at multiply and then sum reduce it down, and you get the output of matmul, so you don't need a matmul op.
- 4:19
Um, convolution is really simple if you have the ability to do pooling through, uh, shape trackers again, and then you do a matmul, like we just discussed, with the, the convolution kernel, and you get the output of a convolution.
- 4:33
And so another thing we realized when we were building this is that all of these existing libraries are built with dynamism at their core. And they were usually, they were mostly built, uh, you know, five to ten years ago when dynamism was very important.
- 4:48
So people were experimenting with RNNs and LSTMs and all these fancy models, and they needed a whole lot of hackability and dynamism, and they didn't so much care about performance.
- 4:59
Um, so they, you know, PyTorch is very, very dynamic, but because of that, it's a lot more complex. So deep learning fundamentally isn't dynamic. It was just there for convenience.
- 5:11
But the dynamism inherent to models here is very, very small and very, very bounded. So in a transformer model, the only real thing that is dynamic is the KV cache length and the actual sequence length.
- 5:26
And aside from that, the entire model is static. Um, and so what we do is we specify these models as directed acyclic graphs of operations. So right in front of us, we see a really simple example where we're loading in a tensor, we're loading in a weight, and then we are element-wise multiplying them together, and then we
- 5:46
are sum reducing them. And right here, like we talked about, this is a matmul. This is what a matrix multiply is. And so right on screen, this is all that a, a single dense, uh, neural network layer is.
- 6:00
It's just your, your, your matrix multiplying by a weight matrix. So on the right-hand side here, we see a much larger model. And so these graphs, you can kind of see get quite a bit more compli- complex, uh, but they are capable of fully specifying these models.
- 6:17
So the consequence of this is that Luminal is really, really simple. Despite it being able to actually represent and run all of these different models in the world, it's under five thousand lines of code.
- 6:29
Uh, it's very easy to understand, and our goal here is that we are going to, uh, this whole library really should be learnable in an afternoon. So you should be able to sit down and understand the core structure and core concepts of it in, you know, a couple hours here.
- 6:44
So that doesn't necessarily mean it's fast, though. So Llama 7B, uh, right out of the box runs, uh, you know, it takes about all day to generate a single sentence.
- 6:57
Super-duper slow. But the point isn't to run these primitive graphs of operations here. The point is to take these graphs and then run them through some functions to transform them into faster graphs.
- 7:10
And so we do that with compilers. So we compile our way back to high performance. So before we get to talk about compilers here, I just wanna again highlight, um, what this simplification gets us.
- 7:23
A traditional stack is you might be using something like Hugging Face Transformers. It's a great library. It sits on top of PyTorch and xFormers and, uh, you know, some other libraries that provide optimized kernels.
- 7:36
These libraries inside them have a bunch of handwritten kernels, uh, that try to be adaptable for all these different use cases. And then those handwritten kernels may call operations in cuDNN or cuBLAS, which then sit on top of CUDA.
- 7:53
And so all of this creates a very complex dependency story. Anybody who's tried to install, uh, a deep learning setup on a new machine knows that this is a non-trivial thing.
- 8:04
Uh, a lot of the times it's, you know, dependency hell that you're stuck in. Um, and it's just, it's just, you know, having very complex, uh, stacks means that once there's a bug, tracing that down through a really complex stack is, is a huge pain as well.
- 8:18
So generally, we wanna keep our stack as simple and straightforward as possible. Uh, with Luminal, we directly emit CUDA code. We directly generate CUDA code. And so there really is nothing between us and CUDA.
- 8:30
It's just our library, our graph, our compilers, and CUDA right beneath.
- 8:37
So like I said, Luminal is slow by default, but you're never meant to run any of these, uh, primitive op graphs. So you're supposed to... We're gonna take these graphs, and we're gonna feed them through compilers, and they're going to spit out much faster graphs.
- 8:53
So how does that actually work? I mean, we're not the first to think of compilers here, and we're not the first to think of ML compilers. Why haven't other ML compilers taken over the world?
- 9:04
Um, it's really because as your code that you want to generate grows in complexity, your compiler scales with the, the square or the cube of that complexity. It scales really, really fast because your compiler needs to now emit, needs to generate that final code.
- 9:23
And so if you double the, the complexity of your kernels, your compiler might have to 10X in complexity. So at some point, these compilers just become too complex for people to write.
- 9:35
Uh, and so that really has put a bottleneck on the current ecosystem. It's put a bottleneck on a lot of hardware startups with very fancy, uh, hardware that, you know, they, they wanna compile stuff down to.
- 9:47
Um, and, and it's actually getting worse. So as we demand more and more out of our hardware, the hardware we need to keep and make simpler and simpler. Because generally, the simpler your hardware can get, uh, the more uniform it can get and the faster it can get.
- 10:03
So as an example of this, CPUs are very, very complex. They try to predict what the programmer wanted to do. Um, they try to make life easier for the programmer.
- 10:13
But as a consequence, they're very complex pieces of hardware, and they don't run very fast for operations that we care about. GPUs are a lot simpler. GPUs require the programmer to specify things ahead of time, set things up, um, explicitly use certain memories, uh, dispatch kernels, and do certain communications explicitly, and the GPU doesn't handle that for
- 10:37
you. It handles some things for you, like scheduling, but, uh, the software needs to do a lot more. But as a consequence, the hardware is simpler, and it's a lot faster.
- 10:47
You get much better performance per watt. Uh, TPUs are even simpler, so the programmer has to handle everything. The programmer has to schedule things and has to, uh, explicitly allocate and manage all this memory.
- 11:00
Um, but the TPUs are very, very simple. So the TPUs are extremely fast, very good performance per watt, and we wanna basically keep walking down this path of simpler, faster hardware and, uh, more complex software.
- 11:16
So what that means is our, our compiler needs to get more complex. Uh, we actually run into a very typical traditional problem in, uh, CS, which is the VLIW compiler problem.
- 11:28
Uh, VLIW stands for very large instruction width, and it's, it's a very, very standard paradox where companies want to make their hardware simple, so they want to, to basically have the compiler statically schedule and set everything up beforehand.
- 11:46
But the problem is that beyond a certain point, compilers just get way too complex, and humans just can't write those compilers anymore. It's just too hard. So how do we actually get past that in Luminal?
- 11:59
Uh, well, we actually turn to the same solution that the AlphaGo, AlphaGo guys did when they were creating AlphaGo, and they wanted to crack the game of Go. They said that they were not super genius Go players, that Go was a very tough problem, and instead of trying to write, like, the perfect algorithm that would be able
- 12:18
to solve Go in one shot and always find the best move
- 12:22
Instead, what they did is they were-- they turned to search, and they said, "We're going to search through a whole bunch of, uh, boards here, of board states. And so we're gonna do this, like, really fancy tree search, and we're gonna use this guided neural network to, to go through it."
- 12:37
Uh, but the point is that search ate the complexity of Go. And so what we're doing is the exact same thing. We are searching instead through... Instead, instead of Go boards, we are searching through logically equivalent GPU kernels.
- 12:51
So this means that we don't need to hand write out a whole bunch of rules and then hope that they always work to produce fast code. What we can do is we can write a whole bunch of simple rules to build this big search space and then let the search go through and find the fastest kernels.
- 13:09
So what does this actually look like? Well, we take our graphs, uh, the graphs that we were talking about before, and we convert them into expressions in this library called Aglog.
- 13:18
Um, this library basically uses E graphs to represent these, this search space in a very memory efficient way, and then it goes ahead and it does this search through all of these equivalent expressions, this equivalent intermediate representation.
- 13:33
And we specify out, you know, twenty or twenty-five different rewrite rules. These rewrite rules are very, very simple. All they do is make a small alteration to a given GPU kernel, and we know that the output is logically equivalent.
- 13:49
We don't know if it's necessarily faster or slower, but we know it's logically equivalent, so it adds to our search space. And our search space, as we iteratively apply and apply and apply these, these simple rewrite rules as many times as we can, we build up a super, super large, uh, search space.
- 14:07
And then what we do is we just go through and we look through all of these different equivalent kernels, and we test the runtimes, and we see how fast they are, and then we choose the fastest one.
- 14:16
It's really that simple. And beyond a certain point, it becomes infeasible to search, uh, to, to profile the runtime of every p- every possible kernel, and so we start to use things like Monte Carlo tree search to sort of prune down the search space.
- 14:32
But at the end of the day, uh, it is fundamentally a search problem.
- 14:38
So what are these kinds of optimizations that end up getting found through this search space? Um, kernel fusion is a very popular one. Uh, simply, you know, you have operation A, operation B.
- 14:52
Operation B operates on the output of operation A. In this example here, we have sine and then followed by exp2. So exp2 operates on the output of sine. So the naive way is we go and we, uh, we do...
- 15:08
We load the tensor from memory into the compute unit. We do sine. We write the tensor back into global memory, and then we read the same [laughs] stuff back into the compute unit, do exp2 again, and then we write it back into memory.
- 15:23
Uh, but this is really bad. Uh, in fact, data movement in GPUs is usually like ninety-nine percent of the energy spent and the time spent. Very few, very small amounts of time and energy are spent on actual compute.
- 15:36
So instead of doing all this round tripping, what we can do is we can just merge exp and sine into the same kernel, load the data in once, and then write the data back out once we are f- we done, we're done, we have our final results.
- 15:53
So what does this actually look like in practice? Well, on the left, we have an unfused graph. It's a very, very sloppy, naive graph where we're doing a whole bunch of these different operations, and then in between them, we always have to write our result back to memory and then read it back into the compute unit for
- 16:09
the next operation. And what our compiler has done here on the right is been able to merge all of them down into one kernel. And like I said how data movement is ninety-nine percent of, uh, runtime, the, the crazy thing is that this real complex kernel on the right here actually doesn't take much longer than any one
- 16:30
of these kernels on the left here. So this whole kernel in ag-- or this whole graph in aggregate is far, far faster than the graph on the left in aggregate.
- 16:41
So one of the real big achievements that we've had, uh, recently with our search technique is we were able to find FlashAttention. FlashAttention is a very, very, very complicated algorithm that, uh, took about five years for the industry to discover, uh, for, for somebody in the industry to discover.
- 16:59
So, uh, TreeDAO discovered it in, uh, twenty twenty-two. Transformers came out in twenty seventeen, and yet this is, like, a really, really important optimization. And our, our compiler now is able to find this completely by itself.
- 17:14
Uh, so again, what do we do? We take in the naive multi-head attention graph. We run all of these different simple rewrite rules. We build out this huge search space.
- 17:24
We profile a bunch of these kernels and find the fastest one, and the fastest one in this case just happens to be FlashAttention. And to our knowledge, we're the only compiler in the world that can do something like this, uh, and it's because we're able to leverage search.
- 17:39
Again, this is an extremely complex optimization here. It's, it's not at all obvious, um, to program into a compiler.
- 17:48
So we did a little announcement about this. Uh, on the right you can see my a-announcement tweet. Here on the left you can see, uh, the generated FlashAttention kernel in green here, and then in white we have the intermediate representation.
- 18:01
It might be a little bit tough to see. Um, but yeah, this is the generated output code.
- 18:08
And so, okay, once we have this really fast, uh, kernels that are generated out of this search function,
- 18:15
what do we do? Do we just directly, uh, generate the CUDA code from that and then run it? We could do that, but there are a set of optimizations that we know will never be harmful.
- 18:26
We don't know exactly how much they will help, but we know they will always be helpful. And so what we do is we run these deterministic optimizations on the output of our search process.
- 18:36
And these optimizations are things like buffer reuse. So obviously, we wanna minimize the amount of memory, uh, we use, and so we want to optimally reuse all of our memory buffers.
- 18:50
And because we have the entire workload specified as this big graph ahead of time, we can have our compiler go in there and say, like, "Okay, uh, in this example right here, buffer one is never being used at the exact same time as buffer three."
- 19:05
And so anytime we-- once we need buffer three, we know buffer one is done. It's not going to be used again. And so what we can say is, "Oh, buffer one and buffer three should actually just be the same buffers."
- 19:17
And so we see in the bottom here, that's exactly what we're doing. We're just saying that these two are the same exact memory buffer. So this is how we can optimally, uh, uh, reduce our memory usage.
- 19:29
Another way we can optimize our final graph here is we issue the kernels all at once. So in traditional, uh, inference, what you do is you have a CPU dispatch a GPU kernel, and then the GPU runs that kernel.
- 19:44
We wait, we wait for it to finish, it goes back to the CPU, the CPU then dispatches the next kernel. That round trip to the CPU and then waiting on the CPU to dispatch the next kernel takes a lot of time.
- 19:56
And so what if we were to dispatch all of our kernels ahead of time, uh, and then the GPU would just run through them one by one by one?
- 20:03
So we do that in our compiler as well. Uh, and so we, we build this big queue. We can actually see the difference on the left-hand side here. The launch time, we actually have to wait quite a while to launch.
- 20:16
Whereas here, we can launch all of the kernels at once, and we save a whole bunch of time.
- 20:23
So Luminal was from day one always an inference library. Uh, it, it was never really designed with training in mind, but due to the s- extreme flexibility that our graph representation gives us, we were able to actually build an external crate, an external library that is an autograd engine.
- 20:42
And it works, uh, directly in Luminal, and it basically derives, given a forward graph, it derives a backward graph and then attaches that to it. And then we run our downstream compilers, which means we basically get training for free.
- 20:55
Uh, so all of the compilers that we have for inference, the search process, all of that also works for training. So it runs right on the, the backward pass as well.
- 21:05
Um, this is pretty neat too that it was added as an extension because to my knowledge, I don't think any other ML library out there is able to do this.
- 21:13
Any, any library that, uh, supports training has to have it as part of their core, uh, whereas we're able to add it in as an external thing, which means somebody else can come in, external contributors can come in and just write their own autograds or their own gradient sharding or their own really fancy training setups.
- 21:33
So that's sort of a brief overview of where we are today, the features we have today. Uh, what's to come? Well, we're really excited about adding more hardware support in.
- 21:43
So right now we support CPU, uh, CUDA, and Metal. Uh, what we really wanna do is support AMD, uh, Tensorring, uh, Groq, and TPUs, um, because these are all, like, really exciting hardwares out there.
- 21:57
We wanna break the CUDA moat ideally and, uh, sort of democratize ML across all these different hardwares. Um, [lip smacks]
- 22:05
we wanna do distributed inference and training, so we wanna do full 3D distributed, uh, through data parallel, pipeline parallel, tensor parallel. Um, [lip smacks] and, uh, we wanna do RL. So a common bottleneck in RL is we want to-- Basically, we run our model on the GPU, but we run our environment on the CPU.
- 22:25
Uh, and then that back and forth is the huge bottleneck. So if we can codify environments, we've done this for very simple environments, but we wanna see how complex we can go, codify the environment in the Luminal graph, and that gets optimized with the rest of the model through our, our same compiler flow.
- 22:43
And so basically, we run the forward pass of the model and step the environment all on the GPU. Um, so this is, this is super exciting 'cause I think it could dramatically accelerate, uh, reinforcement, reinforcement learning workflows.
- 22:57
Um, our Dyson Sphere unfortunately is pending our Sequoia fundraise, so, you know, reach out to us if you have any info on that. Um, but what we've really been working on recently is the Luminal Cloud.
- 23:09
So what we've done, because we were able to represent these models as graphs, if you're working on a model in Luminal, you can do graph.export, get a file out, upload that file to the cloud, and then get a serverless inference endpoint, and we handle everything else.
- 23:23
So we handle optimization, we handle batching and queuing, we handle turning the, you know, provisioning the machines. Um, it's totally serverless. You only pay for when your graph is actually executing.
- 23:35
So we think we can deliver the simplest, fastest, uh, most straightforward cloud experience out there. Um, so yes, come join us. Uh, there's the link to the, uh, link to the, the repo.
- 23:48
We would love, uh, PRs. If you have any ideas, uh, please join us. And we're, we're really pushing into territory that's only been pr- covered by frameworks that are orders of magnitude more complex here.
- 24:01
So it's, it's a really exciting time. [lip smacks] Uh, simplicity really allows us to do these innovations far faster than frameworks that have so much more overhead. Uh, so it's super exciting.
- 24:13
And then if you're a startup or a company that has an inference workload, uh, reach out. Um, we're building again the simplest, fastest ML cloud in the world. And so please reach out to me.
- 24:23
I'm at [REDACTED:email_address], or you can just go to luminalai.com. Um, we'd love to hear what your workload is and if we could help you out. Thanks, guys.