· 13 min read

The fast path is an annotation

Exo-GPU keeps a sequential program as the meaning, then lets performance engineers add threads, tensor cores, and synchronization while the compiler checks that the fast version still means the same thing.

Four NVIDIA H100 GPUs and their cooling hoses inside a TSUBAME 4.0 compute node.
Fukumoto, CC BY-SA 4.0

The awkward thing about modern GPU optimization is that some of the fastest instructions are also instructions the programmer has to keep in their head while other work is still happening. A tensor-core operation can be outstanding while memory moves underneath it. A barrier can order one timeline and leave another one loose. The machine is fast because software gets to expose the overlap.

Exo-GPU, a preprint submitted September 14 and listed in the September 16 programming-languages feed, takes a pleasantly stubborn position: keep the program's meaning sequential, then treat parallel execution as something the compiler must prove compatible with that meaning. David Zhao Akeley, Yuka Ikarashi, and Jonathan Ragan-Kelley are aiming for CUDA-level control without making every synchronization mistake part of the job description.

That is a different answer to GPU complexity than hiding more of the machine. Exo-GPU still lets the performance engineer choose warps, warpgroups, register budgets, tensor-core instructions, TMA transfers, split barriers, and cluster behavior. The unusual part is where the semantic line is drawn: those choices are the fast path, not the definition of the computation.

The slow-looking program owns the meaning#

Exo already works by starting from a simple program and applying checked scheduling rewrites. A rewrite can tile a loop, reorder statements, stage data into another memory, or replace a block with a hardware instruction. The resulting code can look nothing like the starting point, while each accepted rewrite is supposed to preserve its sequential behavior.

Exo-GPU keeps that chain and adds another one. Parallel loops and synchronization are treated as annotations when Exo checks the sequential rewrites. After the schedule is finished, a separate synchronization checker asks whether the final parallel interpretation agrees with the final sequential interpretation. If both links hold, the original simple program and the generated parallel program are connected by transitivity.

  1. Intent

    Write a sequential procedure

    This is the semantic baseline.

  2. Schedule

    Apply checked rewrites

    Tile, reorder, stage, and substitute instructions.

  3. Expose

    Add the GPU execution plan

    Threads, collectives, memory spaces, and synchronization become explicit.

  4. Verify

    Check sequential vs. parallel

    The abstract machine rejects visibility and synchronization hazards.

Exo checks the rewrite chain under sequential semantics; Exo-GPU adds the final sequential-to-parallel equivalence check.

A tiny vector copy shows why this split is useful. The first loop says what the program means. A later schedule is free to assign iterations to CUDA threads as long as the parallel version still implements that same copy.

Meaning first, thread assignment second
# Sequential meaningfor i in seq(0, 32):    B[i] = A[i]# A scheduled version can assign iterations to threads,# but it is still supposed to mean the loop above.for i in cuda_threads(0, 32, unit=cuda_thread):    B[i] = A[i]

This sounds obvious until an optimization stops being a nicer spelling of the same control flow. Hopper-class kernels deliberately overlap memory movement and computation, split responsibilities between warps, and issue asynchronous operations whose effects finish later. At that point “looks like the same loop” is no longer a useful correctness argument. Exo-GPU wants a machine-checkable one.

A register cannot secretly become 32 registers#

One of the paper's most revealing choices is its distributed-memory model. CUDA programmers are used to a local scalar inside a kernel behaving as per-thread state. Exo-GPU refuses to smuggle that duplication into the semantics, because doing so would make its sequential interpretation lie about what the source means.

Consider this deliberately wrong sketch. There is one logical temporary, but 32 threads write it and then 32 threads read it.

A temporary that has not been sharded
# One logical scalar temporary.tmp: f32 @ CudaRmemfor i in cuda_threads(0, 32, unit=cuda_thread):    tmp = A[i]for i in cuda_threads(0, 32, unit=cuda_thread):    B[i] = tmp

If this were read as ordinary sequential code, the first loop would leave tmp holding the last value written. The second loop would broadcast that value. If the compiler silently gave every thread its own tmp, the same source would become a vector copy. Those are different programs.

Exo-GPU therefore makes the per-thread ownership visible in the logical allocation and infers a sharding from how the value is used. A thread may access its own shard; crossing shards requires an explicit communication mechanism such as a shuffle or a supported multicast. Collective analysis also tracks which set of threads executes each statement, so an instruction that requires a full warpgroup can be checked against the actual collective assigned to it.

