Virtual Memory Management

重點總覽

項目 重點
動機 cudaEnablePeerAccess 會把「過去與未來」所有 cudaMalloc 配置都映射到 peer device,成本高且難擴展到多節點
VMM 核心 把「虛擬位址保留」與「實體記憶體配置」分離,使用者顯式控制 reserve / map / access
主要好處 非連續實體 chunk 映射到連續 VA(降碎裂)、依需求 map、動態成長(類 realloc/std::vector)、跨 GPU 共享
層級 屬低階 Driver API;需直接使用 cu* 函式;NCCL、NVSHMEM 內部大量採用
前提 系統須支援 UVA;逐 device 用 cuDeviceGetAttribute 查詢 VMM / fabric / multicast 支援
Handle 種類 OS-specific(POSIX fd / Win32,限單節點 IPC)vs Fabric(CU_MEM_HANDLE_TYPE_FABRIC,可跨節點,需 IMEX)
Fabric Memory 透過 NVLink/NVSwitch fabric 存取的一致性高頻寬記憶體;多節點需 NVIDIA IMEX daemon
五步驟(Unicast) Allocate & Export → Share & Import → Reserve & Map → Access Rights → Releasing
cuMemCreate 配置實體記憶體;回傳 CUmemGenericAllocationHandle(非指標、尚不可存取);size 須對齊 granularity
cuMemAddressReserve 保留 VA 範圍(類 mmap / VirtualAlloc);cuMemMap 把 VA 綁定到實體 handle
cuMemSetAccess 只 map 不會可存取;必須顯式設 access,否則 kernel 存取會 crash;做到 allocation 粒度的 peer 映射
釋放順序 必須 cuMemUnmap → cuMemRelease → cuMemAddressFree(嚴格此序)
Multicast cuMulticastCreate 建 N-GPU multicast object,每 GPU 一份實體 replica;配 multimem PTX 用 NVLink SHARP
Advanced memory type(allocFlags)、compressible memory、virtual aliasing(多 proxy 一致性)、OS-specific IPC
VMM 解決的核心問題

傳統 cudaMalloc + cudaEnablePeerAccess 是「全有」模型:一旦對某 peer 開啟存取,所有過去與未來的配置都被映射過去,付出不必要的 runtime 成本。VMM 把控制權下放到 allocation 粒度,只把真正要共享的少數配置映射出去,並能延伸到多節點。

動機與設計(為何需要 VMM)

CUDA 一般配置(如 cudaMalloc)直接回傳可用的 GPU 位址,位址與實體記憶體綁死。VMM 仿照 OS 的虛擬記憶體模型,分兩階段:先保留一段連續虛擬位址(不配實體),用到時再提交/映射實體儲存。

何時用 VMM

一般應用建議用更高階的 MPI / NCCL / NVSHMEM。VMM 適合「函式庫作者」與需要自訂 allocator、KV-cache、zero-copy、細粒度共享的進階情境。

Preliminaries:定義與支援查詢

關鍵名詞

名詞 定義
Fabric Memory 透過 NVLink/NVSwitch fabric 存取的記憶體,提供跨 GPU/節點的一致性與高頻寬,如同記憶體掛在統一 fabric 上
Memory Handle 不透明識別子,唯一代表一份實體配置;不暴露指標,可跨 process/device export/import
IMEX Channel internode memory exchange;driver 提供的使用者層隔離與安全機制,跨節點 fabric 共享的必要條件
Unicast Access 把實體記憶體受控、直接映射到某特定 device/process 的單一 VA 範圍,賦予明確讀寫權
Multicast Access 單一實體區域同時映射到多個 device 的 VA,一對多共享,減少冗餘傳輸(NVLink SHARP)

逐 device 查詢支援

int deviceSupportsVmm;
cuDeviceGetAttribute(&deviceSupportsVmm,
    CU_DEVICE_ATTRIBUTE_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED, device);
// fabric:CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_FABRIC_SUPPORTED
// multicast:CU_DEVICE_ATTRIBUTE_MULTICAST_SUPPORTED

IMEX channels 環境檢查(fabric 共享)

