Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REVIEW] Change ways to access ptr in Buffer #12587

Merged
merged 29 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
15cbedf
get_ptr & _array_view refactor
galipremsagar Jan 20, 2023
3697367
Apply suggestions from code review
galipremsagar Jan 20, 2023
2b1b48d
address reviews
galipremsagar Jan 20, 2023
0bba2a5
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 20, 2023
9824471
Apply suggestions from code review
galipremsagar Jan 23, 2023
7868437
Merge remote-tracking branch 'upstream/branch-23.02' into ptr_refactor_1
galipremsagar Jan 23, 2023
07a34b4
drop internal_write
galipremsagar Jan 23, 2023
afd3588
add docstring
galipremsagar Jan 23, 2023
91a9b60
Apply suggestions from code review
galipremsagar Jan 23, 2023
e53a5b7
address reviews
galipremsagar Jan 23, 2023
360ce3f
Merge branch 'ptr_refactor_1' of https://github.com/galipremsagar/cud…
galipremsagar Jan 23, 2023
1c44bd2
address reviews
galipremsagar Jan 23, 2023
8292ed7
add locks
galipremsagar Jan 23, 2023
01e8883
Apply suggestions from code review
galipremsagar Jan 24, 2023
e399211
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 24, 2023
04897ef
Apply suggestions from code review
galipremsagar Jan 24, 2023
8f0f017
make mode a required key-arg
galipremsagar Jan 24, 2023
af0e264
rename to _readonly_proxy_cai_obj
galipremsagar Jan 24, 2023
f04c2a1
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 24, 2023
046025a
Update python/cudf/cudf/core/column/column.py
galipremsagar Jan 24, 2023
bf18485
drop default
galipremsagar Jan 25, 2023
517c2ef
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 25, 2023
9619201
Update python/cudf/cudf/core/buffer/buffer.py
galipremsagar Jan 25, 2023
98c3892
Update python/strings_udf/strings_udf/tests/test_string_udfs.py
galipremsagar Jan 25, 2023
1cf727b
Update python/strings_udf/strings_udf/tests/test_string_udfs.py
galipremsagar Jan 25, 2023
535128c
Update python/strings_udf/strings_udf/tests/test_string_udfs.py
galipremsagar Jan 25, 2023
f1bb022
Fix
galipremsagar Jan 26, 2023
22418fb
Merge remote-tracking branch 'upstream/branch-23.02' into ptr_refactor_1
galipremsagar Jan 26, 2023
810d921
Fix
galipremsagar Jan 26, 2023
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
4 changes: 2 additions & 2 deletions docs/cudf/source/developer_guide/library_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,11 @@ Spilling consists of two components:
- A spill manager that tracks all instances of `SpillableBuffer` and spills them on demand.
A global spill manager is used throughout cudf when spilling is enabled, which makes `as_buffer()` return `SpillableBuffer` instead of the default `Buffer` instances.

Accessing `Buffer.ptr`, we get the device memory pointer of the buffer. This is unproblematic in the case of `Buffer` but what happens when accessing `SpillableBuffer.ptr`, which might have spilled its device memory. In this case, `SpillableBuffer` needs to unspill the memory before returning its device memory pointer. Furthermore, while this device memory pointer is being used (or could be used), `SpillableBuffer` cannot spill its memory back to host memory because doing so would invalidate the device pointer.
Accessing `Buffer.get_ptr(mode="read"|"write"|"internal_write")`, we get the device memory pointer of the buffer. This is unproblematic in the case of `Buffer` but what happens when accessing `SpillableBuffer.get_ptr(mode="write")`, which might have spilled its device memory. In this case, `SpillableBuffer` needs to unspill the memory before returning its device memory pointer. Furthermore, while this device memory pointer is being used (or could be used), `SpillableBuffer` cannot spill its memory back to host memory because doing so would invalidate the device pointer.
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved

To address this, we mark the `SpillableBuffer` as unspillable, we say that the buffer has been _exposed_. This can either be permanent if the device pointer is exposed to external projects or temporary while `libcudf` accesses the device memory.