I like this because it makes a recurring GPU mistake structurally harder: accidentally relying on a storage rule that exists only because a particular execution model duplicated state behind your back. The price is more explicit code. The benefit is that the sequential baseline remains an honest thing to reason about.

A barrier is not one thing anymore#

The synchronization part is where the paper gets genuinely interesting. On a simple mental model of a GPU, a barrier means “everyone waits here.” Hopper has several kinds of outstanding work, and waiting for ordinary in-order memory operations does not necessarily retire an asynchronous tensor-core operation or a TMA transfer. A barrier that waits on the wrong timeline can be perfectly real and still be insufficient.

The paper demonstrates a nasty version of this. A wgmma can still be reading shared memory while another synchronization path allows a later TMA operation to overwrite that memory. Each synchronization operation can look locally plausible. The missing edge is between the async tensor read and the later reuse of its input buffer.

I wrote a tiny executable model for the article because this is easier to feel when the failure is concrete. It is emphatically not a CUDA memory model and not Exo-GPU's implementation; it only models the one idea that a fence retires the timelines it names, not every kind of outstanding work in the universe.

A toy visibility ledgerts
type Timeline = "in-order" | "tensor-async";type PendingRead = {  resource: "B";  timeline: Timeline;};const pending: PendingRead[] = [];function issueAsyncTensorRead() {  pending.push({ resource: "B", timeline: "tensor-async" });}function fence(waitFor: readonly Timeline[]) {  for (let i = pending.length - 1; i >= 0; i--) {    if (waitFor.includes(pending[i].timeline)) pending.splice(i, 1);  }}function canOverwriteB() {  return !pending.some(read => read.resource === "B");}issueAsyncTensorRead();fence(["in-order"]);console.log(canOverwriteB()); // false: async reader is still live

With only "in-order" in the fence, the simulated tensor read remains outstanding, so overwriting B is rejected. Add "tensor-async" to the waited timelines and the toy reports that reuse is safe. The full runnable fixture lives in examples/async-visibility-check.ts and asserts both cases.

The checker stores history instead of values#

The synchronization checker does not need to know that a matrix element currently contains 2.75. It needs to know who read or mutated that location, which execution collective performed the access, whether the access came from ordinary or asynchronous work, and what later synchronization has made visible.

Exo-GPU translates the GPU IR into an abstract-machine program used only for validation. Data locations hold histories of reads and mutations rather than numeric values. Synchronization operations update the visibility of those records. Checks fail when a later read, write, free, or barrier requires an ordering that the history cannot establish.

What the checker recordsWhy it matters
Read historiesA later overwrite must not race an earlier reader that is still live.
Mutation historiesA later read or write needs enough visibility of earlier mutations.
Thread / collective identityA CTA barrier cannot magically synchronize a different CTA or cluster scope.
Async timelineWGMMA, TMA, ordinary CUDA operations, and other classes retire under different synchronization rules.
Barrier arrive / await countsSplit barriers have to pair consistently rather than merely exist nearby in source.
The abstract machine turns synchronization into bookkeeping over access histories and visibility, not a simulation of floating-point values.

There is a nice compiler-design lesson hiding here. If the property you need is ordering, interpreting every arithmetic operation is noise. Throw away the values and keep exactly the state that can witness a race. The resulting machine is still detailed, but the detail is about the property being checked.

The programmer still owns the schedule#

Exo-GPU is not an auto-scheduler pretending the hardware is simpler than it is. The paper explicitly contrasts that approach with tile-oriented systems where the compiler chooses more of the mapping and synchronization. Here the performance engineer can pick the warp specialization, the exact accelerator instruction, the memory layout, and the placement of split barriers.

That control is why the Exo lineage matters. The Exo project treats schedules as programs: reusable transformations applied to a simple algorithm. Exo-GPU reports reusing more than 60 existing verified Exo rewrites while extending the frontend, analyses, semantics, and code generation for GPUs. The schedule can stay explicit without turning every optimization into a hand-edited blob of final CUDA.

This is a middle position I find more convincing than the usual “safe abstraction versus full control” framing. The abstraction boundary moves. The compiler does not have to choose the schedule to provide safety; it can instead check a schedule chosen by someone who cares about one particular GPU generation and one particular kernel.

