Scientists need supercomputers but can't write mapper code. This paper uses LLMs + a DSL + structured profiling feedback to automatically optimize parallel program mappings, beating expert-tuned code by up to 1.34x and OpenTuner by 3.8x.
You're a physicist simulating combustion chemistry. Your simulation runs on a supercomputer with hundreds of GPUs, dozens of CPU cores, and multiple tiers of memory. The physics code is done. It works. But it runs 10x slower than it should because your tasks are landing on the wrong processors and your data is sitting in the wrong memory.
The bottleneck isn't the algorithm. It's the mapper — a piece of system code that decides which task runs on which processor and which data lives in which memory. Getting this right can yield an order-of-magnitude speedup. Getting it wrong means your simulation takes days instead of hours.
Why is this so hard? Three reasons compound:
Each task must be assigned to a processor, each data region to a memory, and each memory layout configured. Click "Randomize" to see how different mappings affect simulated throughput.
Traditional auto-tuning approaches like OpenTuner treat this as a black-box optimization: try a mapping, measure the throughput, try another. But with a search space this large, scalar feedback is almost useless. After 1000 iterations, OpenTuner is still 3.8x slower than the approach in this paper using just 10 iterations.
Here is the central idea: give an LLM a high-level DSL to write mappers in, run the mapper, and feed back structured profiling data — not just a scalar throughput number — so the LLM knows what to fix.
This is the Agent-System Interface (ASI): an abstraction layer between the LLM agent and the parallel runtime system. It has two parts:
The combination is powerful for a surprising reason. The DSL is a brand-new language with zero examples in any LLM's training data. C++ has millions of examples. Yet LLMs generate correct DSL mappers at an 80% success rate on the first try, versus 0% for C++. The abstraction makes the task tractable even without prior exposure.
The full system runs for just 10 iterations. In 10 minutes per application, it finds mappers that surpass expert-written mappers by up to 1.34x — the same mappers that took human experts days to write.
Before we can optimize mappers, we need to understand what decisions they encode. A mapper is a piece of code that makes three kinds of assignments for every task and data region in a parallel program:
Each task can run on a GPU, a CPU, or the OpenMP runtime. This isn't a one-size-fits-all decision. Small tasks may prefer CPUs because GPU kernel launch overhead dominates. Tasks with huge memory footprints may need CPUs when GPU memory is insufficient. Compute-dense tasks belong on GPUs. The right choice depends on the task's specific characteristics.
Each data region must live somewhere in the memory hierarchy. The options for GPU-targeted tasks include:
Each choice trades off speed, capacity, and data transfer cost. And this decision is per-task, per-argument — the same data region might need to be in different places for different tasks.
How data is arranged in memory affects cache efficiency dramatically:
When a parallel for-loop launches thousands of tasks over partitioned data, an index mapping function determines which partition runs on which processor. Think of it as a function from a task index (e.g., grid point [i, j]) to a processor index (e.g., GPU 3 on node 2). A bad mapping means data shuffles between GPUs on every iteration. A good mapping keeps communication local.
This is why expert-written mappers represent the gold standard. A systems researcher who understands both the application and the machine can make informed choices about all these dimensions simultaneously. The question this paper asks: can an LLM match that expertise?
This is the core contribution. The Agent-System Interface (ASI) sits between the LLM and the Legion parallel runtime, making it possible for the LLM to generate and iterate on mapper code.
Here's what a mapper looks like in the original C++ API versus the DSL. The same mapping — running task0 on GPUs, placing ghost regions in ZeroCopy memory, using C-order SOA layout with 64-byte alignment, and distributing tasks cyclically across GPUs:
The DSL expresses the same mapping decisions in a fraction of the code. Click "Toggle" to compare. The highlighted lines in the DSL correspond to 126+ lines of C++ system code.
The DSL has four statement types, each controlling one dimension of the mapping decision:
# 1. Processor selection: which processor type runs this task Task task0 GPU; # 2. Memory placement: where this data lives Region * ghost_region GPU ZCMEM # 3. Memory layout: how data is arranged Layout * * * C_order SOA Align==64 # 4. Index mapping: custom distribution function def cyclic(Task task): ip = task.ipoint; mgpu = Machine(GPU); node_idx = ip[0] % mgpu.size[0]; gpu_idx = ip[0] % mgpu.size[1]; return mgpu[node_idx, gpu_idx]; IndexTaskMap task4 cyclic
Each statement is independent. The processor choice for task0 doesn't constrain the memory choice for task1. This separation of concerns is the key design principle: it eliminates the tangled dependencies in C++ mapper code and gives the LLM independent knobs to turn.
Behind the scenes, a compiler translates each DSL statement into the low-level C++ system API calls that the Legion runtime actually executes. The LLM never sees the C++. It writes DSL, the compiler handles the rest.
Layout * * * Align==64 F_order is tiny. The gap between that same English description and the C++ implementation — which requires a sequence of API calls to enforce alignment and ordering constraints — is enormous. The DSL collapses that gap.The optimization process is iterative: the LLM proposes a mapper, runs it, reads the feedback, and proposes a better one. Here's how it works step by step.
The LLM agent receives two things before it starts:
These define the search space. The agent knows what processors exist and what tasks need mapping.
The agent doesn't generate the entire mapper as one monolithic block. Instead, the task is decomposed into independent segments — one for processor selection, one for memory placement, one for layout, one for index mapping. The agent decides what to generate for each segment separately.
The agent is implemented in the Trace framework, which treats each code segment as an independent optimization variable. This aligns with the DSL's design: each decision dimension is a separate knob the optimizer can turn.
The entire process takes 10 iterations and completes in about 10 minutes per application. For context, expert mapper development takes days.
This is where the paper diverges most sharply from traditional auto-tuning. Instead of just feeding back a throughput number, AutoGuide interprets raw execution output into three layers of feedback.
The runtime produces raw output: either an error message (if the mapper crashed) or performance metrics (execution time, throughput). This is what OpenTuner sees. It's almost useless for guiding the next iteration.
AutoGuide pattern-matches on the raw output to explain what went wrong. Examples:
| Raw Output | Explanation |
|---|---|
Assertion failed: stride does not match expected value | Memory layout is unexpected — the data arrangement doesn't match what the task expects. |
Invalid mapper output from map_task: GPU has no affinity | Task was assigned to a GPU that can't access the required data — the memory placement is incompatible with the processor selection. |
Execution time: 0.03s | (No error to explain — passes through to suggestion layer.) |
Based on the explanation, AutoGuide suggests specific mapper modifications:
| Explanation | Suggestion |
|---|---|
| Memory layout mismatch | Adjust layout constraints or move tasks to different processor types. |
| GPU has no affinity | Change memory placement to FrameBuffer or ZeroCopy for the affected region. |
| Execution time: 0.03s (but slow) | Move more tasks to GPU to reduce execution time. |
The authors tested four feedback configurations on three benchmarks:
The system is evaluated on 9 benchmarks: 3 scientific computing workloads (Circuit, Stencil, PENNANT) and 6 parallel matrix multiplication algorithms (Cannon's, SUMMA, PUMMA, Johnson's, Solomonik's, COSMA). Hardware: 2 Intel 10-core CPUs, 256 GB memory, 4 NVIDIA Tesla P100 GPUs. LLM: GPT-4o.
The expert-written mappers are the gold standard — created by domain scientists who spent years mastering both the application and the programming framework. The best mappers found by the LLM optimizer match or surpass every single expert mapper:
Throughput normalized to expert-written mappers (1.0 = expert level). Values above 1.0 mean the LLM found a faster mapping. Click benchmarks to highlight.
The largest gain is on Circuit: 1.34x speedup over the expert mapper. The improvement comes from a subtle memory placement decision — the LLM placed two data collections in GPU FrameBuffer instead of ZeroCopy memory. This trades slightly higher inter-GPU communication for much faster memory access, a net win.
OpenTuner uses reinforcement learning with scalar throughput feedback. The comparison is stark:
Random mappers sample uniformly from the DSL-defined search space. Out of 90 random mappers (10 per benchmark), 74 (82.2%) crash with runtime errors. The ones that survive perform poorly. This confirms that the search space is hostile — most of it is invalid — and any effective method must navigate it intelligently.
What does the LLM actually change when it optimizes a mapper? The case studies reveal specific, interpretable optimization patterns.
The expert mapper placed two data collections in GPU ZeroCopy memory (shared CPU-GPU, no explicit transfer). The LLM discovered that moving them to GPU FrameBuffer (dedicated GPU memory, fastest access) yields 1.34x speedup. Why?
The LLM found this through iteration: it tried FrameBuffer, saw the speedup in the profiling data, and kept it. A human expert chose ZeroCopy as the "safe" default. The LLM wasn't biased by convention.
The expert mapper for COSMA (a matrix multiplication algorithm) achieved 1.31x less throughput than the LLM's mapper. The difference was in the index mapping function — how matrix subblocks are distributed across GPUs. The LLM discovered a distribution that reduces inter-GPU communication by keeping related subblocks on the same GPU.
The authors tested LLM code generation across 10 natural-language mapping strategies:
| Setting | C++ Success | DSL Success |
|---|---|---|
| Single trial | 0% | 80% |
| Iterative refinement (10 tries) | 0% | 100% |
The DSL isn't just "simpler C++." It embodies specific design principles that make it suitable as an LLM-agent interface. Understanding these principles is important because they generalize: if you want an LLM to optimize any complex system, the interface design matters as much as the LLM itself.
The DSL says what to achieve, not how to implement it. Task task0 GPU means "run task0 on a GPU." The compiler handles the 50+ lines of C++ needed to actually implement GPU task scheduling. This means the LLM's output is a specification, and correctness is the compiler's responsibility.
Each DSL statement controls one dimension independently. Processor selection doesn't affect memory placement syntax. Layout decisions don't constrain index mapping. This orthogonality means the LLM can reason about each dimension separately and iterate on one without destabilizing others.
The * wildcard applies a decision to all matching entities. Region * ghost_region GPU ZCMEM applies to all tasks using ghost_region. Layout * * * applies to all tasks, all data, all processors. This gives the LLM a coarse-to-fine control strategy: start with broad defaults, then specialize.
Each statement has a finite set of valid options. Processor selection: {CPU, GPU, OMP}. Memory type: {FBMEM, ZCMEM, SYSMEM}. Layout order: {C_order, F_order}. Layout type: {SOA, AOS}. The DSL doesn't allow arbitrary code for these decisions — it constrains the LLM to valid choices. Only index mapping functions allow arbitrary arithmetic.
The DSL is restricted for most decisions (finite option sets) but expressive for index mapping (arbitrary functions). This matches the problem structure: processor and memory choices are categorical decisions, but the best index mapping might require custom arithmetic. The def statement lets the LLM write real computation:
def block_cyclic(Task task): ip = task.ipoint; mgpu = Machine(GPU); # Block-cyclic distribution: # group 4 consecutive indices, then cycle block = ip[0] / 4; gpu = block % mgpu.size[1]; node = (block / mgpu.size[1]) % mgpu.size[0]; return mgpu[node, gpu];
This paper sits at the intersection of several active research threads. Each connection reveals a broader pattern about how LLMs can improve systems.
The core technique — LLM proposes code, gets rich feedback, iterates — appears in several recent systems:
The idea of creating a domain-specific interface for LLM agents appears across domains:
| Aspect | This Paper |
|---|---|
| Target | Parallel program mappers (Legion) |
| LLM | GPT-4o |
| Interface | Custom DSL (14x code reduction) |
| Feedback | AutoGuide: Explain + Suggest |
| Iterations | 10 (vs 1000 for OpenTuner) |
| Best speedup | 1.34x over expert mappers |
| vs RL baseline | 3.8x faster (at 10 vs 1000 iters) |
| Code gen success | DSL: 80% first try, 100% iterative |
| Time savings | Days to minutes |