AI Engineer World's Fair 2025

Luminal - Search-Based Deep Learning Compilers - Joe Fioti

Joe Fioti24:35

Read the talk

Luminal - Search-Based Deep Learning Compilers

Deep learning needs only a small mathematical vocabulary. Luminal builds models from that vocabulary, then searches equivalent implementations to recover efficient GPU execution.

From a talk by Joe Fioti

Before you start: Familiarity with tensor shapes, matrix multiplication and the distinction between CPU and GPU execution will help you follow the compiler examples.

Why does simple linear algebra need such complicated libraries?

Deep learning works with scalars, vectors, matrices and tensors, combining addition, multiplication, matrix multiplication and element-wise functions. Why, then, are the libraries implementing it so complicated? Joe Fioti, a co-creator of Luminal, starts with that mismatch: a small mathematical foundation has produced an enormous software ecosystem. His proposed way back to simplicity is to make compilation—and specifically search—do more of the work.

Fioti describes PyTorch as having more than 1,200 operations and more than 15 data types, across targets including CPUs, CUDA, AMD, TPUs and NPUs. The implementation burden grows across combinations, not merely by adding features:

Nimplementations≈Noperations×Ndata types×NdevicesN_{\text{implementations}} \approx N_{\text{operations}} \times N_{\text{data types}} \times N_{\text{devices}}

Supporting another operation potentially requires implementations for every relevant data type and device. A new device creates the corresponding problem across operations and types.

Slide titled “Why is ML so complicated?” lists PyTorch operations, data types, and devices, with the formula “Number of operation implementations = ops * dtypes * devices.”Open full source frame
ML framework complexity multiplies across operations, data types, and devices.

Fioti puts PyTorch at roughly three million lines of code. These figures are his scale estimates for the framework at the time, without a version or counting methodology. The practical concern is not size alone: a large implementation surface creates more opportunities for bugs and makes internals harder to understand, extend and debug.

0:020:14
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:02 · section reference included

Compose familiar layers from primitive operations

Luminal starts from the other direction: what is the smallest vocabulary needed to represent useful models? Fioti describes a twelve-operation core and names the following mathematical operations:

FamilyOperations
Unaryexp2, log2, sine, reciprocal, square root
BinaryAddition, multiplication, modulo, less than
ReductionSum, maximum

The spoken list contains eleven entries; Luminal’s primitive-graph introduction includes Contiguous as the twelfth. That is the architecture described here, rather than the expanded primitive set in current project documentation. Fioti says this small vocabulary can represent language models, vision-language models, CNNs, RNNs and diffusion models.

Many apparently missing operations are compositions. Subtraction multiplies the second input by −1 and adds it; division multiplies by its reciprocal:

a−b=a+(−1)ba/b=a reciprocal⁡(b)\begin{aligned} a-b &= a+(-1)b \\ a/b &= a\,\operatorname{reciprocal}(b) \end{aligned}

The distinction is between the operation a model author wants to express and the primitives the compiler must understand. A convenient frontend need not make every convenience a separate backend primitive.

Matrix multiplication becomes broadcasted multiplication followed by sum reduction. For A shaped [M, K] and B shaped [K, N], shape metadata presents them as [M, K, 1] and [1, K, N]. Broadcasting yields products indexed by [M, K, N]; reducing over K yields [M, N]:

Cij=∑k=0K−1AikBkjC_{ij}=\sum_{k=0}^{K-1} A_{ik}B_{kj}

This is a representation of the computation, not a requirement to materialize the entire broadcasted intermediate. Convolution follows the same approach: shape trackers describe pooled input windows, then a matrix multiplication combines those windows with the convolution kernel.

2:262:38
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

2:26 · section reference included

Keep the graph static and the changing dimensions bounded

Dynamic frameworks made experimentation convenient when researchers were exploring RNNs, LSTMs and changing model structures. Luminal instead asks how much dynamism a particular workload actually needs. In the transformer workload Fioti describes, the changing quantities are sequence length and KV-cache length; the surrounding computation stays fixed. That makes a static representation useful even when some dimensions vary.

The representation is a directed acyclic graph. In the dense-layer example, one node loads the input tensor and another loads the weights. Their outputs feed an element-wise multiply, followed by a sum reduction—the matrix multiplication just decomposed above. Larger models produce larger graphs, but the same vocabulary still specifies their computations.

“Going static” slide with tensor and weight load nodes feeding Mul, then SumReduce, alongside a taller operation graph.Open full source frame
A static graph connects tensor and weight loads through multiplication and sum reduction.

Fioti reports that Luminal’s implementation at the time was under 5,000 lines of code, without specifying the counting scope. The design goal is equally concrete: a developer should be able to understand the library’s core structure in an afternoon. A small graph language makes that plausible, but it does not yet make execution fast.

4:334:48
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

4:33 · section reference included

The primitive graph is the starting point, not the execution plan

