Unified Memory:平台差異與效能提示 (Platforms and Performance Hints)

重點總覽

延續 04-CUDA-Features/01-Unified-Memory-Full-Support,本筆記聚焦於「非 full support」平台(只有 managed memory 支援的裝置,以及 Windows/WSL/Tegra 上 concurrentManagedAccess = 0 的裝置)的行為限制,以及適用於所有 unified memory 的 效能提示 (performance hints)

項目 重點
only Managed Memory 裝置 CC 6.x+ 但無 pageable memory access;managed memory 完整且 coherent,但 GPU 不能 存取 system-allocated memory
Windows/WSL/Tegra 限制 指 CC < 6.0 或 Windows 上 concurrentManagedAccess = 0 的裝置
三大限制 (1) 無 on-demand 細粒度遷移、(2) 不可 oversubscribe、(3) CPU/GPU 不可同時存取
GPU page faulting 這些平台缺少;page faulting 只能從 CPU 側
Multi-GPU managed 配置經 P2P 對所有 GPU 可見;home 是 active device,他者走 PCIe 降頻
Linux 退路 若用到無 P2P 的 GPU,driver 把所有 managed 配置遷回 system memory
Windows 退路 無 peer mapping 時 fallback 到 mapped memory;可用 CUDA_VISIBLE_DEVICES / CUDA_MANAGED_FORCE_DEVICE_ALLOC
Coherency/Concurrency kernel 執行期間 GPU 獨佔 所有 managed data;CPU 同時存取 → segfault
Stream-associated UM cudaStreamAttachMemAsync 把 managed 配置綁到某 stream,縮小獨佔粒度到 per-stream
cudaMemAttachHost 配置初始對 device 不可見,供多執行緒安全私有化
Performance hints 只影響效能、永不影響正確性;適用任何 unified memory
cudaMemPrefetchAsync async stream-ordered,把資料遷到指定 processor 附近
cudaMemAdvise 設定資料使用提示(ReadMostly / PreferredLocation / AccessedBy)
Memory discarding cudaMemDiscardBatchAsync 告知內容已無用,省去多餘搬移
查詢屬性 cudaMemRangeGetAttribute(s) 查 advise/prefetch 設定
Oversubscription unified memory 讓配置超過任一 processor 記憶體容量(out-of-core)
Tip

unified memory 的行為高度依賴平台:寫可攜程式前先用 cudaDeviceGetAttributeconcurrentManagedAccesspageableMemoryAccess 等屬性,再決定能否同時存取、能否 oversubscribe。


4.1.2 僅支援 Managed Memory 的裝置

針對 compute capability 6.x 或更高、但沒有 pageable memory access 的裝置(見 Overview of Unified Memory Paradigms):

Warning

因為缺少 system allocator 支援,full support 章節中以下子題在此不適用:In-Depth Examples、CPU and GPU Page Tables(Hardware vs Software Coherency)、Atomic Accesses and Synchronization Primitives、Access Counter Migration、Avoid Frequent Writes to GPU-Resident Memory from the CPU、Exploiting Asynchronous Access to System Memory。


4.1.3 Windows / WSL / Tegra 上的 Unified Memory

Important

本節僅針對 compute capability < 6.0,或 Windows 平台上 concurrentManagedAccess 屬性為 0 的裝置。這些裝置支援 managed memory,但帶有以下三大限制。

限制 說明
Data Migration / Coherency 不支援把 managed data 細粒度 on-demand 搬到 GPU。kernel 一啟動,所有 managed memory 通常都要先搬到 GPU memory 以免存取時 fault。page faulting 只支援 CPU 側
GPU Memory Oversubscription 不能配置超過 GPU 實體記憶體大小的 managed memory
Coherency / Concurrency 無法同時存取 managed memory;因缺 GPU page faulting,kernel 執行中若 CPU 存取則無法保證 coherence

4.1.3.1 Multi-GPU

   active device = home (full bandwidth)
        ┌──────────┐
        │  GPU0    │ ← managed 實體配置在此
        └────┬─────┘
   PCIe (降頻) │   PCIe (降頻)
        ┌────┴─────┐  ┌──────────┐
        │  GPU1    │  │  GPU2    │  ← 經 P2P over PCIe 存取
        └──────────┘  └──────────┘

