<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Compiler Optimization | 木叶吟</title><link>https://yezhisheng.me/tag/compiler-optimization/</link><atom:link href="https://yezhisheng.me/tag/compiler-optimization/index.xml" rel="self" type="application/rss+xml"/><description>Compiler Optimization</description><generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><copyright> 又拍云提供CDN服务
京ICP备16021535号-1</copyright><lastBuildDate>Mon, 18 May 2026 12:00:00 +0800</lastBuildDate><image><url>https://yezhisheng.me/media/icon_hu585778a5d9441f07b7d64e1beae1be58_320895_512x512_fill_lanczos_center_3.png</url><title>Compiler Optimization</title><link>https://yezhisheng.me/tag/compiler-optimization/</link></image><item><title>Helix: Automating Communication-Computation Overlap with Graph Scheduling</title><link>https://yezhisheng.me/post/helix/</link><pubDate>Mon, 18 May 2026 12:00:00 +0800</pubDate><guid>https://yezhisheng.me/post/helix/</guid><description>&lt;p>Large models are rarely trained or served with one clean parallelism strategy. Tensor parallelism splits matrix operations. Pipeline parallelism splits layers. Sequence parallelism stretches context length across devices. Expert parallelism routes tokens through distributed experts. Real deployments increasingly compose several of these dimensions at once.&lt;/p>
&lt;p>That composition is powerful, but it creates a familiar systems tax: communication bubbles.
When an AllReduce, AllGather, ReduceScatter, or All-to-All sits on the critical path, GPU compute units wait. In a dense tensor-parallel block, the waiting may come from sharded matrix results. In a long-context sequence-parallel block, it may come from exchanging sequence chunks. In a MoE layer, it may come from routing tokens across experts. The parallel strategy changes, but the shape of the problem is the same: computation and communication are both present, yet the execution graph does not expose enough safe overlap.&lt;/p>
&lt;p>Helix is built around one idea: communication-computation overlap should be a graph scheduling problem, not a hand-written kernel trick for each new parallel pattern. Helix is currently a WIP research project starts from early 2026. Please check back for updates.&lt;/p>
&lt;h2 id="why-manual-overlap-does-not-scale">Why Manual Overlap Does Not Scale&lt;/h2>
&lt;p>The fastest overlap techniques often go deep into kernels. They split an operation into small pieces, launch communication early, and fuse enough computation around it to hide latency. This can work extremely well for one pattern. Ring-style attention can overlap sequence exchange with local attention blocks. Tensor-parallel kernels can pipeline collectives with partial matrix multiplications. MoE systems can schedule expert computation around token dispatch.&lt;/p>
&lt;p>The problem is that each of these optimizations tends to encode assumptions about the model, the collective, the tiling shape, and the synchronization protocol. Once the model architecture changes, or a deployment combines tensor, sequence, and expert parallelism, the optimization becomes harder to reuse. A local trick may also miss a global opportunity: a communication operation produced by one parallel dimension might be hidden under computation from another dimension, but a pattern-specific optimizer will not necessarily see that.&lt;/p>
&lt;p>Helix moves the optimization boundary up to the compiled execution graph. After &lt;code>torch.compile&lt;/code> captures the model-parallel program, Helix sees compute operators, communication operators, waits, and dependency edges in one intermediate representation. That unified graph is the key abstraction. The compiler no longer needs a separate overlap recipe for every parallel strategy; it can schedule visible compute and communication nodes under the same correctness rules.&lt;/p>
&lt;h2 id="the-scheduling-objective">The Scheduling Objective&lt;/h2>
&lt;p>At a high level, Helix treats the model-parallel program as a directed graph. Nodes are compute or communication operators. Edges are precedence constraints: an operator can run only after the values it depends on are ready.&lt;/p>
&lt;p>The optimization goal is straightforward but constrained:&lt;/p>
&lt;ul>
&lt;li>reduce the graph makespan by hiding communication under independent computation;&lt;/li>
&lt;li>preserve every data dependency in the original graph;&lt;/li>
&lt;li>keep peak memory below the available device budget.&lt;/li>
&lt;/ul>
&lt;p>That last constraint matters. Aggressive overlap is not free. If the compiler launches too much work early, intermediate activations and communication buffers live longer. A schedule that looks faster on the timeline can become unusable because it inflates peak memory. Helix therefore optimizes both time and memory, guided by a lightweight graph simulator.&lt;/p>
&lt;p>The system uses three compiler passes: tiling, reordering, and bucketing.&lt;/p>
&lt;h2 id="tiling-create-overlap-opportunities">Tiling: Create Overlap Opportunities&lt;/h2>
&lt;p>The original execution graph is often too coarse. A large compute operator may wait for a large communication operator, even though smaller chunks of the work could have been interleaved. Helix first applies graph tiling: it partitions operators into multiple tile streams while preserving the dependency structure inside each stream.&lt;/p>
&lt;p>By default, the paper tiles along the batch dimension because it is broadly applicable and easy to reason about. Other dimensions, such as sequence length, can also be used when correctness is guaranteed for that region of the graph.&lt;/p>
&lt;p>Tiling has two benefits. First, it exposes overlap. Communication from one tile stream can be launched while computation from another tile stream is still running. This turns one rigid graph into several smaller streams that can be woven together. Second, it can reduce activation memory. Smaller tiles mean smaller live inputs and intermediate tensors, so the peak memory footprint can drop when lifetimes are well controlled.&lt;/p>
&lt;p>But tiling also has a cost. Smaller compute kernels can lose efficiency, especially for memory-bound operators such as normalization, softmax, and pointwise functions. Smaller communication messages can also lose effective bandwidth. The paper&amp;rsquo;s profiling shows that a small tiling factor is usually the practical choice; Helix uses &lt;code>K = 2&lt;/code> by default because it exposes useful overlap without creating excessive fragmentation.&lt;/p>
&lt;h2 id="reordering-make-overlap-safe">Reordering: Make Overlap Safe&lt;/h2>
&lt;p>After tiling, the compiler has several independent tile streams. The next question is launch order.&lt;/p>
&lt;p>A naive schedule would simply execute the streams one by one. That preserves correctness, but it leaves communication bubbles exposed. An overly aggressive schedule would launch many asynchronous operations early, which may improve overlap but keep too many tensors alive and push peak memory upward.&lt;/p>
&lt;p>Helix uses Segmented Round-Robin Reordering to sit between those extremes. The key observation is that explicit wait operators are natural segment boundaries. Within a tile stream, Helix groups contiguous non-blocking compute and communication operators into a segment until it reaches a wait. It then schedules segments across streams in a round-robin style. Communication from one stream can be injected into the compute-heavy region of another stream, but waits still force the graph to respect the original data dependencies.&lt;/p>
&lt;p>This segment-level granularity is important. It is coarse enough to avoid the memory explosion of operator-by-operator eager scheduling, because segments are flushed at synchronization boundaries and their intermediates can be released. It is also fine enough to move communication earlier than the original graph would allow under strict serial execution.&lt;/p>
&lt;p>In practice, this is where Helix gets much of its generality. The scheduler does not need to know that a node came from tensor parallelism, sequence parallelism, or expert parallelism. If the node is visible in the graph and its dependencies are explicit, the reordering pass can reason about it.&lt;/p>
&lt;h2 id="bucketing-recover-kernel-efficiency">Bucketing: Recover Kernel Efficiency&lt;/h2>
&lt;p>Tiling creates flexibility, but too much fragmentation hurts hardware efficiency. The bucketing pass repairs that damage selectively.&lt;/p>
&lt;p>The idea is to merge compatible operators across tile streams back into larger buckets when doing so improves end-to-end performance. This sounds simple, but it creates a trade-off. Bucketing can reduce kernel-launch overhead and improve compute or communication efficiency. At the same time, it may reintroduce synchronization, reduce scheduling freedom, and extend tensor lifetimes by moving some work earlier.&lt;/p>
&lt;p>Helix treats bucketing as a constrained search. For a candidate merge, the graph simulator estimates two quantities: the new makespan and the new peak memory. A merge is useful only if the saved time is worth the additional memory cost and does not destroy the overlap created by tiling and reordering. The implementation uses dynamic programming over candidate buckets, choosing the set of merges that gives the best schedule under the memory budget.&lt;/p>
&lt;p>This pass is the reason Helix is not just &amp;ldquo;split everything and hope.&amp;rdquo; It deliberately creates overlap granularity, then fuses back the pieces that should not remain separate.&lt;/p>
&lt;h2 id="the-simulator-is-the-control-loop">The Simulator Is the Control Loop&lt;/h2>
&lt;p>The graph simulator is small but central. It runs at compile time and estimates both runtime and peak memory for candidate schedules. For compute and communication cost, it combines graph-visible operator semantics, tensor shapes, analytical modeling, and automated benchmarking. For memory, it simulates execution order and tracks the lifetimes of tensors and communication buffers.&lt;/p>
&lt;p>The simulator does not have to be perfect to be useful. It needs to rank scheduling choices well enough that the compiler avoids obviously bad trade-offs. The paper reports close agreement between estimated and real traces across GPT-3, LLaMA3, and Qwen3-MoE configurations. For example, on a GPT-3 Curie setup with &lt;code>TP=2&lt;/code> and &lt;code>SP=4&lt;/code>, the estimated runtime is 6.80 seconds versus 6.41 seconds measured, and the estimated peak memory is 65.9 GiB versus 66.0 GiB measured.&lt;/p>
&lt;p>That fidelity matters because the optimizer is making decisions before the real run. Without a simulator, the compiler would either need expensive trial execution or rely on brittle heuristics.&lt;/p>
&lt;h2 id="what-it-buys">What It Buys&lt;/h2>
&lt;p>Across GPT-3, LLaMA3, and Qwen3-MoE workloads, Helix shows the same pattern: once communication is exposed to graph scheduling, bubbles shrink and useful GPU work rises. End-to-end training throughput improves by 4% to 9% within a node, and by 12% to 30% when communication crosses nodes. At the layer level, communication bubbles are often reduced by more than 60%, which is the direct evidence that the scheduler is hiding communication rather than merely shifting overhead around.&lt;/p>
&lt;p>The memory result is also important. In long-context inference, Helix reduces activation memory by up to 30%, lowering peak memory from 23.5 GiB to 21.4 GiB in the measured trace. This comes from the same design principle as the performance gain: the graph scheduler controls when tiles become live and when their intermediates can be released, instead of letting overlap inflate memory lifetime accidentally.&lt;/p>
&lt;p>Helix also compares favorably with hand-tuned tensor-parallel overlap. On large GPT and LLaMA training runs, it reaches 17% and 16% speedups over the baseline, while AsyncTP reports 12% and 13% in the same comparison. The point is not that compiler scheduling makes specialized kernels obsolete. The point is that a graph-level optimizer can find cross-dimensional overlap while keeping correctness, synchronization, kernel efficiency, and memory lifetime in one place.&lt;/p>
&lt;p>That is the technical core of Helix: make communication visible, make dependencies explicit, and let the compiler schedule the overlap that manual implementations would otherwise have to rediscover for each workload.&lt;/p></description></item></channel></rss>