Fioti illustrates unoptimized execution with Llama 7B: generating a sentence could take roughly all day, with no hardware, precision or token count specified. Executing each primitive separately is not the intended endpoint. The graph first expresses the model correctly; compilers then transform it into faster graphs. A minimal representation and an efficient implementation are separate responsibilities.

The conventional stack he sketches starts with Hugging Face Transformers, passes through PyTorch, xFormers and handwritten kernels, and may call cuDNN or cuBLAS before reaching CUDA. Those kernels must accommodate many workloads. The resulting dependencies complicate both installation and tracing a bug through the stack.

In the path demonstrated here, Luminal’s graph and compilers directly generate CUDA code. That shortens the route from model representation to device execution:

StackRoute to CUDA
Conventional exampleModel library → framework and kernel libraries → cuDNN/cuBLAS → CUDA
Luminal’s demonstrated pathPrimitive graph → compilers → generated CUDA

The simplification does not eliminate optimization work. It concentrates that work in the transformation from a primitive graph to an efficient executable graph.

6:446:57
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

6:44 · section reference included

Simpler hardware asks more of the compiler

Why have compilers not already removed all this complexity? Fioti’s answer is that generating sophisticated kernels can require disproportionately sophisticated compiler logic. He uses square-or-cube growth, and a possible tenfold compiler expansion for a doubling of kernel complexity, as illustrations rather than measured scaling laws. The difficulty is especially acute for hardware startups: a promising device still needs software that can schedule and feed it effectively.

His hardware comparison follows the movement of responsibility from circuitry into software:

HardwareResponsibilities emphasized in the talk
CPUHardware predicts and manages more execution details
GPUSoftware explicitly manages memory use, dispatch and communication; hardware retains scheduling responsibilities
TPUSoftware takes greater responsibility for scheduling and memory allocation

The intended tradeoff is simpler, more uniform hardware with better throughput and performance per watt for suitable workloads. But the work removed from hardware must be handled elsewhere, increasingly by the compiler.

This leads to the familiar VLIW, or Very Long Instruction Word, scheduling problem. Hardware can become simpler when the compiler decides ahead of time which work runs together and how resources are used. Beyond a certain level of scheduling complexity, however, writing a compiler that consistently makes good choices becomes the bottleneck.

8:539:04
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

8:53 · section reference included

Fusion removes an intermediate trip through global memory

Consider a tensor passed through sine and then exp2. With separate kernels, the GPU loads the input, computes sine, and writes the intermediate tensor to global memory. The next kernel reads that same intermediate, computes exp2, and writes the result. The arithmetic is simple; the extra write and read are avoidable.

Fioti characterizes data movement as roughly 99% of GPU time and energy; that is a broad claim, not a measured share for this example. The useful distinction is whether a workload is limited by memory traffic, arithmetic or latency, as described in NVIDIA’s GPU performance guide. For this dependent element-wise chain, kernel fusion removes the global-memory intermediate: load once, compute both functions, then store the final output.

The resulting CUDA computation can be expressed directly:

cuda

#include <cuda_runtime.h>
#include <math.h>

__global__ void sine_then_exp2(const float* input, float* output, int n) {
    const int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < n) {
        const float intermediate = sinf(input[i]);
        output[i] = exp2f(intermediate);
    }
}

intermediate is local to the computation; there is no separate global-memory tensor between sine and exp2. The mathematical dependency remains intact while the memory path changes.

The larger graph Fioti next shows in the recording applies the same principle repeatedly. Many separate operations, each with an intermediate memory round trip, collapse into one kernel. Fioti reports that this fused kernel takes little longer than an individual kernel from the original graph. His explanation is that the additional arithmetic costs relatively little compared with repeatedly moving intermediate results through memory; the talk supplies no timing configuration for that comparison.

14:3814:52
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

14:38 · section reference included

From naive attention to a FlashAttention implementation

The more ambitious example is FlashAttention, the IO-aware exact-attention algorithm introduced by Tri Dao and colleagues in 2022. Fioti contrasts that date with the introduction of transformers in 2017: an important optimization took years to emerge after the underlying architecture. He reports that Luminal’s compiler can find the optimization through search rather than requiring the full algorithm to be manually encoded as a compiler pass.

The input is a naive multi-head attention graph. Repeated rewrites construct a large space of equivalent implementations; kernel profiling then selects an implementation that Fioti identifies as FlashAttention. He qualifies the claim of compiler uniqueness as being to the team’s knowledge. This is a reported prototype result, without a supplied search budget, numerical-tolerance specification or reproducible runtime comparison.

“Naive to Flash Attention” slide lists rewrite rules, search-space construction, and kernel profiling beside graphs labeled Naive and Flash.Open full source frame
Naive and Flash attention graphs accompany the rewrite-and-profile search process.

Later in the recording, Fioti shows an announcement slide with generated kernel code in green and the intermediate representation in white. It makes the output of the search concrete: an optimized representation becomes CUDA code, rather than stopping at an abstract graph transformation.

16:4116:59
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

16:41 · section reference included

Reuse buffers and submit kernels together