The `SpillableBuffer.get_ptr()` returns the device pointer of the buffer memory just like `.ptr` but if given an instance of `SpillLock`, the buffer is only unspillable as long as the instance of `SpillLock` is alive.
The `SpillableBuffer.get_ptr(mode="read")` returns the device pointer of the buffer memory but if given an instance of `SpillLock`, the buffer is only unspillable as long as the instance of `SpillLock` is alive.

For convenience, one can use the decorator/context `acquire_spill_lock` to associate a `SpillLock` with a lifetime bound to the context automatically.
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
2 changes: 0 additions & 2 deletions python/cudf/cudf/_lib/column.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ class Column:
@property
def base_mask(self) -> Optional[Buffer]: ...
@property
def base_mask_ptr(self) -> int: ...
@property
def mask(self) -> Optional[Buffer]: ...
@property
def mask_ptr(self) -> int: ...
Expand Down
35 changes: 16 additions & 19 deletions python/cudf/cudf/_lib/column.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ cdef class Column:
if self.data is None:
return 0
else:
return self.data.ptr
return self.data.get_ptr(mode="write")

def set_base_data(self, value):
if value is not None and not isinstance(value, Buffer):
Expand All @@ -124,13 +124,6 @@ cdef class Column:
def base_mask(self):
return self._base_mask

@property
def base_mask_ptr(self):
if self.base_mask is None:
return 0
else:
return self.base_mask.ptr

@property
def mask(self):
if self._mask is None:
Expand All @@ -145,7 +138,7 @@ cdef class Column:
if self.mask is None:
return 0
else:
return self.mask.ptr
return self.mask.get_ptr(mode="write")

def set_base_mask(self, value):
"""
Expand Down Expand Up @@ -206,7 +199,7 @@ cdef class Column:
elif hasattr(value, "__cuda_array_interface__"):
if value.__cuda_array_interface__["typestr"] not in ("|i1", "|u1"):
if isinstance(value, Column):
value = value.data_array_view
value = value.data_array_view(mode="write")
value = cp.asarray(value).view('|u1')
mask = as_buffer(value)
if mask.size < required_num_bytes:
Expand Down Expand Up @@ -329,10 +322,10 @@ cdef class Column:

if col.base_data is None:
data = NULL
elif isinstance(col.base_data, SpillableBuffer):
data = <void*><uintptr_t>(col.base_data).get_ptr()
else:
data = <void*><uintptr_t>(col.base_data.ptr)
data = <void*><uintptr_t>(col.base_data.get_ptr(
mode="internal_write")
)

cdef Column child_column
if col.base_children:
Expand All @@ -341,7 +334,9 @@ cdef class Column:

cdef libcudf_types.bitmask_type* mask
if self.nullable:
mask = <libcudf_types.bitmask_type*><uintptr_t>(self.base_mask_ptr)
mask = <libcudf_types.bitmask_type*><uintptr_t>(
self.base_mask.get_ptr(mode="internal_write")
)
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
else:
mask = NULL

Expand Down Expand Up @@ -387,10 +382,8 @@ cdef class Column:

if col.base_data is None:
data = NULL
elif isinstance(col.base_data, SpillableBuffer):
data = <void*><uintptr_t>(col.base_data).get_ptr()
else:
data = <void*><uintptr_t>(col.base_data.ptr)
data = <void*><uintptr_t>(col.base_data.get_ptr(mode="read"))

cdef Column child_column
if col.base_children:
Expand All @@ -399,7 +392,9 @@ cdef class Column:

cdef libcudf_types.bitmask_type* mask
if self.nullable:
mask = <libcudf_types.bitmask_type*><uintptr_t>(self.base_mask_ptr)
mask = <libcudf_types.bitmask_type*><uintptr_t>(
self.base_mask.get_ptr(mode="read")
)
else:
mask = NULL

Expand Down Expand Up @@ -549,7 +544,9 @@ cdef class Column:
f"{data_owner} is spilled, which invalidates "
f"the exposed data_ptr ({hex(data_ptr)})"
)
data_owner.ptr # accessing the pointer marks it exposed.
# accessing the pointer marks it exposed.
data_owner.get_ptr(mode="write")

galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
else:
data = as_buffer(
rmm.DeviceBuffer(ptr=data_ptr, size=0)
Expand Down
4 changes: 2 additions & 2 deletions python/cudf/cudf/_lib/copying.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020-2022, NVIDIA CORPORATION.
# Copyright (c) 2020-2023, NVIDIA CORPORATION.

import pickle

Expand Down Expand Up @@ -765,7 +765,7 @@ cdef class _CPackedColumns:
gpu_data = Buffer.deserialize(header["data"], frames)

dbuf = DeviceBuffer(
ptr=gpu_data.ptr,
ptr=gpu_data.get_ptr(mode="write"),
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
size=gpu_data.nbytes
)

Expand Down
4 changes: 3 additions & 1 deletion python/cudf/cudf/_lib/transform.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def mask_to_bools(object mask_buffer, size_type begin_bit, size_type end_bit):
if not isinstance(mask_buffer, cudf.core.buffer.Buffer):
raise TypeError("mask_buffer is not an instance of "
"cudf.core.buffer.Buffer")
cdef bitmask_type* bit_mask = <bitmask_type*><uintptr_t>(mask_buffer.ptr)
cdef bitmask_type* bit_mask = <bitmask_type*><uintptr_t>(
mask_buffer.get_ptr(mode="read")
)

cdef unique_ptr[column] result
with nogil:
Expand Down
61 changes: 53 additions & 8 deletions python/cudf/cudf/core/buffer/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ def _getitem(self, offset: int, size: int) -> Buffer:
"""
return self._from_device_memory(
cuda_array_interface_wrapper(
ptr=self.ptr + offset, size=size, owner=self.owner
ptr=self.get_ptr(mode="read") + offset,
size=size,
owner=self.owner,
)
)

