Unified Memory:完整支援深入 (Unified Memory Full Support)

重點總覽

項目 重點
Full support 系統 hardware-coherent(如 NVIDIA Grace Hopper)與啟用 HMM 的現代 Linux;皆提供相同的程式模型
HMM 需求 Linux kernel 6.1.24+ / 6.2.11+ / 6.3+、compute capability 7.5+、CUDA driver 535+、Open Kernel Modules
In-depth 能力 device 可存取 host process 擁有的「任何」記憶體:malloc、stack、static、global、extern、file-backed
Hardware coherency CPU/GPU 共用「邏輯合併的 page table」,cache-line 粒度一致性,無需 page fault
Software coherency CPU/GPU 各自獨立 page table,以 page fault + migration 模擬一致性,page 粒度
Page size 取捨 小頁→碎片少但 TLB miss 多;大頁→碎片多但 TLB miss 少、migration 較貴
Direct host access cudaDevAttrDirectManagedMemAccessFromHost=1 時 host 可無 fault 直接讀 GPU 記憶體
Host native atomics cudaDevAttrHostNativeAtomicSupported=1 時對 CPU 記憶體的 atomic 硬體加速、不需 fault 模擬
Atomics 限制 software-coherent 系統不支援 device 對 file-backed host 記憶體的 atomic(undefined behavior)
名詞約定(貫穿全章)

CPU 與 GPU 共用「合併 page table」的系統稱為 hardware-coherent;CPU/GPU 各自有獨立 page table 的系統稱為 software-coherent。HMM 是軟體式記憶體管理,提供與硬體一致性系統「相同」的程式模型,但底層行為(fault/migration)屬 software-coherent。

Full CUDA Unified Memory Support 概觀

統一記憶體共有四種範式,本筆記聚焦前三種「完整支援」:

前三者的行為與程式模型「非常相似」,差異會逐點標註;第四種「有限支援」(Windows、WSL、Tegra)另行討論,見 04-CUDA-Features/02-Unified-Memory-Platforms-and-Hints。

只在 hardware-coherent 系統適用的功能

原文特別指出 Access Counter Migration 一節僅適用於 hardware-coherent 系統。

In-Depth Examples:存取各種 host 記憶體

具備完整支援的系統,允許 device 存取 host process 擁有的「任何」記憶體。以下 kernel 印出輸入字元陣列前 8 個字元,可用多種 host 記憶體來源呼叫:

__global__ void kernel(const char* type, const char* data) {
  static const int n_char = 8;
  printf("%s - first %d characters: '", type, n_char);
  for (int i = 0; i < n_char; ++i) printf("%c", data[i]);
  printf("'\n");
}

可傳入的記憶體來源涵蓋:malloc/new heap、cudaMallocManaged、stack 區域變數、file-scope static、global-scope 變數、以及由第三方函式庫擁有的 extern 變數(該函式庫可完全不接觸 CUDA)。

global 變數必須以「指標」傳入 kernel

stack、file-scope、global-scope 變數只能透過「指標」由 GPU 存取。未加 __managed__ 的 global 變數預設被宣告為 __host__-only,多數編譯器不允許在 device code 直接使用。

int global_variable;                 // global scope, __host__-only
__global__ void kernel(int* p) { printf("%d\n", *p); }
int main() {
  // 在 pageableMemoryAccess=1 系統上,傳「位址」而非直接存取變數
  kernel<<<1, 1>>>(&global_variable);
}

重點:直接在 device code 中讀 global_variable 會編譯失敗;必須改傳其位址。

File-backed Unified Memory

由於 device 可存取 host process 的任意記憶體,因此能直接存取 file-backed 記憶體。做法是用 mmap 把檔案映射進記憶體,再把回傳指標直接交給 kernel:

int fd = open(INPUT_FILE_NAME, O_RDONLY);
char* mapped = (char*)mmap(0, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
kernel<<<1, 1>>>("file-backed", mapped);   // GPU 直接讀檔案映射記憶體
cudaDeviceSynchronize();
munmap(mapped, file_stat.st_size);

實體檔案與 memory-backed 檔案皆適用。

file-backed 的 atomic 限制

在「沒有」hostNativeAtomicSupported 屬性的系統(包含啟用 Linux HMM 的系統)上,對 file-backed 記憶體的 atomic 存取不被支援。此外 file-backed 記憶體「不會」依存取頻率 migration。

IPC with Unified Memory

許多應用偏好一個 process 管一張 GPU,但仍需用統一記憶體(如 over-subscription)並從多張 GPU 存取。

兩個限制

  1. 此技術「無法」在不同 hosts 與其 devices 之間共享記憶體(僅限同一 host 上的多 device)。 2) 目前用 IPC 搭配統一記憶體可能有「顯著的效能影響」。跨 host 網路存取請改用 NCCL / NVSHMEM / OpenMPI / UCX 等通訊函式庫。

詳見 04-CUDA-Features/19-Interprocess-Communication。

Memory Paging 與 Page Sizes

所有支援統一記憶體的系統都使用 virtual address space 與 memory paging,因此存在兩類頁:

處理器 實體 page size
x86_64 CPU 預設 4KiB
Arm CPU 4KiB / 16KiB / 32KiB / 64KiB(依 CPU 而定)
NVIDIA GPU 多種,但偏好 2MiB 或更大

映射關係由 page table 追蹤,每筆映射稱 PTE;處理器以 TLB 快取 page table 以加速 virtual→physical 位址轉換。效能調校的兩大關鍵:選擇 virtual page size、以及系統是「合併 page table」還是「各自獨立」。

不要對「實體」page size 做最佳化

實體 page size 會隨硬體變動,調校建議只適用於「virtual page size」。

Choosing the Right Page Size

Page size 記憶體碎片 TLB miss Migration 成本
小 較少 較多 較便宜(latency spike 小)
大 較多 較少 較貴(latency spike 大,因整頁搬移)

CPU/GPU Page Tables:Hardware vs Software Coherency

Hardware-coherent (Grace Hopper)        Software-coherent (HMM 等)
  CPU ─┐                                  CPU ── 自己的 page table
       ├─ 邏輯合併 page table             GPU ── 自己的 page table
  GPU ─┘                                   存取對方實體頁 → page fault + migration
  cache-line 粒度一致                      page 粒度一致
hardware-coherent 的兩大優勢

當 CPU 與 GPU thread「頻繁並行存取同一頁」時:1) 更少 page fault(不需以 fault 模擬一致性或搬移記憶體);2) 更少 contention(一致性粒度為 cache-line 而非整頁,同頁不同 cache-line 不互相干擾)。這直接影響「CPU/GPU 並行對同位址做 atomic」與「CPU↔GPU thread 互相 signal」兩種情境的效能。

Mixing Hardware and Software Coherency

部分 hardware-coherent 系統(如 NVIDIA DGX Station)也支援安裝獨立的非一致性 GPU。兩類 GPU 共享同一統一位址空間,但行為不同:

最佳實務

限制兩類 GPU 間的資料共享,或改用明確 copy;可呼叫 cudaMemAdviseSetPreferredLocation 讓頻繁共享的資料常駐於 CPU 或 coherent GPU 記憶體(預設存取 software-coherent 記憶體需 fault + migration)。

Direct Unified Memory Access from the Host

部分裝置具備從 host 對「GPU-resident 統一記憶體」做 coherent 讀/寫/atomic 的硬體支援,屬性 cudaDevAttrDirectManagedMemAccessFromHost=1(所有 hardware-coherent 系統對 NVLink 連接的 device 皆設此屬性)。此時 host 直接存取 GPU 記憶體「無 page fault、無 migration」。