Search does not have to make every optimization decision. After selecting kernels, Luminal applies deterministic passes before final execution code. Fioti characterizes these as transformations that will not hurt, although their benefit varies. Buffer reuse is the first example: the complete graph exposes when each intermediate is needed and when its storage becomes available again.

In the demonstration, buffer 1 and buffer 3 have non-overlapping lifetimes. By the time buffer 3 is needed, the last use of buffer 1 has finished. They can therefore share an allocation instead of reserving two independent buffers. This is a change to the memory plan, not to the logical identity of the two intermediate results. The comparison below uses allocation-a and allocation-b as illustrative allocation names.

Two logical buffers share storage when their lifetimes do not overlap

Constructed example: The allocation names allocation-a and allocation-b and the symbolic lifetime notation are teaching details. The comparison depicts a memory plan, not an executed allocation trace.

Lifetime constraint — unchanged
last_use(buffer 1) < first_use(buffer 3)

Operation: Revise the allocation plan so buffer 3 reuses buffer 1's storage after its final use.

buffer 1

Before: Separate allocation plan
allocation-a; live during the earlier interval
After: Compiler's reuse plan · Unchanged
allocation-a; live during the earlier interval

buffer 3

Before: Separate allocation plan
allocation-b; live during the later interval
After: Compiler's reuse plan · Changed
allocation-a; live during the later interval

Separate storage for buffer 3

Before: Separate allocation plan
allocation-b reserved
After: Compiler's reuse plan · Removed
Not present
Logical results remain distinct even when the compiler assigns them the same storage at different times.

The next pass addresses launch overhead. Fioti’s illustrative baseline alternates CPU dispatch, GPU execution and a return to the CPU before the next dispatch. Luminal instead builds a queue ahead of execution so the GPU can proceed through successive kernels with less launch waiting.

That baseline should not be read as a requirement of ordinary CUDA: kernel launches are asynchronous with respect to the host, and operations in a stream can execute in order without a host wait after each kernel. The optimization target here is avoidable host submission and synchronization overhead; the talk does not identify the queue mechanism as CUDA Graphs.

18:0818:15
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

18:08 · section reference included

Attach a backward graph without rebuilding the compiler

Luminal began as an inference library, but its graph representation also provides an extension point for training. An external autograd crate takes the forward graph, derives a backward graph and attaches it. The combined graph then passes through the existing downstream compilers.

The compiler optimizes computations, whether they belong to inference or differentiation. Kernel search and the other graph transformations therefore apply to the backward pass as well. Fioti’s characterization of getting training for free refers to this reuse of the compilation machinery, not to eliminating the work of building autograd or running training.

Fioti describes external training support as unusual, qualifying his comparison with other libraries as being to his knowledge. The architectural opportunity is more important than the priority claim: contributors can supply alternative autograd engines, gradient sharding or specialized training arrangements without making each one part of Luminal’s core.

20:2320:42
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

20:23 · section reference included

Extend the graph across hardware, environments and deployment

At the time of the talk, Fioti lists CPU, CUDA and Metal support. AMD, Tenstorrent, Groq and TPUs are proposed targets. The aim is to make more hardware usable through the same model representation and reduce dependence on CUDA.

Two further directions extend how much work the graph describes:

  • Distributed inference and training: combine data, pipeline and tensor parallelism into full 3D distributed execution.
  • GPU-resident reinforcement-learning environments: remove the back-and-forth between a model running on the GPU and an environment stepping on the CPU.

For the latter, the environment itself becomes a Luminal graph and passes through the same compiler flow as the model. Fioti says the team had done this for very simple environments; handling more complex ones remained an open goal. If both pieces fit that representation, the model’s forward pass and the environment step can run together on the GPU.

After a joke about a Dyson Sphere awaiting a Sequoia fundraise, Fioti turns to Luminal Cloud. The proposed deployment interface follows directly from treating a model as a portable graph:

  1. Export the model graph using the talk’s graph.export interface.
  2. Upload the resulting file.
  3. Receive a serverless inference endpoint.

This is the workflow presented in the recording, rather than a current SDK call signature.

“The Future” slide lists backend, distributed, and reinforcement-learning plans, with a Luminal Cloud bullet stating “Upload a model, get a serverless api endpoint. We handle everything else.”Open full source frame
Luminal Cloud proposes uploading a model to get a serverless API endpoint.

The service would handle optimization, batching, queuing and machine provisioning. Fioti describes billing only while the graph executes. The cloud proposition therefore extends the same separation used locally: the user supplies the computation, and the system takes responsibility for turning it into an efficient execution plan. His simplest-and-fastest positioning is a product ambition, not a demonstrated cloud comparison.

The closing invitation is practical: contribute ideas and pull requests to the Luminal repository, or bring an inference workload that can test the approach. Fioti connects the small core to the ability to develop new capabilities without the overhead of much larger frameworks. He invites startups and companies to discuss workloads through the Luminal site.

21:3321:43
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

21:33 · section reference included

Resources

From the talk

Transcript download
Read the transcript and explore slides

Loading transcript and slides…