Expand All @@ -202,11 +204,6 @@ def nbytes(self) -> int:
"""Size of the buffer in bytes."""
return self._size

@property
def ptr(self) -> int:
"""Device pointer to the start of the buffer."""
return self._ptr

@property
def owner(self) -> Any:
"""Object owning the memory of the buffer."""
Expand All @@ -215,18 +212,66 @@ def owner(self) -> Any:
@property
def __cuda_array_interface__(self) -> Mapping:
"""Implementation of the CUDA Array Interface."""
return self._get_cuda_array_interface(readonly=False)

def _get_cuda_array_interface(self, readonly=False):
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
return {
"data": (self.ptr, False),
"data": (
self.get_ptr(mode="read" if readonly else "write"),
readonly,
),
"shape": (self.size,),
"strides": None,
"typestr": "|u1",
"version": 0,
}

@property
def _get_readonly_proxy_obj(self):
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
"""
Returns a proxy object with a read-only CUDA Array Interface.
"""
return cuda_array_interface_wrapper(
ptr=self.get_ptr(mode="read"),
size=self.size,
owner=self,
readonly=True,
typestr="|u1",
version=0,
)

def get_ptr(self, mode="write") -> int:
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
"""Device pointer to the start of the buffer.

Parameters
----------
mode : str, default 'write'
Supported values are {"read", "write", "internal_write"}
If "write", the modification to the buffer
is allowed. Hence can pass it onto a
third-party library as well.
If "read", then modification to the buffer
isn't expected to be done by the user via this pointer.
Hence mostly intended for internal read purposes or
device to host copies.
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
If "internal_write", then modification to the buffer
is allowed and strictly for internal libcudf function
call usage only.

Notes
-----
In case of `Buffer` class, any value passed to `mode` is a no-op.
The significance of each value explained above is a
guidance for any implementation of `Buffer` class.
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
"""
return self._ptr

