多 GPU 程式設計 (Multi-GPU Programming)

重點總覽

多 GPU 程式設計透過聚合多顆 GPU 的算力、記憶體容量與頻寬,突破單一 GPU 的問題規模與效能上限。基本流程為:枚舉裝置 → 選擇裝置並建立 context → 分配資料、啟動 kernel → 透過 peer-to-peer 傳輸或存取進行通訊與收集結果。

項目 重點
多 GPU 編程支柱 host thread context 管理、unified virtual addressing、P2P bulk 傳輸、fine-grained P2P load/store、上層抽象 (IPC / NCCL / NVSHMEM / GPUDirect RDMA)
Device Enumeration cudaGetDeviceCount() 取得裝置數、cudaGetDeviceProperties()cudaDeviceProp(含 major/minor compute capability)
Device Selection cudaSetDevice() 設定 host thread 的 current device;分配/launch 在 current device 上;未呼叫前預設 device 0
Stream/Event 綁定 stream/event 建立時即綁定 current device;kernel 必須 launch 到綁定 current device 的 stream,否則失敗
Memory Copy 行為 memcpy 即使 stream 非 current device 也能成功;implicit NULL stream 的跨裝置 copy 具同步語意
P2P Transfers cudaMemcpyPeer() 系列;可走 copy engine 與 NVLink;啟用 P2P 後不需經 host 中轉,更快
P2P Access cudaDeviceCanAccessPeer() 查詢、cudaDeviceEnablePeerAccess() 啟用;kernel 可直接 deref 對方記憶體指標;非 NVSwitch 系統每裝置上限 8 個 peer 連線
P2P Consistency 跨裝置同步屬 thread_scope_system;atomic RMW 僅限單一 GPU 存取該物件時
Managed Memory 在具 P2P 支援的多 GPU 系統上可用 managed memory
IOMMU / PCI ACS / VM Linux bare-metal 須關閉 IOMMU(否則 silent 記憶體損壞);VM pass-through 須開啟 IOMMU + VFIO;PCI ACS 會把流量繞經 CPU root complex 而降速

多 GPU 程式設計總覽

CUDA 透過 host API、driver 基礎設施與 GPU 硬體技術支援多 GPU 程式設計,核心能力包含:

Important

CUDA 本身提供實作 collective 所需的低階 API,但不提供上層 multi-GPU collective API。Multi-GPU collectives 由更高抽象的 NCCLNVSHMEM 等通訊函式庫提供。

常見的多 GPU 編程映射方式(依平行度與既有程式結構選擇):

[單 host thread] ──► 驅動多顆 GPU
[多 host threads] ──► 每個 thread 各驅動一顆 GPU
[多個單執行緒 process] ──► 每個 process 各驅動一顆 GPU
[多執行緒多 process] ──► 每 thread 各驅動一顆 GPU
[Multi-node NVLink cluster] ──► 跨節點 OS instance 由 thread/process 驅動
Tip

同一 host process 內的多 GPU 通訊,靠 unified virtual addressing 只需極少步驟即可查詢並啟用高效能 P2P access/transfer(如經 NVLink)。跨 host process 的多 GPU 通訊則需 IPCVirtual Memory Management (VMM) API;VMM 支援 intra-node 與 multi-node,Linux/Windows 皆可用,並能以 per-allocation 粒度控制 IPC 共享。


3.4.1.1 Device Enumeration(裝置枚舉)

使用前先查詢可用的 CUDA 裝置數量、逐一枚舉並查詢其硬體屬性。

int deviceCount;
cudaGetDeviceCount(&deviceCount);
int device;
for (device = 0; device < deviceCount; ++device) {
    cudaDeviceProp deviceProp;
    cudaGetDeviceProperties(&deviceProp, device);
    printf("Device %d has compute capability %d.%d.\n",
           device, deviceProp.major, deviceProp.minor);
}

重點:cudaGetDeviceCount() 回傳裝置總數;對每個 device index 用 cudaGetDeviceProperties() 填入 cudaDeviceProp,其中 major/minor 即 compute capability。


3.4.1.2 Device Selection(裝置選擇與 current device 語意)

Host thread 可在任意時刻呼叫 cudaSetDevice() 切換目前操作的裝置。

size_t size = 1024 * sizeof(float);
cudaSetDevice(0);              // Set device 0 as current
float* p0;
cudaMalloc(&p0, size);         // Allocate memory on device 0
MyKernel<<<1000, 128>>>(p0);   // Launch kernel on device 0
cudaSetDevice(1);              // Set device 1 as current
float* p1;
cudaMalloc(&p1, size);         // Allocate memory on device 1
MyKernel<<<1000, 128>>>(p1);   // Launch kernel on device 1

