Tutorial: running LOOPer end-to-end

July 18, 2026 · View on GitHub

This walks you through optimizing a benchmark with LOOPer in both modes. If you have not yet, skim the concepts primer — it defines schedule, beam size, and the two modes.

About the numbers you will see. The exact schedules, speedups, and timings a run produces depend on your CPU, the search parameters (beam size, number of measurements, …), and which data the bundled cost model was trained on. They can differ substantially from the results reported in the LOOPer paper — those were measured on specific hardware with a model trained for it. Treat any output below as illustrative of the format, not a target to reproduce; see the paper for the reported speedups.

0. Prerequisites

  • A compiled Tiramisu master checkout with the LOOPer autoscheduler and Halide 14. $TIRAMISU_ROOT/build must contain both libtiramisu and libtiramisu_auto_scheduler.
  • The LOOPerCostModel repo available, for the model↔search interface.
  • A system C++ compiler and Python ≥ 3.9.

1. Set up the environment (once)

scripts/setup_env.sh

This creates a Python virtualenv (.venv) with the interface's dependencies (torch, numpy, sympy), autodetects your Tiramisu build and the interface, verifies the bundled weights load, and writes config.env. If autodetection is wrong, override it:

TIRAMISU_ROOT=/path/to/tiramisu \
LOOPER_INTERFACE=/path/to/LOOPerCostModel/looper_search_interface \
scripts/setup_env.sh

Everything after this reads config.env; no paths are hard-coded.

2. Model-guided run

We optimize gemm (generalized matrix multiply) at the MEDIUM problem size. Model-guided search never runs your program during the search — it asks the cost model to predict each candidate's speedup.

scripts/autoschedule.sh examples/function_gemm_MEDIUM --mode model

The script compiles the program, runs the beam search, and — because model mode never executes your program during the search — also measures the original program and the best-predicted schedule at the end, so you get a real, measured speedup for LOOPer's pick. As the search runs it prints one block per candidate; the format is:

Schedule number <n>
Tiralib schedule string <the transformations in this candidate>
Evaluation : <negated predicted speedup>
Number of measurements : 1

Evaluation is the negated predicted speedup (LOOPer minimizes, so more negative = better). At the end the log prints the initial execution time, the search time, the best evaluation, and — because we asked it to measure the winner — the best schedule's real execution time; dividing the initial time by that gives the measured speedup of LOOPer's choice. (See output.md for the full log/JSON reference.)

The search scored every candidate without running your program — each is a millisecond-scale model prediction. Section 4 contrasts this with execution-guided search.

Predicted vs. measured. The model predicts a speedup to rank candidates, not to nail the absolute number, so the measured speedup of its pick will differ. The schedule LOOPer selects is deterministic, but its measured time varies from run to run (good schedules often parallelize, and parallel timing shifts with machine load). Treat measured speedups as indicative.

3. Execution-guided run

Same benchmark, but now every candidate is actually compiled and run; the search is guided by ground-truth measurements. This needs the target hardware and is much slower per candidate.

scripts/autoschedule.sh examples/function_gemm_MEDIUM --mode execution

In --mode execution, Evaluation and Best evaluation are measured times in seconds (not negated speedups), because the search is measuring directly. Expect this run to take far longer than the model-guided one — it compiles and runs every candidate several times.

4. Model vs. execution — the trade-off

Model-guidedExecution-guided
How a candidate is scoredcost-model prediction (a few ms)compiled and run (≈ program runtime × repeats)
Needs the target machine during search?NoYes
Search costcheap, roughly constant per candidategrows with how long the program runs
Rolethe deployable approximationthe ground-truth upper bound
  1. Model-guided is far cheaper to search — no program executions, a few milliseconds per candidate, and a per-candidate cost that does not grow with how long the program takes to run. This is what lets you optimize without access to the target machine and without waiting on real runs.
  2. Execution-guided is the quality yardstick — what the search could achieve with a perfect evaluator, at the cost of running every candidate. Model-guided may pick a worse schedule, especially when the bundled weights were trained for a different microarchitecture than yours; the paper notes that porting to very different hardware calls for regenerating data and fine-tuning (see LOOPerCostModel). When model-guided disappoints on your hardware, execution-guided gives ground truth, and fine-tuning restores the model's quality.

For the aggregate speedups LOOPer achieves across PolyBench (geometric mean over Pluto and over the previous Tiramisu autoscheduler), see the paper.

5. A fast warm-up: gemm_MINI

function_gemm_MINI is a good first check that the whole pipeline works:

scripts/autoschedule.sh examples/function_gemm_MINI --mode model
scripts/autoschedule.sh examples/function_gemm_MINI --mode execution

It is tiny — the program runs in a fraction of a millisecond, so there is almost nothing to optimize and any "speedup" is dominated by measurement noise. It validates the toolchain, not the optimizer; use MEDIUM (above) for a meaningful result.

6. Other shipped examples & your own program

Two more examples ship alongside gemm, chosen to exercise different program shapes:

  • function_jacobi1d_MEDIUM — a 1-D stencil, structurally different from gemm's BLAS kernel. Its stencil pattern admits transformations like skewing and interchange, so the search explores a richer set of candidates:
    scripts/autoschedule.sh examples/function_jacobi1d_MEDIUM --mode model
    
  • function_gramschmidt_LARGE — Gram-Schmidt orthogonalization, a larger kernel with several loop nests (multiple computations) and non-rectangular iteration domains — both program classes LOOPer added over the earlier Tiramisu autoscheduler. The multi-nest structure gives the search a rich set of candidates to explore:
    scripts/autoschedule.sh examples/function_gramschmidt_LARGE --mode model
    

For more kernels, Tiramisu ships roughly 30 PolyBench benchmarks in this autoscheduler-ready format under $TIRAMISU_ROOT/benchmarks/autoscheduler_benchmarks/polybench/<SIZE>/ (also available from polybench-tiramisu). Copy a benchmark directory into examples/ and run it the same way.

Model-guided results vary by kernel and hardware. With the bundled weights, some kernels see a clear model-guided speedup and others little or none on a given machine — the model may even pick a schedule slightly slower than the original, because its rankings were learned on different hardware. That is expected; use --mode execution for ground truth, or fine-tune the cost model for your machine.

To optimize your own Tiramisu program, see bring-your-own.md.

7. Where results land

Each run creates runs/<func>_<mode>_<timestamp>/ with:

  • search_output.txt — the full human-readable log.
  • <func>_explored_schedules.json — the exploration trace, every candidate, and the best_schedule (string + measured time).

output.md explains how to read them, including a copy-paste snippet that prints the speedup and best schedule from the JSON.