4.1.3.2 Coherency and Concurrency

為確保 coherency,當 CPU 與 GPU 同時執行時,programming model 對資料存取加上約束:

__device__ __managed__ int x, y = 2;
__global__ void kernel() { x = 10; }
int main() {
    kernel<<<1, 1>>>();
    y = 20;                    // 在不支援 concurrent access 的 GPU 上出錯
    cudaDeviceSynchronize();
}

上例在 CC 6.x(有 GPU page faulting)可成功,但在 pre-6.x 與 Windows 上失敗,因 CPU 觸碰 y 時 kernel 仍在執行。正確寫法是先同步再存取

int main() {
    kernel<<<1, 1>>>();
    cudaDeviceSynchronize();   // 確保 GPU 完工
    y = 20;                    // 成功
}

4.1.3.3 Stream-Associated Unified Memory

stream 表達 kernel launch 間的依賴與獨立關係:同一 stream 內保證連續執行,不同 stream 間允許並行(見 02-Programming-GPUs/14-Async-Streams-and-Events)。

Stream callbacks:在 stream callback 內 CPU 可存取 managed data,前提是沒有其他可能存取 managed data 的 stream 仍 active。後面不接 device work 的 callback 還可當同步點(例如在 callback 內 signal condition variable)。

四個重點:

  1. CPU 隨時可存取非 managed 的 mapped memory,即使 GPU active。
  2. 只要有任何 kernel 在跑,GPU 即視為 active,即使該 kernel 沒用到 managed data;只要 kernel 可能用到,存取就被禁止。
  3. 對 managed memory 的並行 inter-GPU 存取,限制與非 managed 的 multi-GPU 存取相同。
  4. 並行 GPU kernel 存取 managed data 沒有額外約束——也就是允許 kernel 間 race(與非 managed memory 現況相同)。從 GPU 角度看,managed 與非 managed memory 行為一致。

綁定 stream 以取得更細粒度控制

cudaError_t cudaStreamAttachMemAsync(cudaStream_t stream, void *ptr,
                                     size_t length = 0, unsigned int flags = 0);
預設 (global visibility)            綁定 stream 後
┌─────────────────────────┐        ┌─────────────────────────┐
│ 任一 kernel active       │        │ 只有 stream1 active 才   │
│  → CPU 全面禁止存取      │        │   禁止;其他 stream 跑   │
│                         │        │   時 CPU 仍可存取        │
└─────────────────────────┘        └─────────────────────────┘
Warning

綁定 stream 是程式對系統的「承諾」:保證只有該 stream 的 kernel 會碰這塊資料。unified memory 系統不做任何錯誤檢查;若其他 stream 的 kernel 存取,結果未定義。

多執行緒 host 程式:每個 CPU thread 建立自己的 stream(用 NULL stream 會在 thread 間造成依賴),並用 cudaStreamAttachMemAsync 把該 thread 的 managed 配置綁到自己的 stream,通常一生不變。

void run_task(int *in, int *out, int length) {
    cudaStream_t stream;
    cudaStreamCreate(&stream);
    int *data;
    cudaMallocManaged((void**)&data, length, cudaMemAttachHost); // 初始 device 不可見
    cudaStreamAttachMemAsync(stream, data);                      // 綁到私有 stream
    cudaStreamSynchronize(stream);
    // ... host 與 device 交替使用 data ...
}

Stream-associated UM 的資料搬移concurrentManagedAccess = 0 時):

操作 行為
cudaMemcpyHostTo* 來源是 UM 若在 copy stream 中對 host coherent(既無 global visibility 也未綁該 stream)則從 host 存取,否則從 device
cudaMemcpyDeviceTo* 來源是 UM 從 device 存取;來源須對 device coherent(有 global visibility 或綁該 stream),否則回傳錯誤
cudaMemcpyDefault 若無法對 device coherent,或 preferred location 是 cudaCpuDeviceId 且可對 host coherent,則從 host,否則從 device
cudaMemset* 資料須對該 stream 的 device coherent,否則回傳錯誤

當 memcpy/memset 從 device 存取資料時,該 stream 視為 GPU active;期間 CPU 存取「綁該 stream」或「global visibility」的資料會 segfault(若 concurrentManagedAccess = 0)。