# 1) 確認 character device 存在(取得 major number,如 234)
cat /proc/devices | grep nvidia    # 出現 nvidia-caps-imex-channels
# 2) exporter 與 importer 須能存取同一 channel 檔
mknod /dev/nvidia-caps-imex-channels/channelN c <major_number> 0
本節 API 的硬性前提

整套 VMM API 需要系統支援 UVA。fabric handle 還額外要求 sysadmin 啟用 IMEX channels,否則 export/import 會失敗。

API Overview 與工作流程

VMM 是非常低階的 API,必須直接用 CUDA Driver API,可用於單節點與多節點。使用者需具備 OS 虛擬記憶體、記憶體階層、IPC 方法(socket/message passing)、與記憶體存取權安全的基本知識。

工作流程的核心是「在 source device 配置實體記憶體後,用 handle 把必要資訊傳給 target」。export 的 handle 可為 OS-specific(限單節點 IPC)或 fabric-specific(單/多節點,需 IMEX)。share 用何種 IPC 由開發者自選;之後雙方各自 reserve VA、map、設 access。

              ┌─ 評估環境設定 ─┐
              │  選擇 handle 類型 │
        ┌─────┴─────┐     ┌─────┴─────┐
   OS-specific handle   Fabric handle
   (單節點 IPC)        (單/多節點,需 IMEX)
        └─────┬─────┘     └─────┬─────┘
              └────────┬────────┘
        ─── 後段操作對兩種 handle 完全相同 ───
            Reserve VA → Map → 設定 Access Rights
關鍵不對稱

前段(export / share / import)依 handle 類型而異;但後段的 map、reserve、access 設定對兩種 handle 完全相同

Unicast Memory Sharing:五步驟

Allocate & Export → Share & Import → Reserve & Map → Access Rights → Releasing。
此流程同時涵蓋「VA → GPU 實體位址(PA)→ 網路 Fabric 位址(FA)」的映射。

1. Allocate & Export

cuMemCreate 配置實體記憶體 backing。回傳的 CUmemGenericAllocationHandle 不是指標、尚不可存取CUmemAllocationProp 描述位置、是否分享、實體屬性;size 須對齊以 cuMemGetAllocationGranularity 查到的 granularity。

CUmemAllocationProp prop = {};
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
prop.location.id = device;
prop.requestedHandleType = CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR; // 或 _FABRIC
size_t granularity = 0;
cuMemGetAllocationGranularity(&granularity, &prop, CU_MEM_ALLOC_GRANULARITY_MINIMUM);
size_t padded_size = ROUND_UP(size, granularity);   // 對齊到 granularity
CUmemGenericAllocationHandle allocHandle;
cuMemCreate(&allocHandle, padded_size, &prop, 0);

export 把 handle 轉成可分享形式:

int fd;
cuMemExportToShareableHandle(&fd, handle, CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR, 0);
// fabric:
CUmemFabricHandle fh;
cuMemExportToShareableHandle(&fh, handle, CU_MEM_HANDLE_TYPE_FABRIC, 0);

2. Share & Import

export 後須用 IPC 把 handle 傳給接收方,方法由開發者自選。OS-specific IPC(如 Unix domain socket 配 SCM_RIGHTS、Win32 DuplicateHandle)效能高但限同機、不可攜;fabric IPC(如 MPI_Send/MPI_RecvCUmemFabricHandle)簡單可攜但需系統層支援。

// Linux socket:以 SCM_RIGHTS 經 cmsg 傳送 fd(sendmsg / recvmsg)
// Fabric:MPI_Send(&fh, sizeof(CUmemFabricHandle), MPI_BYTE, 1, 0, MPI_COMM_WORLD);
cuMemImportFromShareableHandle(handle, (void*)&fd, CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR);
// fabric:cuMemImportFromShareableHandle(handle, (void*)&fh, CU_MEM_HANDLE_TYPE_FABRIC);

3. Reserve & Map

VMM 中「位址」與「記憶體」分離。先用 cuMemAddressReserve 保留一段 VA(至少等於要放入的所有實體配置 size 總和),再用 cuMemMap 把 VA 與實體 handle 綁定。

