10.2More Heterogeneity Than Just GPUs

book pp. 237–246 · ~10 min read

  • compound consistency model
  • SC for HRF
  • shim
  • GetS vs GetV
  • coarse-grained coherence tracking
  • selective GPU caching

§10.1 solved consistency and coherence inside one accelerator. Now the harder composition problem: expose one global shared memory across multiple multicore devices — CPUs, GPUs, other accelerators — when each device arrives with its own consistency model, enforced by its own coherence protocol. A CPU might guarantee TSO via a consistency-agnostic, SWMR-enforcing directory protocol; a GPU might guarantee scoped RC via a lazy consistency-directed protocol. Bolt them together and two questions follow, both under active research: what does the combined machine promise (§10.2.1), and how do the two protocols physically interoperate (§10.2.2)?

10.2.1 Heterogeneous consistency models

Suppose multicore A enforces SC and multicore B enforces TSO, sharing memory. What is the resulting model? The tempting shortcut — “declare the weaker model the overall model” — fails twice over: if the two models are incomparable there is no weaker one, and even when one subsumes the other, discarding the stronger device’s guarantees is wasteful.

The right semantics is intuitive (though, the book notes, not yet formalized): a compound consistency model , in which memory operations from each component satisfy the memory ordering rules of that component. Operationally, picture §3.6’s switch, heterogeneous edition (book Fig 10.2):

C1 (SC)C2 (SC)C3 (TSO)C4 (TSO)storebufferstorebufferloads + storesstoresloads (may bypass)SwitchMemory

Figure 10.2 (recreated): the SC cores’ loads and stores go straight to the switch, in program order; the TSO cores’ stores drain through FIFO store buffers, and their loads may bypass (§4.4’s machinery). Each thread obeys its own core’s rules.

Dekker’s algorithm shows the compound model biting (book Table 10.20 — T1 on an SC core, T2 on a TSO core):

Thread T1 (SC)Thread T2 (TSO)Comments
St1: St flag1 = NEW;St2: St flag2 = NEW;Initially all 0. Can both r1 and r2 read 0? Yes! TSO does not enforce St2 → Ld2.
Ld1: Ld r1 = flag2;Ld2: Ld r2 = flag1;
Program Order (<p) of T1 on C1 (SC)Program Order (<p) of T2 on C3 (TSO)Memory Order (<m)Ld2: r2 = flag1; /* 0 */St1: flag1 = NEW; /* NEW */Ld1: r1 = flag2; /* 0 */St2: flag2 = NEW; /* NEW */Outcome: (r1, r2) = (0, 0) — allowed: only the TSO thread reordered (Ld2 above St2, upward arrow); the SC thread stayed in program order

The fix respects the asymmetry (book Table 10.21): a FENCE between St2 and Ld2 on the TSO side only restores St2 → Ld2 and forbids (0,0). T1 needs nothing — SC already orders St1 → Ld1. One device’s threads follow one rulebook, the other’s another, and the FENCEs go exactly where a rulebook is weak.

Programming with heterogeneous consistency models

1. Target the compound model

Reason directly about every component’s rules, as in the Dekker example. Workable for small kernels; with very different models in play it gets tricky, fast.

2. Program in SC for HRF

HSA and OpenCL extend SC-for-DRF with scopes: Sequential Consistency for Heterogeneous-Race-Free programs. Label your synchronization with the right scopes and the machine looks SC. Language-level primitives map to each device’s instructions per-device.

3. Pick one model, translate the rest

Program against one component’s hardware model and instrument the code running on every other component with FENCEs so it behaves compatibly. The ArMOR framework specifies models precisely enough to derive those mappings algorithmically.