int *ret;
cudaMallocManaged(&ret, 1000 * sizeof(int));
cudaMemLocation location = {.type = cudaMemLocationTypeHost};
// managed memory 需此 hint 才能啟用「無 fault」的 direct host access
cudaMemAdvise(ret, 1000 * sizeof(int), cudaMemAdviseSetAccessedBy, location);
write<<<1, 1000>>>(ret, 10, 100);   // 資料 populate 於 GPU 記憶體
cudaDeviceSynchronize();
for (int i = 0; i < 1000; i++) printf("%d: A+B = %d\n", i, ret[i]);  // CPU 讀
append<<<1, 1000>>>(ret, 10, 100);  // GPU 再次存取

兩種系統行為對比:

屬性值 CPU 存取 managed buffer 隨後 GPU 存取
directManagedMemAccessFromHost=1 不觸發 migration,資料留在 GPU GPU 直接存取、無 fault/migration
directManagedMemAccessFromHost=0 page fault + device→host migration page fault + host→device migration
system-allocator 與 managed 的差異

在「共用 page table」系統上,對 malloc 記憶體不需要 cudaMemAdviseSetAccessedBy 這個 hint;但對 cudaMallocManaged 記憶體,需要此 hint(location type cudaMemLocationTypeHost)才能啟用無 fault 的 direct host access。

Host Native Atomics

部分裝置(含 hardware-coherent 系統中 NVLink 連接的 device)支援「對 CPU-resident 記憶體的硬體加速 atomic」,意即 host 記憶體的 atomic「不需用 page fault 模擬」。這類裝置屬性 cudaDevAttrHostNativeAtomicSupported=1。

缺此屬性的後果

沒有 hostNativeAtomicSupported(含 Linux HMM 系統)時,對 file-backed 記憶體的 atomic 存取不被支援,見上方 File-backed 段落。

Atomic Accesses 與 Synchronization Primitives

CUDA 統一記憶體支援 host 與 device thread「所有」可用的 atomic 操作,讓所有 thread 能並行存取同一共享位址協作。libcu++ 提供多種為 host/device 並行調校的異質同步原語:cuda::atomic、cuda::atomic_ref、cuda::barrier、cuda::semaphore 等。

__global__ void kernel(int* ptr) { cuda::atomic_ref{*ptr}.store(2); }
// host 與 device 並行對同一 file-backed ptr 做 atomic(僅 hardware-coherent 系統合法)
kernel<<<1, 1>>>(ptr);
while atomic_ref{*ptr}.load() != 2;   // 最終必為 2
software-coherent 上的 undefined behavior

上述對「file-backed host 記憶體」做 device→host atomic 的範例,只在 hardware-coherent 系統合法,在其他系統屬 undefined behavior。

詳見 04-CUDA-Features/11-Asynchronous-Barriers-Deep-Dive 的 barrier 與 02-Programming-GPUs/09-SIMT-Atomics-Cooperative-Occupancy 的 atomic 入門。

其他效能調校重點

Memcpy() / Memset() 行為

cudaMemcpy*() 與 cudaMemset*() 接受任何統一記憶體指標。對 cudaMemcpy*() 而言,cudaMemcpyKind 方向是「效能 hint」,當任一引數為統一記憶體指標時影響更大。建議:

統一記憶體 allocator 概觀

API Placement 可存取自 依存取 migrate Page sizes
malloc / new / mmap first-touch/hint CPU, GPU Yes(file-backed 例外) system 或 huge page size
cudaMallocManaged first-touch/hint CPU, GPU Yes CPU 常駐:system;GPU 常駐:2MB
cudaMalloc GPU GPU No 2MB
cudaHostAlloc / cudaMallocHost / cudaHostRegister CPU CPU, GPU No CPU 映射:system;GPU 映射:2MB
Memory pool(host)cuMemCreate / cudaMemPoolCreate CPU CPU, GPU No CPU:system;GPU:2MB
Memory pool(device)cuMemCreate / cudaMemPoolCreate / cudaMallocAsync GPU GPU No 2MB
migration 例外

