Register pressure and spilling
Account for rounded register allocation, distinguish local memory from on-chip storage, and optimize measured work rather than occupancy alone.
On this page
The question for today
What you’ll understand
- Explain why a source variable does not guarantee one register.
- Compute a scoped rounded-register budget.
- Distinguish residency, spilling, and useful kernel performance.
A fused tensor kernel removes intermediate global writes but keeps many accumulators live. Register demand rises, fewer warps may reside on an SM, and some values may spill. Those effects interact rather than forming one monotonic rule. This lesson uses CUDA 12.8.1 documentation and explicitly scoped allocation arithmetic; register-file sizes, allocation units, and residency limits must be checked for the compiled target rather than copied across GPU generations.
The compiler allocates live values
Register demand depends on live ranges, unrolling, inlining, indexing, and generated instructions, not simply the number of variables declared in source. Large or dynamically indexed thread-private arrays may reside in local memory even without a simple register-exhaustion story. Inspect compilation resource reports and final generated code for the actual architecture. PTX is an intermediate representation, so later compilation can change placement. A favorable source rewrite can unexpectedly add registers through temporaries or a different instruction selection.
CUDA local memory is private in its address-space semantics but backed by device memory, not a private on-chip register extension. Spilled accesses can therefore add traffic and latency, subject to caching and their access pattern. Threads accessing corresponding local offsets can have coalesced accesses, but that does not make spills free. Do not confuse local memory with block-shared memory, whose ownership, lifetime, and synchronization rules are different. The word local describes visibility here, not physical proximity.
Rounding creates resource thresholds
A small register increase can cross an allocation boundary and reduce resident blocks abruptly. Other limits, including threads, warps, shared memory, and architectural block caps, may bind first. Thus dividing total registers by threads and a reported per-thread count is not a complete occupancy calculation. CUDA’s occupancy APIs and profiling tools can account for target-specific constraints. State which bottleneck your arithmetic includes and which it deliberately omits before reporting a resident-block prediction.
The CUDA 12.8.1 best-practices guide gives a Volta compute-capability 7.0 example with 256-register allocation granularity per warp. Use that scoped rule to understand rounding rather than assuming every newer target shares every detail. Reducing a compiler register cap may raise nominal residency while forcing more spills or instructions. Conversely, a lower-occupancy kernel can still perform well when it has sufficient independent work and reuse. Compare execution time, memory traffic, and numerical behavior after each meaningful change.
Optimize the full data path
Tune fusion, tile dimensions, or unrolling based on measured bottlenecks. Shorter live ranges can reduce pressure without reducing useful parallelism, while storing values in shared memory trades registers for another limited resource and introduces access and synchronization costs. A kernel that passes a small benchmark can spill heavily for another template specialization or input shape. Record compile options, target architecture, launch size, and specialization alongside resource reports so regressions can be traced to the code actually running.
The Roofline lab provides only a traffic analogy. Reset, set intensity to 8 FLOP per byte and bandwidth to 80 GB/s, predict the synthetic ceiling, then Step. Compare a fresh Reset with both intensity at 4 and bandwidth at 80, interpreting the changed intensity as an assumed increase in bottleneck traffic. Its compute ceiling remains 1000 GFLOP/s. It has no registers, warps, occupancy, or spill compiler; explain why a lower traffic ceiling cannot determine whether a real register-cap change is beneficial.
Work it through
Round one warp’s register demand
Use a teaching allocation model with G = 256 32-bit registers per warp, eight full warps per block, and R_SM = 65,536. Compare reported r = 32 and r = 33. Treat the result as register-only accounting, not a full target occupancy prediction.
At r = 32, a warp requests 1,024 registers, already four allocation units. Eight warps consume 8,192 per block, and the register-only bound is floor(65,536/8,192) = 8 blocks.
At r = 33, raw demand is 1,056 per warp, rounded to 1,280. Eight warps consume 10,240 per block, giving floor(65,536/10,240) = 6 blocks.
The rounded budget changes discontinuously. Check actual architecture rules, other block limits, and whether the compiler introduces spills before interpreting either count as achieved residency or performance.
One extra reported register per thread can cross an allocation threshold. It does not imply a proportional one-register change in the block’s allocated storage.
The living notebook
Explore the model
Interactive lab · roofline
Spill traffic changes an assumed intensity
Reset, set intensity I = 8 and bandwidth B = 80, predict the ceiling, then Step. Compare a fresh Reset with both I = 4 and B = 80. The fixed 1000-GFLOP/s Roofline is synthetic and contains no register allocation or occupancy model.
- Attainable ceiling
- 640 GFLOP/s
- Ridge intensity
- 12.5 FLOP/byte
- Ceiling regime
- Memory-bound ceiling
Synthetic roofline ceiling min(1000, B·I), in GFLOP/s, with B in decimal GB/s and I in FLOP/byte. Playback traces the roof without changing the selected kernel. This upper-bound model is not a measured throughput prediction, hardware comparison or price model. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forTraffic is one consequence to measure; a register or occupancy count alone does not predict useful kernel speed.
Pause & reason
Check your understanding
Your turn
Try it yourself
Under the same assumed G = 256 model, r = 40 and four warps per block, calculate registers per warp and per block. With 65,536 registers, find the register-only block bound and name one omitted constraint.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Forty times thirty-two is already a multiple of 256.
Compare with the solution
Each warp uses 1,280 registers and each four-warp block uses 5,120. The register-only bound is floor(65,536/5,120) = 12 blocks. Thread or warp limits, shared-memory allocation, architectural block limits, and target-specific rules can reduce actual residency. The bound does not predict execution time or spill behavior.
Keep the idea
Take it with you
- Live ranges and compilation determine register placement and spills.
- Allocation rounding and several hardware limits shape residency.
- Local memory is device-memory-backed; optimize measured work rather than occupancy alone.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.