3.4.3 Adding MIMD Capability
The boldest response to
- Vector-Thread (VT) architecture (Krashinsky et al.): lanes normally march in lockstep, fed from a shared L1 I-cache; on divergence, each lane detaches and runs at its own pace from a private L0 cache. Lee et al.’s comparison: parity with SIMT on regular code, much better on irregular parallelism.
- Temporal SIMT (Keckler, Krashinsky): rotate the warp 90° — instead of 32 threads across 32 lanes in one cycle, stream the warp through one lane over 32 cycles, one instruction fetch per warp. Each lane hosts its own warps and may drop into MIMD independently — control overhead amortized over time rather than space.
- Variable Warp Sizing (VWS, Rogers et al.): a middle path — eight 4-wide slices, each with its own fetch/decode and 256-byte L0, whose narrow warps are ganged into wide execution entities. Coherent code runs ganged (one fetch); divergence splits the gang; matching PCs re-form it:
| A (gang) | br | B (s0) | C (s1) | B+C | D (re-gang) | |
|---|---|---|---|---|---|---|
| t0 | ||||||
| t1 | ||||||
| t2 | ||||||
| t3 | ||||||
| t4 | ||||||
| t5 | ||||||
| t6 | ||||||
| t7 |
time →
No divergence: the gang executes in lockstep. ONE shared fetch/decode serves both slices — SIMD-style efficiency from a shared L1 I-cache.
Even for divergent workloads, those tiny L0s filter most instruction fetches, so the shared L1 needs only ~2× baseline bandwidth.
- Simultaneous Branch Interweaving (SBI, Brunie et al.) belongs here too: issue two different instructions per cycle to fill divergence holes (also in §3.4.1).
3.4.4 Complexity-Effective Divergence Management
The opposite instinct: divergence is rare in typical GPU code, so spend less hardware on it, not more. The baseline stack costs 32×64 bits per warp (down to 6×64 with tricks) — small, but it scales with every in-flight warp and sits idle in coherent kernels.
- The SIMT stack in scalar registers (AMD GCN): no divergence hardware
at all — the compiler emulates the stack using the scalar register
file and
exec-mask arithmetic (you watched exactly this in §2.2.2), with special instructions to accelerate it. The elegant bound: always run the less-populated target first, and worst-case divergence needs only log₂(threads/wavefront) scalar registers — 6 for a 64-thread wavefront. Kernels with no divergent branches reclaim those registers for real scalar work. (Diamos et al., from §3.4.2) double as a cheap design: per-thread PCs live in the register file, allocated only in kernels that can actually diverge — everywhere else the storage boosts occupancy instead.Thread frontiers thread frontiersPer-thread PCs + topologically sorted code; always run the lowest-PC threads so laggards catch up — no stack needed.Glossary →- Stackless SIMT with
syncwarp(Asanovic et al.): on a temporal-SIMT substrate, let diverged threads run on their private PCs and have the compiler place asyncwarpat each divergent branch’s reconvergence point. No nested-reconvergence capture — but comparable performance when divergence is rare, at near-zero hardware cost. The enabling compiler machinery is a combined convergent/variant analysis, worth stepping through because §3.5 reuses it for scalarization:
Step 1: Optimistic start: everything thread-INVARIANT
The compiler begins by assuming every basic block and every instruction computes the same value in all threads — i.e., warps never diverge, and any operation could in principle be done once for the whole warp.
idx = threadIdx.x; // invariant? (for now)
v = y[idx] + a; // invariant? (for now)
if (v > THRESHOLD) // uniform branch? (for now)
y[idx] = Y_MAX; // invariant? (for now)One tick = one pass of the convergent/variant compiler analysis (Asanovic et al.) — the analysis behind stackless SIMT, and behind scalarization in §3.5.
- Thread-aware predication (Lee et al.): skip divergence hardware
entirely — extend the control-dependence graph with predicate nodes and
compile arbitrary control flow to
, with two rescue optimizations: static branch-uniformity (provably uniform branch → plain uniform branch instruction) and runtime branch-uniformity via consensual branches (predication predicationExecuting an instruction conditionally under a predicate register (@%p1 in PTX, @P0 in SASS) instead of branching.Glossary →cbranch.ifnoneskips code whose predicate is null — restoring the stack’s whole-path skipping). Honest trade-offs: predicated code lets the compiler interleave both targets (free TLP!), but raises register pressure (occupancy ↓) and can inflate dynamic instruction count;find_unique(serialize indirect calls) andcbranch.ifanyround out the proposed ISA support.
| Vector-Thread | Lanes detach to private L0s on divergence | Per-lane fetch hardware |
| Temporal SIMT | Warp streams through one lane over time; one fetch/warp | Longer warp latency; per-lane divergence handling |
| Variable Warp Sizing | 4-wide slice warps ganged; split/re-gang on (re)convergence | 8 front-ends + L0s (~2× L1 fetch bandwidth needed) |
| GCN software stack | Compiler-managed stack in scalar registers | Extra scalar instructions around branches |
| Thread frontiers | Per-thread PCs in the register file, min-PC scheduling | PC storage only in divergent kernels |
| Stackless syncwarp | Private PCs + compiler-placed syncwarp at reconvergence | No nested reconvergence capture |
| Thread-aware predication | Compile all control flow to predicates + consensual branches | Register pressure; dynamic instruction count |
Check yourself
1. All §3.4.3 proposals share the same two-mode shape. Which?
2. How does Temporal SIMT differ from a conventional (spatial) SIMT core?
3. Predict the gang (use the animation): a VWS gang of two 4-wide warps diverges, splits, and later both split-gangs reach block D. What happens?
4. AMD GCN emulates the SIMT stack in software using scalar registers. What trick bounds the registers needed to log₂(warp size)?
5. Thread-aware predication (Lee et al.) can beat a SIMT stack, but predication has a classic cost. Which?