CUDA C++ 記憶體模型 (CUDA C++ Memory Model)

重點總覽

標準 C++ 假設執行緒同步的成本是均勻且低的;CUDA C++ 不同:執行緒距離越遠,同步成本越高——block 內極低,跨多 GPU/CPU 的系統範圍則很高。為了表達這種非均勻成本,CUDA C++ 在 cuda:: namespace 中以 thread scope 擴充標準 C++ 記憶體模型與並行設施,預設仍保留標準 C++ 的語法與語意

項目 重點
核心擴充 cuda:: 在標準模型上加入 thread scope
Scope 列舉 thread_scope_system / _device / _block / _thread 四級
同步原語 cuda::atomiccuda::barrier 等,以 scope 為模板參數
Atomicity 關鍵 操作只在它指定的 scope 內保證 atomic
Data race 兩個衝突存取,至少一個在「涵蓋對方執行緒的 scope」下不是 atomic,且無 happens-before → UB
std::/cuda::std:: 等同於 cuda:: 型別以 thread_scope_system 實例化的行為

Thread Scopes

thread scope 指定「哪一類執行緒」可以透過某個同步原語(如 cuda::atomiccuda::barrier)彼此同步。

namespace cuda {
enum thread_scope {
  thread_scope_system,
  thread_scope_device,
  thread_scope_block,
  thread_scope_thread
};
} // namespace cuda

Scope Relationships(涵蓋範圍)

每個程式執行緒透過一或多個 scope 關係彼此關聯,由大到小(外層涵蓋內層):

Scope 關聯的執行緒範圍
cuda::thread_scope_system 系統中的每個執行緒,與系統中其他每個執行緒(含多 GPU/CPU)
cuda::thread_scope_device 同一 CUDA device 且同一 memory synchronization domain 內的每個 GPU 執行緒
cuda::thread_scope_block 同一 CUDA thread block 內的每個 GPU 執行緒
cuda::thread_scope_thread 每個執行緒只與自己關聯
system  ┌──────────────────────────────────────────┐ 多 GPU + CPU,成本最高
        │ device ┌───────────────────────────────┐ │ 單一 device(同 sync domain)
        │        │ block ┌──────────────────────┐ │ │ 單一 thread block,成本最低
        │        │       │ thread (僅自身)      │ │ │
        │        │       └──────────────────────┘ │ │
        │        └───────────────────────────────┘ │
        └──────────────────────────────────────────┘
Note

thread_scope_device 的涵蓋範圍除了「同一 device」外,還限定在同一 memory synchronization domain 內;跨 domain 不在此 scope 的保證範圍。

Synchronization primitives

std::cuda::std:: 中的型別,當以 cuda::thread_scope_system 實例化對應的 cuda:: 型別時,行為相同

Atomicity

一個 atomic 操作只在它所指定的 scope 下保證為 atomic,條件為以下之一:

條件 需滿足的裝置屬性
影響 system allocated memory 的物件 pageableMemoryAccess 為 1(見 [0])
影響 managed memory 的物件 concurrentManagedAccess 為 1
影響 mapped memory 的物件 hostNativeAtomicSupported 為 1
對 mapped memory 上自然對齊、大小 1/2/4/8/16 bytes 物件的 load 或 store 直接成立(見 [1])
影響 GPU memory 物件且只有 GPU 執行緒存取 見下方兩個子條件

GPU memory 且僅 GPU 執行緒存取時,需再滿足任一:

Warning

  • [0]PageableMemoryAccessUsesHostPagetables 為 0,則對 memory mapped file 或 hugetlbfs 配置做的 atomic 操作不是 atomic
  • [1]hostNativeAtomicSupported 為 0,system scope 下對 system allocated memory 或 mapped memory 中自然對齊 16-byte 物件的 atomic load/store 需要系統支援。NVIDIA 不知道有任何系統缺乏此支援,且沒有可偵測的 CUDA API。

Data Races

CUDA C++ 對 ISO/IEC 14882(C++ 標準)intro.races 第 21 段做如下修改(加入 scope 概念):

