Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions dpctl/_backend.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ cdef extern from "syclinterface/dpctl_sycl_enum_types.h":
_emulated "emulated",
_is_component "is_component",
_is_composite "is_composite",
_ext_oneapi_ipc_memory "ext_oneapi_ipc_memory",

ctypedef enum _partition_affinity_domain_type \
"DPCTLPartitionAffinityDomainType":
Expand Down Expand Up @@ -595,6 +596,7 @@ cdef extern from "syclinterface/dpctl_sycl_extension_interface.h":
DPCTLSyclWorkGroupMemoryRef Ref)

cdef bint DPCTLWorkGroupMemory_Available()
cdef bint DPCTLIPCMem_Available()

cdef struct DPCTLOpaqueRawKernelArg
ctypedef DPCTLOpaqueRawKernelArg *DPCTLSyclRawKernelArgRef
Expand All @@ -606,3 +608,19 @@ cdef extern from "syclinterface/dpctl_sycl_extension_interface.h":
DPCTLSyclRawKernelArgRef Ref)

cdef bint DPCTLRawKernelArg_Available()

cdef extern from "syclinterface/dpctl_sycl_ipc_memory_interface.h":
cdef int DPCTLIPCMem_GetHandle(
DPCTLSyclUSMRef Ptr,
DPCTLSyclContextRef CRef,
char **DataOut,
size_t *SizeOut)
cdef DPCTLSyclUSMRef DPCTLIPCMem_OpenHandle(
const char *HandleData,
size_t HandleDataSize,
DPCTLSyclContextRef CRef,
DPCTLSyclDeviceRef DRef)
cdef void DPCTLIPCMem_CloseHandle(
DPCTLSyclUSMRef MappedPtr,
DPCTLSyclContextRef CRef)
cdef void DPCTLIPCMem_FreeHandleData(char *Data)
12 changes: 12 additions & 0 deletions dpctl/_sycl_device.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,18 @@ cdef class SyclDevice(_SyclDevice):
cdef _aspect_type AT = _aspect_type._is_composite
return DPCTLDevice_HasAspect(self._device_ref, AT)

@property
def has_aspect_ext_oneapi_ipc_memory(self):
""" Returns ``True`` if this device supports inter-process
communication (IPC) memory handles, ``False`` otherwise.

Returns:
bool:
Indicates if device supports IPC memory.
"""
cdef _aspect_type AT = _aspect_type._ext_oneapi_ipc_memory
return DPCTLDevice_HasAspect(self._device_ref, AT)

@property
def image_2d_max_width(self):
""" Returns the maximum width of a 2D image or 1D image in pixels.
Expand Down
187 changes: 187 additions & 0 deletions dpctl/memory/_memory.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ from dpctl._backend cimport ( # noqa: E211
DPCTLDevice_Copy,
DPCTLEvent_Delete,
DPCTLEvent_Wait,
DPCTLIPCMem_Available,
DPCTLIPCMem_CloseHandle,
DPCTLIPCMem_FreeHandleData,
DPCTLIPCMem_GetHandle,
DPCTLIPCMem_OpenHandle,
DPCTLmalloc_device,
DPCTLmalloc_host,
DPCTLmalloc_shared,
Expand Down Expand Up @@ -744,6 +749,188 @@ cdef class _Memory:
_out = mem_ty(<object>_mem)
return _out

# ─── IPC memory methods ───────────────────────────────────────────

def get_ipc_handle(self):
"""Export this USM allocation as IPC handle bytes.

Returns an opaque ``bytes`` payload suitable for inter-process
transport (e.g. via pickle, ZMQ, shared-memory). The receiving
process can reconstruct a mapping via
:meth:`MemoryUSMDevice.open_ipc_handle`.

Returns
-------
bytes
Opaque IPC handle data.

Raises
------
RuntimeError
If the device does not support IPC memory.
"""
if not DPCTLIPCMem_Available():
raise RuntimeError(
"IPC memory not supported in this build"
)

if not isinstance(self, MemoryUSMDevice):
raise TypeError(
"IPC handles are only supported for USM device "
"allocations, not " + type(self).__name__
)

cdef DPCTLSyclUSMRef ptr = self._memory_ptr
if ptr is NULL:
raise ValueError("USM memory object has a null pointer")

cdef SyclDevice dev = self.sycl_device
if not dev.has_aspect_ext_oneapi_ipc_memory:
raise RuntimeError(
"Device does not support IPC memory "
"(aspect::ext_oneapi_ipc_memory)"
)

cdef SyclContext ctx = self._context
cdef DPCTLSyclContextRef ctx_ref = ctx.get_context_ref()
cdef char *data_out = NULL
cdef size_t size_out = 0

cdef int rc = DPCTLIPCMem_GetHandle(ptr, ctx_ref, &data_out, &size_out)
if rc != 0:
raise RuntimeError(
"DPCTLIPCMem_GetHandle failed — IPC handle export failed"
)

try:
return PyBytes_FromStringAndSize(data_out, <Py_ssize_t>size_out)
finally:
DPCTLIPCMem_FreeHandleData(data_out)

@staticmethod
def open_ipc_handle(bytes handle_bytes not None,
SyclDevice device not None,
Py_ssize_t nbytes,
SyclContext context=None):
"""Open an IPC handle and return a MemoryUSMDevice.