4.1.4 Performance Hints(效能提示)

效能提示給 CUDA 更多 unified memory 用法資訊,讓 driver 更有效率地管理 managed memory。

Important

效能提示永不影響正確性,只影響效能。應只在確實提升效能時才使用。提示可用於任何 unified memory 配置(含 CUDA managed memory);在 full support 系統上,也可套用到所有 system-allocated memory。

4.1.4.1 Data Prefetching(資料預取)

cudaMemPrefetchAsyncasync、stream-ordered API,可把資料遷到指定 processor 附近:

cudaError_t cudaMemPrefetchAsync(const void *devPtr, size_t count,
                                 struct cudaMemLocation location,
                                 unsigned int flags, cudaStream_t stream = 0);

4.1.4.2 Data Usage Hints(cudaMemAdvise

當多個 processor 同時存取同一資料時,用 cudaMemAdvise 提示存取模式:

cudaError_t cudaMemAdvise(const void *devPtr, size_t count,
                          enum cudaMemoryAdvise advice,
                          struct cudaMemLocation location);
advice 意義
cudaMemAdviseSetReadMostly 資料大多被讀、偶爾寫;用 read bandwidth 換 write bandwidth。配合 prefetch 會造成 read duplication(多處複本)而非遷移
cudaMemAdviseSetPreferredLocation 設偏好位置為指定裝置實體記憶體(cudaMemLocationTypeHost 則為 CPU memory)。只是鼓勵保留於此、不保證;其他提示如 prefetch 可覆寫並遷走
cudaMemAdviseSetAccessedBy 在從某 processor 存取前先建立 mapping;告知資料會被 location.id 頻繁存取。不暗示資料該放哪,可與 PreferredLocation 合用。在 hardware-coherent 系統上會開啟 access counter migration
cudaMemLocation loc = {.type = cudaMemLocationTypeDevice, .id = myGpuId};
cudaMemAdvise(dataPtr, dataSize, cudaMemAdviseSetReadMostly, loc);
// 外層: CPU 每圈改寫資料 -> 對各 GPU prefetch (read duplication)
// 內層: kernel 只讀 dataPtr
Tip

ReadMostly + 對多個 GPU cudaMemPrefetchAsync = read duplication:每個讀者保有自己的唯讀複本,避免反覆遷移。代價是寫入時需失效所有複本,故適合「寫一次、多讀」的型態。

4.1.4.3 Memory Discarding(記憶體丟棄)

cudaMemDiscardBatchAsync 告知 runtime 指定範圍的內容已無用。UM driver 會因 fault-based migration 或 oversubscription 的 eviction 自動搬移資料,這些搬移有時是多餘的、嚴重拖慢效能;標記為 discard 後,driver 在 prefetch 或 page eviction 時不需再搬移這塊資料。

cudaError_t cudaMemDiscardBatchAsync(void **dptrs, size_t *sizes, size_t count,
                                     unsigned long long flags, cudaStream_t stream);
Warning

Memory discarding 的注意事項:

  • 讀取已 discard 的範圍而未先寫入或 prefetch → 回傳不定值 (indeterminate)
  • discard 可被「寫入該範圍」或「以 cudaMemPrefetchAsync prefetch」還原;discard 後的新寫入保證被後續讀取看到。
  • 與 discard 同時發生的任何 read/write/prefetch → undefined behavior
  • 所有裝置的 cudaDevAttrConcurrentManagedAccess 必須為非零。

4.1.4.4 查詢資料使用屬性

cudaMemRangeGetAttribute 查詢經 cudaMemAdvise/cudaMemPrefetchAsync 設於 managed memory 的屬性(範圍須為 cudaMallocManaged__managed__):

cudaMemRangeGetAttribute(void *data, size_t dataSize,
                         enum cudaMemRangeAttribute attribute,
                         const void *devPtr, size_t count);
attribute 回傳
cudaMemRangeAttributeReadMostly 整段都設了 ReadMostly 回 1,否則 0
cudaMemRangeAttributePreferredLocation 整段同一偏好位置時回 GPU device id 或 cudaCpuDeviceId,否則 cudaInvalidDeviceId(實際位置可能不同於偏好位置)
cudaMemRangeAttributeAccessedBy 回傳設了 AccessedBy 的裝置清單
cudaMemRangeAttributeLastPrefetchLocation 最後一次以 cudaMemPrefetchAsync 請求 prefetch 的位置(只反映「請求」,不保證已開始或完成)
cudaMemRangeAttributePreferredLocationType 偏好位置型別:Device / Host / HostNuma / Invalid(不一致或部分無偏好)
cudaMemRangeAttributePreferredLocationId Type 為 Device 回 device ordinal;HostNuma 回 host NUMA node ID;否則忽略
cudaMemRangeAttributeLastPrefetchLocationType 最後 prefetch 的位置型別:Device / Host / HostNuma / Invalid
cudaMemRangeAttributeLastPrefetchLocationId Type 為 Device 回 device ordinal;HostNuma 回 host NUMA node ID;否則忽略

4.1.4.5 GPU Memory Oversubscription(超額配置)

unified memory 讓應用程式超額配置 (oversubscribe) 任一 processor 的記憶體:可配置並共享大於系統中任一 processor 記憶體容量的陣列,在不顯著增加程式複雜度下,支援放不進單一 GPU 的資料集做 out-of-core 處理。

Warning

Oversubscription 僅限 full support 與 only-managed 等支援 GPU page faulting 的平台。在 4.1.3 的 Windows/WSL/Tegra(concurrentManagedAccess = 0)平台上無法 oversubscribe——不能配置超過 GPU 實體記憶體大小的 managed memory。


考試/測驗重點

情境/關鍵字 答案
only-managed 裝置的硬限制 GPU 不能存取 system-allocated memory;不能用 system allocator 配置 UM
4.1.3 適用對象 CC < 6.0,或 Windows 上 concurrentManagedAccess = 0 的裝置
這些平台缺什麼機制 GPU page faulting(page faulting 只能 CPU 側)
kernel 啟動時 managed memory 如何處理 通常整批先搬到 GPU memory 以免 fault
能否 oversubscribe (Win/WSL/Tegra) 不能;上限為 GPU 實體記憶體大小
kernel 執行中 CPU 碰 managed data segfault(即使 kernel 沒用到該塊)
正確存取 y 的做法 先 cudaDeviceSynchronize 再存取
multi-GPU managed 的 home 當前 active device;他者經 PCIe 降頻
Linux 用到無 P2P 的 GPU 會怎樣 driver 把所有 managed 配置遷回 system memory
Windows 無 peer mapping fallback 到 mapped memory
限制只用一個 GPU 的環境變數 CUDA_VISIBLE_DEVICES
強制 device memory 儲存的環境變數 CUDA_MANAGED_FORCE_DEVICE_ALLOC(須全 P2P 相容,否則 cudaErrorInvalidDevice)
cudaStreamAttachMemAsync 作用 把 managed 配置綁某 stream,獨佔縮為 per-stream
未綁 stream 的預設可見性 對所有 stream 的 kernel 可見(global visibility)
cudaMemAttachHost flag 配置初始對 device 不可見,供多執行緒安全私有化
stream 銷毀對綁定的影響 配置還原為預設可見性
coherently accessible from host 定義 既無 global visibility 也未綁該 stream
coherently accessible from device 定義 有 global visibility 或綁該 stream
performance hint 是否影響正確性 否;只影響效能
cudaMemPrefetchAsync 何時開始遷移 stream 前序操作全完成後,後續操作前完成
ReadMostly + 多 GPU prefetch read duplication(複本),非遷移
SetPreferredLocation 是保證嗎 否;只鼓勵,prefetch 等可覆寫
SetAccessedBy 在 HW-coherent 系統 開啟 access counter migration
讀取已 discard 範圍 不定值 (indeterminate),除非先寫或 prefetch
discard 如何還原 寫入該範圍或 cudaMemPrefetchAsync
discard 的裝置前提 所有裝置 cudaDevAttrConcurrentManagedAccess 非零
查 advise/prefetch 屬性的 API cudaMemRangeGetAttribute(s)
LastPrefetchLocation 語意 最後「請求」prefetch 的位置,不保證已完成
oversubscription 用途 out-of-core 處理放不進單 GPU 的資料集