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) |
unified memory 的行為高度依賴平台:寫可攜程式前先用 cudaDeviceGetAttribute 查 concurrentManagedAccess、pageableMemoryAccess 等屬性,再決定能否同時存取、能否 oversubscribe。
4.1.2 僅支援 Managed Memory 的裝置
針對 compute capability 6.x 或更高、但沒有 pageable memory access 的裝置(見 Overview of Unified Memory Paradigms):
- CUDA managed memory 完整支援且 coherent。
- 但 GPU 不能存取 system-allocated memory(
malloc/new/mmap配置的記憶體)。 - programming model 與效能調校大致與 full support 相同,唯一例外:不能用 system allocator 配置 unified memory,必須用
cudaMallocManaged等 CUDA API。
因為缺少 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
本節僅針對 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
- managed 配置透過 GPU 的 peer-to-peer (P2P) 能力,自動對系統中所有 GPU 可見。
- 行為類似
cudaMalloc():當前 active device 是實體配置的 home,其他 GPU 經 PCIe 以降低的頻寬存取。
active device = home (full bandwidth)
┌──────────┐
│ GPU0 │ ← managed 實體配置在此
└────┬─────┘
PCIe (降頻) │ PCIe (降頻)
┌────┴─────┐ ┌──────────┐
│ GPU1 │ │ GPU2 │ ← 經 P2P over PCIe 存取
└──────────┘ └──────────┘
- Linux:只要所有「正在使用」的 GPU 都有 P2P,managed memory 就留在 GPU memory。一旦程式開始使用某個與其他持有 managed 配置的 GPU 沒有 P2P 的 GPU,driver 會把所有 managed 配置遷回 system memory,此時所有 GPU 都受 PCIe 頻寬限制。
- Windows:若 peer mapping 不可用(例如不同架構的 GPU 之間),系統自動 fallback 到 mapped memory,不論兩個 GPU 是否真的都被用到。若實際上只用一個 GPU,需在啟動前設定
CUDA_VISIBLE_DEVICES限制可見 GPU,才能讓 managed memory 配置在 GPU memory。 - Windows 替代方案:設
CUDA_MANAGED_FORCE_DEVICE_ALLOC為非零值,強制 driver 一律用 device memory 作為實體儲存。此時 process 中所有支援 managed memory 的裝置必須彼此 P2P 相容,否則回傳cudaErrorInvalidDevice(即使已對該裝置呼叫cudaDeviceReset也一樣)。
4.1.3.2 Coherency and Concurrency
為確保 coherency,當 CPU 與 GPU 同時執行時,programming model 對資料存取加上約束:
- 任何 kernel 執行期間,GPU 獨佔所有 managed data,不論該 kernel 是否實際用到那塊資料。
- CPU 同時存取(即使是不同的 managed 配置)會造成 segmentation fault,因為該 page 被視為對 CPU 不可存取。
__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; // 成功
}
- 任何「邏輯上保證 GPU 完工」的呼叫都有效(見 Explicit Synchronization)。
- 若在 GPU 仍 active 時用
cudaMallocManaged()/cuMemAllocManaged()動態配置,其行為在「再啟動工作或同步 GPU」之前未定義,期間 CPU 存取可能(也可能不)segfault。例外:用cudaMemAttachHost/CU_MEM_ATTACH_HOSTflag 配置者不受此限。
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)。
四個重點:
- CPU 隨時可存取非 managed 的 mapped memory,即使 GPU active。
- 只要有任何 kernel 在跑,GPU 即視為 active,即使該 kernel 沒用到 managed data;只要 kernel 可能用到,存取就被禁止。
- 對 managed memory 的並行 inter-GPU 存取,限制與非 managed 的 multi-GPU 存取相同。
- 並行 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);
- 把
ptr起算lengthbytes 綁到stream:只要該 stream 的所有操作完成,CPU 就可存取這塊記憶體,不論其他 stream 是否 active。 - 等於把「active GPU 對 managed 區的獨佔」從 whole-GPU 縮小為 per-stream。
- 未綁定 stream 的配置對所有 stream 的 kernel 可見(
cudaMallocManaged與__managed__的預設)——這正是「任何 kernel 在跑時 CPU 不可碰」規則的由來。
預設 (global visibility) 綁定 stream 後
┌─────────────────────────┐ ┌─────────────────────────┐
│ 任一 kernel active │ │ 只有 stream1 active 才 │
│ → CPU 全面禁止存取 │ │ 禁止;其他 stream 跑 │
│ │ │ 時 CPU 仍可存取 │
└─────────────────────────┘ └─────────────────────────┘
綁定 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 ...
}
cudaMemAttachHostflag 讓配置初始對 device-side 執行不可見,避免「配置到取得」之間被別的 thread 的 kernel 意外視為 in-use。- 替代方案是配置後跨所有 thread 做 process-wide barrier;但 stream 銷毀會讓配置還原為預設可見性,故銷毀前還需第二道 barrier。
cudaMemAttachHost正是為簡化此流程而存在。
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。
效能提示永不影響正確性,只影響效能。應只在確實提升效能時才使用。提示可用於任何 unified memory 配置(含 CUDA managed memory);在 full support 系統上,也可套用到所有 system-allocated memory。
4.1.4.1 Data Prefetching(資料預取)
cudaMemPrefetchAsync 是 async、stream-ordered API,可把資料遷到指定 processor 附近:
cudaError_t cudaMemPrefetchAsync(const void *devPtr, size_t count,
struct cudaMemLocation location,
unsigned int flags, cudaStream_t stream = 0);
[devPtr, devPtr + count)在 stream 中執行 prefetch task 時遷到:location.type == cudaMemLocationTypeDevice時遷到location.id指定的 GPU;cudaMemLocationTypeHost時遷到 CPU。- 遷移在 stream 中所有前序操作完成後才開始,並在後續操作前完成;prefetch 進行中仍可存取資料。
- 用法:kernel 使用前先 prefetch 到 GPU,使用後再 prefetch 回 CPU。system allocator(
malloc)與 managed(cudaMallocManaged)配置寫法一致。
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 |
- 三者各有對應的 Unset 版本:
cudaMemAdviseUnsetReadMostly、cudaMemAdviseUnsetPreferredLocation、cudaMemAdviseUnsetAccessedBy。
cudaMemLocation loc = {.type = cudaMemLocationTypeDevice, .id = myGpuId};
cudaMemAdvise(dataPtr, dataSize, cudaMemAdviseSetReadMostly, loc);
// 外層: CPU 每圈改寫資料 -> 對各 GPU prefetch (read duplication)
// 內層: kernel 只讀 dataPtr
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);
- 對
dptrs/sizes指定的多個範圍批次丟棄;兩陣列長度皆為count。每個範圍必須是cudaMallocManaged或__managed__的 managed memory。 cudaMemDiscardAndPrefetchBatchAsync合併 discard 與 prefetch,語意等同「cudaMemDiscardBatchAsync後接cudaMemPrefetchBatchAsync」但更省。用於「需要記憶體在目標位置、但不需要其內容」時。其prefetchLocs指定各 prefetch 目的地、prefetchLocIdxs指出每個 prefetch location 套用到哪些操作(例:10 個操作前 6 個到 A、後 4 個到 B →numPrefetchLocs=2、prefetchLocIdxs={0, 6})。
Memory discarding 的注意事項:
- 讀取已 discard 的範圍而未先寫入或 prefetch → 回傳不定值 (indeterminate)。
- discard 可被「寫入該範圍」或「以
cudaMemPrefetchAsyncprefetch」還原;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;否則忽略 |
- 可用對應的
cudaMemRangeGetAttributes(複數)一次查多個屬性。
4.1.4.5 GPU Memory Oversubscription(超額配置)
unified memory 讓應用程式超額配置 (oversubscribe) 任一 processor 的記憶體:可配置並共享大於系統中任一 processor 記憶體容量的陣列,在不顯著增加程式複雜度下,支援放不進單一 GPU 的資料集做 out-of-core 處理。
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 的資料集 |
Related Notes
- 04-CUDA-Features/01-Unified-Memory-Full-Support
- 04-CUDA-Features/06-Stream-Ordered-Memory-Allocator
- 04-CUDA-Features/20-Virtual-Memory-Management
- 04-CUDA-Features/21-Extended-GPU-Memory
- 04-CUDA-Features/19-Interprocess-Communication
- 02-Programming-GPUs/16-Unified-and-System-Memory
- 02-Programming-GPUs/14-Async-Streams-and-Events
- 02-Programming-GPUs/15-Async-Callbacks-Ordering-Graphs
- 04-CUDA-Features/Practice-CUDA-Features
- 00-Dashboard/Exam-Traps