關鍵語意:current device 決定後續操作的歸屬。

        cudaSetDevice(0)                cudaSetDevice(1)
            │                               │
   current=0│  cudaMalloc → p0@dev0   current=1│  cudaMalloc → p1@dev1
            │  launch     → @dev0             │  launch     → @dev1

3.4.1.3 Multi-Device Stream / Event / Memory Copy 行為

跨裝置時,stream 與 event 的「綁定裝置」會決定操作成敗。

cudaSetDevice(0);
cudaStream_t s0;
cudaStreamCreate(&s0);              // stream s0 綁定 device 0
MyKernel<<<100, 64, 0, s0>>>();     // OK:current=0,s0 屬 device 0
cudaSetDevice(1);
cudaStream_t s1;
cudaStreamCreate(&s1);              // stream s1 綁定 device 1
MyKernel<<<100, 64, 0, s1>>>();     // OK:current=1,s1 屬 device 1
// 失敗:current=1,但 s0 綁定的是 device 0
MyKernel<<<100, 64, 0, s0>>>();

重點:kernel launch 必須送往綁定 current device 的 stream,否則 launch 失敗。

各 API 的跨裝置行為整理:

API 跨裝置條件 結果
kernel launch stream 未綁定 current device 失敗
memory copy issue 到非 current device 的 stream 成功
cudaEventRecord() event 與 stream 綁定不同裝置 失敗
cudaEventElapsedTime() 兩個 event 綁定不同裝置 失敗
cudaEventSynchronize() / cudaEventQuery() event 綁定裝置 ≠ current device 成功
cudaStreamWaitEvent() stream 與 event 綁定不同裝置 成功(可用於跨裝置同步)
Tip

cudaStreamWaitEvent() 即使 stream 與 event 屬不同裝置仍會成功,因此可作為多裝置間互相同步的工具。

Warning

每個裝置都有自己的 default stream。送往某裝置 default stream 的命令,相對於送往其他裝置 default stream 的命令,可能 out-of-order 或並行執行——不要假設跨裝置 default stream 之間有隱含順序。


3.4.2.1 Peer-to-Peer Memory Transfers(P2P 記憶體傳輸)

CUDA 可在裝置間搬移記憶體,並在 P2P access 可行時利用專屬 copy engineNVLink 硬體最大化效能。

cudaSetDevice(1);
cudaMemcpyPeer(p1, 1, p0, 0, size); // 將 device 0 的 p0 複製到 device 1 的 p1
MyKernel<<<1000, 128>>>(p1);        // Launch kernel on device 1

cudaMemcpyPeer(dst, dstDevice, src, srcDevice, size) 明確指定來源/目的裝置編號。

implicit NULL stream 的跨裝置 copy 具有同步語意

其他命令(任一裝置) ──┐
                     ▼  copy 不會開始,直到先前所有命令完成
              [ 跨裝置 copy ](NULL stream)
                     │  copy 完成前,之後送往任一裝置的命令都不能開始
                     ▼
   copy 後續命令(任一裝置) ───►
Important

若兩裝置間已啟用 P2P access,P2P copy 就不需再經由 host 中轉 (staging),因此更快。


3.4.2.2 Peer-to-Peer Memory Access(P2P 記憶體存取)

依系統的 PCIe / NVLink 拓樸,一個裝置上執行的 kernel 可以直接 dereference 指向另一裝置記憶體的指標。

cudaSetDevice(0);
float* p0;
cudaMalloc(&p0, size);
MyKernel<<<1000, 128>>>(p0);       // Launch kernel on device 0

cudaSetDevice(1);
cudaDeviceEnablePeerAccess(0, 0);  // device 1 啟用對 device 0 的 peer access
// 此 kernel 在 device 1 執行,可存取 device 0 上位於 p0 的記憶體
MyKernel<<<1000, 128>>>(p0);
   ┌─────────┐  cudaDeviceEnablePeerAccess(0,0)  ┌─────────┐
   │ Device 1│ ───────────────(NVLink/PCIe)────► │ Device 0│
   │ kernel  │   deref p0 (UVA 同一指標)          │  p0 mem │
   └─────────┘                                   └─────────┘
Warning

NVSwitch 系統中,每個裝置的 system-wide peer 連線上限為 8 個

