Two Ways to Tame a Schedule Space: Tiramisu, Telamon, and Pruning without Regrets
A comparative deep dive into three papers on generating fast tensor kernels — and what they teach a from-scratch deep learning compiler like OCANNL.
The gap between a naive loop nest and a vendor-library kernel is
routinely an order of magnitude, sometimes two. Everyone in the
compilers-for-tensor-code business agrees on the diagnosis: the best
implementation of gemm or a convolution is separated from
the textbook code by a long chain of transformations — tiling, fusion,
vectorization, memory placement, communication staging — and the space
of such chains is astronomically large. Where the schools part ways is
on what to do about the space.
This post reads three papers side by side, two of which form a single research arc:
- Tiramisu — Baghdadi et al., Tiramisu: A Polyhedral Compiler for Expressing Fast and Portable Code (arXiv 1804.10694, CGO 2019). A polyhedral compiler with a scheduling language, from the Halide lineage at MIT.
- Telamon — Beaugnon, Pouille, Pouzet, Pienaar, Cohen, Optimization Space Pruning without Regrets (CC 2017). A branch-and-bound search over GPU kernel implementations, with a provable no-regret pruning guarantee.
- The CSP generalization — Beaugnon, Clément, Tollenaere, Cohen, On the Representation of Partially Specified Implementations and its Application to the Optimization of Linear Algebra Kernels on GPU (arXiv 1904.03383, 2019). Telamon’s ad-hoc IR rebuilt as a constraint satisfaction problem over a “semantic backbone.”
They make an instructive pairing because they attack the same problem from opposite ends. Tiramisu is about mechanism: make every transformation expressible, composable, and correct, and let a human (or a later autoscheduler) supply the policy. Telamon is about policy: make the space of implementations itself a first-class mathematical object, so that a search algorithm can reason about regions of it — including bounding, with certainty, how fast any implementation in a region could possibly be.
Tiramisu: a scheduling language over a four-layer polyhedral IR
Tiramisu is a C++-embedded DSL in the Halide tradition: the user writes a pure, architecture-independent algorithm (computations over iteration domains, communicating by producer–consumer relationships, no memory), plus a separate list of scheduling commands that determine how the algorithm becomes code. The pitch over Halide is twofold: the representation is polyhedral rather than interval-based, and the scheduling language reaches further down the stack than any predecessor — all the way to explicit communication.
The command vocabulary has four groups: classic loop-nest
transformations (tile, split,
interchange, shift, compute_at,
unroll, and a raw set_schedule that accepts an
arbitrary affine map in ISL syntax); hardware mapping
(parallelize, vectorize, gpu,
distribute); data commands (store_in for
layout remapping, buffer allocation placement,
cache_shared_at/cache_local_at for GPU
scratchpad staging, host_to_device copies); and
synchronization (barrier_at, plus point-to-point
send/receive with async/blocking properties
for distributed execution). The bolded novelty in the paper is precisely
the last two groups: buffer-hierarchy mapping, communication, and
synchronization as schedulable operations — a copy or a barrier
is an operation with an automatically inferred iteration domain that can
itself be tiled, ordered, and distributed.
The four-layer IR is the paper’s organizing idea, and it is best understood as a discipline for phase ordering of concerns:
- Layer I — abstract algorithm. Computations with iteration domains (integer sets), values flowing through producer–consumer edges. No order, no memory, no processors.
- Layer II — computation management. A schedule has
been applied: every computation instance now has time dimensions
(lexicographic order = execution order) and processor dimensions (tags
like
cpu,gpuB,gpuT,node,vec(s),unroll). Still no memory. - Layer III — data management. Access relations map
each computation instance to buffer elements; allocations appear. Layout
is any affine relation — struct-of-arrays, transposition, modulo storage
(
c(i%2, j%2)), contraction to scalars. - Layer IV — communication management. Copies, sends/receives, barriers, and allocation statements are placed into the time-processor domain.
The rationale is the classic phase-ordering complaint, inverted into an architecture. If your IR bakes memory in from the start (as LLVM-level or even Halide-level representations do), then every scheduling decision must first undo memory-based dependencies — the compiler needs array privatization, expansion, renaming — and the literature Tiramisu cites for this is long and gnarly. By deciding order-and-place before layout, and layout before communication, each pass gets to assume the later concerns are still fluid. The scheduling commands are the user-facing façade over transformations between these layers; ISL supplies the machinery (composition of affine maps, dependence analysis for legality checking, Cloog-style code generation at the end, lowering through Halide’s backend to LLVM/CUDA/MPI).
What does the polyhedral foundation buy over Halide, concretely? The
paper’s evaluation is unusually honest about isolating this: on four
image-processing benchmarks scheduled identically by Halide experts,
Tiramisu merely matches Halide. The wins come exactly where the
representation differs: a 3.77× speedup on a four-stage pipeline
(nb) because Halide’s conservative fusion legality rule
(never fuse loops updating the same buffer — it cannot prove correctness
in the interval representation) forbids a fusion that Tiramisu’s exact
dependence analysis licenses; a cyclic-dataflow filter
(edgeDetector) and a triangular iteration domain (Halide
bug ticket #2373) that Halide cannot express or miscompiles; distributed
stencils where Halide over-approximates halo exchange while Tiramisu’s
send/receive transfer exactly two rows.
Against MKL, Tiramisu matches sgemm (with the full
Goto-style bag of tricks: two-level tiling, packing, register blocking,
full/partial tile separation — tile sizes found by autotuning) and beats
the library by up to 2.3× on convolution blocks by specializing filter
sizes at compile time. Against fully automatic polyhedral compilers
(Pluto, PENCIL, Polly), the schedule-language camp wins on performance
essentially everywhere it matters, because the Pluto scheduling
heuristic optimizes a proxy (locality distance + outer parallelism) that
ignores layout, control overhead, and redundant computation.
The thing Tiramisu deliberately does not do — in this paper — is choose the schedule. Policy is the user’s job. (The group’s later work, the Tiramisu autoscheduler of Baghdadi et al. 2021, bolts a learned cost model and beam search on top; that lineage runs through today’s ML-guided autoschedulers.)
Telamon: search spaces you can bound
The Telamon paper starts from the other end: assume the space of implementations is given, and ask how to find the best point in it without evaluating more than a sliver of it. The distinctive contribution is a guarantee: branch-and-bound pruning that provably never discards the best implementation — “without regrets.”
Two ingredients make this work, and they are co-designed:
An IR whose instances denote sets of
implementations. A Telamon IR instance is a list of
instructions and iteration dimensions where every open decision
explicitly lists its remaining alternatives: each dimension can still be
a for-loop, unrolled, vectorized, a thread dimension, or a block
dimension; each memory access lists its available cache directives; each
pair of dimensions carries a set of possible orderings
(before/after/inner/outer/fused, kept transitively closed in an
adjacency matrix); each point-to-point communication between dimensions
lists its placements (register, shared, global). Taking a decision =
restricting one list to a singleton, and the IR propagates: alternatives
incompatible with the new decision are deleted elsewhere. Crucially,
lists only ever shrink — every optimization that could ever
apply is visible from the root. A fully constrained instance is a
program; a partially constrained one is a region of the search space.
(One confessed gap: strip-mining is not a decision inside the IR — you
enumerate tiling structures as multiple root instances. For their
sgemm space this meant 250,000 roots, which cost all of one
extra second, because the model prunes almost all of them
immediately.)
A performance model that lower-bounds partial implementations. For an IR instance X with candidates x ∈ X, the model computes B(X) ≤ T(x) for all x. The trick that makes bounding sets (not just points) possible: wherever a choice is open, assume the locally optimal alternative, independently for every instruction and resource — even if the resulting combination of assumptions is globally infeasible. Incoherent optimism is fine: it can only lower the bound, never break its validity. The model itself is a hierarchical roofline: lower-bound each instruction’s pressure on ALUs, memory units, dispatchers, and MSHRs; bound thread time by max(resource pressure / per-thread throughput, longest dependency chain); bound block time by intra-block contention with a “waste ratio” for partial warps; bound kernel time by device-wide contention and by occupancy (blocks in flight limited by shared memory). Missing bottlenecks (they model no caches, no register pressure!) cost accuracy, never correctness — actual GPU runs of the leaves catch what the model misses, so the model only needs to be good enough to prune, not to predict.
The search is then textbook best-first branch-and-bound (expand the node with the lowest bound; run leaves on the GPU; prune any node whose bound exceeds the incumbent’s measured time), with decision order chosen by impact heuristic: dimension kinds first, then memory placement, then ordering, then cache directives.
The numbers are striking for 2017. Their sgemm space
contains 2.7 × 10⁹ implementations; exhaustive enumeration of the tree
alone (no codegen, no runs) took 5+ hours on 24 threads, and evaluating
everything would take 149 days. Telamon finds the best implementation in
13 minutes, running only 17,664 candidates on the GPU
and visiting only 121,335 tree nodes. 77% of all candidates die in cuts
at the top two levels of the tree — the bound is loose on partial
implementations, but loose is enough when a cut near the root
kills millions of candidates at once. The generated code beats PPCG
everywhere, sits within 11–38% of cuBLAS at 1024×1024 (the gap traced,
via the model, to texture memory their space doesn’t contain), and beats
cuBLAS by 4.2× on average at 256×256 — libraries ship a
few implementations per function; a search adapts to the input size.
That last diagnostic deserves emphasis, because it is the paper’s subtlest gift: the bound is interpretable. Which inequality produced B(X) tells you which bottleneck binds — a resource shared across the device, contention within a block, a dependency chain within a thread — for an entire region of the space at once, independent of the decisions not yet taken. That is how they discovered texture memory was the missing dimension in their space, and it is a capability no black-box learned cost model offers: performance counters explain one implementation; an analytic bound on a partial implementation explains a subspace.
The 2019 paper: partial implementations as constraint satisfaction
Two years later, Beaugnon and colleagues generalized the recipe. The CC’17 IR hard-coded its decision kinds and their interaction logic; the 2019 paper factors that into a formalism plus a code generator, and swaps exact branch-and-bound for statistical search.
The factorization: a semantic backbone describes
what to compute — objects (instructions, dimensions, memory
regions) and properties over them — and is essentially constant during
search. A decision vector describes how: one
CSP variable per choice instance (the domain of cache(inst)
is {L1, L2, read-only, none}; order(lhs, rhs) ranges over
before/after/inner/outer/merged; size(dim) over precomputed
divisors; mem_space(region) over global/shared), with
constraints — universally quantified disjunctions over at most two
choices — enforcing transitivity of ordering, data dependencies, and
hardware limits via counters (running sums for shared-memory
bytes, thread counts). A candidate is the partially instantiated vector;
code generation needs a fully instantiated one. A small DSL (their
exh language, compiled to Rust) generates the sets,
domains, and propagators — under 10 lines to expose a new property;
quotient sets handle the classes-of-fused-loops bookkeeping;
monotonic triggers lower constructs lazily (deciding a
communication goes through memory materializes the load/store
instructions and their decisions) without ever invalidating existing
variables.
Three properties of this representation do the heavy lifting:
- Decisions commute. The candidate reached by deciding A then B is the candidate reached by deciding B then A. There is no phase ordering at all — and no “enabling transformation” problem, their explicit critique of rewrite-rule systems (SPIRAL, LGen, LIFT), where a profitable rewrite can be invisible until another rewrite exposes it, and different rule orders reach the same point along exponentially many paths.
- The structure need not be coherent until the end.
Their outer-product example has
load Anested in dᵐ,load Bin dⁿ, and the multiply in both — an impossible loop structure that is nonetheless a legal candidate, because the ordering decisions that would force a contradiction are still open. A conventional IR must pick a nesting; a decision vector can stay uncommitted. - The vector has fixed size — pleasant for feature extraction, cheap to clone (essential for tree search), trivial to serialize.
The search side is where ambition retreats, informatively. The spaces
are now 10¹¹–10²¹ candidates (they strip-mine every dimension and give
every instruction its own loop nest with point-to-point communication
between them — far more decisions than CC’17). Exact branch-and-bound
cannot close such spaces, so exploration is Monte Carlo tree search (a
Threshold-Ascent-on-Graphs bandit, following De Mesmay’s SPIRAL work),
with the CC’17 lower-bound model retained in two supporting roles:
hard-pruning children whose bound exceeds the incumbent, and biasing
Monte Carlo rollouts with probability ∝ max(T_best − bound, 0). The
guarantee quietly changes character: pruning still never discards
anything better than the incumbent — that much survives — but nobody
claims to find the optimum anymore, and the paper reports real variance
across search repetitions (“a real issue,” they admit). Results on a
Kepler GPU: 1.47× over cuBLAS on axpy, 2.42× on a skinny
matmul (256×256×32), 0.78× on square 1024³ matmul (texture memory and
register-bank tricks again out of space), and 66.7× over a naive kernel
for a strided matmul that cuBLAS simply doesn’t cover — the
“specialization beats libraries off the beaten path” lesson again.
Two experiments stand out as generalizable methodology. First, dead-end rates: random descents hit contradictions ≤14% of the time on most spaces — constraint propagation is doing its job, and they stress that the CSP is used to represent the space, not to solve for feasibility. Second, decision ordering: although decisions commute semantically, the tree built over them differs by order. Making high-impact decisions first (layout, then sizes, then dimension kinds…) lets the model prune 24× of the tree’s top levels; the reverse order manages only 1.8×, and the search ran out of memory. Commutativity doesn’t mean order doesn’t matter — it means order is a tuning knob of the search rather than a semantic straitjacket.
The comparison
| Tiramisu | Telamon (CC’17) | Beaugnon et al. 2019 | |
|---|---|---|---|
| A “schedule” is… | a program: a sequence of commands applied to a polyhedral IR | a path from root to leaf in a decision tree over one IR instance | a fully instantiated decision vector; partial vectors are first-class |
| Who decides | the user (or a later autoscheduler) | best-first branch-and-bound with admissible bounds | MCTS + lower-bound pruning; experts may pre-set decisions |
| Legality | exact polyhedral dependence analysis validates user schedules | alternatives lists + propagation keep candidates consistent by construction | CSP constraints + generated propagators; rare dead ends, backtrack |
| Phase ordering | disciplined: four layers fix the order of concern classes (order → layout → communication) | dissolved: all decisions visible from the root, taken in any order | dissolved and formalized: decisions commute |
| Performance model | none (user intuition; autotuning for numeric parameters) | analytic lower bound on partial implementations; hierarchical roofline | same model, demoted from oracle to filter and rollout bias |
| Guarantee | none claimed | optimum of the space, certainly | never prune better-than-incumbent; no optimality claim |
| Scope of space | everything the commands express: CPU, GPU, distributed, FPGA; layout; non-affine extensions | static-control linear algebra kernels on one GPU; no strip-mining in-IR | same domain, richer decisions (tiling in-space via size
choices) |
| Interpretability | the schedule is the explanation | binding inequality names the bottleneck of a whole subspace | same, demonstrated as space-design feedback |
The deepest contrast is what each system considers the primary artifact. For Tiramisu it is the transformation: the paper’s contributions are new commands (communication, memory hierarchy) and the layered IR that makes commands composable without stepping on each other. For the Telamon line it is the space: the CC’17 paper’s IR exists to make regions boundable, and the 2019 paper goes as far as saying the compiler “is not limited to a set of predefined high-level transformations — it does not even have to be aware of them”; tiling, interchange, and fusion are emergent patterns in an assignment of pairwise decisions, not operations anyone wrote.
Both are reactions to the same enemy — the phase-ordering / enabling-transformation swamp of rewrite-based compilation — and their answers are duals. Tiramisu sequences concerns so that transformations within a layer can assume later concerns are undecided (schedule before layout before communication: exactly the ordering that makes each class of decision maximally free). The Telamon line abolishes sequencing by making all decisions simultaneously visible and commutative. Notice that Tiramisu’s four layers reappear inside Telamon’s decision vocabulary as kinds of variables — dimension kinds and ordering (Layer II), memory placement and layout of temporaries (Layer III), communication placement and synchronization-inducing thread-dimension ordering (Layer IV) — but stripped of any ordering among them. And notice, conversely, that when the 2019 paper needed its search to work, the impact-ordered decision heuristic (layout first, then sizes, then kinds…) crept back in as search policy: phases return as heuristics because some decisions really are more consequential than others.
There is also an honest scaling story across the two Beaugnon papers. Admissible branch-and-bound found the true optimum of a 10⁹ space in 13 minutes; at 10²¹, with a bound that cannot be tightened without micro-architectural documentation that doesn’t exist, exact search is gone and what remains of the guarantee is a safe filter inside a statistical searcher. The residual value is real — cutting the first ten tree levels shrinks the space by 2–3 orders of magnitude, and the filter provably costs nothing — but “without regrets” as an end-to-end property did not survive contact with richer spaces. The transferable lesson: an admissible lower bound scales as a pruning filter and an explanation device, not as a complete search strategy. Meanwhile the model’s other virtue — bottleneck attribution over subspaces — scales perfectly, because it never depended on closing the search.
Aftermath, briefly. Tiramisu the system lived on (FPGA backend, sparse and recurrent workloads, and the learned-cost-model autoscheduler at MLSys 2021), and its ideas are visible wherever scheduling became an IR — most prominently MLIR’s transform dialect. Telamon was Beaugnon’s thesis and the repository is archived; but its ideas have aged remarkably well: Ansor/TVM’s sketch-then-fill search rediscovers “make coarse structural decisions first, treat the rest as a fillable subspace”; equality saturation (egg and descendants) attacks rewrite-ordering by keeping all rewrites like Telamon keeps all decisions; and every autotuner that prunes with a roofline is doing a coarse version of B(X) ≤ T(x).
Lessons for OCANNL
This section is why the deep dive exists (ahrefs/ocannl#267). OCANNL sits, by construction, between the two schools, and each paper reads like a commentary on a different piece of it.
OCANNL already has a four-layer separation — it just grew it
independently. The %op/%cd surface
plus shape inference builds a pure, memory-free algorithm over iteration
domains (Layer I; einsum specs and row variables play the role of ISL
sets, with inference instead of explicit domains). Lowering to
Low_level fixes execution order; the schedule IR’s optops
(Split, Swap, Retype,
Unroll, Partition, Stage,
Privatize, Tensorize,
Fuse_epilogue) retype loops onto hardware axes — Layer II,
as a Halide/Tiramisu-style command vocabulary applied at the
lowered_transform seam. Memory modes
(Virtual/Local/On_device),
placement decisions on the optimization context, and
Stage’s shared-tile staging are Layer III. Streams, events,
merge buffers, and explicit to_host/from_host
are Layer IV — OCANNL shares Tiramisu’s conviction that communication
must be explicit and schedulable rather than inferred-and-hidden. The
validation Tiramisu offers is that this ordering of concerns is not an
accident of our history but the ordering that keeps each phase maximally
free; the warning it offers is about what we still lack at each layer:
no affine skewing (our
Split/Swap/Partition vocabulary
is sub-polyhedral; the gh-494 affine legality engine could license
skewing when a use case appears, e.g. wavefront schedules for scan-like
patterns), no general store_in-style layout remapping (we
have exactly one layout decision today — the XOR swizzle on staged
shared tiles), and no cross-node communication scheduling (multidev CPU
exists; a Tiramisu-grade
send/recv-as-schedulable-operation story does
not).
Telamon’s central theorem is directly actionable for OCANNL’s
autotuner. OCANNL’s cost model (gh-491) computes exactly a
roofline lower bound — Cost_model.roofline_seconds,
documented “rank with it, do not predict” — and the autotuner currently
uses it as a keep-fraction pre-filter, i.e., a relative
heuristic. Telamon’s message is that a lower bound can do strictly more:
prune, with certainty, any candidate whose bound exceeds the
incumbent’s measured time. That is an absolute, no-regret
filter — it never discards a candidate that could have won — and it
composes with beam search as well as it composed with MCTS in the 2019
paper. One caveat must be engineered away first: admissibility requires
the bound to be a true lower bound, and our byte/op counts are
deliberately upper bounds (guards-taken, union-of-accesses,
non-injective fallbacks). The model already tracks exactness
(fp_approx, flops_approx,
opaque), so incumbent-pruning can be enabled where counts
are exact, or the analysis can carry dual (lower, upper) counts — lower
bounds for pruning, upper bounds for ranking. Cheap to add, and it
converts timing budget into search depth on precisely the workloads
where tuning is slowest.
The bound-as-explanation idea is worth stealing
outright. Telamon reports which inequality binds —
compute throughput, per-block contention, occupancy, dependency chain —
and uses it to find missing dimensions of the search space (texture
memory). OCANNL’s decline diagnostics
(schedule_log_declines) explain why transforms didn’t
apply; the dual diagnostic — “this candidate’s roofline arm is
bandwidth, at 78% of envelope” per tuned kernel, aggregated over the
cache — would tell us which sketch families are missing,
turning per-workload tuning logs into compiler-development signal.
Telamon’s hierarchical refinement (per-thread, per-block, per-device
bounds with occupancy) is also the natural next step beyond our
two-constant envelope when the flat roofline stops discriminating
between candidates.
The 2019 paper names a disease OCANNL has, and its cure’s
price. Optop pipelines do not commute — Stage
composes with Tensorize only in one order,
Privatize is incompatible with Tensorize,
sketch composition has cost us real bugs (index-pairing poisoning,
scope-alpha collisions). Because a beam over transform sequences is
exactly the rewrite-system regime Beaugnon criticizes, valuable
compositions are unreachable incrementally — a bare
Tensorize loses its beam round before the Grid retypes that
would justify it can join — which is why OCANNL grew sketch seeding.
It’s the same mitigation the 2019 paper endorses (“an expert programmer
can even manually set decisions upfront”), and Ansor arrived at it
independently; sketches are partial assignments by another name. The
principled cure — reformulating the schedule space as a decision vector
(per-loop kind, per-pair order, per-tnode placement) with legality
constraints supplied by the affine engine, making sketches literally
pre-constrained candidates and making all decisions commute — is a
beautiful target but a rewrite of the autotuner’s core representation;
the 2019 paper is the design document to reach for if
beam-plus-sketches hits a wall it cannot seed its way past. Their
decision-ordering experiment (24× vs 1.8× prunable, out-of-memory on the
bad order) is also a transferable warning for us today: the order in
which a beam considers transform kinds is a search knob worth measuring,
not an implementation accident.
Finally, all three papers vindicate an economic premise OCANNL is built on: specialization beats libraries off the beaten path. Telamon beats cuBLAS 4.2× at small sizes and 66.7× on strided shapes the library doesn’t cover; Tiramisu beats MKL 2.3× by fixing convolution filter sizes at compile time. A compiler that tunes per shape and per digest, cache-persistently, is playing the game these papers proved winnable — the flip side, which all three also demonstrate against cuBLAS/MKL at flagship sizes, is that the last 20–30% on the benchmark the vendor cares about lives in space dimensions (texture paths, register allocation, undocumented micro-architecture) that a portable search space doesn’t contain. Choosing not to chase that tail at v0.9 is a defensible reading of both papers’ evaluation sections.
References
- Riyadh Baghdadi, Jessica Ray, Malek Ben Romdhane, Emanuele Del Sozzo, Abdurrahman Akkas, Yunming Zhang, Patricia Suriana, Shoaib Kamil, Saman Amarasinghe. Tiramisu: A Polyhedral Compiler for Expressing Fast and Portable Code. CGO 2019. arXiv:1804.10694.
- Ulysse Beaugnon, Antoine Pouille, Marc Pouzet, Jacques Pienaar, Albert Cohen. Optimization Space Pruning without Regrets. CC 2017. DOI 10.1145/3033019.3033023.
- Ulysse Beaugnon, Basile Clément, Nicolas Tollenaere, Albert Cohen. On the Representation of Partially Specified Implementations and its Application to the Optimization of Linear Algebra Kernels on GPU. 2019. arXiv:1904.03383.
- Related lineage: Ragan-Kelley et al., Halide (PLDI 2013); Baghdadi et al., A Deep Learning Based Cost Model for Automatic Code Optimization (MLSys 2021); Zheng et al., Ansor (OSDI 2020); Tate et al., Equality Saturation (POPL 2009); Lai & Seznec, Performance Upper Bound Analysis of SGEMM (CGO 2013).