LAB / GPU PROGRAMMING

DAY 23 / 30 · TRITON & MODERN KERNEL AUTHORING · ~12 MIN READ

Kernel fusion

Why frameworks fuse elementwise ops, and what it saves in memory traffic.

01 · The elementwise-chain tax

Every intermediate tensor is a full trip through DRAM

Consider the most common shape in a transformer's feed-forward path — a chain of elementwise ops: y = gelu(x) * scale + residual. Eager mode runs three kernels. The GELU reads x and writes a temporary. The multiply reads the temporary and writes another. The add reads that plus the residual and writes y. Count it in tensor-sized units: 2 + 2 + 3 = 7 tensor-trips through DRAM for math that, done in registers, needs 3 — read x, read residual, write y.

Nothing about the arithmetic is hard — GELU is a handful of FLOPs. The cost is that each intermediate tensor exists: 100M FP32 elements is 400 MB, and day 10's bandwidth charges ~0.8 ms for every 400 MB round trip at 504 GB/s. Fusion — doing the whole chain between one load and one store — deletes the round trips and, with them, the intermediates' VRAM footprint too (the same accounting as the VRAM budgeting playbook on this site).

FUSION CALCULATOR · RTX 4070 · 504 GB/S DRAM
OPSEXECUTIONTENSOR

BYTES MOVED

1400.0 MB

TIME AT 504 GB/S

2.8 ms

FUSION SPEEDUP

2.33×

KERNELS

3

gelu → scale → residual-add in one kernel: 7 tensor-trips become 3.

simplified: assumes perfectly memory-bound execution, cold L2, and ignores ~5–10 µs launch overhead per kernel — at small tensors, launches dominate and fusion saves even more than the byte count suggests.

02 · Why frameworks fuse

torch.compile is a fusion engine with a compiler attached

This is the load-bearing reason torch.compile and XLA speed up real models: they trace the op graph, find runs of elementwise (and reduction-shaped) ops, and emit one kernel per run — automatically doing what you're about to do by hand in Triton. The wins concentrate exactly where the calculator says: long chains of cheap elementwise math between expensive memory moves. When a compiled model's speedup disappoints, it's usually because its hot path was already fused — or because the bottleneck was reductions and GEMMs all along, which fusion alone can't restructure.

03 · Fusion in Triton, and its limits

Between tl.load and tl.store, registers are free real estate

Writing the fused version is almost anticlimactic — the chain just happens between the load and the store, in registers (day 09), without ever being named in memory:

@triton.jit
def fused_chain(x_ptr, r_ptr, y_ptr, n, scale, BLOCK: tl.constexpr):
    pid = tl.program_id(0)
    offs = pid * BLOCK + tl.arange(0, BLOCK)
    mask = offs < n

    x = tl.load(x_ptr + offs, mask=mask)
    t = tl.math.gelu(x) * scale   # lives in registers
    r = tl.load(r_ptr + offs, mask=mask)
    tl.store(y_ptr + offs, t + r, mask=mask)

The limits are structural, not syntactic. Fusing ops that need different loop structures — an elementwise op into a GEMM's inner loop, or two reductions over different axes — requires the tiling to be compatible, which is exactly the engineering inside FlashAttention (day 24) and inside fused attention kernels generally. Fusion also couples scheduling: one mega-kernel can't start until all its inputs are ready, so over-fusing can cost concurrency that separate kernels would have exploited. Day 11's discipline applies: fuse where the roofline says memory-bound and the calculator says round trips.

CHEATSHEET · DAY 23
  • 01Each intermediate tensor is a full DRAM round trip. gelu → scale → residual-add in eager mode moves 7 tensor-trips of data for 3 trips of necessary I/O.
  • 02Fusion deletes round trips, launches, and VRAM at once. The chain computes in registers between one load and one store: ~2.3× less traffic here, no intermediate allocations, one launch.
  • 03torch.compile is automated fusion. It traces elementwise runs and emits fused kernels. Its wins — and its limits — are both explained by the round-trip arithmetic.
  • 04Fusion needs compatible loop structures. Elementwise chains fuse trivially; fusing across reductions or GEMMs requires matching tilings — that's day 24's engineering.

Next: day 24 — the Reading Real-World Kernels phase. Anatomy of FlashAttention: the kernel that made long context possible, built from ideas you've been collecting for weeks.

get in touch