Fusion, layout, scheduling, and memory planning
Connect algebra, fusion, layout, schedule, and buffer planning to device work and memory traffic
Before you begin
Follow a linked prerequisite when you cannot explain it from memory. Background skills without links are stated as abilities rather than hidden terminology.
- Fusion economics
- IR and lowering
- Tensor lifetimes
A compiled transformer subgraph initially launches matrix multiplication, bias addition, GELU, and a layout conversion. After optimization, a graph dump may show the bias and activation inside a matrix epilogue and no separate layout conversion. Nsight Systems should then show fewer launches, while Nsight Compute should report fewer device-memory bytes. A second shape can regress if the fused schedule spills registers or replaces a faster library matrix kernel.
A compiler optimization is a semantics-preserving program rewrite chosen to improve a declared cost such as time, memory, or code size. Fusion, layout assignment, schedule selection, buffer reuse, and recomputation change different physical costs. The first empirical test compares the before-and-after IR with launches, memory traffic, register use, and elapsed time so the rewrite remains connected to real execution.
Compiler optimization for tensor programs is a joint problem. Algebra decides what work is required; layout decides where values sit; schedule decides when and by whom work occurs; memory planning decides which storage can be reused. Improving one in isolation can harm another. Fusing operators saves traffic but may increase live memory; changing layout unlocks matrix instructions but creates conversions; a larger tile improves reuse but reduces occupancy.
The compiler must evaluate the expected performance of each valid rewrite. Static models estimate FLOPs, bytes, launch cost, and target resources. Autotuning measures candidate schedules. Profile-guided information supplies observed shapes and frequencies. Performance analysis must identify cost-model errors and constraints that the compiler could not infer.
Separate legality from profitability. A rewrite is legal when the rewrite preserves the declared semantics. A rewrite is profitable when resource use and execution cost improve the selected objective on the target workload. Compilers need both decisions, and a legal rewrite can still create a severe performance regression.
Algebraic simplification and common expressions
Continue with the running graph `y = GELU(x @ w + bias)`. Suppose shape code also computes `hidden = 2048 * 2`, the graph contains `transpose(transpose(w))`, and an earlier diagnostic branch produces a tensor that no output reads. Before scheduling a GPU kernel, the compiler can simplify these concrete pieces while leaving the matrix multiplication, bias, and GELU behavior unchanged.
The inputs `2048` and `2` are known while the program is compiled, so the compiler evaluates the multiplication once and replaces it with `4096`. This rewrite is constant folding. The runtime no longer needs an operation to calculate the hidden dimension, and the known value can make a fixed tile or vector width legal.
Applying a transpose twice with the same two-dimensional permutation returns the original logical layout. Replacing `transpose(transpose(w))` with `w` is an algebraic simplification. Removing the pair can also prevent a later physical layout conversion. The rewrite is legal only when the transpose operations have the assumed semantics and no intervening mutation changes the value.
If two branches independently compute the same pure expression, such as `shape_of(x)[1]`, the compiler can retain one result and direct both readers to it. This rewrite is common-subexpression elimination. The compiler cannot make that substitution merely because two calls have the same printed arguments when a call reads mutable state, produces randomness, or has another side effect.
The unused diagnostic tensor has no reader and does not affect an output, mutation, exception, or other observable behavior, so dead-code elimination removes its producing operations. Constant folding and removal of the transpose pair change later opportunities: the constant `4096` can expose a static tile, and the original weight layout can now remain available for fusion. Floating-point reassociation is more restricted because changing the order of additions can change rounding; the declared numerical mode determines whether such a rewrite is permitted.
Fusion and layout propagation
Producer-consumer fusion computes a value near the operation that consumes it, which can remove an HBM intermediate. Sibling fusion lets two outputs share a common input traversal. A reduction, transpose, or multiple-consumer graph can impose incompatible iteration spaces. Resource estimates must account for all live values in the fused schedule.
Layout propagation selects physical representations that serve a chain of operations. A layout suited to a matrix instruction may differ from one suited to a following reduction. Inserting a conversion has a cost, but forcing one layout through the complete region can make several operations inefficient. The compiler must compare these alternatives with the full consumer graph.
Schedule selection
A schedule chooses tile sizes, loop order, parallel dimensions, vector widths, pipeline stages, and memory placement. The schedule parameters determine reuse, coalescing, instruction eligibility, register count, shared-memory allocation, and available parallel blocks. Two programs with identical arithmetic can differ substantially because of their schedules.
The schedule space is too large for exhaustive search on every input. Heuristics remove unlikely candidates. Analytical cost models rank candidates from estimated work, bytes, and resources. Autotuning measures a bounded set. A model trained or tuned on square matrices can rank narrow decode shapes poorly, so workload coverage and cache keys are part of schedule-system correctness.
Memory planning and rematerialization
Buffer planning uses value lifetimes and alias rules to assign storage. Values whose lifetimes do not overlap can reuse the same buffer. In-place updates can remove allocations only when no later operation needs the original value and aliasing permits mutation. Reuse can reduce peak memory but can also add dependencies that limit execution overlap.
Rematerialization discards a value and computes the value again near a later use. Rematerialization increases arithmetic but can reduce saved-tensor storage, high-bandwidth-memory traffic, or live range. The rewrite is profitable when the recomputed operation is inexpensive relative to the storage and movement avoided. FlashAttention backward is a concrete large-scale example: the backward pass recomputes score blocks instead of saving the complete probability matrix.
Specialization and compilation cost
Known shapes and alignments permit unrolling, fixed tiles, vector operations, and removal of bounds checks. A dynamic implementation covers more inputs but may retain predicates and conservative schedules. Guarded variants combine these approaches by specializing important cases and using a general fallback.
Every variant consumes compilation time, tuning time, code-cache memory, and maintenance attention. Include these costs when shapes are rare or processes are short lived. Profile-guided frequency can concentrate specialization on shapes that contribute most time or latency. Do not assess an optimization pipeline only from warmed execution of one compiled graph.
Worked example: Performance regression from a valid fusion
A compiler fuses a matrix operation, normalization, and several elementwise consumers into one generated kernel, but the result is slower.
- Inspect resource estimates and generated code. Keeping matrix accumulators plus normalization statistics and consumer temporaries live causes register spills.
- Compare the fused register count with the separate kernels and inspect local-memory transactions. A spill is an additional memory path, not only a lower occupancy number.
- The monolithic schedule may also prevent use of a highly tuned library GEMM. Saved intermediate traffic is smaller than lost matrix throughput and spill traffic.
- Try a boundary after the matrix epilogue or normalization. Materialize one compact tensor, then fuse the bandwidth-bound tail.
- Compare end-to-end launches, HBM bytes, matrix throughput, spills, and compile time. The profitable partition balances kernel quality with traffic savings.
- Repeat across shape families. A fusion boundary that is profitable for large K can be inferior for a narrow matrix where epilogue state dominates.
Conclusion: Legality asks whether operations can be fused. Profitability asks whether their resource and schedule requirements belong together.
Common errors
- Treating fusion count as an optimization score. A smaller number of launches can contain much worse kernels.
- Ignoring compile and tuning latency. Dynamic workloads may pay those costs repeatedly.
- Expecting one cost model to predict all hardware and shapes. Measurements and feedback are part of compiler-system design.
- Using buffer reuse without considering the new dependency. Sharing storage can prevent two otherwise independent operations from overlapping.
Reference summary
- Algebraic simplification and constant folding remove work proven unnecessary.
- Common-subexpression elimination shares equivalent computation when side effects permit.
- Fusion removes materialization and launch overhead while respecting resource models.
- Layout assignment chooses physical ordering compatible with consumers and libraries.
- Buffer reuse and scheduling reduce peak memory from non-overlapping lifetimes.
- Rematerialization recomputes values to shorten live ranges or reduce storage.
Compiler optimization is global resource allocation under uncertainty. Exact shapes enable aggressive specialization; dynamic workloads require robust code or guarded variants. Autotuners measure schedules that analytical models cannot rank precisely, but need bounded search spaces and representative inputs.
Knowledge check
How can rematerialization simultaneously reduce memory and increase speed? Give a condition where the saved traffic matters more than extra arithmetic.
Show the answer guide
- Rematerialization discards an intermediate and recomputes it near each use. Peak memory falls because the intermediate does not remain live or occupy an HBM allocation between producer and consumer.
- Speed can increase when retaining the intermediate would require an HBM write and later read, a spill caused by register pressure, or a synchronization and allocation on the critical path. Recomputing a few inexpensive operations can cost less than those transfers.
- The favorable condition is that saved bytes divided by sustainable bandwidth, plus removed allocation or synchronization time, exceeds the latency of the added arithmetic without creating a new compute or occupancy bottleneck.
- For example, recomputing GELU inputs or attention probabilities can be beneficial on a bandwidth-bound long-sequence workload, while recomputing an expensive matrix multiplication on a compute-bound workload is unlikely to be beneficial.
Measure the memory-speed trade of rematerialization
Construct an FP16 program on a [8192,8192] tensor that computes y = GELU(x) and uses y in two later elementwise expressions. Compare a version that stores y in an explicit intermediate with a fused or checkpointed version that recomputes GELU at each use. Add a second experiment in which the recomputed producer is a matrix multiplication to show an unfavorable case.
Evidence to keep: Submit both program variants and correctness tests, profiler reports with peak memory, HBM bytes, arithmetic instructions, and latency, and a two-row comparison table for the cheap and expensive producers. Give a causal conclusion that applies the measured saved-transfer time and added-compute time.
When compiled performance is below the objective, inspect graph capture, transformations, schedules, generated code, and runtime execution in that order.
Sources and further reading
The lesson explanations synthesize primary papers and official references. Follow the originals for proofs, exact APIs, architecture details, and version-sensitive behavior.