CuPy:IPC
CUDA:IPC 항목을 참조하여 Low-Level API 를 사용해야 한다. (CuPy에서는 High-Level API 지원이 없다)
Low-level CUDA support
CUDA:Memory 관련 IPC Wrapping:
-
cupy.cuda.runtime.ipcGetMemHandle(intptr_t devPtr)
-
cupy.cuda.runtime.ipcOpenMemHandle(bytes handle, unsigned int flags=cudaIpcMemLazyEnablePeerAccess)
-
cupy.cuda.runtime.ipcCloseMemHandle(intptr_t devPtr)
CUDA:Event 관련 IPC Wrapping:
-
cupy.cuda.runtime.ipcGetEventHandle(intptr_t event)
-
cupy.cuda.runtime.ipcOpenEventHandle(bytes handle)
- 이벤트 에서는 Close API 가 없다.
Memory Handle IPC Example
메모리를 소유하고 있는 프로세스 01:
import numpy as np
import cupy as cp
def pinned_array(array):
# first constructing pinned memory
mem = cp.cuda.alloc_pinned_memory(array.nbytes)
src = np.frombuffer(mem, array.dtype, array.size).reshape(array.shape)
src[...] = array
return src
a_cpu_ori = np.ones((10000, 10000), dtype=np.float32)
a_cpu = pinned_array(a_cpu_ori)
a_stream = cp.cuda.Stream(non_blocking=True)
a_gpu = cp.empty_like(a_cpu) # numpy.empty_like 와 동일, 단 GPU 를 곁들인
print(a_gpu) # 모두 0 으로 출력됨
a_gpu.set(a_cpu, stream=a_stream)
a_stream.synchronize()
print(a_gpu) # 모두 1로 출력됨. (출력시 자동 동기화 되는듯?)
a_gpu *= 2
print(a_gpu) # 모두 2로 출력됨. (출력시 자동 동기화 되는듯?)
a_gpu_ipc_h = cp.cuda.runtime.ipcGetMemHandle(a_gpu.data.ptr)
assert isinstance(a_gpu_ipc_h, bytes) # 값이 꽤 긴 bytes 값이다.
## 대략 다음과 같이 출력된다:
## >>> a_gpu.data
## <MemoryPointer 0x7fb67a000000 device=0 mem=<cupy.cuda.memory.PooledMemory object at 0x7fb698cd3030>>
## >>> a_gpu.device
## <CUDA Device 0>
## >>> a_gpu.data.ptr
## 140421707595776
## >>> a_gpu.data.mem
## <cupy.cuda.memory.PooledMemory object at 0x7fb698cd3030>
## >>> a_gpu.data.ptr
## 140421707595776
## >>> a_gpu_ipc_h
## b'0o\xf1}\x81U\x00\x00O\x12-\x00 .... \x00'
## >>> a_gpu.shape
## (10000, 10000)
## >>> a_gpu.size
## 100000000
# 이 값을 복사하여 통신을 원하는 프로세스에 전달하자!!
a_send_data = (a_gpu_ipc_h, a_gpu.shape, a_gpu.size)
print(a_send_data) # 이 값을 복사하여 통신을 원하는 프로세스에 전달하자!!
메모리를 공유 받고자 하는 프로세스 02:
import numpy as np
import cupy as cp
# 이전 프로세스에서 전달 받은 데이터:
a_gpu_ipc_h, shape, size = a_send_data
mem = cp.cuda.runtime.ipcOpenMemHandle(a_gpu_ipc_h)
assert isinstance(mem, int)
print(mem) # 140389159796736
um = cp.cuda.UnownedMemory(mem, size, 0)
mp = cp.cuda.MemoryPointer(um, 0)
## >>> um
## <cupy.cuda.memory.UnownedMemory object at 0x7faf381ce3c0>
## >>> mp
## <MemoryPointer 0x7faee6000000 device=0 mem=<cupy.cuda.memory.UnownedMemory object at 0x7faf381ce3c0>>
array = cp.ndarray(shape, dtype=cp.float32, memptr=mp)
print(array) # 모두 2로 출력됨. (출력시 자동 동기화 되는듯?)
cp.cuda.runtime.ipcCloseMemHandle(mem) # 열었으면 닫자.
Event Handle IPC Example
이벤트를 소유하고 있는 프로세스 01:
import numpy as np
import cupy as cp
e1 = cp.cuda.Event(block=False, disable_timing=True, interprocess=True)
## `interprocess=True` 일 때 `disable_timing=False` 이면 "ValueError: Timing must be disabled for interprocess events" 에러가 발생된다.
## 즉 `disable_timing=True, interprocess=True` 는 IPC 할 때 고정 값 이다.
e1_h = cp.cuda.runtime.ipcGetEventHandle(e1.ptr)
## 만약 `interprocess=False` 로 설정하면 "cupy_backends.cuda.api.runtime.CUDARuntimeError: cudaErrorInvalidResourceHandle: invalid resource handle" 에러가 발생된다.
assert isinstance(e1_h, bytes)
a_send_data = (e1_h,)
print(a_send_data) # 이 값을 복사하여 통신을 원하는 프로세스에 전달하자!!
이벤트를 공유 받고자 하는 프로세스 02:
import numpy as np
import cupy as cp
# 이전 프로세스에서 전달 받은 데이터:
e1_h, = a_send_data
e1 = cp.cuda.runtime.ipcOpenEventHandle(e1_h)
assert isinstance(e1, int)
## cupy.cuda.Event 객체로 Wrapping 하는 방법이 제공되지 않으므로 Low-Level API 를 사용해야 한다.
## -----------------------------------
cp.cuda.runtime.eventRecord(e1, 0) # 두 번째 인자는 stream ptr 인데, NULL 이면 현재(기본)의 스트림으로 적용된다.
## 즉, 다음과 같다:
s = cp.cuda.get_current_stream()
cp.cuda.runtime.eventRecord(e1, s.ptr)
## -----------------------------------
호환성 확인
리눅스 Only, 64비트, 커널 버전 2.6.18 이상 이여야 한다.
import platform
import sys
if platform.system() != "Linux":
print("The application only supported on Linux OS.")
exit(1)
# https://docs.python.org/3/library/platform.html#platform.architecture
if sys.maxsize <= 2**32:
print("The application must be built as a 64-bit target.")
exit(1)
kernel_version_text = platform.release()
print(kernel_version_text) # e.g. "5.15.0-107-generic"
kernel_version_tuple = tuple(map(lambda x: int(x), kernel_version_text.split('-')[0].split('.')))
# https://github.com/ndd314/cuda_examples/blob/master/0_Simple/simpleIPC/simpleIPC.cu
if kernel_version_tuple < (2, 6, 18):
print("IPC is only supported with Linux OS kernel version 2.6.18 and higher. Test is being waived.")
exit(1)
CUDA는 compute capability 2.0 이상이여야 하고,
메모리 복사 Event 동기화를 위한 CUDA Stream 을 사용해야 하는데 Device Overlap 속성이 지원되어야 한다.
import cupy as cp
num_devices = cp.cuda.runtime.getDeviceCount()
for i in range(num_devices):
prop = cp.cuda.runtime.getDeviceProperties(i)
major = prop['major']
minor = prop['minor']
computeCapability = major, minor
if computeCapability < (2, 0):
print(f"Computing capability must be 2.0 or higher in Device({i})")
exit(1)
deviceOverlap = prop['deviceOverlap']
if not deviceOverlap:
print(f"Unsupported deviceOverlap in Device({i})")
exit(1)