Skip to content
3 changes: 3 additions & 0 deletions python/pyarrow/includes/libarrow.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,9 @@ cdef extern from "arrow/api.h" namespace "arrow" nogil:
const shared_ptr[CMemoryManager] memory_manager()
CDeviceAllocationType device_type()

@staticmethod
CResult[shared_ptr[CBuffer]] Copy(shared_ptr[CBuffer] source, const shared_ptr[CMemoryManager]& to)

CResult[shared_ptr[CBuffer]] SliceBufferSafe(
const shared_ptr[CBuffer]& buffer, int64_t offset)
CResult[shared_ptr[CBuffer]] SliceBufferSafe(
Expand Down
35 changes: 35 additions & 0 deletions python/pyarrow/io.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,41 @@ cdef class Buffer(_Weakrefable):
"""
return _wrap_device_allocation_type(self.buffer.get().device_type())

def copy(self, destination):
"""
Copy the buffer to the destination device.

The buffer contents will be copied into a new buffer allocated onto
the destination MemoryManager or Device. It will return the new Buffer.
This function supports cross-device copies.

Parameters
----------
destination : pyarrow.MemoryManager or pyarrow.Device
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not required, but it may be nice to also allow a MemoryPool here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have a way to map a given MemoryPool to a device manager (AFAIK), so in that case you mean to use a different API under the hood to copy to the memory pool? (Buffer::CopySlice seems to allow that, or first explicitly allocate a new Buffer of the correct size using the memory pool and then copying the data to it?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use

/// \brief Create a MemoryManager
///
/// The returned MemoryManager will use the given MemoryPool for allocations.
static std::shared_ptr<MemoryManager> memory_manager(MemoryPool* pool);

Used to allocate the new buffer.

Returns
-------
Buffer
"""
cdef:
shared_ptr[CBuffer] c_buffer
shared_ptr[CMemoryManager] c_memory_manager

if isinstance(destination, Device):
c_memory_manager = (<Device>destination).unwrap().get().default_memory_manager()
elif isinstance(destination, MemoryManager):
c_memory_manager = (<MemoryManager>destination).unwrap()
else:
raise TypeError(
"Argument 'destination' is incorrect type (expected pyarrow.Device or "
f"pyarrow.MemoryManager, got {type(destination)})"
)

with nogil:
c_buffer = GetResultValue(CBuffer.Copy(self.buffer, c_memory_manager))
return pyarrow_wrap_buffer(c_buffer)

@property
def parent(self):
cdef shared_ptr[CBuffer] parent_buf = self.buffer.get().parent()
Expand Down
19 changes: 19 additions & 0 deletions python/pyarrow/tests/test_cuda.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ def make_random_buffer(size, target='host'):
raise ValueError('invalid target value')


def test_copy_from_buffer():
arr, buf = make_random_buffer(128)
cudabuf = global_context.buffer_from_data(buf)

mm2 = global_context1.memory_manager
for dest in [mm2, mm2.device]:
buf2 = cudabuf.copy(dest)
assert buf2.device_type == pa.DeviceAllocationType.CUDA
assert buf2.copy(pa.default_cpu_memory_manager()).equals(buf)
cudabuf2 = cuda.CudaBuffer.from_buffer(buf2)
assert cudabuf2.size == cudabuf.size
assert not cudabuf2.is_cpu
assert cudabuf2.device_type == pa.DeviceAllocationType.CUDA

assert cudabuf2.copy_to_host().equals(buf)

assert cudabuf2.device == mm2.device


@pytest.mark.parametrize("size", [0, 1, 1000])
def test_context_device_buffer(size):
# Creating device buffer from host buffer;
Expand Down
9 changes: 9 additions & 0 deletions python/pyarrow/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,15 @@ def test_non_cpu_buffer(pickle_module):
# Sliced buffers with same address
assert buf_on_gpu_sliced.equals(cuda_buf[2:4])

# Testing copying buffer
mm = ctx.memory_manager
for dest in [mm, mm.device]:
buf2 = buf_on_gpu.copy(dest)
cuda_buf2 = cuda.CudaBuffer.from_buffer(buf2)
assert cuda_buf2.size == cuda_buf.size
assert cuda_buf2.to_pybytes() == b'testing'
assert buf2.device == mm.device

# Buffers on different devices
msg_device = "Device on which the data resides differs between buffers"
with pytest.raises(ValueError, match=msg_device):
Expand Down