Two details about HRF are worth keeping. First, its relationship to §10.1.4’s scoped RC model is exactly the DRF-to-RC relationship one level up: HRF is a language-level contract with no semantics for racy programs, while scoped RC is a virtual-ISA-level model that gives even racy code defined behavior. Second, HRF comes in two strengths: HRF-direct, where releases and acquires synchronize only if their scopes match exactly, and HRF-indirect, which adds transitive synchronization across different scopes — if T1 syncs with T2 at CTA scope and T2 syncs with T3 at GPU scope, T1 has synchronized with T3. HRF-direct is simpler for hardware and painfully restrictive for programmers; scoped RC (like NVIDIA’s PTX model) follows HRF-indirect.

10.2.2 Heterogeneous coherence protocols

Now the plumbing. Each device has a local coherence protocol keeping its own L1s coherent. Stitching them into one machine means the composed system must satisfy the compound model — every device’s operations obeying that device’s rules — while the protocols speak to each other through something both can understand (book Fig 10.3):

Device ACoreCoreL1L1L2Local coherencecontroller AShimDevice BCoreCoreL1L1L2Local coherencecontroller BShimGlobal Coherence Controller + Optional LLCMemory

Figure 10.3 (recreated): hierarchical heterogeneous coherence. The approach extends §9.1.6’s hierarchical coherence from homogeneous to heterogeneous composition — the amber shims are the new ingredient.

The recipe: a local controller tries to satisfy each request inside its device; anything it can’t complete locally (say, a GetM while the other device has sharers) is forwarded to the global coherence controller, which forwards it to the other device’s local controller, collects the response, and routes it back. Making that work takes two things:

  1. a global coherence interface rich enough to carry every kind of request the devices’ local protocols may need to exchange; and
  2. per-device shims — translators between each local protocol and the global interface, choosing which global requests to emit and how to interpret the ones that arrive.

What must that interface look like? Walk through the three possible pairings.

Scenario 1: consistency-agnostic + consistency-agnostic

A directory-protocol CPU joined to a snooping-bus accelerator — both SWMR-enforcing. The global directory keeps a coarse per-device sharer list, and the interface only needs the classic Table 6.4 request vocabulary. The interesting move is the shim’s: a global Inv arriving at a snooping device becomes a local GetM — because on a bus, that is what invalidates sharers.

CPU coreLocal dir. (CPU)Global directorySnoop ctrl (Accel)Accel. bus/cores

■ requests/forwards · ■ data · ■ acks

1 / 9Scenario 1: consistency-agnostic + consistency-agnostic (Fig 10.4)

A CPU (directory protocol, SC) and a CPU-like accelerator (snooping on a bus) are stitched by a global directory with a coarse per-device sharer list. A CPU core now stores to a block that is shared across BOTH devices — every sharer everywhere must be invalidated before the store may perform.

Scenario 2: consistency-directed + consistency-directed

An LRCC GPU joined to an RCC accelerator through a shared LLC. Here the global LLC is the point of coherence: shims forward GetV requests up and write-backs through, and the LRCC ownership machinery works across the device boundary unchanged — the deferred flush just gets triggered by another device’s acquire.

Accel core (T2)L2 (Accel)Global LLCL2 (GPU)L1 (GPU)

■ requests/forwards · ■ data · ■ acks

1 / 9Scenario 2: consistency-directed + consistency-directed (Fig 10.5)

A GPU running LRCC and a GPU-like accelerator running RCC share a global LLC. T1 (on the GPU) already ran St1: data1=NEW and the release St2: flag=SET — so flag sits OWNED in a GPU L1, data1 dirty beside it. Now T2 (on the accelerator) performs the acquire Ld1.

Scenario 3: consistency-agnostic + consistency-directed

The instructive one — a CPU (SC, MSI directory) and a GPU (non-scoped RC, LRCC) joined by a global directory embedded in the shared LLC. First, CPU as producer (book Table 10.22): St1: St data1 = NEW then St2: St flag = SET on the CPU; Acq Ld1 spinning on flag, then Ld2 r2 = data1 on the GPU, with everything initially cached in both devices.

The CPU’s stores upgrade as usual — but watch what the global directory doesn’t do:

CPU core (T1)Local dir. (CPU)Global directory

