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::atomiccuda::atomic_refcuda::barriercuda::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-Allocator04-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 工作重疊,但不保證使用)