Important

程式執行若包含兩個可能並行的衝突動作,其中至少一個在「涵蓋了執行另一操作之執行緒」的 scope 下不是 atomic,且兩者之間沒有 happens-before 關係(signal handler 特例除外),即構成 data race。任何此類 data race 都導致未定義行為(UB)

Example: Message Passing

Block 0 的執行緒把訊息寫進 x,透過旗標 f 傳給 Block 1 的執行緒。正確版:兩端都用 thread_scope_device(涵蓋跨 block 的兩個執行緒)。

// 初始
int x = 0, f = 0;

// Thread 0, Block 0
x = 42;
cuda::atomic_ref<int, cuda::thread_scope_device> flag(f);
flag.store(1, memory_order_release);

// Thread 0, Block 1
cuda::atomic_ref<int, cuda::thread_scope_device> flag(f);
while (flag.load(memory_order_acquire) != 1);
assert(x == 42); // 必成立:release/acquire 在 device scope 下成對同步

錯誤版(data race / UB):store 端誤用 thread_scope_block

// Thread 0, Block 0
x = 42;
cuda::atomic_ref<int, cuda::thread_scope_block> flag(f);
flag.store(1, memory_order_release); // UB: data race

// Thread 0, Block 1
cuda::atomic_ref<int, cuda::thread_scope_device> flag(f);
while (flag.load(memory_order_acquire) != 1); // UB: data race
assert(x == 42);
Warning

雖然對 f 的 store 與 load 本身都是 atomic 操作,但 store 的 scope 是 block scope——它由 Block 0 的 Thread 0 執行,只涵蓋 Block 0 內的其他執行緒。做 load 的執行緒在 Block 1,不在 store 操作所涵蓋的 scope 內,於是 store 與 load 對彼此而言不是 atomic,構成 data race。更多範例見 PTX memory consistency model litmus tests。

考試/測驗重點

題型 關鍵答案
CUDA C++ 為何擴充標準記憶體模型 同步成本非均勻:距離越遠成本越高,需用 thread scope 表達
四個 thread scope 由大到小 systemdeviceblockthread
scope 擴充放在哪個 namespace cuda::(預設保留標準 C++ 語法與語意)
thread_scope_device 的範圍 同一 device 同一 memory synchronization domain 的 GPU 執行緒
thread_scope_thread 涵蓋誰 只涵蓋執行緒自己
std::/cuda::std:: 型別等同哪種 scope 等同 cuda:: 型別以 thread_scope_system 實例化
atomic 操作何時必為 atomic 指定的 scope 不是 system scope 時,自動成立
system scope + system allocated memory 需要 pageableMemoryAccess 為 1
system scope + managed memory 需要 concurrentManagedAccess 為 1
system scope + mapped memory 需要 hostNativeAtomicSupported 為 1
mapped memory 上哪些 load/store 直接 atomic 自然對齊、大小 1/2/4/8/16 bytes 的物件
GPU memory 跨 GPU atomic 的查詢 API cudaDeviceGetP2PAttributecudaDevP2PAttrNativeAtomicSupported 為 1
單一 GPU 內的 GPU 執行緒並行存取 對 GPU memory 物件保證 atomic
data race 的定義(含 scope) 兩並行衝突動作,至少一個在涵蓋對方執行緒的 scope 下非 atomic,且無 happens-before
data race 的後果 未定義行為(UB)
barrier/latch/semaphore 成員函式並行呼叫 除解構子外不引入 data race(視為 atomic)
fence 同步是否受 scope 約束 是,A/B/X(/Y) 各操作都須指定涵蓋對方執行緒的 scope
Message Passing 範例為何 UB store 用 block scope,load 執行緒在別的 block,scope 不涵蓋對方
正確的 Message Passing 用哪個 scope 兩端都用 thread_scope_device
PageableMemoryAccessUsesHostPagetables 為 0 的影響 對 memory mapped file / hugetlbfs 的 atomic 不再是 atomic