■ requests/forwards · ■ data · ■ acks

1 / 5Scenario 3a: a CPU store — with GPU sharers! (Fig 10.6, Table 10.22)

CPU (SC, consistency-agnostic MSI directory) + GPU (non-scoped RC, LRCC), stitched by a global directory in the shared LLC. T1 on the CPU performs St1: data1=NEW — while the GPU is caching data1. Watch what DOESN’T happen.

GPU core (T2)L2 (GPU)Global LLCDirectory (CPU)L1 (CPU)

■ requests/forwards · ■ data · ■ acks

1 / 9Scenario 3b: the GPU acquire pulls from the CPU (Fig 10.7, Table 10.22)

Same system: the CPU’s T1 wrote data1=NEW and flag=SET (now Modified in a CPU L1). T2 on the GPU performs the acquire Ld1 of flag.

Now the roles reverse (book Table 10.23): the GPU produces (St1, then Rel St2), the CPU consumes (Ld1 spin, Ld2). The GPU’s release needs ownership of flag — and this time the CPU’s copies must die, because nothing in a consistency-agnostic CPU ever self-invalidates:

GPU core (T1)L2 (GPU)Global LLCDirectory (CPU)L1 (CPU)

■ requests/forwards · ■ data · ■ acks

1 / 9Scenario 3c: the GPU release must invalidate the CPU (Fig 10.8, Table 10.23)

Roles reversed: T1 on the GPU (LRCC) produces; T2 on the CPU (SC, MSI directory) consumes. T1’s plain store data1=NEW stayed local. Now its release St2: Rel flag=SET issues a GetO — and flag is cached in the CPU.

CPU core (T2)L2 (CPU)Global LLCL2 (GPU)L1 (GPU)

■ requests/forwards · ■ data · ■ acks

1 / 11Scenario 3d: the CPU load triggers the lazy flush (Fig 10.9, Table 10.23)

Continuing 3c: flag is owned by a GPU L1 (with data1=NEW dirty beside it); the CPU still holds a stale data1=0 in S. T2 on the CPU spins: Ld1 r1=flag.

Mitigating the bandwidth bill

For multi-chip CPU-GPU systems the global directory sits across an off-chip coherent interconnect — and GPU workloads, with their high miss rates, hammer it. Coarse-grained coherence tracking relieves the pressure: track coherence state at, say, page granularity inside each device; when a GPU miss touches a page known to be GPU-private or read-only, skip the global directory entirely and fetch straight from memory over a high-bandwidth bus.

A low-complexity alternative: selective GPU caching

Hierarchical coherence needs shims in both devices — awkward when CPU and GPU come from different vendors. The minimal-invasion option: selective GPU caching . The GPU simply never caches (1) any data mapped to CPU memory, nor (2) any GPU-memory data currently cached by the CPU — the latter tracked by a coarse-grained remote directory in the GPU (a region enters it when the CPU touches a GPU-memory block; if the GPU had the line cached, it flushes it). Coherence holds trivially: no shared cacheable data, no coherence problem. The naive cost — every CPU-cached location must be fetched from the CPU — is offset by GPU request coalescing (merge concurrent requests to the same line) and CPU-side caching of GPU remote requests.

Check yourself: heterogeneous consistency and coherence

1.Devices A (SC) and B (TSO) share memory. What is the consistency model of the combined machine?

2.In the compound SC+TSO Dekker (Table 10.20/10.21), where must a FENCE go to forbid (r1,r2) = (0,0)?

3.HRF-direct vs HRF-indirect: T1 synchronizes with T2 using CTA scope; T2 then synchronizes with T3 using GPU scope. Does T1 synchronize with T3?

4.Why does a universal heterogeneous coherence interface need BOTH GetS and GetV read requests?

5.CA+CD stitching (CPU stores, GPU caches the block — Fig 10.6): why does the global directory NOT send an invalidation to the GPU?

6.Selective GPU caching enforces CPU-GPU coherence by…

6 questions