CUdeviceptr ptr;
cuMemAddressReserve(&ptr, size, 0, 0, 0);   // alignment=0 用預設
// allocHandle 來自 cuMemCreate 或 cuMemImportFromShareableHandle
cuMemMap(ptr, size, 0, allocHandle, 0);
VMM 位址 vs 記憶體分離:

  Reserve:   [────── VA range (無實體) ──────]
  Map:       [─VA─]──cuMemMap──►[ allocHandle(實體) ]
  Access:    設 cuMemSetAccess 後才可被 kernel 讀寫

4. Access Rights

cuMemMap 不會讓位址可存取;未設 access 就被 kernel 存取會 crash。必須在 source 與存取端 device 上呼叫 cuMemSetAccess

CUmemAccessDesc accessDesc = {};
accessDesc.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
accessDesc.location.id   = device;
accessDesc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
cuMemSetAccess(ptr, size, &accessDesc, 1);   // 讓位址可存取

5. Releasing

source 與 target 都必須依序呼叫三個函式,確保實體記憶體與 VA 都乾淨釋放。

cuMemUnmap(ptr, size);        // 1) 解除 VA 與實體的映射
cuMemRelease(handle);         // 2) 釋放實體記憶體還給系統
cuMemAddressFree(ptr, size);  // 3) 釋放 VA 範圍供未來重用
順序與 handle 收尾

順序固定為 unmap → release → addressFree,顛倒會出錯。OS-specific 情況下,export 出的 handle 需用 fclose 關閉;fabric-based 則不需此步。

Multicast Memory Sharing

Multicast Object 結合 VMM,使支援的 NVLink+NVSwitch GPU 能用 NVLink SHARP 做 in-fabric 運算(broadcast、reduction)。N 個 GPU 組成 multicast team,每個 GPU 各自用實體記憶體 back 一份 replica,因此一個 multicast object 有 N 份本地 replicamultimem PTX 指令對所有 replica 生效。

Multicast team(N GPUs,NVSwitch 連接):

  mcHandle ──┬─ GPU0: replica0(本地實體)
             ├─ GPU1: replica1(本地實體)
             └─ GPUn: replican(本地實體)
  multimem PTX 一次操作 → 同時作用於所有 replica

四步驟:

步驟 API / 動作
Create cuMulticastCreate(傳 numDevices、handleTypes、對齊後 size;此時尚無 device/實體)
Share cuMemExportToShareableHandle 把 mcHandle 分享給所有參與 process
Add device cuMulticastAddDevice(&mcHandle, device)所有 device 須先加入才可綁任何實體
Bind cuMulticastBindMem(mcHandle, mcOffset, memHandle, memOffset, size, 0) 逐 GPU 綁實體
CUmemAllocationProp mcProp = {};
mcProp.numDevices  = numDevices;
mcProp.handleTypes = CU_MEM_HANDLE_TYPE_FABRIC; // 單節點可用 POSIX_FILE_DESCRIPTOR
cuMulticastGetGranularity(&granularity, &mcProp, CU_MEM_ALLOC_GRANULARITY_MINIMUM);
mcProp.size = ROUND_UP(size, granularity);
cuMulticastCreate(&mcHandle, &mcProp);

綁定後再像 unicast 一樣 reserve VA、map mcHandle、設 access,並可同時建立 unicast 與 multicast 映射到同一實體(一致性見 Virtual Aliasing)。最後在 kernel 用 inline PTX 的 multimem 指令:

// 對所有 replica 做 atomic add reduction(需 CC 9.0+)
cuda::ptx::multimem_red(cuda::ptx::release_t, cuda::ptx::scope_sys_t,
                        cuda::ptx::op_add_t, arrival_counter_mc, n);
cuda::ptx::fence_proxy_alias();   // mc 與 uc 存取同一記憶體間需 fence
asm volatile("multimem.ld_reduce.relaxed.sys.global.add.f32 %0, [%1];"
             : "=f"(l2_norm_sum) : "l"(partial_l2_norm_mc) : "memory");
受眾定位

Multicast / fabric 例子是給 NCCL、NVSHMEM 這類函式庫開發者;一般應用開發者應改用更高階的 MPI / NCCL / NVSHMEM 介面。

Advanced Configuration

Memory Type 與 Compressible Memory