Warning

cudaDeviceEnablePeerAccess() 會對 peer 裝置上所有先前與後續的記憶體分配全域生效。這會讓該 peer 上的 device memory 分配付出額外 runtime 成本(須讓分配立即對 current device 與其他 peer 可見),且開銷會隨 peer 數量呈乘性 (multiplicative) 增長。

Tip

更具擴展性的替代方案:使用 CUDA Virtual Memory Management API,在 allocation time 按需只將需要的記憶體區域標為 peer-accessible。如此不影響非 peer 可存取分配的成本,且 peer-accessible 資料結構範圍正確,利於除錯與可靠性。


3.4.2.3 Peer-to-Peer Memory Consistency(P2P 一致性)

跨多裝置分散的 grid 中,並行執行的 thread 之間必須以同步操作強制記憶體存取的順序與正確性。

Warning

peer device memory 的 atomic RMW 不是無條件成立——只有在單一 GPU 存取該物件的前提下才保證。多 GPU 同時對同一物件做 atomic 並不在此保證範圍內。


3.4.2.4 Multi-Device Managed Memory(多裝置 managed memory)


3.4.2.5 Host IOMMU / PCI ACS / VMs 的影響

硬體與虛擬化設定會直接影響 P2P 是否可用與效能。

環境 IOMMU 設定 後果 / 要求
Linux bare-metal 必須關閉 IOMMU CUDA + display driver 不支援 IOMMU-enabled bare-metal PCIe P2P;若不關閉會 silent device memory corruption
Linux VM pass-through 開啟 IOMMU 須使用 VFIO driver 做 PCIe pass-through;此情況下 CUDA 支援 IOMMU
Windows 無此限制 上述 IOMMU 限制在 Windows 不存在
Warning

PCI Access Control Services (ACS) 可在支援 IOMMU 的系統上啟用,但 ACS 會把所有 PCI point-to-point 流量改道經過 CPU root complex,因 bisection bandwidth 降低而造成顯著效能損失。做 P2P 時通常應留意並停用不必要的 ACS。


考試/測驗重點

情境/關鍵字 答案
取得裝置數量 cudaGetDeviceCount()
查 compute capability major/minor cudaGetDeviceProperties()cudaDeviceProp
切換 current device cudaSetDevice()(任意時刻可呼叫)
未呼叫 cudaSetDevice 前的 current device 預設 device 0
分配/launch 落在哪個裝置 current device
stream/event 綁定哪個裝置 建立時的 current device
kernel launch 到非 current device 的 stream 失敗
memory copy 到非 current device 的 stream 成功
cudaEventRecord event 與 stream 不同裝置 失敗
cudaEventElapsedTime 兩 event 不同裝置 失敗
cudaEventSynchronize/cudaEventQuery 跨裝置 成功
用什麼做跨裝置同步 cudaStreamWaitEvent()(跨裝置仍成功)
不同裝置的 default stream 之間順序 無保證,可能亂序/並行
顯式指定來源/目的裝置的 copy cudaMemcpyPeer() 系列
cudaMemcpy 可用的跨裝置 copy type cudaMemcpyDeviceToDevice / cudaMemcpyDefault
NULL stream 跨裝置 copy 的順序語意 先等任一裝置先前命令完成;copy 完成前任一裝置後續命令不可開始
查詢兩裝置可否 P2P 存取 cudaDeviceCanAccessPeer()(true 才支援)
啟用 P2P 存取 cudaDeviceEnablePeerAccess()
非 NVSwitch 系統 peer 連線上限 每裝置 8 個
為何同一指標可定址兩裝置 unified virtual address space (UVA)
EnablePeerAccess 的成本特性 對 peer 所有分配全域生效,隨 peer 數呈乘性開銷
更可擴展的 peer 記憶體做法 用 VMM API 於 allocation time 按需標 peer-accessible
跨裝置同步的 thread scope thread_scope_system
peer device memory atomic RMW 限制 僅單一 GPU 存取該物件時
managed memory 多 GPU 前提 系統具 P2P 支援
Linux bare-metal IOMMU 必須關閉,否則 silent memory corruption
Linux VM PCIe pass-through 開啟 IOMMU + 用 VFIO driver
Windows 的 IOMMU 限制 不存在
PCI ACS 啟用後果 流量繞經 CPU root complex,bisection bandwidth 下降、效能損失
CUDA 是否提供 multi-GPU collective 否;由 NCCL / NVSHMEM 提供