The H100 results are good, and narrow#

The authors built TF32 GEMM kernels for NVIDIA's sm_90a architecture using WGMMA and TMA, plus a full-precision FP32 GEMV kernel using warp shuffles and scalar math. The test system used an H100 80 GB SXM5 with CUDA 12.3. Each problem-size and kernel combination ran through warmups and 100 timed samples, with the reported number taken from the middle half of those timings.

For large square GEMMs, the Exo-GPU kernels land close to cuBLAS and above 80% of the paper's stated theoretical peak. Some unusual shapes favor an Exo-GPU specialization; GEMV mostly favors cuBLAS. The authors also distinguish a per-size “specialized” choice from one fixed Exo-GPU kernel, which is important because selecting the best schedule separately for every shape is a stronger advantage than shipping one general kernel.

MeasurementMachinePaper result
Large square GEMMH100 80 GB SXM5Exo-GPU reaches more than 80% of theoretical peak and is close to cuBLAS.
Irregular GEMM shapesH100 80 GB SXM5Performance moves around more; specialized kernels sometimes beat cuBLAS.
FP32 GEMVH100 80 GB SXM5Similar trend, with cuBLAS holding an almost-consistent advantage.
Sync check, GEMM 1024i7-11850H, one CPU threadabout 2 s
Sync check, GEMM 2048i7-11850H, one CPU threadabout 16 s
Sync check, GEMM 4096i7-11850H, one CPU threadabout 128 s
Performance and checker numbers reported by the Exo-GPU authors. They are not benchmarks reproduced for this article.

The last three rows are the less glamorous numbers I care about. Synchronization checking currently interprets a concrete problem size, and the work scales with the number of memory operations it sees. For the paper's GEMM, doubling the square dimension from 1024 to 2048 to 4096 moves the reported checker time from roughly 2 to 16 to 128 seconds. During development, the authors say a much smaller size such as 768 is usually enough for quick bug feedback, with a full-size check left for later confidence.

So the safety story is not “compile an arbitrary symbolic GEMM once and receive a theorem for all sizes.” It is a concrete-size synchronization check layered on top of static collective and memory analyses. That is still useful, but the boundary matters.

The proof stops before the hardware manual#

The paper is unusually clear about where its guarantees end. Exo-GPU's CUDA and PTX behavior is based on the authors' reading of NVIDIA's natural-language documentation. They do not prove the generated code correct against a formal CUDA semantics. Blackwell tensor cores are not supported in this version, and the implementation depends on CUDA C++ plus inline PTX.

Two transformations needed by their Hopper GEMM also sit outside Exo's existing sequential proof machinery. The issue is value sensitivity: removing a guard can be semantically harmless when the newly written locations are never observed, but dependency analysis alone still sees the writes. The authors mark those schedule steps unsafe rather than pretending the checker proved something it did not.

That detail makes the project more interesting, not less. A verification system becomes trustworthy partly by making its blind spots legible. Here the blind spots are specific: observationally dead writes, a concrete-size interpreter, hardware semantics inherited from documentation, and a target set centered on NVIDIA Hopper.

The fast path can stay understandable#

GPU programming keeps drifting toward software-managed hardware: larger asynchronous tensor operations, explicit data movement engines, clusters, specialized warps, and barriers whose meaning depends on which timeline they cover. More compiler automation is one response. Exo-GPU explores the opposite direction: expose the machine, but make the exposure accountable to a simpler program.

The sequential version is useful because it gives every optimization something stable to answer to. The schedule can become deeply hardware-specific without becoming the only surviving specification of the algorithm. A performance engineer can say “use this warpgroup, stage this tile here, overlap this copy with that tensor operation” while the checker keeps asking the boring question: does the result still mean what the simple program meant?

That is the part I want to steal for other systems work. Let experts describe the fast path in as much ugly detail as the hardware demands. Keep a small semantic baseline nearby. Then spend compiler complexity on proving the distance between the two instead of forcing either the hardware or the programmer to pretend the distance is small.

The paper is only a September 2026 preprint, and its checker has real scope limits. But the design instinct feels durable: parallelism can be explicit without being semantic bedrock. Sometimes the safest place to put the clever part is in an annotation the compiler is allowed to distrust.