cuMemCreate 可透過 CUmemAllocationProp::allocFlags opt-in 特殊記憶體特性(須 device 支援)。Compressible memory 可加速非結構稀疏等可壓縮資料的存取,省下 DRAM 頻寬、L2 讀頻寬與 L2 容量。

int compressionSupported = 0;
cuDeviceGetAttribute(&compressionSupported,
    CU_DEVICE_ATTRIBUTE_GENERIC_COMPRESSION_SUPPORTED, device);
prop.allocFlags.compressionType = CU_MEM_ALLOCATION_COMP_GENERIC;  // 配置時 opt-in
壓縮不保證生效

因硬體資源等因素,配置可能拿不到壓縮屬性。務必用 cuMemGetAllocationPropertiesFromHandle 回查 allocFlags.compressionType 是否仍為 CU_MEM_ALLOCATION_COMP_GENERIC

Virtual Aliasing Support

對同一份實體配置用多次 cuMemMap(不同 VA)建立多個「proxy」即 virtual aliasing。除非 PTX ISA 另有說明,對一個 proxy 的寫入,在寫入端操作(grid launch、memcpy、memset…)完成前,對其他 proxy 視為不一致、不一致快取

__global__ void foo(char *A, char *B) {  // A、B 為同一記憶體的 alias
    *A = 0x1;
    printf("%d\n", *B);   // 未定義行為!可能讀到舊值或中間值
}
__global__ void foo(char *A, char *B) {
    *A = 0x1;
    cuda::ptx::fence_proxy_alias();   // 建立 alias proxy 間的排序
    printf("%d\n", *B);               // *B == *A == 0x1
}

OS-Specific Handle Details for IPC

cuMemCreate 時可用 CUmemAllocationProp::requestedHandleTypes 預先標記某配置供 IPC 或 graphics interop。

#if defined(__linux__)
  cuDeviceGetAttribute(&ok, CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR_SUPPORTED, device);
  prop.requestedHandleTypes = CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR;
#else
  cuDeviceGetAttribute(&ok, CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_WIN32_HANDLE_SUPPORTED, device);
  prop.requestedHandleTypes = CU_MEM_HANDLE_TYPE_WIN32;
  prop.win32HandleMetaData  = /* LPSECURITYATTRIBUTES */;  // 定義可被轉移到哪些 process
#endif

考試/測驗重點

主題 必記重點
前提 整套 VMM API 需系統支援 UVA;屬 Driver API(cu* 前綴)
動機關鍵字 cudaEnablePeerAccess 映射「所有過去/未來」配置;VMM 提供 allocation 粒度控制
兩階段 reserve VA(不配實體)→ map 提交實體;位址與記憶體分離
handle 兩類 OS-specific(POSIX fd / Win32,限單節點)vs Fabric(單/多節點,需 IMEX)
Fabric handle 條件 CU_MEM_HANDLE_TYPE_FABRIC(CUDA 12.4+)+ IMEX channels + IMEX daemon
配置 API cuMemCreate 回傳 CUmemGenericAllocationHandle,非指標、尚不可存取
granularity size 須 ROUND_UP 到 cuMemGetAllocationGranularity 查到的最小 granularity
reserve / map cuMemAddressReserve(類 mmap/VirtualAlloc)+ cuMemMap 綁定 VA 與實體
access 陷阱 只 map 不可存取,未設 cuMemSetAccess 就存取會 crash
釋放順序 cuMemUnmap → cuMemRelease → cuMemAddressFree(嚴格此序)
OS handle 收尾 OS-specific 需 fclose 關 handle;fabric 不需
multicast 結構 N GPU team,每 GPU 一份本地 replica;multimem PTX 作用於所有 replica
multicast 順序 所有 device 須先 cuMulticastAddDevice,才可 cuMulticastBindMem 綁實體
NVLink SHARP multicast + NVSwitch 做 in-fabric broadcast / reduction,需 CC 9.0+ 用 multimem
compressible allocFlags.compressionType = COMP_GENERIC;須查 GENERIC_COMPRESSION_SUPPORTED 並回查
virtual aliasing 多 proxy 寫入跨操作邊界前不一致;同 kernel 內跨 proxy 須 fence.proxy.alias
Win32 IPC requestedHandleTypes = WIN32 時必填 win32HandleMetaData(LPSECURITYATTRIBUTES)