Parameters
----------
handle_bytes : bytes
Opaque payload from :meth:`get_ipc_handle` (possibly from
another process).
device : dpctl.SyclDevice
Device to map the memory on.
nbytes : int
Byte size of the original allocation. Must be > 0.
context : dpctl.SyclContext, optional
SYCL context to use. Defaults to the default context for
*device*'s platform.

Returns
-------
dpctl.memory.MemoryUSMDevice
A USM device memory object backed by the IPC-mapped pointer.
Call :meth:`close_ipc_mapping` when done.

Raises
------
RuntimeError
If the device does not support IPC memory or the handle
cannot be opened.
ValueError
If *nbytes* <= 0.
"""
if not DPCTLIPCMem_Available():
raise RuntimeError(
"IPC memory not supported in this build"
)

if not device.has_aspect_ext_oneapi_ipc_memory:
raise RuntimeError(
"Device does not support IPC memory "
"(aspect::ext_oneapi_ipc_memory)"
)

if nbytes <= 0:
raise ValueError("nbytes must be > 0 for IPC open")

cdef const char *raw = PyBytes_AS_STRING(handle_bytes)
cdef size_t raw_size = <size_t>len(handle_bytes)

if context is None:
context = device.sycl_platform.default_context

cdef DPCTLSyclContextRef ctx_ref = context.get_context_ref()
cdef DPCTLSyclDeviceRef dev_ref = device.get_device_ref()

cdef DPCTLSyclUSMRef mapped_ptr = DPCTLIPCMem_OpenHandle(
raw, raw_size, ctx_ref, dev_ref)
if mapped_ptr is NULL:
raise RuntimeError("DPCTLIPCMem_OpenHandle failed")

# Build a SyclQueue for the device+context, then wrap the
# mapped pointer. If anything fails, close the mapping.
cdef SyclQueue q
try:
q = dpctl.SyclQueue(context, device)
# Wrap as MemoryUSMDevice — memory_owner=True skips the
# OpaqueSmartPtr so __dealloc__ won't call sycl::free.
mem = _Memory.create_from_usm_pointer_size_qref(
mapped_ptr, nbytes, q.get_queue_ref(), memory_owner=True)
except Exception:
DPCTLIPCMem_CloseHandle(mapped_ptr, ctx_ref)
raise
return mem

def close_ipc_mapping(self, SyclContext context=None):
"""Explicitly close an IPC mapping.

After calling this method, the object is invalidated and must
not be used again. The destructor will not attempt to free the
pointer.

Parameters
----------
context : dpctl.SyclContext, optional
Context used when opening. Defaults to the memory's queue
context.

Raises
------
RuntimeError
If IPC memory is not supported in this build.
"""
if not DPCTLIPCMem_Available():
raise RuntimeError(
"IPC memory not supported in this build"
)

cdef DPCTLSyclUSMRef ptr = self._memory_ptr
if ptr is NULL:
return

# Only IPC-mapped objects are safe to close here. A regular
# allocation owns its memory via _opaque_ptr; refusing avoids
# leaking the smart pointer and calling CloseHandle on a
# non-IPC pointer.
if self._opaque_ptr is not NULL:
raise RuntimeError(
"close_ipc_mapping called on an owning USM allocation; "
"this method is only valid for IPC-mapped memory"
)

if context is None:
context = self._context

cdef DPCTLSyclContextRef ctx_ref = context.get_context_ref()
DPCTLIPCMem_CloseHandle(ptr, ctx_ref)

# Fully invalidate so subsequent use is inert.
self._memory_ptr = NULL
self._opaque_ptr = NULL
self.nbytes = 0


cdef class MemoryUSMShared(_Memory):
"""
Expand Down
43 changes: 43 additions & 0 deletions libsyclinterface/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,35 @@ endif()
message(STATUS "LIB_ZE: ${LIBZE_LOADER_FILENAME}")
message(STATUS "LIB_CL: ${LIBCL_LOADER_FILENAME}")


