External Resource Interoperability (Vulkan/Direct3D/NVSCI)
重點總覽
| 項目 | 重點 |
|---|---|
| 目的 | 讓 CUDA 匯入其他 API(Direct3D 11/12、Vulkan、NVSCI)顯式匯出的資源,零複製共享 |
| 交換媒介 | OS-native handle:Linux 為 file descriptor (fd)、Windows 為 NT handle / D3DKMT handle |
| 兩類資源 | Memory object(記憶體)與 Synchronization object(semaphore / fence) |
| 匯入記憶體 | cudaImportExternalMemory();映射為 device pointer 或 mipmapped array |
| 映射記憶體 | cudaExternalMemoryGetMappedBuffer() / cudaExternalMemoryGetMappedMipmappedArray() |
| 釋放記憶體 | cudaDestroyExternalMemory()(不釋放映射);映射另需 cudaFree() / cudaFreeMipmappedArray() |
| 匯入同步物件 | cudaImportExternalSemaphore();用 cudaSignal/WaitExternalSemaphoresAsync() signal/wait |
| 釋放同步物件 | cudaDestroyExternalSemaphore()(須先完成所有 outstanding signal/wait) |
| 裝置配對 | Vulkan 比對 device UUID;Direct3D12 比對 device LUID;NVSCI 用 GPU id (CUuuid) |
| handle 所有權 | fd 匯入後 CUDA 取得所有權;NT handle 由應用程式負責 close;D3DKMT/NvSci 由應用程式維護 |
External Resource Interoperability 總論(4.19.2)
External resource interoperability 讓 CUDA 能匯入其他 API「顯式匯出」的資源。這些資源通常以 OS-native handle 匯出(Linux 的 file descriptor、Windows 的 NT handle),使 CUDA 與其他 API 之間能高效共享資源,不需中途複製或重製。支援的 API 為 Direct3D 11/12、Vulkan 與 NVSCI。
可匯入的資源分兩類:
- Memory objects(記憶體物件)
- 用
cudaImportExternalMemory()匯入。 - 匯入後須先「映射」才能在 kernel 中存取:用
cudaExternalMemoryGetMappedBuffer()取得 device pointer,或用cudaExternalMemoryGetMappedMipmappedArray()取得 CUDA mipmapped array。 - 視記憶體物件型別,單一物件可能可設多個映射;映射的 offset/size 等必須與匯出端 API 的設定一致,否則為 undefined behavior。
- 釋放:
cudaDestroyExternalMemory()。注意 destroy 不會釋放映射,device pointer 須另以cudaFree()、mipmapped array 須以cudaFreeMipmappedArray()釋放。物件 destroy 後再存取其映射屬非法。
- 用
- Synchronization objects(同步物件)
- 用
cudaImportExternalSemaphore()匯入。 - 用
cudaSignalExternalSemaphoresAsync()signal、cudaWaitExternalSemaphoresAsync()wait。在對應 signal 之前發出 wait 屬非法。 - 釋放:
cudaDestroyExternalSemaphore();destroy 前所有 outstanding signal/wait 必須完成。
- 用
其他 API (Vulkan / D3D12 / NVSCI) CUDA
create + export resource
│ OS handle (fd / NT / D3DKMT / NvSciObj)
▼
┌─ memory ──► cudaImportExternalMemory ─► cudaExternalMemoryGetMappedBuffer
│ / ...GetMappedMipmappedArray ─► kernel 可用
└─ semaphore ► cudaImportExternalSemaphore ─► cudaSignal/WaitExternalSemaphoresAsync
共享的是「同一塊底層資源」而非副本。映射參數(offset、size、dimensions、format、mip levels)必須完全對齊匯出端設定,任何不一致都導致 undefined behavior。
Vulkan Interoperability(4.19.2.1)
把 Vulkan graphics/compute 與 CUDA compute 部署在同一 GPU 上可最大化使用率並省去複製。整體流程:
- 初始化 Vulkan,建立並匯出 external buffer 與 / 或 synchronization object。
- 以「相符的 device UUID」設定 CUDA 要用的裝置。
- 取得 memory / synchronization handle。
- 用 handle 在 CUDA
cudaImportExternalMemory()/cudaImportExternalSemaphore()匯入。 - 把 device pointer 或 mipmapped array 映射到記憶體物件上。
- 透過對 semaphore 的 signal/wait 定義執行順序,CUDA 與 Vulkan 交替使用同一記憶體。
Setting up a Vulkan device
匯出資源需在建立 Vulkan instance / device 時啟用對應 extension:
- 記憶體:instance 端
VK_KHR_external_memory_capabilities,device 端VK_KHR_external_memory。 - 同步:instance 端
VK_KHR_external_semaphore_capabilities,device 端VK_KHR_external_semaphore。 - 平台專屬 handle:Windows 用
..._win32、UNIX 用..._fd(memory 與 semaphore 各一)。
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
extensions.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
extensions.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME);
#ifdef _WIN64 // Windows:win32 handle
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
#else // Unix:file descriptor
extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
#endif
重點:handle 型別 enum 為平台專屬,這些 extension 名稱要在 instance/device 建立資訊中啟用。
Matching device UUIDs
匯出的物件必須在「建立它的同一裝置」上匯入並映射。做法是把 CUDA device 的 UUID 與 Vulkan physical device 的 UUID(vkPhysicalDeviceIDProperties.deviceUUID)比對。
cudaGetDeviceProperties(&deviceProp, current_device);
if (deviceProp.computeMode != cudaComputeModeProhibited) {
// 比對 CUDA 與 Vulkan 的 UUID
if (memcmp(&deviceProp.uuid, vkDeviceUUID, UUID_SIZE) == 0)
cudaSetDevice(current_device); // 找到對應 GPU
}
該 Vulkan physical device 不可屬於「含一個以上 physical device 的 device group」。即 vkEnumeratePhysicalDeviceGroups 回傳、含此 device 的 group,physical device count 必須為 1。
Exporting Vulkan memory / synchronization objects
- 記憶體:建立 buffer 時帶 export flag(
VkExternalMemoryBufferCreateInfo.handleTypes),配置記憶體時帶VkExportMemoryAllocateInfoKHR,handle 型別平台專屬(Win32 / OPAQUE_FD)。 - 同步:Vulkan GPU 呼叫為非同步,用 semaphore / fence 定序。semaphore 分兩種:
- Binary semaphore:1-bit 計數,只有 signaled / unsignaled。
- Timeline semaphore:64-bit 計數,可用同一個 semaphore 定義多步執行順序。
Importing memory objects
dedicated 與 non-dedicated 物件皆可匯入;匯入 dedicated 物件須設 cudaExternalMemoryDedicated flag。handle 型別決定 cudaExternalMemoryHandleDesc.type:
cudaExternalMemoryHandleDesc desc = {};
// Win32:OpaqueWin32 / OpaqueWin32Kmt;Linux:OpaqueFd
desc.type = cudaExternalMemoryHandleTypeOpaqueFd;
desc.size = size;
#ifdef _WIN64
desc.handle.win32.handle = (HANDLE)getMemHandle(vkMem, handleType);
#else
desc.handle.fd = (int)(uintptr_t)getMemHandle(vkMem, handleType);
#endif
cudaImportExternalMemory(&cudaMem, &desc);
handle 所有權差異(重要考點):
- Linux fd:匯入後 CUDA 取得所有權,匯入成功後再用該 fd 屬 undefined behavior。
- Windows NT handle(OPAQUE_WIN32):CUDA 不取得所有權,應用程式須在不需要時自行
CloseHandle();NT handle 持有資源 reference,須先釋放才能釋放底層記憶體。 - D3DKMT (OPAQUE_WIN32_KMT) 全域共享 handle:不持有 reference,當其他 reference 全部釋放時自動銷毀。
也可用 named handle(desc.handle.win32.name)匯入 OPAQUE_WIN32 記憶體。
Mapping buffers / mipmapped arrays
- Buffer:填
cudaExternalMemoryBufferDesc(offset/size/flags),呼叫cudaExternalMemoryGetMappedBuffer();offset/size 須與 Vulkan API 設定一致,映射出的 pointer 須以cudaFree()釋放。 - Mipmapped array:填
cudaExternalMemoryMipmappedArrayDesc(offset、formatDesc、extent、flags、numLevels),呼叫cudaExternalMemoryGetMappedMipmappedArray();offset、dimensions、format、mip level 數須一致。若該 array 在 Vulkan 被綁為 color target,須設cudaArrayColorAttachment;映射須以cudaFreeMipmappedArray()釋放。
cudaExternalMemoryBufferDesc bufDesc = {};
bufDesc.offset = 0; bufDesc.size = size; bufDesc.flags = 0;
cudaExternalMemoryGetMappedBuffer(cudaPtr, cudaMem, &bufDesc); // -> 之後 cudaFree()
實務上需把 Vulkan 的 VkFormat、VkExtent3D/VkImageViewType、VkImageUsageFlags 轉成 CUDA 的 cudaChannelFormatDesc、cudaExtent、array flags(cube/layered/colorAttachment/surfaceLoadStore)。
Importing synchronization objects
handle 型別映射到 cudaExternalSemaphoreHandleDesc.type:
- Timeline:
...TimelineSemaphoreWin32/...TimelineSemaphoreFd。 - Binary(非 timeline):
...OpaqueWin32/...OpaqueWin32Kmt/...OpaqueFd。 - 所有權規則同 memory:fd 由 CUDA 接管、NT handle 應用程式自行 close、D3DKMT 自動銷毀。
Signaling / Waiting
- Signal:把 semaphore 設為 signaled;timeline 則把 counter 設為 signal call 指定值。對應的 wait 必須在 Vulkan 端發出。
- Wait:等待直到 signaled 或達到指定 wait value;binary semaphore 被等到後會重設回 unsignaled。對應 signal 必須在 Vulkan 端發出。
- Binary 額外限制:wait 必須在對應 signal「已發出之後」才能發出。
// timeline:以遞增 value 定序,CUDA 等 Vulkan 完成 -> 跑 kernel -> signal 讓 Vulkan 繼續
cudaExternalSemaphoreWaitParams waitParams = {}; waitParams.params.fence.value = waitValue;
cudaExternalSemaphoreSignalParams signalParams = {}; signalParams.params.fence.value = signalValue;
cudaWaitExternalSemaphoresAsync(&m_cudaTimelineSemaphore, &waitParams, 1, m_stream);
m_sim.stepSimulation(time, m_stream); // CUDA kernel
cudaSignalExternalSemaphoresAsync(&m_cudaTimelineSemaphore, &signalParams, 1, m_stream);
waitValue += 2; signalValue += 2; // binary 版則 value 固定為 0
Vulkan render ──signal──► CUDA wait ──► CUDA kernel(step) ──signal──► Vulkan wait ──► render(更新後 buffer)
(timeline: 同一 semaphore,value 1,2,3,4...;binary: 兩個 semaphore,value 恆 0)
Vulkan 也能匯入 Vulkan 匯出的 memory/semaphore,因此 OpenGL-CUDA interop 可走「Vulkan 匯出 → OpenGL 與 CUDA 各自匯入」的替代路徑(見 GL_EXT_memory_object* / GL_EXT_semaphore* extension)。
Direct3D Interoperability(4.19.2.2)
支援 Direct3D 11 與 12 資源匯入 CUDA(本節聚焦 D3D12)。
Matching device LUIDs
匯出物件須在同一裝置匯入;以 CUDA device 的 LUID 與 Direct3D12 device 的 LUID 比對。
LUID d3d12Luid = d3d12Device->GetAdapterLuid();
// 逐 CUDA device 比對 LowPart 與 HighPart
if (!memcmp(&d3d12Luid.LowPart, cudaLuid, sizeof(d3d12Luid.LowPart)) &&
!memcmp(&d3d12Luid.HighPart, cudaLuid + sizeof(d3d12Luid.LowPart), sizeof(d3d12Luid.HighPart)))
return cudaDevice;
Direct3D12 device 不可建立在 linked node adapter 上,即 ID3D12Device::GetNodeCount 必須回傳 1。
Importing memory objects
D3D12 資源從 NT handle 匯入,一律須設 cudaExternalMemoryDedicated(committed resource 尤其)。NT handle 由應用程式負責 close,且須先釋放才能釋放底層記憶體。
| 來源 | desc.type |
建立時的 flag |
|---|---|---|
Shared heap(CreateHeap) |
cudaExternalMemoryHandleTypeD3D12Heap |
D3D12_HEAP_FLAG_SHARED |
Committed resource(CreateCommittedResource) |
cudaExternalMemoryHandleTypeD3D12Resource |
D3D12_HEAP_FLAG_SHARED + cudaExternalMemoryDedicated |
cudaExternalMemoryHandleDesc desc = {};
desc.type = cudaExternalMemoryHandleTypeD3D12Resource;
desc.handle.win32.handle = (void *)handle; // 或 desc.handle.win32.name = name
desc.size = size;
desc.flags |= cudaExternalMemoryDedicated;
cudaImportExternalMemory(&extMem, &desc);
CloseHandle(handle); // NT handle 由應用程式負責關閉
Mapping & Importing sync
- Buffer / mipmapped array 映射方式同 Vulkan(
cudaExternalMemoryGetMappedBuffer/...MipmappedArray),參數須對齊 D3D12 API;可 render target 時設cudaArrayColorAttachment。格式轉換用DXGI_FORMAT→cudaChannelFormatDesc、D3D12_SRV_DIMENSION→cudaExtent/ array flags。 - 同步:shareable D3D12 fence(
CreateFence設D3D12_FENCE_FLAG_SHARED)以 NT handle 匯入,desc.type = cudaExternalSemaphoreHandleTypeD3D12Fence。
// signal:設定 fence value,對應 wait 須在 D3D12 端、且在此 signal 之後發出
params.params.fence.value = value;
cudaSignalExternalSemaphoresAsync(&extSem, ¶ms, 1, stream);
// wait:等到 fence value >= 指定值,對應 signal 須在 D3D12 端、且在此 wait 之前發出
cudaWaitExternalSemaphoresAsync(&extSem, ¶ms, 1, stream);
D3D12 fence 為單調遞增:wait 等到「value >= 指定值」即通過。
NVSCI Interoperability(4.19.2.3)
NVSCI 提供兩個介面:NvSciBuf(配置與交換記憶體 buffer)與 NvSciSync(在操作邊界管理同步物件),常見於 NVIDIA DRIVE 平台。
Importing memory objects(NvSciBuf)
配置與 CUDA device 相容的 NvSciBuf 物件時,須在 attribute list 用 NvSciBufGeneralAttrKey_GpuId 設定對應 GPU id(取自 cuDeviceGetUuid 的 CUuuid)。可選屬性:
NvSciBufGeneralAttrKey_NeedCpuAccess:是否需 CPU access。NvSciBufRawBufferAttrKey_Align:raw buffer 對齊需求。NvSciBufGeneralAttrKey_RequiredPerm:可逐 UMD 設定權限。例如要給 GPU read-only,用NvSciBufObjDupWithReducePerm()搭NvSciBufAccessPerm_Readonly建立縮權限的副本再匯入。NvSciBufGeneralAttrKey_EnableGpuCache(L2 cacheability)、NvSciBufGeneralAttrKey_EnableGpuCompression(壓縮)。
匯入 CUDA 時填 cudaExternalMemoryHandleDesc:
cudaExternalMemoryHandleDesc memHandleDesc = {};
memHandleDesc.type = cudaExternalMemoryHandleTypeNvSciBuf;
memHandleDesc.handle.nvSciBufObject = bufferObjRo; // 以縮減權限的物件匯入
memHandleDesc.size = ret_size; // 由 NvSciBuf 屬性查得
cudaImportExternalMemory(&extMemBuffer, &memHandleDesc);
attribute list 與 NvSciBuf 物件須由「應用程式自行維護」。若該 NvSciBuf 也被其他 driver 映射,須依輸出屬性 NvSciBufGeneralAttrKey_GpuSwNeedCacheCoherency 用 NvSciSync 做適當 barrier 維持 CUDA 與其他 driver 間的一致性。cache/compression 為 per-GPU 屬性,須以 UUID 比對讀取對應 GPU 的值。
Mapping(NvSciBuf)
- Buffer:用
cudaExternalMemoryGetMappedBuffer(),offset/size 依 NvSciBufObj 屬性填,須cudaFree()釋放。 - Mipmapped array:用
cudaExternalMemoryGetMappedMipmappedArray(),須cudaFreeMipmappedArray()釋放。
NvSciBuf 映射 mipmapped array 時,mip level 數必須為 1。
Importing synchronization objects(NvSciSync)
用 cudaDeviceGetNvSciSyncAttributes() 取得與某 CUDA device 相容的 NvSciSync 屬性,分別生成 signaler / waiter attribute list(CUDA_NVSCISYNC_ATTR_SIGNAL / CUDA_NVSCISYNC_ATTR_WAIT),reconcile 後 NvSciSyncObjAlloc() 建立物件,再匯入 CUDA:
cudaDeviceGetNvSciSyncAttributes(signalerAttrList, cudaDev0, CUDA_NVSCISYNC_ATTR_SIGNAL);
cudaDeviceGetNvSciSyncAttributes(waiterAttrList, cudaDev1, CUDA_NVSCISYNC_ATTR_WAIT);
// ... NvSciSyncAttrListReconcile -> NvSciSyncObjAlloc ...
cudaExternalSemaphoreHandleDesc desc = {};
desc.type = cudaExternalSemaphoreHandleTypeNvSciSync;
desc.handle.nvSciSyncObj = nvSciSyncObj;
cudaImportExternalSemaphore(&extSem, &desc);
NvSciSyncObj 的所有權匯入後仍屬應用程式;在匯入後刪除 / 釋放 nvSciSyncObj 會導致 CUDA undefined behavior。
Signaling / Waiting(NvSciSync)
- Signal:初始化作為輸入的
nvSciSync.fence,供對應 wait 等待;wait 須在此 signal 之後發出。 - Wait:等到輸入 fence 被對應 signaler signal;signal 須在 wait 之前發出。
- 跳過 mem sync flag:當
NvSciBufGeneralAttrKey_GpuSwNeedCacheCoherency為 FALSE 時,可設cudaExternalSemaphoreSignalSkipNvSciBufMemSync/cudaExternalSemaphoreWaitSkipNvSciBufMemSync,跳過對所有已匯入 NvSciBuf 的記憶體同步操作。
signalParams.params.nvSciSync.fence = (void*)fence;
signalParams.flags = 0; // 或 cudaExternalSemaphoreSignalSkipNvSciBufMemSync
cudaSignalExternalSemaphoresAsync(&extSem, &signalParams, 1, stream);
三種 API 對照
| 面向 | Vulkan | Direct3D12 | NVSCI |
|---|---|---|---|
| 裝置配對 | device UUID | device LUID | GPU id(CUuuid) |
| 裝置限制 | device group count = 1 | GetNodeCount = 1 | 由 GpuId 屬性指定 |
| memory handle | fd / NT handle / D3DKMT | NT handle(heap / resource) | NvSciBufObj |
| dedicated | dedicated 物件須設 flag | 一律須設 cudaExternalMemoryDedicated | 不適用 |
| sync 物件 | binary / timeline semaphore | D3D12 fence(單調遞增) | NvSciSync object |
| sync type | OpaqueFd/Win32(/Kmt)、Timeline... | D3D12Fence | NvSciSync |
考試/測驗重點
| 問題 | 答案 |
|---|---|
| 匯入外部記憶體 / 同步物件的函式 | cudaImportExternalMemory() / cudaImportExternalSemaphore() |
| 把外部記憶體映射成 device pointer / array 的函式 | cudaExternalMemoryGetMappedBuffer() / cudaExternalMemoryGetMappedMipmappedArray() |
| 釋放外部記憶體物件用什麼?它會釋放映射嗎 | cudaDestroyExternalMemory();不會,映射另以 cudaFree() / cudaFreeMipmappedArray() 釋放 |
| signal / wait 外部 semaphore 的函式 | cudaSignalExternalSemaphoresAsync() / cudaWaitExternalSemaphoresAsync() |
| Vulkan / D3D12 / NVSCI 各用什麼配對裝置 | UUID / LUID / GPU id (CUuuid) |
| Linux fd 與 Windows NT handle 的所有權差異 | fd 匯入後 CUDA 接管(勿再用);NT handle 由應用程式自行 CloseHandle |
| D3DKMT (Win32_KMT) handle 的生命週期 | 不持有 reference,其他 reference 全釋放時自動銷毀 |
| 匯入 D3D12 資源必設的 flag | cudaExternalMemoryDedicated |
| Vulkan binary vs timeline semaphore | binary 1-bit、value 恆 0;timeline 64-bit、可用 value 定多步順序 |
| D3D12 fence wait 的通過條件 | fence value >= 指定值(單調遞增) |
| NVSCI 映射 mipmapped array 的 mip level 數限制 | 必須為 1 |
| NvSciSyncObj 匯入後可否釋放 | 否,匯入後刪除 / 釋放會造成 CUDA undefined behavior |
| 何時設 SkipNvSciBufMemSync flag | 當 NvSciBufGeneralAttrKey_GpuSwNeedCacheCoherency 為 FALSE 時 |
| binary semaphore signal/wait 的順序限制 | wait 必須在對應 signal 已發出之後才能發出 |
| Vulkan physical device / D3D12 device 的單裝置限制 | device group count 須為 1 / GetNodeCount 須為 1 |
Related Notes
- 04-CUDA-Features/19-Interprocess-Communication
- 04-CUDA-Features/20-Virtual-Memory-Management
- 04-CUDA-Features/23-Graphics-Interoperability
- 04-CUDA-Features/25-Driver-Entry-Point-Access
- 04-CUDA-Features/17-L2-Cache-Control
- 04-CUDA-Features/18-Memory-Synchronization-Domains
- 03-Advanced-CUDA/08-CUDA-Driver-API
- 03-Advanced-CUDA/10-Tour-of-CUDA-Features
- 04-CUDA-Features/Practice-CUDA-Features
- 00-Dashboard/Exam-Traps