「依存取 migrate」可被 cudaMemAdvise 覆寫;即使停用 access-based migration,若 backing 記憶體已滿,記憶體「仍可能」migration。hardware-coherent 系統若把 device 記憶體曝露為 NUMA domain,可用 numa_alloc_on_node / mbind 把記憶體 pin 在指定 NUMA node,host/device 皆可存取且不 migrate。

Access Counter Migration(僅 hardware-coherent)

access counters 追蹤 GPU 對「他處理器記憶體」的存取頻率,把頁搬到存取最頻繁者的實體記憶體(可在 CPU↔GPU、peer GPU↔GPU 之間 guide migration)。

避免 CPU 頻繁寫入 GPU-resident 記憶體

許多 CPU 架構所有記憶體操作(含 write)都須經 cache。若記憶體常駐於 GPU,CPU 頻繁寫入會 cache miss,先把資料 GPU→CPU 搬回再寫入。建議「寫入 CPU-resident 記憶體、由 device 直接讀」:用 cudaMemAdviseSetPreferredLocation + cudaMemAdviseSetAccessedBy(皆 cudaMemLocationTypeHost)把資料釘在 host。

善用對 system memory 的非同步存取

device 與 host 交換結果的三種方式:

  1. device 寫 GPU 記憶體 → cudaMemcpyAsync 傳到 host → host 讀。
  2. device 直接寫 CPU-resident 記憶體 → host 讀。
  3. device 寫 GPU 記憶體 → host 直接存取。
如何選擇

若 device 在傳輸/存取期間還有獨立工作可排程,選 1 或 3;若 device 在 host 取得結果前都閒置,選 2。一般 device 寫頻寬高於 host 讀。大量連續資料優先用 copy-engine(可與 host/device 工作重疊),但 cudaMemcpy* 不保證一定用 copy-engine;copy-engine 數量有限,若傳輸在關鍵路徑上,有時改用 device 明確傳輸反而更快。詳見 04-CUDA-Features/06-Stream-Ordered-Memory-Allocator 與 04-CUDA-Features/20-Virtual-Memory-Management。

考試/測驗重點

問題 答案
hardware-coherent vs software-coherent 的判別 合併 page table = hardware;CPU/GPU 各自 page table = software
Linux HMM 最低需求 kernel 6.1.24+ / 6.2.11+ / 6.3+、CC 7.5+、driver 535+、Open Kernel Modules
global 變數為何不能直接在 kernel 用 預設 host-only,須傳「位址」進 kernel(pageableMemoryAccess=1)
file-backed atomic 不支援的條件 系統無 hostNativeAtomicSupported(含 Linux HMM)
CUDA IPC 是否支援 managed memory 否;但 system-allocated memory 具 IPC 能力
大 page size 的取捨 碎片多 / TLB miss 少 / migration 較貴、latency spike 大
GPU vs CPU 的 TLB miss 成本 GPU 的 TLB miss 明顯更貴
hardware coherency 的一致性粒度 cache-line(software 為 page)
host 無 fault 直接讀 GPU 記憶體的屬性 cudaDevAttrDirectManagedMemAccessFromHost=1
managed memory 啟用 direct host access 需要的 hint cudaMemAdviseSetAccessedBy + cudaMemLocationTypeHost
host native atomics 屬性 cudaDevAttrHostNativeAtomicSupported=1
software-coherent 對 file-backed 的 device atomic undefined behavior(僅 hardware-coherent 合法)
access counter migration 適用系統 僅 hardware-coherent;CUDA 12.4 起支援 system-allocated memory
cudaMemcpyKind 在統一記憶體中的角色 效能 hint;不確定方向時用 cudaMemcpyDefault
大量連續資料傳輸首選 copy-engine(可與 host/device 工作重疊,但不保證使用)