# --- IPC Memory support detection ---
option(DPCTL_ENABLE_IPC_MEMORY
"Enable IPC memory support (requires SYCL IPC runtime)"
ON
)
if(DPCTL_ENABLE_IPC_MEMORY)
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS "${SYCL_FLAGS}")
set(CMAKE_REQUIRED_INCLUDES "${SYCL_INCLUDE_DIR}")
check_cxx_source_compiles("
#include <sycl/ext/oneapi/experimental/ipc_memory.hpp>
int main() { return 0; }
" DPCTL_IPC_MEMORY_HEADER_FOUND)
if(DPCTL_IPC_MEMORY_HEADER_FOUND)
# Header found; enable IPC memory support
set(DPCTL_HAS_IPC_MEMORY 1)
set(DPCTL_HAS_IPC_MEMORY 1 PARENT_SCOPE)
message(STATUS "SYCL IPC memory support: ENABLED (ipc_memory.hpp found)")
else()
message(STATUS "SYCL IPC memory support: DISABLED (ipc_memory.hpp not found)")
endif()
unset(CMAKE_REQUIRED_FLAGS)
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES)
else()
message(STATUS "SYCL IPC memory support: DISABLED (DPCTL_ENABLE_IPC_MEMORY=OFF)")
endif()

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/include/syclinterface/Config/dpctl_config.h.in
${CMAKE_CURRENT_SOURCE_DIR}/include/syclinterface/Config/dpctl_config.h
Expand Down Expand Up @@ -222,8 +251,22 @@ file(GLOB_RECURSE sources
list(REMOVE_ITEM
sources
"${CMAKE_CURRENT_SOURCE_DIR}/source/dpctl_vector_templ.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/source/dpctl_sycl_ipc_memory_interface.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/source/dpctl_sycl_ipc_memory_stubs.cpp"
)

# IPC memory: use real implementation or stubs depending on header availability.
if(DPCTL_HAS_IPC_MEMORY)
list(APPEND sources
"${CMAKE_CURRENT_SOURCE_DIR}/source/dpctl_sycl_ipc_memory_interface.cpp"
)
else()
list(APPEND sources
"${CMAKE_CURRENT_SOURCE_DIR}/source/dpctl_sycl_ipc_memory_stubs.cpp"
)
message(STATUS "Using IPC memory stubs (header not found)")
endif()

file(GLOB_RECURSE helper_sources
${CMAKE_CURRENT_SOURCE_DIR}/helper/source/*.cpp
)
Expand Down
18 changes: 18 additions & 0 deletions libsyclinterface/helper/source/dpctl_utils_helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ std::string DPCTL_AspectToStr(aspect aspectTy)
case aspect::ext_oneapi_is_composite:
ss << "is_composite";
break;
#ifdef SYCL_EXT_ONEAPI_INTER_PROCESS_COMMUNICATION
case aspect::ext_oneapi_ipc_memory:
ss << "ext_oneapi_ipc_memory";
break;
#endif
default:
throw std::runtime_error("Unsupported aspect type");
}
Expand Down Expand Up @@ -299,6 +304,11 @@ aspect DPCTL_StrToAspectType(const std::string &aspectTyStr)
else if (aspectTyStr == "is_composite") {
aspectTy = aspect::ext_oneapi_is_composite;
}
#ifdef SYCL_EXT_ONEAPI_INTER_PROCESS_COMMUNICATION
else if (aspectTyStr == "ext_oneapi_ipc_memory") {
aspectTy = aspect::ext_oneapi_ipc_memory;
}
#endif
else {
// \todo handle the error
throw std::runtime_error("Unsupported aspect type");
Expand Down Expand Up @@ -351,6 +361,10 @@ aspect DPCTL_DPCTLAspectTypeToSyclAspect(DPCTLSyclAspectType AspectTy)
return aspect::ext_oneapi_is_component;
case DPCTLSyclAspectType::is_composite:
return aspect::ext_oneapi_is_composite;
#ifdef SYCL_EXT_ONEAPI_INTER_PROCESS_COMMUNICATION
case DPCTLSyclAspectType::ext_oneapi_ipc_memory:
return aspect::ext_oneapi_ipc_memory;
#endif
default:
throw std::runtime_error("Unsupported aspect type");
}
Expand Down Expand Up @@ -401,6 +415,10 @@ DPCTLSyclAspectType DPCTL_SyclAspectToDPCTLAspectType(aspect Aspect)
return DPCTLSyclAspectType::is_composite;
case aspect::ext_oneapi_is_component:
return DPCTLSyclAspectType::is_component;
#ifdef SYCL_EXT_ONEAPI_INTER_PROCESS_COMMUNICATION
case aspect::ext_oneapi_ipc_memory:
return DPCTLSyclAspectType::ext_oneapi_ipc_memory;
#endif
default:
throw std::runtime_error("Unsupported aspect type");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@

#define DPCTL_LIBZE_LOADER_FILENAME "@LIBZE_LOADER_FILENAME@"
#define DPCTL_LIBCL_LOADER_FILENAME "@LIBCL_LOADER_FILENAME@"
/* Defined when the SYCL runtime supports IPC memory (ipc::memory API). */
#cmakedefine DPCTL_HAS_IPC_MEMORY 1
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ typedef enum
host_debuggable,
emulated,
is_component,
is_composite
is_composite,
ext_oneapi_ipc_memory
} DPCTLSyclAspectType;

/*!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ void DPCTLWorkGroupMemory_Delete(__dpctl_take DPCTLSyclWorkGroupMemoryRef Ref);
DPCTL_API
bool DPCTLWorkGroupMemory_Available();

DPCTL_API
bool DPCTLIPCMem_Available();

typedef struct DPCTLOpaqueSyclRawKernelArg *DPCTLSyclRawKernelArgRef;

DPCTL_API
Expand Down
Loading