Skip to content

Commit c37c8b8

Browse files
authored
[CORE][NVEP]: add support for Vulkan interop (#27456)
### Description <!-- Describe your changes. --> The Vulkan interop works in a similar way as D3D12 interop. The shared handles from Vulkan work for CUDA the same way as D3D12 handles. For Linux, we can use file descriptors. As a sync primitive we use Vulkan timeline semaphores. They are widely supported since Vulkan 1.2 and work in a similar way as the existing `ID3D12Fence`s. ### Motivation and Context <!-- - Why is this change required? What problem does it solve? - If it fixes an open issue, please link to the issue here. --> This change allows to use graphics interop also on Vulkan and on Linux. It addresses a TODO in the external memory API.
1 parent bfec0b1 commit c37c8b8

6 files changed

Lines changed: 996 additions & 25 deletions

File tree

cmake/deps.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ kleidiai;https://github.com/ARM-software/kleidiai/archive/refs/tags/v1.20.0.tar.
6161
# this entry will be updated to use refs/tags/<version> instead of the raw commit hash.
6262
kleidiai-qmx;https://github.com/qualcomm/kleidiai/archive/2f10c9a8d32f81ffeeb6d4885a29cc35d2b0da87.zip;5e855730a2d69057a569f43dd7532db3b2d2a05c
6363
duktape;https://github.com/svaarala/duktape/releases/download/v2.7.0/duktape-2.7.0.tar.xz;8200c8e417dbab7adcc12c4dbdef7651cfc55794
64+
vulkan_headers;https://codeload.github.com/KhronosGroup/Vulkan-Headers/tar.gz/refs/tags/v1.4.344;57bc528ef7c4a3f7bfbb59e64a187e3734bd29d8

cmake/onnxruntime_unittests.cmake

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,10 +734,21 @@ if(onnxruntime_USE_TENSORRT)
734734
endif()
735735

736736
if(onnxruntime_USE_NV)
737+
# If an external project (e.g. dawn from Webgpu EP has already added a Vulkan::Headers target we shouldn't try to import another version of the Vulkan headers)
738+
if (NOT TARGET Vulkan::Headers)
739+
onnxruntime_fetchcontent_declare(
740+
vulkan_headers
741+
URL ${DEP_URL_vulkan_headers}
742+
URL_HASH SHA1=${DEP_SHA1_vulkan_headers}
743+
EXCLUDE_FROM_ALL
744+
)
745+
onnxruntime_fetchcontent_makeavailable(vulkan_headers)
746+
endif()
737747
list(APPEND onnxruntime_test_framework_src_patterns ${TEST_SRC_DIR}/providers/nv_tensorrt_rtx/*)
738748
list(APPEND onnxruntime_test_framework_src_patterns "${ONNXRUNTIME_ROOT}/core/providers/nv_tensorrt_rtx/nv_execution_provider_utils.h")
739749
list(APPEND onnxruntime_test_providers_dependencies onnxruntime_providers_nv_tensorrt_rtx onnxruntime_providers_shared)
740750
list(APPEND onnxruntime_test_providers_libs ${TENSORRT_LIBRARY_INFER})
751+
list(APPEND onnxruntime_test_providers_libs Vulkan::Headers)
741752
endif()
742753

743754

include/onnxruntime/core/providers/nv_tensorrt_rtx/nv_provider_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ constexpr const char* kCudaGraphEnable = "enable_cuda_graph";
3939
constexpr const char* kMultiProfileEnable = "nv_multi_profile_enable";
4040
constexpr const char* kUseExternalDataInitializer = "nv_use_external_data_initializer";
4141
constexpr const char* kRuntimeCacheFile = "nv_runtime_cache_path";
42+
constexpr const char* kExternalComputeQueueDataParamNV_data = "VkExternalComputeQueueDataParamsNV_data";
4243

4344
} // namespace provider_option_names
4445
namespace run_option_names {

include/onnxruntime/core/session/onnxruntime_c_api.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,15 +1035,15 @@ typedef void (*RunAsyncCallbackFn)(void* user_data, OrtValue** outputs, size_t n
10351035

10361036
/** \brief External memory handle type for importing GPU resources.
10371037
*
1038-
* \todo Add OPAQUE_WIN32 for Windows Vulkan-specific memory handles
1039-
* \todo Add POSIX file descriptor (OPAQUE_FD) for Linux Vulkan/CUDA/OpenCL interop
10401038
* \todo Add Linux DMA-BUF file descriptor for embedded GPU memory sharing
10411039
*
10421040
* \since Version 1.24.
10431041
*/
10441042
typedef enum OrtExternalMemoryHandleType {
1045-
ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE = 0, /**< Shared HANDLE from ID3D12Device::CreateSharedHandle(resource) */
1046-
ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP = 1, /**< Shared HANDLE from ID3D12Device::CreateSharedHandle(heap) */
1043+
ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE = 0, /**< Shared HANDLE from ID3D12Device::CreateSharedHandle(resource) */
1044+
ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP = 1, /**< Shared HANDLE from ID3D12Device::CreateSharedHandle(heap) */
1045+
ORT_EXTERNAL_MEMORY_HANDLE_TYPE_VK_MEMORY_WIN32 = 2, /**< Shared HANDLE from vkGetMemoryWin32HandleKHR, non-dedicated allocation */
1046+
ORT_EXTERNAL_MEMORY_HANDLE_TYPE_VK_MEMORY_OPAQUE_FD = 3, /**< File descriptor from vkGetMemoryOpaqueFdKHR, non-dedicated allocation */
10471047
} OrtExternalMemoryHandleType;
10481048

10491049
/** \brief Descriptor for importing external memory.
@@ -1067,7 +1067,9 @@ typedef struct OrtExternalMemoryDescriptor {
10671067
* \since Version 1.24.
10681068
*/
10691069
typedef enum OrtExternalSemaphoreType {
1070-
ORT_EXTERNAL_SEMAPHORE_D3D12_FENCE = 0, /**< Shared HANDLE from ID3D12Device::CreateSharedHandle(fence) */
1070+
ORT_EXTERNAL_SEMAPHORE_D3D12_FENCE = 0, /**< Shared HANDLE from ID3D12Device::CreateSharedHandle(fence) */
1071+
ORT_EXTERNAL_SEMAPHORE_VK_TIMELINE_SEMAPHORE_WIN32 = 1, /**< Shared HANDLE from vkGetSemaphoreWin32HandleKHR of a VkSemaphore created as VK_SEMAPHORE_TYPE_TIMELINE */
1072+
ORT_EXTERNAL_SEMAPHORE_VK_TIMELINE_SEMAPHORE_OPAQUE_FD = 2, /**< File descriptor from vkGetSemaphoreFdKHR of a VkSemaphore created as VK_SEMAPHORE_TYPE_TIMELINE */
10711073
} OrtExternalSemaphoreType;
10721074

10731075
/** \brief Descriptor for importing external semaphores.
@@ -1134,14 +1136,16 @@ typedef struct OrtGraphicsInteropConfig {
11341136
* works; streams use the default context.
11351137
*
11361138
* For D3D12: ID3D12CommandQueue*
1137-
* For Vulkan: VkQueue (cast to void*)
1139+
* For Vulkan: pass NULL
11381140
*/
11391141
void* command_queue;
11401142

11411143
/** \brief Additional API-specific options (optional).
11421144
*
11431145
* Can be used for future extensibility without changing the struct layout.
1144-
* For example, Vulkan-specific queue family index, or D3D12 fence sharing flags.
1146+
* For example, D3D12 fence sharing flags or provider-specific options like
1147+
* onnxruntime::nv::provider_option_names::kExternalComputeQueueDataParamNV_data
1148+
* for Vulkan interop for the NvTensorRTRTX provider.
11451149
*/
11461150
const OrtKeyValuePairs* additional_options;
11471151
} OrtGraphicsInteropConfig;

onnxruntime/core/providers/nv_tensorrt_rtx/nv_provider_factory.cc

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <cstdlib>
1010
#include <mutex>
1111
#include <unordered_map>
12+
#include <cuda.h>
1213

1314
#include "core/providers/shared_library/provider_api.h"
1415
#include "core/session/onnxruntime_c_api.h"
@@ -539,8 +540,6 @@ struct NvTrtRtxSyncStreamImpl : OrtSyncStreamImpl {
539540
const OrtApi& ort_api;
540541
};
541542

542-
#if defined(_WIN32)
543-
544543
// External Resource Import Implementation (D3D12 to CUDA)
545544
/**
546545
* @brief Derived handle for imported external memory from D3D12 to CUDA.
@@ -642,8 +641,13 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
642641
_In_ OrtExternalMemoryHandleType handle_type) noexcept {
643642
(void)this_ptr;
644643
// CUDA supports both D3D12 resource and heap handles
644+
#if defined(_WIN32)
645645
return handle_type == ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE ||
646-
handle_type == ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP;
646+
handle_type == ORT_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP ||
647+
handle_type == ORT_EXTERNAL_MEMORY_HANDLE_TYPE_VK_MEMORY_WIN32;
648+
#elif __linux__
649+
return handle_type == ORT_EXTERNAL_MEMORY_HANDLE_TYPE_VK_MEMORY_OPAQUE_FD;
650+
#endif
647651
}
648652

649653
static OrtStatus* ORT_API_CALL ImportMemoryImpl(
@@ -694,6 +698,14 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
694698
cu_handle_type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP;
695699
is_dedicated = false; // D3D12 heaps are not dedicated
696700
break;
701+
case ORT_EXTERNAL_MEMORY_HANDLE_TYPE_VK_MEMORY_WIN32:
702+
cu_handle_type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32;
703+
is_dedicated = false; // API header currently, documents that this handle currently is non-dedicated
704+
break;
705+
case ORT_EXTERNAL_MEMORY_HANDLE_TYPE_VK_MEMORY_OPAQUE_FD:
706+
cu_handle_type = CU_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD;
707+
is_dedicated = false; // API header currently, documents that this handle currently is non-dedicated
708+
break;
697709
default:
698710
// Should not reach here - CanImportMemory already validated handle type
699711
return impl.ort_api.CreateStatus(ORT_EP_FAIL, "Unexpected external memory handle type");
@@ -702,7 +714,11 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
702714
// Setup external memory handle descriptor
703715
CUDA_EXTERNAL_MEMORY_HANDLE_DESC ext_mem_desc = {};
704716
ext_mem_desc.type = cu_handle_type;
717+
#if defined(_WIN32)
705718
ext_mem_desc.handle.win32.handle = desc->native_handle;
719+
#else
720+
ext_mem_desc.handle.fd = static_cast<int>(reinterpret_cast<intptr_t>((desc->native_handle)));
721+
#endif
706722
ext_mem_desc.size = desc->size_bytes;
707723
ext_mem_desc.flags = is_dedicated ? CUDA_EXTERNAL_MEMORY_DEDICATED : 0;
708724

@@ -825,8 +841,11 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
825841
_In_ const OrtExternalResourceImporterImpl* this_ptr,
826842
_In_ OrtExternalSemaphoreType type) noexcept {
827843
(void)this_ptr;
828-
// CUDA supports D3D12 timeline fences
829-
return type == ORT_EXTERNAL_SEMAPHORE_D3D12_FENCE;
844+
#if defined(_WIN32)
845+
return type == ORT_EXTERNAL_SEMAPHORE_D3D12_FENCE || type == ORT_EXTERNAL_SEMAPHORE_VK_TIMELINE_SEMAPHORE_WIN32;
846+
#else
847+
return type == ORT_EXTERNAL_SEMAPHORE_VK_TIMELINE_SEMAPHORE_OPAQUE_FD;
848+
#endif
830849
}
831850

832851
static OrtStatus* ORT_API_CALL ImportSemaphoreImpl(
@@ -857,8 +876,25 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
857876

858877
// Setup external semaphore handle descriptor for D3D12 fence
859878
CUDA_EXTERNAL_SEMAPHORE_HANDLE_DESC ext_sem_desc = {};
860-
ext_sem_desc.type = CU_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE;
879+
switch (desc->type) {
880+
case ORT_EXTERNAL_SEMAPHORE_D3D12_FENCE:
881+
ext_sem_desc.type = CU_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE;
882+
break;
883+
case ORT_EXTERNAL_SEMAPHORE_VK_TIMELINE_SEMAPHORE_WIN32:
884+
ext_sem_desc.type = CU_EXTERNAL_SEMAPHORE_HANDLE_TYPE_TIMELINE_SEMAPHORE_WIN32;
885+
break;
886+
case ORT_EXTERNAL_SEMAPHORE_VK_TIMELINE_SEMAPHORE_OPAQUE_FD:
887+
ext_sem_desc.type = CU_EXTERNAL_SEMAPHORE_HANDLE_TYPE_TIMELINE_SEMAPHORE_FD;
888+
break;
889+
default:
890+
// Should not reach here - ImportSemaphoreImpl already validated handle type
891+
return impl.ort_api.CreateStatus(ORT_EP_FAIL, "Unexpected external memory handle type");
892+
}
893+
#if defined(_WIN32)
861894
ext_sem_desc.handle.win32.handle = desc->native_handle;
895+
#else
896+
ext_sem_desc.handle.fd = static_cast<int>(reinterpret_cast<intptr_t>(desc->native_handle));
897+
#endif
862898
ext_sem_desc.flags = 0;
863899

864900
// Import the external semaphore
@@ -956,7 +992,7 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
956992
// Get the CUDA stream from OrtSyncStream
957993
cudaStream_t cuda_stream = static_cast<cudaStream_t>(impl.ort_api.SyncStream_GetHandle(stream));
958994

959-
// Setup signal parameters for D3D12 fence (timeline semaphore)
995+
// Setup signal parameters for D3D12 fence / VK timeline semaphore
960996
CUDA_EXTERNAL_SEMAPHORE_SIGNAL_PARAMS signal_params = {};
961997
signal_params.params.fence.value = value;
962998
signal_params.flags = 0;
@@ -994,8 +1030,6 @@ struct NvTrtRtxExternalResourceImporterImpl : OrtExternalResourceImporterImpl {
9941030
const OrtEpApi& ep_api;
9951031
};
9961032

997-
#endif // defined(_WIN32)
998-
9991033
// OrtEpApi infrastructure to be able to use the NvTensorRTRTX EP as an OrtEpFactory for auto EP selection.
10001034
struct NvTensorRtRtxEpFactory : OrtEpFactory {
10011035
using MemoryInfoUniquePtr = std::unique_ptr<OrtMemoryInfo, std::function<void(OrtMemoryInfo*)>>;
@@ -1280,17 +1314,11 @@ struct NvTensorRtRtxEpFactory : OrtEpFactory {
12801314

12811315
*out_importer = nullptr;
12821316

1283-
#if defined(_WIN32)
12841317
// Create the external resource importer
12851318
auto importer = std::make_unique<NvTrtRtxExternalResourceImporterImpl>(ep_device, factory.ort_api);
12861319
*out_importer = importer.release();
12871320

12881321
return nullptr;
1289-
#else
1290-
ORT_UNUSED_PARAMETER(ep_device);
1291-
return factory.ort_api.CreateStatus(ORT_NOT_IMPLEMENTED,
1292-
"External resource import is only available on Windows builds.");
1293-
#endif
12941322
}
12951323

12961324
/**
@@ -1529,9 +1557,40 @@ struct NvTensorRtRtxEpFactory : OrtEpFactory {
15291557
"[NvTensorRTRTX EP] D3D12 CIG context creation not supported on this platform");
15301558
#endif
15311559
} else if (config->graphics_api == ORT_GRAPHICS_API_VULKAN) {
1532-
// TODO: Add Vulkan CIG context support if needed
1533-
return onnxruntime::CreateStatus(ORT_NOT_IMPLEMENTED,
1534-
"[NvTensorRTRTX EP] Vulkan CIG context not yet implemented");
1560+
int cig_supported{false};
1561+
if (cudaSuccess != cudaDeviceGetAttribute(&cig_supported, cudaDevAttrVulkanCigSupported, device_id)) {
1562+
return onnxruntime::CreateStatus(ORT_EP_FAIL,
1563+
"[NvTensorRTRTX EP] Could not determine CiG support for CUDA device");
1564+
}
1565+
if (!cig_supported) {
1566+
LOGS_DEFAULT(INFO) << "[NvTensorRTRTX EP] InitGraphicsInterop: CiG for Vulkan is not supported on the given device. Will use the default CUDA context";
1567+
return nullptr;
1568+
}
1569+
const char* nv_blob_ptr_str = factory.ort_api.GetKeyValue(config->additional_options, onnxruntime::nv::provider_option_names::kExternalComputeQueueDataParamNV_data);
1570+
if (!nv_blob_ptr_str) {
1571+
LOGS_DEFAULT(WARNING) << "[NvTensorRTRTX EP] InitGraphicsInterop: Can't enable CUDA in Graphics (CiG) for Vulkan without onnxruntime::nv::provider_option_names::kExternalComputeQueueDataParamNV_data";
1572+
return nullptr;
1573+
}
1574+
uint64_t nv_blob_ptr = std::stoull(nv_blob_ptr_str);
1575+
if (nv_blob_ptr == 0) {
1576+
return onnxruntime::CreateStatus(ORT_EP_FAIL,
1577+
"[NvTensorRTRTX EP] Could not parse provided values for onnxruntime::nv::provider_option_names::kExternalComputeQueueDataParamNV_data or onnxruntime::nv::provider_option_names::kExternalComputeQueueDataParamNV_data_len");
1578+
}
1579+
1580+
CUctxCigParam cig_params{};
1581+
cig_params.sharedDataType = CIG_DATA_TYPE_NV_BLOB;
1582+
cig_params.sharedData = reinterpret_cast<void*>(nv_blob_ptr);
1583+
CUctxCreateParams params{};
1584+
params.cigParams = &cig_params;
1585+
1586+
cu_result = cuCtxCreate_v4(&cig_context, &params, 0, device_id);
1587+
if (cu_result != CUDA_SUCCESS) {
1588+
const char* error_str = nullptr;
1589+
cuGetErrorString(cu_result, &error_str);
1590+
std::string error_msg = "[NvTensorRTRTX EP] Failed to create CIG context for Vulkan: ";
1591+
error_msg += error_str ? error_str : "unknown error";
1592+
return onnxruntime::CreateStatus(ORT_FAIL, error_msg.c_str());
1593+
}
15351594
} else {
15361595
return onnxruntime::CreateStatus(ORT_INVALID_ARGUMENT,
15371596
"[NvTensorRTRTX EP] Unsupported graphics API for CIG context");

0 commit comments

Comments
 (0)