def memoryview(self) -> memoryview:
"""Read-only access to the buffer through host memory."""
host_buf = host_memory_allocation(self.size)
rmm._lib.device_buffer.copy_ptr_to_host(self.ptr, host_buf)
rmm._lib.device_buffer.copy_ptr_to_host(
self.get_ptr(mode="read"), host_buf
)
return memoryview(host_buf).toreadonly()

def serialize(self) -> Tuple[dict, list]:
Expand Down
51 changes: 30 additions & 21 deletions python/cudf/cudf/core/buffer/spillable_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __len__(self):

def __getitem__(self, i):
if i == 0:
return self._buf.ptr
return self._buf.get_ptr(mode="write")
elif i == 1:
return False
raise IndexError("tuple index out of range")
Expand Down Expand Up @@ -269,7 +269,7 @@ def spill_lock(self, spill_lock: SpillLock) -> None:
self.spill(target="gpu")
self._spill_locks.add(spill_lock)

def get_ptr(self) -> int:
def get_ptr(self, mode="write") -> int:
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
"""Get a device pointer to the memory of the buffer.

If this is called within an `acquire_spill_lock` context,
Expand All @@ -279,6 +279,23 @@ def get_ptr(self) -> int:
If this is *not* called within a `acquire_spill_lock` context,
this buffer is marked as unspillable permanently.

Parameters
----------
mode : str, default 'write'
Supported values are {"read", "write", "internal_write"}
If "write" is passed, the SpillableBuffer
is marked as exposed and make it un-spillable
permanently. This mode is to be used if the
buffer is being handed off to an external library.
If "read" is passed, the SpillableBuffer
returns a pointer by not marking it as
exposed, this mode is intended for internal
use only.
If "internal_write" is passed, a spill lock is
created if none exists and we ensure that doesn't
escape to the user, hence to be used only for internal
write purposes like in `mutable_view` for example.

Return
------
int
Expand All @@ -287,7 +304,11 @@ def get_ptr(self) -> int:
from cudf.core.buffer.utils import get_spill_lock

spill_lock = get_spill_lock()
if spill_lock is None:

if mode == "internal_write":
if spill_lock is None:
spill_lock = SpillLock()
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
if spill_lock is None or mode == "write":
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
self.mark_exposed()
else:
self.spill_lock(spill_lock)
Expand Down Expand Up @@ -319,18 +340,6 @@ def memory_info(self) -> Tuple[int, int, str]:
).__array_interface__["data"][0]
return (ptr, self.nbytes, self._ptr_desc["type"])

@property
def ptr(self) -> int:
"""Access the memory directly

Notice, this will mark the buffer as "exposed" and make
it unspillable permanently.

Consider using `.get_ptr()` instead.
"""
self.mark_exposed()
return self._ptr

@property
def owner(self) -> Any:
return self._owner
Expand Down Expand Up @@ -469,12 +478,12 @@ def __init__(self, base: SpillableBuffer, offset: int, size: int) -> None:
self._owner = base
self.lock = base.lock

@property
def ptr(self) -> int:
return self._base.ptr + self._offset

def get_ptr(self) -> int:
return self._base.get_ptr() + self._offset
def get_ptr(self, mode="write") -> int:
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
"""
A passthrough method to `SpillableBuffer.get_ptr`
with factoring in the `offset`.
"""
return self._base.get_ptr(mode=mode) + self._offset

def _getitem(self, offset: int, size: int) -> Buffer:
return SpillableBufferSlice(
Expand Down
5 changes: 2 additions & 3 deletions python/cudf/cudf/core/column/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,9 +956,8 @@ def clip(self, lo: ScalarLike, hi: ScalarLike) -> "column.ColumnBase":
self.astype(self.categories.dtype).clip(lo, hi).astype(self.dtype)
)

@property
def data_array_view(self) -> cuda.devicearray.DeviceNDArray:
return self.codes.data_array_view
def data_array_view(self, mode="read") -> cuda.devicearray.DeviceNDArray:
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
return self.codes.data_array_view(mode=mode)

def unique(self, preserve_order=False) -> CategoricalColumn:
codes = self.as_numerical.unique(preserve_order=preserve_order)
Expand Down
Loading